[
  {
    "path": ".ci/.ci-utility-files/common.sh",
    "content": "#!/usr/bin/env bash\n# Copyright IBM Corp. 2019, 2025\n# SPDX-License-Identifier: MPL-2.0\n#\n# shellcheck disable=SC2119\n# shellcheck disable=SC2164\n\n# If the bash version isn't at least 4, bail\nif [ \"${BASH_VERSINFO:-0}\" -lt \"4\" ]; then\n    printf \"ERROR: Expected bash version >= 4 (is: %d)\" \"${BASH_VERSINFO:-0}\"\n    exit 1\nfi\n\n# Lets have some emojis\nWARNING_ICON=\"⚠️\"\nERROR_ICON=\"🛑\"\n\n# Coloring\n# shellcheck disable=SC2034\nTEXT_BOLD='\\e[1m'\nTEXT_RED='\\e[31m'\n# shellcheck disable=SC2034\nTEXT_GREEN='\\e[32m'\nTEXT_YELLOW='\\e[33m'\nTEXT_CYAN='\\e[36m'\nTEXT_CLEAR='\\e[0m'\n\n# Common variables\nexport full_sha=\"${GITHUB_SHA}\"\nexport short_sha=\"${full_sha:0:8}\"\nexport ident_ref=\"${GITHUB_REF#*/*/}\"\nexport repository=\"${GITHUB_REPOSITORY}\"\nexport repo_owner=\"${repository%/*}\"\nexport repo_name=\"${repository#*/}\"\n# shellcheck disable=SC2153\nexport asset_cache=\"${ASSETS_PRIVATE_SHORTTERM}/${repository}/${GITHUB_ACTION}\"\nexport run_number=\"${GITHUB_RUN_NUMBER}\"\nexport run_id=\"${GITHUB_RUN_ID}\"\nexport job_id=\"${run_id}-${run_number}\"\nreadonly hc_releases_metadata_filename=\"release-meta.json\"\n# This value is used in our cleanup trap to restore the value in cases\n# where a function call may have failed and did not restore it\nreadonly _repository_backup=\"${repository}\"\n\nif [ -z \"${ci_bin_dir}\" ]; then\n    if ci_bin_dir=\"$(realpath ./.ci-bin)\"; then\n        export ci_bin_dir\n    else\n        echo \"ERROR: Failed to create the local CI bin directory\"\n        exit 1\n    fi\nfi\n\n# We are always noninteractive\nexport DEBIAN_FRONTEND=noninteractive\n\n# If we are on a runner and debug mode is enabled,\n# enable debug mode for ourselves too\nif [ -n \"${RUNNER_DEBUG}\" ]; then\n    DEBUG=1\nfi\n\n# If DEBUG is enabled and we are running tests,\n# flag it so we can adjust where output is sent.\nif [ -n \"${DEBUG}\" ] && [ -n \"${BATS_TEST_FILENAME}\" ]; then\n    DEBUG_WITH_BATS=1\nfi\n\n# Write debug output to stderr. Message template\n# and arguments are passed to `printf` for formatting.\n#\n# $1: message template\n# $#: message arguments\n#\n# NOTE: Debug output is only displayed when DEBUG is set\nfunction debug() {\n    if [ -n \"${DEBUG}\" ]; then\n        local msg_template=\"${1}\"\n        local i=$(( ${#} - 1 ))\n        local msg_args=(\"${@:2:$i}\")\n        # Update template to include caller information\n        msg_template=$(printf \"<%s(%s:%d)> %s\" \"${FUNCNAME[1]}\" \"${BASH_SOURCE[1]}\" \"${BASH_LINENO[0]}\" \"${msg_template}\")\n        #shellcheck disable=SC2059\n        msg=\"$(printf \"${msg_template}\" \"${msg_args[@]}\")\"\n\n        if [ -n \"${DEBUG_WITH_BATS}\" ]; then\n            printf \"%b%s%b\\n\" \"${TEXT_CYAN}\" \"${msg}\" \"${TEXT_CLEAR}\" >&3\n        else\n            printf \"%b%s%b\\n\" \"${TEXT_CYAN}\" \"${msg}\" \"${TEXT_CLEAR}\" >&2\n        fi\n    fi\n}\n\n# Wrap the pushd command so we fail\n# if the pushd command fails. Arguments\n# are just passed through.\nfunction pushd() {\n    debug \"executing 'pushd %s'\" \"${*}\"\n    command builtin pushd \"${@}\" > /dev/null || exit 1\n}\n\n# Wrap the popd command so we fail\n# if the popd command fails. Arguments\n# are just passed through.\n# shellcheck disable=SC2120\nfunction popd() {\n    debug \"executing 'popd %s'\" \"${*}\"\n    command builtin popd \"${@}\" || exit 1\n}\n\n# Wraps the aws CLI command to support\n# role based access. It will check for\n# expected environment variables when\n# a role has been assumed. If they are\n# not found, it will assume the configured\n# role. If the role has already been\n# assumed, it will check that the credentials\n# have not timed out, and re-assume the\n# role if so. If no role information is\n# provided, it will just pass the command\n# through directly\n#\n# NOTE: Required environment variable: AWS_ASSUME_ROLE_ARN\n# NOTE: This was a wrapper for the AWS command that would properly\n#       handle the assume role process and and automatically refresh\n#       if close to expiry. With credentials being handled by the doormat\n#       action now, this is no longer needed but remains in case it's\n#       needed for some reason in the future.\nfunction aws_deprecated() {\n    # Grab the actual aws cli path\n    if ! aws_path=\"$(which aws)\"; then\n        (>&2 echo \"AWS error: failed to locate aws cli executable\")\n        return 1\n    fi\n    # First, check if the role ARN environment variable is\n    # configured. If it is not, just pass through.\n    if [ \"${AWS_ASSUME_ROLE_ARN}\" = \"\" ]; then\n        \"${aws_path}\" \"${@}\"\n        return $?\n    fi\n    # Check if a role has already been assumed. If it\n    # has, validate the credentials have not timed out\n    # and pass through.\n    if [ \"${AWS_SESSION_TOKEN}\" != \"\" ]; then\n        # Cut off part of the expiration so we don't end up hitting\n        # the expiration just as we make our call\n        expires_at=$(date -d \"${AWS_SESSION_EXPIRATION} - 20 sec\" \"+%s\")\n        if (( \"${expires_at}\" > $(date +%s) )); then\n            \"${aws_path}\" \"${@}\"\n            return $?\n        fi\n        # If we are here then the credentials were not\n        # valid so clear the session token and restore\n        # original credentials\n        unset AWS_SESSION_TOKEN\n        unset AWS_SESSION_EXPIRATION\n        export AWS_ACCESS_KEY_ID=\"${CORE_AWS_ACCESS_KEY_ID}\"\n        export AWS_SECRET_ACCESS_KEY=\"${CORE_AWS_SECRET_ACCESS_KEY}\"\n    fi\n    # Now lets assume the role\n    if aws_output=\"$(\"${aws_path}\" sts assume-role --role-arn \"${AWS_ASSUME_ROLE_ARN}\" --role-session-name \"VagrantCI@${repo_name}-${job_id}\")\"; then\n        export CORE_AWS_ACCESS_KEY_ID=\"${AWS_ACCESS_KEY_ID}\"\n        export CORE_AWS_SECRET_ACCESS_KEY=\"${AWS_SECRET_ACCESS_KEY}\"\n        id=\"$(printf '%s' \"${aws_output}\" | jq -r .Credentials.AccessKeyId)\" || failed=1\n        key=\"$(printf '%s' \"${aws_output}\" | jq -r .Credentials.SecretAccessKey)\" || failed=1\n        token=\"$(printf '%s' \"${aws_output}\" | jq -r .Credentials.SessionToken)\" || failed=1\n        expire=\"$(printf '%s' \"${aws_output}\" | jq -r .Credentials.Expiration)\" || failed=1\n        if [ \"${failed}\" = \"1\" ]; then\n            (>&2 echo \"Failed to extract assume role credentials\")\n            return 1\n        fi\n        unset aws_output\n        export AWS_ACCESS_KEY_ID=\"${id}\"\n        export AWS_SECRET_ACCESS_KEY=\"${key}\"\n        export AWS_SESSION_TOKEN=\"${token}\"\n        export AWS_SESSION_EXPIRATION=\"${expire}\"\n    else\n        (>&2 echo \"AWS assume role error: ${aws_output}\")\n        return 1\n    fi\n    # And we can execute!\n    \"${aws_path}\" \"${@}\"\n}\n\n# Path to file used for output redirect\n# and extracting messages for warning and\n# failure information sent to slack\nfunction output_file() {\n    if [ \"${1}\" = \"clean\" ] && [ -f \"${ci_output_file_path}\" ]; then\n        rm -f \"${ci_output_file_path}\"\n        unset ci_output_file_path\n    fi\n    if [ -z \"${ci_output_file_path}\" ] || [ ! -f \"${ci_output_file_path}\" ]; then\n        ci_output_file_path=\"$(mktemp)\"\n    fi\n\n    printf \"%s\" \"${ci_output_file_path}\"\n}\n\n# Write failure message, send error to configured\n# slack, and exit with non-zero status. If an\n# \"$(output_file)\" file exists, the last 5 lines will be\n# included in the slack message.\n#\n# $1: Failure message\nfunction failure() {\n    local msg_template=\"${1}\"\n    local i=$(( ${#} - 1 ))\n    local msg_args=(\"${@:2:$i}\")\n\n    # Update template to include caller information if in DEBUG mode\n    if [ -n \"${DEBUG}\" ]; then\n        msg_template=$(printf \"<%s(%s:%d)> %s\" \"${FUNCNAME[1]}\" \"${BASH_SOURCE[1]}\" \"${BASH_LINENO[0]}\" \"${msg_template}\")\n    fi\n    #shellcheck disable=SC2059\n    msg=\"$(printf \"${msg_template}\" \"${msg_args[@]}\")\"\n\n    if [ -n \"${DEBUG_WITH_BATS}\" ]; then\n        printf \"%s %b%s%b\\n\" \"${ERROR_ICON}\" \"${TEXT_RED}\" \"${msg}\" \"${TEXT_CLEAR}\" >&3\n    else\n        printf \"%s %b%s%b\\n\" \"${ERROR_ICON}\" \"${TEXT_RED}\" \"${msg}\" \"${TEXT_CLEAR}\" >&2\n    fi\n\n    if [ -n \"${SLACK_WEBHOOK}\" ]; then\n        if [ -f \"$(output_file)\" ]; then\n            slack -s error -m \"ERROR: ${msg}\" -f \"$(output_file)\" -T 5\n        else\n            slack -s error -m \"ERROR: ${msg}\"\n        fi\n    fi\n    exit 1\n}\n\n# Write warning message, send warning to configured\n# slack\n#\n# $1: Warning message\nfunction warn() {\n    local msg_template=\"${1}\"\n    local i=$(( ${#} - 1 ))\n    local msg_args=(\"${@:2:$i}\")\n\n    #shellcheck disable=SC2059\n    msg=\"$(printf \"${msg_template}\" \"${msg_args[@]}\")\"\n\n    printf \"%s %b%s%b\\n\" \"${WARNING_ICON}\" \"${TEXT_YELLOW}\" \"${msg}\" \"${TEXT_CLEAR}\" >&2\n\n    if [ -n \"${SLACK_WEBHOOK}\" ]; then\n        if [ -f \"$(output_file)\" ]; then\n            slack -s warn -m \"WARNING: ${msg}\" -f \"$(output_file)\"\n        else\n            slack -s warn -m \"WARNING: ${msg}\"\n        fi\n    fi\n}\n\n# Write an informational message\nfunction info() {\n    local msg_template=\"${1}\\n\"\n    local i=$(( ${#} - 1 ))\n    local msg_args=(\"${@:2:$i}\")\n\n    #shellcheck disable=SC2059\n    printf \"${msg_template}\" \"${msg_args[@]}\" >&2\n}\n\n# Execute command while redirecting all output to\n# a file (file is used within fail mesage on when\n# command is unsuccessful). Final argument is the\n# error message used when the command fails.\n#\n# $@{1:$#-1}: Command to execute\n# $@{$#}: Failure message\nfunction wrap() {\n    local i=$((${#} - 1))\n    if ! wrap_raw \"${@:1:$i}\"; then\n        cat \"$(output_file)\"\n        failure \"${@:$#}\"\n    fi\n    rm \"$(output_file)\"\n}\n\n# Execute command while redirecting all output to\n# a file. Exit status is returned.\nfunction wrap_raw() {\n    output_file \"clean\" > /dev/null 2>&1\n    \"${@}\" > \"$(output_file)\" 2>&1\n    return $?\n}\n\n# Execute command while redirecting all output to\n# a file (file is used within fail mesage on when\n# command is unsuccessful). Command output will be\n# streamed during execution. Final argument is the\n# error message used when the command fails.\n#\n# $@{1:$#-1}: Command to execute\n# $@{$#}: Failure message\nfunction wrap_stream() {\n    i=$((${#} - 1))\n    if ! wrap_stream_raw \"${@:1:$i}\"; then\n        failure \"${@:$#}\"\n    fi\n    rm \"$(output_file)\"\n}\n\n# Execute command while redirecting all output\n# to a file. Command output will be streamed\n# during execution. Exit status is returned\nfunction wrap_stream_raw() {\n    output_file \"clean\"\n    \"${@}\" > \"$(output_file)\" 2>&1 &\n    pid=$!\n    until [ -f \"$(output_file)\" ]; do\n        sleep 0.1\n    done\n    tail -f --quiet --pid \"${pid}\" \"$(output_file)\"\n    wait \"${pid}\"\n    return $?\n}\n\n\n# Send command to packet device and wrap\n# execution\n# $@{1:$#-1}: Command to execute\n# $@{$#}: Failure message\nfunction pkt_wrap() {\n    wrap packet-exec run -quiet -- \"${@}\"\n}\n\n# Send command to packet device and wrap\n# execution\n# $@: Command to execute\nfunction pkt_wrap_raw() {\n    wrap_raw packet-exec run -quiet -- \"${@}\"\n}\n\n# Send command to packet device and wrap\n# execution with output streaming\n# $@{1:$#-1}: Command to execute\n# $@{$#}: Failure message\nfunction pkt_wrap_stream() {\n    wrap_stream packet-exec run -quiet -- \"${@}\"\n}\n\n# Send command to packet device and wrap\n# execution with output streaming\n# $@: Command to execute\nfunction pkt_wrap_stream_raw() {\n    wrap_stream_raw packet-exec run -quiet -- \"${@}\"\n}\n\n# Get the full path directory for a given\n# file path. File is not required to exist.\n# NOTE: Parent directories of given path will\n#       be created.\n#\n# $1: file path\nfunction file_directory() {\n    local path=\"${1?File path is required}\"\n    local dir\n    if [[ \"${path}\" != *\"/\"* ]]; then\n        dir=\".\"\n    else\n        dir=\"${path%/*}\"\n    fi\n    if [ ! -d \"${dir}\" ]; then\n        mkdir -p \"${dir}\" ||\n            failure \"Could not create directory (%s)\" \"${dir}\"\n    fi\n    pushd \"${dir}\"\n    dir=\"$(pwd)\" ||\n        failure \"Could not read directory path (%s)\" \"${dir}\"\n    popd\n    printf \"%s\" \"${dir}\"\n}\n\n# Wait until the number of background jobs falls below\n# the maximum number provided. If the max number was reached\n# and waiting was performed until a process completed, the\n# string \"waited\" will be printed to stdout.\n#\n# NOTE: using `wait -n` would be cleaner but only became\n#       available in bash as of 4.3\n#\n# $1: maximum number of jobs\nfunction background_jobs_limit() {\n    local max=\"${1}\"\n    if [ -z \"${max}\" ] || [[ \"${max}\" = *[!0123456789]* ]]; then\n        failure \"Maximum number of background jobs required\"\n    fi\n\n    local debug_printed\n    local jobs\n    mapfile -t jobs <<< \"$(jobs -p)\" ||\n        failure \"Could not read background job list\"\n    while [ \"${#jobs[@]}\" -ge \"${max}\" ]; do\n        if [ -z \"${debug_printed}\" ]; then\n            debug \"max background jobs reached (%d), waiting for free process\" \"${max}\"\n            debug_printed=\"1\"\n        fi\n        sleep 1\n        jobs=()\n        local j_pids\n        mapfile -t j_pids <<< \"$(jobs -p)\" ||\n            failure \"Could not read background job list\"\n        for j in \"${j_pids[@]}\"; do\n            if kill -0 \"${j}\" > /dev/null 2>&1; then\n                jobs+=( \"${j}\" )\n            fi\n        done\n    done\n    if [ -n \"${debug_printed}\" ]; then\n        debug \"background jobs count (%s) under max, continuing\" \"${#jobs[@]}\"\n        printf \"waited\"\n    fi\n}\n\n# Reap a completed background process. If the process is\n# not complete, the process is ignored. The success/failure\n# returned from this function only applies to the process\n# identified by the provided PID _if_ the matching PID value\n# was written to stdout\n#\n# $1: PID\nfunction reap_completed_background_job() {\n    local pid=\"${1}\"\n    if [ -z \"${pid}\" ]; then\n        failure \"PID of process to reap is required\"\n    fi\n    if kill -0 \"${pid}\" > /dev/null 2>&1; then\n        debug \"requested pid to reap (%d) has not completed, ignoring\" \"${pid}\"\n        return 0\n    fi\n    # The pid can be reaped so output the pid to indicate\n    # any error is from the job\n    printf \"%s\" \"${pid}\"\n    if ! wait \"${pid}\"; then\n        local code=\"${?}\"\n        debug \"wait error code %d returned for pid %d\" \"${code}\" \"${pid}\"\n        return \"${code}\"\n    fi\n\n    return 0\n}\n\n# Creates a cache and adds the provided items\n#\n# -d Optional description\n# -f Force cache (deletes cache if already exists)\n#\n# $1: name of cache\n# $2: artifact(s) to cache (path to artifact or directory containing artifacts)\nfunction create-cache() {\n    local body\n    local force\n    local opt\n    while getopts \":d:f\" opt; do\n        case \"${opt}\" in\n            \"d\") body=\"${OPTARG}\" ;;\n            \"f\") force=\"1\" ;;\n            *) failure \"Invalid flag provided\" ;;\n        esac\n    done\n    shift $((OPTIND-1))\n\n    cache_name=\"${1}\"\n    artifact_path=\"${2}\"\n\n    if [ -z \"${cache_name}\" ]; then\n        failure \"Cache name is required\"\n    fi\n    if [ -z \"${artifact_path}\" ]; then\n        failure \"Artifact path is required\"\n    fi\n\n    # Check for the cache\n    if github_draft_release_exists \"${repo_name}\" \"${cache_name}\"; then\n        # If forcing, delete the cache\n        if [ -n \"${force}\" ]; then\n            debug \"cache '%s' found and force is set, removing\"\n            github_delete_draft_release \"${cache_name}\"\n        else\n            failure \"Cache already exists (name: %s repo: %s)\" \"${cache_name}\" \"${repo_name}\"\n        fi\n    fi\n\n    # If no description is provided, then provide a default\n    if [ -z \"${body}\" ]; then\n        body=\"Cache name: %s\\nCreate time: %s\\nSource run: %s/%s/actions/runs/%s\" \\\n            \"${cache_name}\" \"$(date)\" \"${GITHUB_SERVER_URL}\" \"${GITHUB_REPOSITORY}\" \"${GITHUB_RUN_ID}\"\n    fi\n\n    # Make sure body is formatted\n    if [ -n \"${body}\" ]; then\n        body=\"$(printf \"%b\" \"${body}\")\"\n    fi\n\n    response=\"$(github_create_release -o \"${repo_owner}\" -r \"${repo_name}\" -n \"${cache_name}\" -b \"${body}\")\" ||\n        failure \"Failed to create GitHub release\"\n}\n\n# Retrieve items from cache\n#\n# -r Require cache to exist (failure if not found)\n#\n# $1: cache name\n# $2: destination directory\nfunction restore-cache() {\n    local required\n\n    while getopts \":r\" opt; do\n        case \"${opt}\" in\n            \"r\") required=\"1\" ;;\n            *) failure \"Invalid flag provided\" ;;\n        esac\n    done\n    shift $((OPTIND-1))\n\n    cache_name=\"${1}\"\n    destination=\"${2}\"\n\n    if [ -z \"${cache_name}\" ]; then\n        failure \"Cache name is required\"\n    fi\n    if [ -z \"${destination}\" ]; then\n        failure \"Destination is required\"\n    fi\n\n    # If required, check for the draft release and error if not found\n    if [ -n \"${required}\" ]; then\n        if ! github_draft_release_exists \"${repo_name}\" \"${cache_name}\"; then\n            failure \"Cache '%s' does not exist\" \"${cache_name}\"\n        fi\n    fi\n\n    mkdir -p \"${destination}\" ||\n        failure \"Could not create destination directory (%s)\" \"${destination}\"\n\n    pushd \"${destination}\"\n    github_draft_release_assets \"${repo_name}\" \"${cache_name}\"\n    popd\n}\n\n# Submit given file to Apple's notarization service and\n# staple the notarization ticket.\n#\n# -i UUID: app store connect issuer ID (optional)\n# -j PATH: JSON file containing API key\n# -k ID:   app store connect API key ID (optional)\n# -m SECS: maximum number of seconds to wait (optional, defaults to 600)\n# -o PATH: path to write notarized file (optional, will modify input by default)\n#\n# $1: file to notarize\nfunction notarize_file() {\n    local creds_api_key_id\n    local creds_api_key_path\n    local creds_issuer_id\n    local output_file\n    local max_wait=\"600\"\n\n    local opt\n    while getopts \":i:j:k:m:o:\" opt; do\n        case \"${opt}\" in\n            \"i\") creds_api_key_id=\"${OPTARG}\" ;;\n            \"j\") creds_api_key_path=\"${OPTARG}\" ;;\n            \"k\") creds_issuer_id=\"${OPTARG}\" ;;\n            \"m\") max_wait=\"${OPTARG}\" ;;\n            \"o\") output_file=\"${OPTARG}\" ;;\n            *) failure \"Invalid flag provided\" ;;\n        esac\n    done\n    shift $((OPTIND-1))\n\n    # Validate credentials were provided\n    if [ -z \"${creds_api_key_path}\" ]; then\n        failure \"App store connect key path required for notarization\"\n    fi\n    if [ ! -f \"${creds_api_key_path}\" ]; then\n        failure \"Invalid path provided for app store connect key path (%s)\" \"${creds_api_key_path}\"\n    fi\n\n    # Collect auth related arguments\n    local base_args=( \"--api-key-path\" \"${creds_api_key_path}\" )\n    if [ -n \"${creds_api_key_id}\" ]; then\n        base_args+=( \"--api-key\" \"${creds_api_key_id}\" )\n    fi\n    if [ -n \"${creds_issuer_id}\" ]; then\n        base_args+=( \"--api-issuer\" \"${creds_issuer_id}\" )\n    fi\n\n    local input_file=\"${1}\"\n\n    # Validate the input file\n    if [ -z \"${input_file}\" ]; then\n        failure \"Input file is required for signing\"\n    fi\n    if [ ! -f \"${input_file}\" ]; then\n        failure \"Cannot find input file (%s)\" \"${input_file}\"\n    fi\n\n    # Check that rcodesign is available, and install\n    # it if it is not\n    if ! command -v rcodesign > /dev/null; then\n        debug \"rcodesign executable not found, installing...\"\n        install_github_tool \"indygreg\" \"apple-platform-rs\" \"rcodesign\"\n    fi\n    \n    local notarize_file\n    # If an output file path was defined, copy file\n    # to output location before notarizing\n    if [ -n \"${output_file}\" ]; then\n        file_directory \"${output_file}\"\n        # Remove file if it already exists\n        rm -f \"${output_file}\" ||\n            failure \"Could not modify output file (%s)\" \"${output_file}\"\n        cp -f \"${input_file}\" \"${output_file}\" ||\n            failure \"Could not write to output file (%s)\" \"${output_file}\"\n        notarize_file=\"${output_file}\"\n        debug \"notarizing file '%s' and writing to '%s'\" \"${input_file}\" \"${output_file}\"\n    else\n        notarize_file=\"${input_file}\"\n        debug \"notarizing file in place '%s'\" \"${input_file}\"\n    fi\n\n    # Notarize the file\n    local notarize_output\n    if notarize_output=\"$(rcodesign \\\n        notary-submit \\\n        \"${base_args[@]}\" \\\n        --max-wait-seconds \"${max_wait}\" \\\n        --staple \\\n        \"${notarize_file}\" 2>&1)\"; then\n        return 0\n    fi\n\n    debug \"notarization output: %s\" \"${notarize_output}\"\n\n    # Still here means notarization failure. Pull\n    # the logs from the service before failing\n    local submission_id=\"${notarize_output##*submission ID: }\"\n    submission_id=\"${submission_id%%$'\\n'*}\"\n    rcodesign \\\n        notary-log \\\n        \"${base_args[@]}\" \\\n        \"${submission_id}\"\n\n    failure \"Failed to notarize file (%s)\" \"${input_file}\"\n}\n\n# Sign a file using signore. Will automatically apply\n# modified retry settings when larger files are submitted.\n#\n# -b NAME: binary identifier (macOS only)\n# -e PATH: path to entitlements file (macOS only)\n# -o PATH: path to write signed file (optional, will overwrite input by default)\n# $1: file to sign\n#\n# NOTE: If signore is not installed, a HASHIBOT_TOKEN is\n#       required for downloading the signore release. The\n#       token can also be set in SIGNORE_GITHUB_TOKEN if\n#       the HASHIBOT_TOKEN is already set\n#\n# NOTE: SIGNORE_CLIENT_ID, SIGNORE_CLIENT_SECRET, and SIGNORE_SIGNER\n#       environment variables must be set prior to calling this function\nfunction sign_file() {\n    # Set 50M to be a largish file\n    local largish_file_size=\"52428800\"\n\n    # Signore environment variables are required. Check\n    # that they are set.\n    if [ -z \"${SIGNORE_CLIENT_ID}\" ]; then\n        failure \"Cannot sign file, SIGNORE_CLIENT_ID is not set\"\n    fi\n    if [ -z \"${SIGNORE_CLIENT_SECRET}\" ]; then\n        failure \"Cannot sign file, SIGNORE_CLIENT_SECRET is not set\"\n    fi\n    if [ -z \"${SIGNORE_SIGNER}\" ]; then\n        failure \"Cannot sign file, SIGNORE_SIGNER is not set\"\n    fi\n\n    local binary_identifier=\"\"\n    local entitlements=\"\"\n    local output_file=\"\"\n\n    local opt\n    while getopts \":b:e:o:\" opt; do\n        case \"${opt}\" in\n            \"b\") binary_identifier=\"${OPTARG}\" ;;\n            \"e\") entitlements=\"${OPTARG}\" ;;\n            \"o\") output_file=\"${OPTARG}\" ;;\n            *) failure \"Invalid flag provided\" ;;\n        esac\n    done\n    shift $((OPTIND-1))\n\n    local input_file=\"${1}\"\n\n    # Check that a good input file was given\n    if [ -z \"${input_file}\" ]; then\n        failure \"Input file is required for signing\"\n    fi\n    if [ ! -f \"${input_file}\" ]; then\n        failure \"Cannot find input file (%s)\" \"${input_file}\"\n    fi\n\n    # If the output file is not set it's a replacement\n    if [ -z \"${output_file}\" ]; then\n        debug \"output file is unset, will replace input file (%s)\" \"${input_file}\"\n        output_file=\"${input_file}\"\n    fi\n\n    # This will ensure parent directories exist\n    file_directory \"${output_file}\" > /dev/null\n\n    # If signore command is not installed, install it\n    if ! command -v \"signore\" > /dev/null; then\n        local hashibot_token_backup=\"${HASHIBOT_TOKEN}\"\n        # If the signore github token is set, apply it\n        if [ -n \"${SIGNORE_GITHUB_TOKEN}\" ]; then\n            HASHIBOT_TOKEN=\"${SIGNORE_GITHUB_TOKEN}\"\n        fi\n\n        install_hashicorp_tool \"signore\"\n\n        # Restore the hashibot token if it was modified\n        HASHIBOT_TOKEN=\"${hashibot_token_backup}\"\n    fi\n\n    # Define base set of arguments\n    local signore_args=( \"sign\" \"--file\" \"${input_file}\" \"--out\" \"${output_file}\" \"--match-file-mode\" )\n\n    # Check the size of the file to be signed. If it's relatively\n    # large, push up the max retries and lengthen the retry interval\n    # NOTE: Only checked if `wc` is available\n    local file_size=\"0\"\n    if command -v wc > /dev/null; then\n        file_size=\"$(wc -c <\"${input_file}\")\" ||\n            failure \"Could not determine input file size\"\n    fi\n\n    if [ \"${file_size}\" -gt \"${largish_file_size}\" ]; then\n        debug \"largish file detected, adjusting retry settings\"\n        signore_args+=( \"--max-retries\" \"30\" \"--retry-interval\" \"10s\" )\n    fi\n\n    # If a binary identifier was provided then it's a macos signing\n    if [ -n \"${binary_identifier}\" ]; then\n        # shellcheck disable=SC2016\n        template='{type: \"macos\", input_format: \"EXECUTABLE\", binary_identifier: $identifier}'\n        payload=\"$(jq -n --arg identifier \"${binary_identifier}\" \"${template}\")\" ||\n            failure \"Could not create signore payload for macOS signing\"\n        signore_args+=( \"--signer-options\" \"${payload}\" )\n    fi\n\n    # If an entitlement was provided, validate the path\n    # and add it to the args\n    if [ -n \"${entitlements}\" ]; then\n        if [ ! -f \"${entitlements}\" ]; then\n            failure \"Invalid path for entitlements provided (%s)\" \"${entitlements}\"\n        fi\n        signore_args+=( \"--entitlements\" \"${entitlements}\" )\n    fi\n\n    debug \"signing file '%s' with arguments - %s\" \"${input_file}\" \"${signore_args[*]}\"\n\n    signore \"${signore_args[@]}\" ||\n        failure \"Failed to sign file '%s'\" \"${input_file}\"\n\n    info \"successfully signed file (%s)\" \"${input_file}\"\n}\n\n# Create a GPG signature. This uses signore to generate a\n# gpg signature for a given file. If the destination\n# path for the signature is not provided, it will\n# be stored at the origin path with a .sig suffix\n#\n# $1: Path to origin file\n# $2: Path to store signature (optional)\nfunction gpg_sign_file() {\n    # Check that we have something to sign\n    if [ -z \"${1}\" ]; then\n        failure \"Origin file is required for signing\"\n    fi\n\n    if [ ! -f \"${1}\" ]; then\n        failure \"Origin file does not exist (${1})\"\n    fi\n\n    # Validate environment has required signore variables set\n    if [ -z \"${SIGNORE_CLIENT_ID}\" ]; then\n        failure \"Cannot sign file, SIGNORE_CLIENT_ID is not set\"\n    fi\n    if [ -z \"${SIGNORE_CLIENT_SECRET}\" ]; then\n        failure \"Cannot sign file, SIGNORE_CLIENT_SECRET is not set\"\n    fi\n    if [ -z \"${SIGNORE_SIGNER}\" ]; then\n        failure \"Cannot sign file, SIGNORE_SIGNER is not set\"\n    fi\n\n    local origin=\"${1}\"\n    local destination=\"${2}\"\n    if [ -z \"${destination}\" ]; then\n        destination=\"${origin}.sig\"\n        debug \"destination automatically set (%s)\" \"${destination}\"\n    fi\n\n    if ! command -v signore; then\n        debug \"installing signore tool\"\n        install_hashicorp_tool \"signore\"\n    fi\n\n    if [ -e \"${destination}\" ]; then\n        failure \"File already exists at signature destination path (${destination})\"\n    fi\n\n    wrap_stream signore sign --dearmor --file \"${origin}\" --out \"${destination}\" \\\n        \"Failed to sign file\"\n}\n\n# Validate arguments for GitHub release. Checks for\n# two arguments and that second argument is an exiting\n# file asset, or directory.\n#\n# $1: GitHub tag name\n# $2: Asset file or directory of assets\nfunction release_validate() {\n    if [ \"${1}\" = \"\" ]; then\n        failure \"Missing required position 1 argument (TAG) for release\"\n    fi\n    if [ \"${2}\" = \"\" ]; then\n        failure \"Missing required position 2 argument (PATH) for release\"\n    fi\n    if [ ! -e \"${2}\" ]; then\n        failure \"Path provided for release (${2}) does not exist\"\n    fi\n}\n\n# Generate a GitHub release\n#\n# $1: GitHub tag name\n# $2: Asset file or directory of assets\nfunction release() {\n    release_validate \"${@}\"\n    local tag_name=\"${1}\"\n    local assets=\"${2}\"\n    local body\n\n    if [ -z \"${body}\" ]; then\n        body=\"$(release_details \"${tag_name}\")\"\n    fi\n\n    response=\"$(github_create_release -o \"${repo_owner}\" -r \"${repo_name}\" -t \"${tag_name}\" -n \"${tag_name}\" -b \"${body}\")\" ||\n        failure \"Failed to create GitHub release\"\n    local release_id\n    release_id=\"$(printf \"%s\" \"${response}\" | jq -r '.id')\" ||\n        failure \"Failed to extract release ID from response for %s on %s\" \"${tag_name}\" \"${repository}\"\n\n    github_upload_release_artifacts \"${repo_name}\" \"${release_id}\" \"${assets}\"\n}\n\n# Generate a GitHub prerelease\n#\n# $1: GitHub tag name\n# $2: Asset file or directory of assets\nfunction prerelease() {\n    release_validate \"${@}\"\n    local ptag\n    if [[ \"${1}\" != *\"+\"* ]]; then\n        ptag=\"${1}+${short_sha}\"\n    else\n        ptag=\"${1}\"\n    fi\n    local assets=\"${2}\"\n\n    response=\"$(github_create_release -o \"${repo_owner}\" -r \"${repo_name}\" -t \"${ptag}\" -n \"${ptag}\" -b \"${body}\" -p -m)\" ||\n        failure \"Failed to create GitHub prerelease\"\n    local release_id\n    release_id=\"$(printf \"%s\" \"${response}\" | jq -r '.id')\" ||\n        failure \"Failed to extract prerelease ID from response for %s on %s\" \"${tag_name}\" \"${repository}\"\n\n    github_upload_release_artifacts \"${repo_name}\" \"${release_id}\" \"${assets}\"\n\n    printf \"New prerelease published to %s @ %s\\n\" \"${repo_name}\" \"${ptag}\" >&2\n\n    printf \"%s\" \"${ptag}\"\n}\n\n# Generate a GitHub draft release\n#\n# $1: GitHub release name\n# $2: Asset file or directory of assets\nfunction draft_release() {\n    local ptag=\"${1}\"\n    local assets=\"${2}\"\n\n    response=\"$(github_create_release -o \"${repo_owner}\" -r \"${repo_name}\" -t \"${ptag}\" -n \"${ptag}\" -b \"${body}\" -d)\" ||\n        failure \"Failed to create GitHub draft release\"\n    local release_id\n    release_id=\"$(printf \"%s\" \"${response}\" | jq -r '.id')\" ||\n        failure \"Failed to extract draft release ID from response for %s on %s\" \"${tag_name}\" \"${repository}\"\n\n    github_upload_release_artifacts \"${repo_name}\" \"${release_id}\" \"${assets}\"\n\n    printf \"%s\" \"${ptag}\"\n}\n\n\n# Generate details of the release. This will consist\n# of a link to the changelog if we can properly detect\n# it based on current location.\n#\n# $1: Tag name\n#\n# Returns: details content\nfunction release_details() {\n    local tag_name=\"${1}\"\n    local proj_root\n    if ! proj_root=\"$(git rev-parse --show-toplevel)\"; then\n        return\n    fi\n    if [ -z \"$(git tag -l \"${tag_name}\")\" ] || [ ! -f \"${proj_root}/CHANGELOG.md\" ]; then\n        return\n    fi\n    printf \"CHANGELOG:\\n\\nhttps://github.com/%s/blob/%s/CHANGELOG.md\" \"${repository}\" \"${tag_name}\"\n}\n\n# Check if version string is valid for release\n#\n# $1: Version\n# Returns: 0 if valid, 1 if invalid\nfunction valid_release_version() {\n    if [[ \"${1}\" =~ ^v?[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then\n        return 0\n    else\n        return 1\n    fi\n}\n\n# Validate arguments for HashiCorp release. Ensures asset\n# directory exists, and checks that the SHASUMS and SHASUM.sig\n# files are present.\n#\n# $1: Asset directory\nfunction hashicorp_release_validate() {\n    local directory=\"${1}\"\n    local sums\n    local sigs\n\n    # Directory checks\n    debug \"checking asset directory was provided\"\n    if [ -z \"${directory}\" ]; then\n        failure \"No asset directory was provided for HashiCorp release\"\n    fi\n    debug \"checking that asset directory exists\"\n    if [ ! -d \"${directory}\" ]; then\n        failure \"Asset directory for HashiCorp release does not exist (${directory})\"\n    fi\n\n    # SHASUMS checks\n    debug \"checking for shasums file\"\n    sums=(\"${directory}/\"*SHA256SUMS)\n    if [ ${#sums[@]} -lt 1 ]; then\n        failure \"Asset directory is missing SHASUMS file\"\n    fi\n    debug \"checking for shasums signature file\"\n    sigs=(\"${directory}/\"*SHA256SUMS.sig)\n    if [ ${#sigs[@]} -lt 1 ]; then\n        failure \"Asset directory is missing SHASUMS signature file\"\n    fi\n}\n\n# Verify release assets by validating checksum properly match\n# and that signature file is valid\n#\n# $1: Asset directory\nfunction hashicorp_release_verify() {\n    if [ -z \"${HASHICORP_PUBLIC_GPG_KEY_ID}\" ]; then\n        failure \"Cannot verify release without GPG key ID. Set HASHICORP_PUBLIC_GPG_KEY_ID.\"\n    fi\n\n    local directory=\"${1}\"\n    local gpghome\n\n    pushd \"${directory}\"\n\n    # First do a checksum validation\n    debug \"validating shasums are correct\"\n    wrap shasum -a 256 -c ./*_SHA256SUMS \\\n        \"Checksum validation of release assets failed\"\n    # Next check that the signature is valid\n    gpghome=$(mktemp -qd)\n    export GNUPGHOME=\"${gpghome}\"\n    debug \"verifying shasums signature file using key: %s\" \"${HASHICORP_PUBLIC_GPG_KEY_ID}\"\n    wrap gpg --keyserver keyserver.ubuntu.com --recv \"${HASHICORP_PUBLIC_GPG_KEY_ID}\" \\\n        \"Failed to import HashiCorp public GPG key\"\n    wrap gpg --verify ./*SHA256SUMS.sig ./*SHA256SUMS \\\n        \"Validation of SHA256SUMS signature failed\"\n    rm -rf \"${gpghome}\"\n    popd\n}\n\n# Generate releases-api metadata\n#\n# $1: Product Version\n# $2: Asset directory\nfunction hashicorp_release_generate_release_metadata() {\n    local version=\"${1}\"\n    local directory=\"${2}\"\n\n    if ! command -v bob; then\n        debug \"bob executable not found, installing\"\n        install_hashicorp_tool \"bob\"\n    fi\n\n    local hc_releases_input_metadata=\"input-meta.json\"\n    # The '-metadata-file' flag expects valid json. Contents are not used for Vagrant.\n    echo \"{}\" > \"${hc_releases_input_metadata}\"\n\n    debug \"generating release metadata information\"\n    wrap_stream bob generate-release-metadata \\\n        -metadata-file \"${hc_releases_input_metadata}\" \\\n        -in-dir \"${directory}\" \\\n        -version \"${version}\" \\\n        -out-file \"${hc_releases_metadata_filename}\" \\\n        \"Failed to generate release metadata\"\n\n    rm -f \"${hc_releases_input_metadata}\"\n}\n\n# Upload release metadata and assets to the staging api\n#\n# $1: Product Name (e.g. \"vagrant\")\n# $2: Product Version\n# $3: Asset directory\nfunction hashicorp_release_upload_to_staging() {\n    local product=\"${1}\"\n    local version=\"${2}\"\n    local directory=\"${3}\"\n\n    if ! command -v \"hc-releases\"; then\n        debug \"releases-api executable not found, installing\"\n        install_hashicorp_tool \"releases-api\"\n    fi\n\n    if [ -z \"${HC_RELEASES_STAGING_HOST}\" ]; then\n        failure \"Missing required environment variable HC_RELEASES_STAGING_HOST\"\n    fi\n    if [ -z \"${HC_RELEASES_STAGING_KEY}\" ]; then\n       failure \"Missing required environment variable HC_RELEASES_STAGING_KEY\"\n    fi\n\n    export HC_RELEASES_HOST=\"${HC_RELEASES_STAGING_HOST}\"\n    export HC_RELEASES_KEY=\"${HC_RELEASES_STAGING_KEY}\"\n\n    pushd \"${directory}\"\n\n    # Create -file parameter list for hc-releases upload\n    local fileParams=()\n    for file in *; do\n        fileParams+=(\"-file=${file}\")\n    done\n\n    debug \"uploading release assets to staging\"\n    wrap_stream hc-releases upload \\\n        -product \"${product}\" \\\n        -version \"${version}\" \\\n        \"${fileParams[@]}\" \\\n        \"Failed to upload HashiCorp release assets\"\n\n    popd\n\n    debug \"creating release metadata\"\n\n    wrap_stream hc-releases metadata create \\\n        -product \"${product}\" \\\n        -input \"${hc_releases_metadata_filename}\" \\\n        \"Failed to create metadata for HashiCorp release\"\n\n    unset HC_RELEASES_HOST\n    unset HC_RELEASES_KEY\n}\n\n# Promote release from staging to production\n#\n# $1: Product Name (e.g. \"vagrant\")\n# $2: Product Version\nfunction hashicorp_release_promote_to_production() {\n    local product=\"${1}\"\n    local version=\"${2}\"\n\n    if ! command -v \"hc-releases\"; then\n        debug \"releases-api executable not found, installing\"\n        install_hashicorp_tool \"releases-api\"\n    fi\n\n    if [ -z \"${HC_RELEASES_PROD_HOST}\" ]; then\n        failure \"Missing required environment variable HC_RELEASES_PROD_HOST\"\n    fi\n    if [ -z \"${HC_RELEASES_PROD_KEY}\" ]; then\n       failure \"Missing required environment variable HC_RELEASES_PROD_KEY\"\n    fi\n    if [ -z \"${HC_RELEASES_STAGING_KEY}\" ]; then\n       failure \"Missing required environment variable HC_RELEASES_STAGING_KEY\"\n    fi\n\n    export HC_RELEASES_HOST=\"${HC_RELEASES_PROD_HOST}\"\n    export HC_RELEASES_KEY=\"${HC_RELEASES_PROD_KEY}\"\n    export HC_RELEASES_SOURCE_ENV_KEY=\"${HC_RELEASES_STAGING_KEY}\"\n\n    debug \"promoting release to production\"\n    wrap_stream hc-releases promote \\\n        -product \"${product}\" \\\n        -version \"${version}\" \\\n        -source-env staging \\\n        \"Failed to promote HashiCorp release to Production\"\n\n    unset HC_RELEASES_HOST\n    unset HC_RELEASES_KEY\n    unset HC_RELEASES_SOURCE_ENV_KEY\n}\n\n# Send the post-publish sns message\n#\n# $1: Product name (e.g. \"vagrant\") defaults to $repo_name\n# $2: AWS Region of SNS (defaults to us-east-1)\nfunction hashicorp_release_sns_publish() {\n    local message\n    local product=\"${1}\"\n    local region=\"${2}\"\n\n    if [ -z \"${product}\" ]; then\n        product=\"${repo_name}\"\n    fi\n\n    if [ -z \"${region}\" ]; then\n        region=\"us-east-1\"\n    fi\n\n    # Validate the creds properly assume role and function\n    wrap aws_deprecated configure list \\\n        \"Failed to reconfigure AWS credentials for release notification\"\n\n    # Now send the release notification\n    debug \"sending release notification to package repository\"\n    message=$(jq --null-input --arg product \"$product\" '{\"product\": $product}')\n    wrap_stream aws sns publish --region \"${region}\" --topic-arn \"${HC_RELEASES_PROD_SNS_TOPIC}\" --message \"${message}\" \\\n        \"Failed to send SNS message for package repository update\"\n\n    return 0\n}\n\n# Check if a release for the given version\n# has been published to the HashiCorp\n# releases site.\n#\n# $1: Product Name\n# $2: Product Version\nfunction hashicorp_release_exists() {\n    local product=\"${1}\"\n    local version=\"${2}\"\n\n    if curl --silent --fail --head \"https://releases.hashicorp.com/${product}/${product}_${version}/\" > /dev/null ; then\n        debug \"hashicorp release of %s@%s found\" \"${product}\" \"${version}\"\n        return 0\n    fi\n    debug \"hashicorp release of %s@%s not found\" \"${product}\" \"${version}\"\n    return 1\n}\n\n# Generate the SHA256SUMS file for assets\n# in a given directory.\n#\n# $1: Asset Directory\n# $2: Product Name\n# $3: Product Version\nfunction generate_shasums() {\n    local directory=\"${1}\"\n    local product=\"${2}\"\n    local version=\"${3}\"\n\n    pushd \"${directory}\"\n\n    local shacontent\n    debug \"generating shasums file for %s@%s\" \"${product}\" \"${version}\"\n    shacontent=\"$(shasum -a256 ./*)\" ||\n        failure \"Failed to generate shasums in ${directory}\"\n\n    sed 's/\\.\\///g' <( printf \"%s\" \"${shacontent}\" ) > \"${product}_${version}_SHA256SUMS\" ||\n        failure \"Failed to write shasums file\"\n\n    popd\n}\n\n# Generate a HashiCorp releases-api compatible release\n#\n# $1: Asset directory\n# $2: Product Name (e.g. \"vagrant\")\n# $3: Product Version\nfunction hashicorp_release() {\n    local directory=\"${1}\"\n    local product=\"${2}\"\n    local version=\"${3}\"\n\n    # If the version is provided, use the discovered release version\n    if [[ \"${version}\" == \"\" ]]; then\n        version=\"${release_version}\"\n    fi\n\n    debug \"creating hashicorp release - product: %s version: %s assets: %s\" \"${product}\" \"${version}\" \"${directory}\"\n\n    if ! hashicorp_release_exists \"${product}\" \"${version}\"; then\n        # Jump into our artifact directory\n        pushd \"${directory}\"\n\n        # If any sig files happen to have been included in here,\n        # just remove them as they won't be using the correct\n        # signing key\n        rm -f ./*.sig\n\n        # Generate our shasums file\n        debug \"generating shasums file for %s@%s\" \"${product}\" \"${version}\"\n        generate_shasums ./ \"${product}\" \"${version}\"\n\n        # Grab the shasums file and sign it\n        local shasum_files=(./*SHA256SUMS)\n        local shasum_file=\"${shasum_files[0]}\"\n        # Remove relative prefix if found\n        shasum_file=\"${shasum_file##*/}\"\n        debug \"signing shasums file for %s@%s\" \"${product}\" \"${version}\"\n        gpg_sign_file \"${shasum_file[0]}\"\n\n        # Jump back out of our artifact directory\n        popd\n\n        # Run validation and verification on release assets before\n        # we actually do the release.\n        debug \"running release validation for %s@%s\" \"${product}\" \"${version}\"\n        hashicorp_release_validate \"${directory}\"\n        debug \"running release verification for %s@%s\" \"${product}\" \"${version}\"\n        hashicorp_release_verify \"${directory}\"\n\n        # Now that the assets have been validated and verified,\n        # peform the release setps\n        debug \"generating release metadata for %s@%s\" \"${product}\" \"${version}\"\n        hashicorp_release_generate_release_metadata \"${version}\" \"${directory}\"\n        debug \"uploading release artifacts to staging for %s@%s\" \"${product}\" \"${version}\"\n        hashicorp_release_upload_to_staging \"${product}\" \"${version}\" \"${directory}\"\n        debug \"promoting release to production for %s@%s\" \"${product}\" \"${version}\"\n        hashicorp_release_promote_to_production \"${product}\" \"${version}\"\n\n        printf \"HashiCorp release created (%s@%s)\\n\" \"${product}\" \"${version}\"\n    else\n        printf \"hashicorp release not published, already exists (%s@%s)\\n\" \"${product}\" \"${version}\"\n    fi\n\n    # Send a notification to update the package repositories\n    # with the new release.\n    debug \"sending packaging notification for %s@%s\" \"${product}\" \"${version}\"\n    hashicorp_release_sns_publish \"${product}\"\n}\n\n# Check if gem version is already published to RubyGems\n#\n# $1: Name of RubyGem\n# $2: Verision of RubyGem\n# $3: Custom gem server to search (optional)\nfunction is_version_on_rubygems() {\n    local name=\"${1}\"\n    local version=\"${2}\"\n    local gemstore=\"${3}\"\n\n    if [ -z \"${name}\" ]; then\n        failure \"Name is required for version check on %s\" \"${gemstore:-RubyGems.org}\"\n    fi\n\n    if [ -z \"${version}\" ]; then\n        failure \"Version is required for version check on %s\" \"${gemstore:-RubyGems.org}\"\n    fi\n\n    debug \"checking rubygem %s at version %s is currently published\" \"${name}\" \"${version}\"\n    local cmd_args=(\"gem\" \"search\")\n    if [ -n \"${gemstore}\" ]; then\n        debug \"checking rubygem publication at custom source: %s\" \"${gemstore}\"\n        cmd_args+=(\"--clear-sources\" \"--source\" \"${gemstore}\")\n    fi\n    cmd_args+=(\"--remote\" \"--exact\" \"--all\")\n\n    local result\n    result=\"$(\"${cmd_args[@]}\" \"${name}\")\" ||\n        failure \"Failed to retreive remote version list from RubyGems\"\n    local versions=\"${result##*\\(}\"\n    local versions=\"${versions%%)*}\"\n    local oifs=\"${IFS}\"\n    IFS=', '\n    local r=1\n    for v in $versions; do\n        if [ \"${v}\" = \"${version}\" ]; then\n            r=0\n            debug \"rubygem %s at version %s was found\" \"${name}\" \"${version}\"\n            break\n        fi\n    done\n    IFS=\"${oifs}\"\n    return $r\n}\n\n# Check if gem version is already published to hashigems\n#\n# $1: Name of RubyGem\n# $2: Verision of RubyGem\nfunction is_version_on_hashigems() {\n    is_version_on_rubygems \"${1}\" \"${2}\" \"https://gems.hashicorp.com\"\n}\n\n# Build and release project gem to RubyGems\nfunction publish_to_rubygems() {\n    if [ -z \"${RUBYGEMS_API_KEY}\" ]; then\n        failure \"RUBYGEMS_API_KEY is required for publishing to RubyGems.org\"\n    fi\n\n    local gem_file=\"${1}\"\n\n    if [ -z \"${gem_file}\" ]; then\n        failure \"RubyGem file is required for publishing to RubyGems.org\"\n    fi\n\n    if [ ! -f \"${gem_file}\" ]; then\n        failure \"Path provided does not exist or is not a file (%s)\" \"${gem_file}\"\n    fi\n\n    # NOTE: Newer versions of rubygems support setting the\n    #       api key via the GEM_HOST_API_KEY environment\n    #       variable. Config file is still used so that older\n    #       versions can be used for doing pushes.\n    gem_config=\"$(mktemp -p ./)\" ||\n        failure \"Could not create gem configuration file\"\n    # NOTE: The `--` are required due to the double dash\n    #       start of the first argument\n    printf -- \"---\\n:rubygems_api_key: %s\\n\" \"${RUBYGEMS_API_KEY}\" > \"${gem_config}\"\n\n    gem push --config-file \"${gem_config}\" \"${gem_file}\" ||\n        failure \"Failed to publish RubyGem at '%s' to RubyGems.org\" \"${gem_file}\"\n    rm -f \"${gem_config}\"\n}\n\n# Publish gem to the hashigems repository\n#\n# $1: Path to gem file to publish\nfunction publish_to_hashigems() {\n    local path=\"${1}\"\n    if [ -z \"${path}\" ]; then\n        failure \"Path to built gem required for publishing to hashigems\"\n    fi\n\n    debug \"publishing '%s' to hashigems\" \"${path}\"\n\n    # Define all the variables we'll need\n    local user_bin\n    local reaper\n    local invalid\n    local invalid_id\n\n    wrap_stream gem install --user-install --no-document reaper-man \\\n        \"Failed to install dependency for hashigem generation\"\n    user_bin=\"$(ruby -e 'puts Gem.user_dir')/bin\"\n    reaper=\"${user_bin}/reaper-man\"\n\n    debug \"using reaper-man installation at: %s\" \"${reaper}\"\n\n    # Create a temporary directory to work from\n    local tmpdir\n    tmpdir=\"$(mktemp -d -p ./)\" ||\n        failure \"Failed to create working directory for hashigems publish\"\n    mkdir -p \"${tmpdir}/hashigems/gems\" ||\n        failure \"Failed to create gems directory\"\n    wrap cp \"${path}\" \"${tmpdir}/hashigems/gems\" \\\n        \"Failed to copy gem to working directory\"\n    pushd \"${tmpdir}\"\n\n    # Run quick test to ensure bucket is accessible\n    wrap aws s3 ls \"s3://${HASHIGEMS_METADATA_BUCKET}\" \\\n        \"Failed to access hashigems asset bucket\"\n\n    # Grab our remote metadata. If the file doesn't exist, that is always an error.\n    debug \"fetching hashigems metadata file from %s\" \"${HASHIGEMS_METADATA_BUCKET}\"\n    wrap aws s3 cp \"s3://${HASHIGEMS_METADATA_BUCKET}/vagrant-rubygems.list\" ./ \\\n        \"Failed to retrieve hashigems metadata list\"\n\n    # Add the new gem to the metadata file\n    debug \"adding new gem to the metadata file\"\n    wrap_stream \"${reaper}\" package add -S rubygems -p vagrant-rubygems.list ./hashigems/gems/*.gem \\\n        \"Failed to add new gem to hashigems metadata list\"\n    # Generate the repository\n    debug \"generating the new hashigems repository content\"\n    wrap_stream \"${reaper}\" repo generate -p vagrant-rubygems.list -o hashigems -S rubygems \\\n        \"Failed to generate the hashigems repository\"\n    # Upload the updated repository\n    pushd ./hashigems\n    debug \"uploading new hashigems repository content to %s\" \"${HASHIGEMS_PUBLIC_BUCKET}\"\n    wrap_stream aws s3 sync . \"s3://${HASHIGEMS_PUBLIC_BUCKET}\" \\\n        \"Failed to upload the hashigems repository\"\n    # Store the updated metadata\n    popd\n    debug \"uploading updated hashigems metadata file to %s\" \"${HASHIGEMS_METADATA_BUCKET}\"\n    wrap_stream aws s3 cp vagrant-rubygems.list \"s3://${HASHIGEMS_METADATA_BUCKET}/vagrant-rubygems.list\" \\\n        \"Failed to upload the updated hashigems metadata file\"\n\n    # Invalidate cloudfront so the new content is available\n    local invalid\n    debug \"invalidating hashigems cloudfront distribution (%s)\" \"${HASHIGEMS_CLOUDFRONT_ID}\"\n    invalid=\"$(aws cloudfront create-invalidation --distribution-id \"${HASHIGEMS_CLOUDFRONT_ID}\" --paths \"/*\")\" ||\n        failure \"Invalidation of hashigems CDN distribution failed\"\n    local invalid_id\n    invalid_id=\"$(printf '%s' \"${invalid}\" | jq -r \".Invalidation.Id\")\"\n    if [ -z \"${invalid_id}\" ]; then\n        failure \"Failed to determine the ID of the hashigems CDN invalidation request\"\n    fi\n    debug \"hashigems cloudfront distribution invalidation identifer - %s\" \"${invalid_id}\"\n\n    # Wait for the invalidation process to complete\n    debug \"starting wait for hashigems cloudfront distribution invalidation to complete (id: %s)\" \"${invalid_id}\"\n    wrap aws cloudfront wait invalidation-completed --distribution-id \"${HASHIGEMS_CLOUDFRONT_ID}\" --id \"${invalid_id}\" \\\n        \"Failure encountered while waiting for hashigems CDN invalidation request to complete (ID: ${invalid_id})\"\n    debug \"hashigems cloudfront distribution invalidation complete (id: %s)\" \"${invalid_id}\"\n\n    # Clean up and we are done\n    popd\n    rm -rf \"${tmpdir}\"\n}\n\n# Configures git for hashibot usage\nfunction hashibot_git() {\n    wrap git config user.name \"${HASHIBOT_USERNAME}\" \\\n        \"Failed to setup git for hashibot usage (username)\"\n    wrap git config user.email \"${HASHIBOT_EMAIL}\" \\\n        \"Failed to setup git for hashibot usage (email)\"\n    wrap git remote set-url origin \"https://${HASHIBOT_USERNAME}:${HASHIBOT_TOKEN}@github.com/${repository}\" \\\n        \"Failed to setup git for hashibot usage (remote)\"\n}\n\n# Get the default branch name for the current repository\nfunction default_branch() {\n    local s\n    s=\"$(git symbolic-ref refs/remotes/origin/HEAD)\" ||\n        failure \"Failed to determine default branch (is working directory git repository?)\"\n    printf \"%s\" \"${s##*origin/}\"\n}\n\n\n# Send a notification to slack. All flag values can be set with\n# environment variables using the upcased name prefixed with SLACK_,\n# for example: --channel -> SLACK_CHANNEL\n#\n# -c --channel CHAN        Send to channel\n# -u --username USER       Send as username\n# -i --icon URL            User icon image\n# -s --state STATE         Message state (success, warn, error, or color code)\n# -m --message MESSAGE     Message to send\n# -M --message-file PATH   Use file contents as message\n# -f --file PATH           Send raw contents of file in message (displayed in code block)\n# -t --title TITLE         Message title\n# -T --tail NUMBER         Send last NUMBER lines of content from raw message file\n# -w --webhook URL         Slack webhook\nfunction slack() {\n    # Convert any long names to short names\n    for arg in \"$@\"; do\n        shift\n        case \"${arg}\" in\n            \"--channel\") set -- \"${@}\" \"-c\" ;;\n            \"--username\") set -- \"${@}\" \"-u\" ;;\n            \"--icon\") set -- \"${@}\" \"-i\" ;;\n            \"--state\") set -- \"${@}\" \"-s\" ;;\n            \"--message\") set -- \"${@}\" \"-m\" ;;\n            \"--message-file\") set -- \"${@}\" \"-M\" ;;\n            \"--file\") set -- \"${@}\" \"-f\" ;;\n            \"--title\") set -- \"${@}\" \"-t\" ;;\n            \"--tail\") set -- \"${@}\" \"-T\" ;;\n            \"--webhook\") set -- \"${@}\" \"-w\"  ;;\n            *) set -- \"${@}\" \"${arg}\" ;;\n        esac\n    done\n    local OPTIND opt\n    # Default all options to values provided by environment variables\n    local channel=\"${SLACK_CHANNEL}\"\n    local username=\"${SLACK_USERNAME}\"\n    local icon=\"${SLACK_ICON}\"\n    local state=\"${SLACK_STATE}\"\n    local message=\"${SLACK_MESSAGE}\"\n    local message_file=\"${SLACK_MESSAGE_FILE}\"\n    local file=\"${SLACK_FILE}\"\n    local title=\"${SLACK_TITLE}\"\n    local tail=\"${SLACK_TAIL}\"\n    local webhook=\"${SLACK_WEBHOOK}\"\n    while getopts \":c:u:i:s:m:M:f:t:T:w:\" opt; do\n        case \"${opt}\" in\n            \"c\") channel=\"${OPTARG}\" ;;\n            \"u\") username=\"${OPTARG}\" ;;\n            \"i\") icon=\"${OPTARG}\" ;;\n            \"s\") state=\"${OPTARG}\" ;;\n            \"m\") message=\"${OPTARG}\" ;;\n            \"M\") message_file=\"${OPTARG}\" ;;\n            \"f\") file=\"${OPTARG}\" ;;\n            \"t\") title=\"${OPTARG}\" ;;\n            \"T\") tail=\"${OPTARG}\" ;;\n            \"w\") webhook=\"${OPTARG}\" ;;\n            *) failure \"Invalid flag provided to slack\" ;;\n        esac\n    done\n    shift $((OPTIND-1))\n\n    # If we don't have a webhook provided, stop here\n    if [ -z \"${webhook}\" ]; then\n        (>&2 echo \"ERROR: Cannot send Slack notification, webhook unset\")\n        return 1\n    fi\n\n    local footer footer_icon ts\n\n    # If we are using GitHub actions, format the footer\n    if [ -n \"${GITHUB_ACTIONS}\" ]; then\n        if [ -z \"${icon}\" ]; then\n            icon=\"https://ca.slack-edge.com/T024UT03C-WG8NDATGT-f82ae03b9fca-48\"\n        fi\n        if [ -z \"${username}\" ]; then\n            username=\"GitHub\"\n        fi\n        footer_icon=\"https://ca.slack-edge.com/T024UT03C-WG8NDATGT-f82ae03b9fca-48\"\n        footer=\"Actions - <https://github.com/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}/checks|${GITHUB_REPOSITORY}>\"\n    fi\n\n    # If no state was provided, default to good state\n    if [ -z \"${state}\" ]; then\n        state=\"good\"\n    fi\n\n    # Convert state aliases\n    case \"${state}\" in\n        \"success\" | \"good\")\n            state=\"good\";;\n        \"warn\" | \"warning\")\n            state=\"warning\";;\n        \"error\" | \"danger\")\n            state=\"danger\";;\n    esac\n\n    # If we have a message file, read it\n    if [ -n \"${message_file}\" ]; then\n        local message_file_content\n        message_file_content=\"$(<\"${message_file}\")\"\n        if [ -z \"${message}\" ]; then\n            message=\"${message_file_content}\"\n        else\n            message=\"${message}\\n\\n${message_file_content}\"\n        fi\n    fi\n\n    # If we have a file to include, add it now. Files are\n    # displayed as raw content, so be sure to wrap with\n    # backticks\n    if [ -n \"${file}\" ]; then\n        local file_content\n        # If tail is provided, then only include the last n number\n        # of lines in the file\n        if [ -n \"${tail}\" ]; then\n            if ! file_content=\"$(tail -n \"${tail}\" \"${file}\")\"; then\n                file_content=\"UNEXPECTED ERROR: Failed to tail content in file ${file}\"\n            fi\n        else\n            file_content=\"$(<\"${file}\")\"\n        fi\n        if [ -n \"${file_content}\" ]; then\n            message=\"${message}\\n\\n\\`\\`\\`\\n${file_content}\\n\\`\\`\\`\"\n        fi\n    fi\n\n    local attach attach_template payload payload_template ts\n    ts=\"$(date '+%s')\"\n\n    # shellcheck disable=SC2016\n    attach_template='{text: $msg, color: $state, mrkdwn_in: [\"text\"], ts: $time'\n    if [ -n \"${title}\" ]; then\n        # shellcheck disable=SC2016\n        attach_template+=', title: $title'\n    fi\n    if [ -n \"${footer}\" ]; then\n        # shellcheck disable=SC2016\n        attach_template+=', footer: $footer'\n    fi\n    if [ -n \"${footer_icon}\" ]; then\n        # shellcheck disable=SC2016\n        attach_template+=', footer_icon: $footer_icon'\n    fi\n    attach_template+='}'\n\n    attach=$(jq -n \\\n        --arg msg \"$(printf \"%b\" \"${message}\")\" \\\n        --arg title \"${title}\" \\\n        --arg state \"${state}\" \\\n        --arg time \"${ts}\" \\\n        --arg footer \"${footer}\" \\\n        --arg footer_icon \"${footer_icon}\" \\\n        \"${attach_template}\" \\\n        )\n\n    # shellcheck disable=SC2016\n    payload_template='{attachments: [$attachment]'\n    if [ -n \"${username}\" ]; then\n        # shellcheck disable=SC2016\n        payload_template+=', username: $username'\n    fi\n    if [ -n \"${channel}\" ]; then\n        # shellcheck disable=SC2016\n        payload_template+=', channel: $channel'\n    fi\n    if [ -n \"${icon}\" ]; then\n        # shellcheck disable=SC2016\n        payload_template+=', icon_url: $icon'\n    fi\n    payload_template+='}'\n\n    payload=$(jq -n \\\n        --argjson attachment \"${attach}\" \\\n        --arg username \"${username}\" \\\n        --arg channel \"${channel}\" \\\n        --arg icon \"${icon}\" \\\n        \"${payload_template}\" \\\n        )\n\n    debug \"sending slack message with payload: %s\" \"${payload}\"\n    wrap curl -SsL --fail -X POST -H \"Content-Type: application/json\" -d \"${payload}\" \"${webhook}\" \\\n        \"Failed to send slack notification\"\n}\n\n# Install internal HashiCorp tools. These tools are expected to\n# be located in private (though not required) HashiCorp repositories.\n# It will attempt to download the correct artifact for the current\n# platform based on HashiCorp naming conventions. It expects that\n# the name of the repository is the name of the tool.\n#\n# $1: Name of repository\nfunction install_hashicorp_tool() {\n    local tool_name=\"${1}\"\n    local extensions=(\"zip\" \"tar.gz\")\n    local asset release_content tmp\n\n    if [ -z \"${tool_name}\" ]; then\n        failure \"Repository name is required for hashicorp tool install\"\n    fi\n\n    debug \"installing hashicorp tool: %s\" \"${tool_name}\"\n\n    # Swap out repository to force correct github token\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${release_repo}\"\n\n    tmp=\"$(mktemp -d --tmpdir vagrantci-XXXXXX)\" ||\n        failure \"Failed to create temporary working directory\"\n    pushd \"${tmp}\"\n\n    local platform\n    platform=\"$(uname -s)\" || failure \"Failed to get local platform name\"\n    platform=\"${platform,,}\" # downcase the platform name\n\n    local arches=()\n\n    local arch\n    arch=\"$(uname -m)\" || failure \"Failed to get local platform architecture\"\n    arches+=(\"${arch}\")\n\n    # If the architecture is listed as x86_64, add amd64 to the\n    # arches collection. Hashicorp naming scheme is to use amd64 in\n    # the file name, but isn't always followed\n    if [ \"${arch}\" = \"x86_64\" ]; then\n        arches+=(\"amd64\")\n    fi\n\n    release_content=$(github_request -H \"Content-Type: application/json\" \\\n        \"https://api.github.com/repos/hashicorp/${tool_name}/releases/latest\") ||\n      failure \"Failed to request latest releases for hashicorp/${tool_name}\"\n\n    local exten\n    for exten in \"${extensions[@]}\"; do\n        for arch in \"${arches[@]}\"; do\n            local suffix=\"${platform}_${arch}.${exten}\"\n            debug \"checking for release artifact with suffix: %s\" \"${suffix}\"\n            asset=$(printf \"%s\" \"${release_content}\" | jq -r \\\n                '.assets[] | select(.name | contains(\"'\"${suffix}\"'\")) | .url')\n            if [ -n \"${asset}\" ]; then\n                debug \"release artifact found: %s\" \"${asset}\"\n               break\n            fi\n        done\n        if [ -n \"${asset}\" ]; then\n            break\n        fi\n    done\n\n    if [ -z \"${asset}\" ]; then\n        failure \"Failed to find release of hashicorp/${tool_name} for ${platform} ${arch[0]}\"\n    fi\n\n    debug \"tool artifact match found for install: %s\" \"${asset}\"\n\n    github_request -o \"${tool_name}.${exten}\" \\\n        -H \"Accept: application/octet-stream\" \"${asset}\" ||\n        \"Failed to download latest release for hashicorp/${tool_name}\"\n\n    if [ \"${exten}\" = \"zip\" ]; then\n        wrap unzip \"${tool_name}.${exten}\" \\\n            \"Failed to unpack latest release for hashicorp/${tool_name}\"\n    else\n        wrap tar xf \"${tool_name}.${exten}\" \\\n            \"Failed to unpack latest release for hashicorp/${tool_name}\"\n    fi\n\n    rm -f \"${tool_name}.${exten}\"\n\n    local files=( ./* )\n    wrap chmod 0755 ./* \\\n        \"Failed to change mode on latest release for hashicorp/${tool_name}\"\n\n    wrap mv ./* \"${ci_bin_dir}\" \\\n        \"Failed to install latest release for hashicorp/${tool_name}\"\n\n    debug \"new files added to path: %s\" \"${files[*]}\"\n    popd\n    rm -rf \"${tmp}\"\n\n    repository=\"${repository_bak}\" # restore the repository value\n}\n\n# Install tool from GitHub releases. It will fetch the latest release\n# of the tool and install it. The proper release artifact will be matched\n# by a \"linux_amd64\" string. This command is best effort and may not work.\n#\n# $1: Organization name\n# $2: Repository name\n# $3: Tool name (optional)\nfunction install_github_tool() {\n    local org_name=\"${1}\"\n    local r_name=\"${2}\"\n    local tool_name=\"${3}\"\n\n    if [ -z \"${tool_name}\" ]; then\n        tool_name=\"${r_name}\"\n    fi\n\n    local asset release_content tmp\n    local artifact_list artifact basen\n\n    tmp=\"$(mktemp -d --tmpdir vagrantci-XXXXXX)\" ||\n        failure \"Failed to create temporary working directory\"\n    pushd \"${tmp}\"\n\n    debug \"installing github tool %s from %s/%s\" \"${tool_name}\" \"${org_name}\" \"${r_name}\"\n\n    release_content=$(github_request -H \"Content-Type: application/json\" \\\n        \"https://api.github.com/repos/${org_name}/${r_name}/releases/latest\") ||\n        failure \"Failed to request latest releases for ${org_name}/${r_name}\"\n\n    asset=$(printf \"%s\" \"${release_content}\" | jq -r \\\n        '.assets[] | select( ( (.name | contains(\"amd64\")) or (.name | contains(\"x86_64\")) or (.name | contains(\"x86-64\")) ) and (.name | contains(\"linux\")) and (.name | endswith(\"sha256\") | not) and (.name | endswith(\"sig\") | not))  | .url') ||\n        failure \"Failed to detect latest release for ${org_name}/${r_name}\"\n\n    artifact=\"${asset##*/}\"\n    github_request -o \"${artifact}\" -H \"Accept: application/octet-stream\" \"${asset}\" ||\n        \"Failed to download latest release for ${org_name}/${r_name}\"\n\n    basen=\"${artifact##*.}\"\n    if [ \"${basen}\" = \"zip\" ]; then\n        wrap unzip \"${artifact}\" \\\n            \"Failed to unpack latest release for ${org_name}/${r_name}\"\n        rm -f \"${artifact}\"\n    elif [ -n \"${basen}\" ]; then\n        wrap tar xf \"${artifact}\" \\\n            \"Failed to unpack latest release for ${org_name}/${r_name}\"\n        rm -f \"${artifact}\"\n    fi\n\n    artifact_list=(./*)\n\n    # If the artifact only contained a directory, get\n    # the contents of the directory\n    if [ \"${#artifact_list[@]}\" -eq \"1\" ] && [ -d \"${artifact_list[0]}\" ]; then\n        debug \"unpacked artifact contained only directory, inspecting contents\"\n        artifact_list=( \"${artifact_list[0]}/\"* )\n    fi\n\n    local tool_match tool_glob_match executable_match\n    local item\n    for item in \"${artifact_list[@]}\"; do\n        if [ \"${item##*/}\" = \"${tool_name}\" ]; then\n            debug \"tool name match found: %s\" \"${item}\"\n            tool_match=\"${item}\"\n        elif [ -e \"${item}\" ]; then\n            debug \"executable match found: %s\" \"${item}\"\n            executable_match=\"${item}\"\n        elif [[ \"${item}\" = \"${tool_name}\"* ]]; then\n            debug \"tool name glob match found: %s\" \"${item}\"\n            tool_glob_match=\"${item}\"\n        fi\n    done\n\n    # Install based on best match to worst match\n    if [ -n \"${tool_match}\" ]; then\n        debug \"installing %s from tool name match (%s)\" \"${tool_name}\" \"${tool_match}\"\n        mv -f \"${tool_match}\" \"${ci_bin_dir}/${tool_name}\" ||\n            \"Failed to install latest release of %s from %s/%s\" \"${tool_name}\" \"${org_name}\" \"${r_name}\"\n    elif [ -n \"${tool_glob_match}\" ]; then\n        debug \"installing %s from tool name glob match (%s)\" \"${tool_name}\" \"${tool_glob_match}\"\n        mv -f \"${tool_glob_match}\" \"${ci_bin_dir}/${tool_name}\" ||\n            \"Failed to install latest release of %s from %s/%s\" \"${tool_name}\" \"${org_name}\" \"${r_name}\"\n    elif [ -n \"${executable_match}\" ]; then\n        debug \"installing %s from executable file match (%s)\" \"${tool_name}\" \"${executable_match}\"\n        mv -f \"${executable_match}\" \"${ci_bin_dir}/${tool_name}\" ||\n            \"Failed to install latest release of %s from %s/%s\" \"${tool_name}\" \"${org_name}\" \"${r_name}\"\n    else\n        failure \"Failed to locate tool '%s' in latest release from %s/%s\" \"${org_name}\" \"${r_name}\"\n    fi\n\n    popd\n    rm -rf \"${tmp}\"\n}\n\n# Prepare host for packet use. It will validate the\n# required environment variables are set, ensure\n# packet-exec is installed, and setup the SSH key.\nfunction packet-setup() {\n    # First check that we have the environment variables\n    if [ -z \"${PACKET_EXEC_TOKEN}\" ]; then\n        failure \"Cannot setup packet, missing token\"\n    fi\n    if [ -z \"${PACKET_EXEC_PROJECT_ID}\" ]; then\n        failure \"Cannot setup packet, missing project\"\n    fi\n    if [ -z \"${PACKET_SSH_KEY_CONTENT}\" ]; then\n        failure \"Cannot setup packet, missing ssh key\"\n    fi\n\n    install_hashicorp_tool \"packet-exec\"\n\n    # Write the ssh key to disk\n    local content\n    content=\"$(base64 --decode - <<< \"${PACKET_SSH_KEY_CONTENT}\")\" ||\n        failure \"Cannot setup packet, failed to decode key\"\n    touch ./packet-key\n    chmod 0600 ./packet-key\n    printf \"%s\" \"${content}\" > ./packet-key\n    local working_directory\n    working_directory=\"$(pwd)\" ||\n        failure \"Cannot setup packet, failed to determine working directory\"\n    export PACKET_EXEC_SSH_KEY=\"${working_directory}/packet-key\"\n}\n\n# Download artifact(s) from GitHub release. The artifact pattern is simply\n# a substring that is matched against the artifact download URL. Artifact(s)\n# will be downloaded to the working directory.\n#\n# $1: repository name\n# $2: release tag name\n# $3: artifact pattern (optional, all artifacts downloaded if omitted)\nfunction github_release_assets() {\n    local req_args\n    req_args=()\n\n    local  asset_pattern\n    local release_repo=\"${1}\"\n    local release_name=\"${2}\"\n    local asset_pattern=\"${3}\"\n\n    # Swap out repository to force correct github token\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${release_repo}\"\n\n    req_args+=(\"-H\" \"Accept: application/vnd.github+json\")\n    req_args+=(\"https://api.github.com/repos/${repository}/releases/tags/${release_name}\")\n\n    debug \"fetching release asset list for release %s on %s\" \"${release_name}\" \"${repository}\"\n\n    local release_content\n    release_content=$(github_request \"${req_args[@]}\") ||\n        failure \"Failed to request release (${release_name}) for ${repository}\"\n\n    local query=\".assets[]\"\n    if [ -n \"${asset_pattern}\" ]; then\n        debug \"applying release asset list filter %s\" \"${asset_pattern}\"\n        query+=\"$(printf ' | select(.name | contains(\"%s\"))' \"${asset_pattern}\")\"\n    fi\n\n    local asset_list\n    asset_list=$(printf \"%s\" \"${release_content}\" | jq -r \"${query} | .url\") ||\n        failure \"Failed to detect asset in release (${release_name}) for ${release_repo}\"\n\n    local name_list\n    name_list=$(printf \"%s\" \"${release_content}\" | jq -r \"${query} | .name\") ||\n        failure \"Failed to detect asset in release (${release_name}) for ${release_repo}\"\n\n    req_args=()\n    req_args+=(\"-H\" \"Accept: application/octet-stream\")\n\n    local assets asset_names\n    readarray -t assets <  <(printf \"%s\" \"${asset_list}\")\n    readarray -t asset_names < <(printf \"%s\" \"${name_list}\")\n\n    local idx\n    for ((idx=0; idx<\"${#assets[@]}\"; idx++ )); do\n        local asset=\"${assets[$idx]}\"\n        local artifact=\"${asset_names[$idx]}\"\n\n        github_request \"${req_args[@]}\" -o \"${artifact}\" \"${asset}\" ||\n            \"Failed to download asset (${artifact}) in release ${release_name} for ${repository}\"\n        printf \"downloaded release asset %s from release %s on %s\\n\" \"${artifact}\" \"${release_name}\" \"${repository}\"\n    done\n\n    repository=\"${repository_bak}\" # restore the repository value\n}\n\n# Basic helper to create a GitHub prerelease\n#\n# $1: repository name\n# $2: tag name for release\n# $3: path to artifact(s) - single file or directory\nfunction github_prerelease() {\n    local prerelease_repo=\"${1}\"\n    local tag_name=\"${2}\"\n    local artifacts=\"${3}\"\n\n    if [ -z \"${prerelease_repo}\" ]; then\n        failure \"Name of repository required for prerelease release\"\n    fi\n\n    if [ -z \"${tag_name}\" ]; then\n        failure \"Name is required for prerelease release\"\n    fi\n\n    if [ -z \"${artifacts}\" ]; then\n        failure \"Artifacts path is required for prerelease release\"\n    fi\n\n    if [ ! -e \"${artifacts}\" ]; then\n        failure \"No artifacts found at provided path (${artifacts})\"\n    fi\n\n    local prerelease_target=\"${repo_owner}/${prerelease_repo}\"\n\n    # Create the prerelease\n    local response\n    response=\"$(github_create_release -p -t \"${tag_name}\" -o \"${repo_owner}\" -r \"${prerelease_repo}\" )\" ||\n        failure \"Failed to create prerelease on %s/%s\" \"${repo_owner}\" \"${prerelease_repo}\"\n\n    # Extract the release ID from the response\n    local release_id\n    release_id=\"$(printf \"%s\" \"${response}\" | jq -r '.id')\" ||\n        failure \"Failed to extract prerelease ID from response for ${tag_name} on ${prerelease_target}\"\n\n    github_upload_release_artifacts \"${prerelease_repo}\" \"${release_id}\" \"${artifacts}\"\n\n}\n\n# Upload artifacts to a release\n#\n# $1: target repository name\n# $2: release ID\n# $3: path to artifact(s) - single file or directory\nfunction github_upload_release_artifacts() {\n    local target_repo_name=\"${1}\"\n    local release_id=\"${2}\"\n    local artifacts=\"${3}\"\n\n    if [ -z \"${target_repo_name}\" ]; then\n        failure \"Repository name required for release artifact upload\"\n    fi\n\n    if [ -z \"${release_id}\" ]; then\n        failure \"Release ID require for release artifact upload\"\n    fi\n\n    if [ -z \"${artifacts}\" ]; then\n        failure \"Artifacts required for release artifact upload\"\n    fi\n\n    if [ ! -e \"${artifacts}\" ]; then\n        failure \"No artifacts found at provided path for release artifact upload (%s)\" \"${artifacts}\"\n    fi\n\n    # Swap out repository to force correct github token\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${target_repo_name}\"\n\n    local req_args=(\"-X\" \"POST\" \"-H\" \"Content-Type: application/octet-stream\")\n\n    # Now upload the artifacts to the draft release\n    local artifact_name\n    if [ -f \"${artifacts}\" ]; then\n        debug \"uploading %s to release ID %s on %s\" \"${artifact}\" \"${release_id}\" \"${repository}\"\n        artifact_name=\"${artifacts##*/}\"\n        req_args+=(\"https://uploads.github.com/repos/${repository}/releases/${release_id}/assets?name=${artifact_name}\"\n                   \"--data-binary\" \"@${artifacts}\")\n        if ! github_request \"${req_args[@]}\" > /dev/null ; then\n            failure \"Failed to upload artifact '${artifacts}' to draft release on ${repository}\"\n        fi\n        printf \"Uploaded release artifact: %s\\n\" \"${artifact_name}\" >&2\n        # Everything is done so get on outta here\n        return 0\n    fi\n\n    # Push into the directory\n    pushd \"${artifacts}\"\n\n    local artifact_path\n    # Walk through each item and upload\n    for artifact_path in * ; do\n        if [ ! -f \"${artifact_path}\" ]; then\n            debug \"skipping '%s' as it is not a file\" \"${artifact_path}\"\n            continue\n        fi\n        artifact_name=\"${artifact_path##*/}\"\n        debug \"uploading %s/%s to release ID %s on %s\" \"${artifacts}\" \"${artifact_name}\" \"${release_id}\" \"${repository}\"\n        local r_args=( \"${req_args[@]}\" )\n        r_args+=(\"https://uploads.github.com/repos/${repository}/releases/${release_id}/assets?name=${artifact_name}\"\n                   \"--data-binary\" \"@${artifact_path}\")\n        if ! github_request \"${r_args[@]}\" > /dev/null ; then\n            failure \"Failed to upload artifact '${artifact_name}' in '${artifacts}' to draft release on ${repository}\"\n        fi\n        printf \"Uploaded release artifact: %s\\n\" \"${artifact_name}\" >&2\n    done\n\n    repository=\"${repository_bak}\"\n}\n\n# Basic helper to create a GitHub draft release\n#\n# $1: repository name\n# $2: tag name for release\n# $3: path to artifact(s) - single file or directory\nfunction github_draft_release() {\n    local draft_repo=\"${1}\"\n    local tag_name=\"${2}\"\n    local artifacts=\"${3}\"\n\n    if [ -z \"${draft_repo}\" ]; then\n        failure \"Name of repository required for draft release\"\n    fi\n\n    if [ -z \"${tag_name}\" ]; then\n        failure \"Name is required for draft release\"\n    fi\n\n    if [ -z \"${artifacts}\" ]; then\n        failure \"Artifacts path is required for draft release\"\n    fi\n\n    if [ ! -e \"${artifacts}\" ]; then\n        failure \"No artifacts found at provided path (%s)\" \"${artifacts}\"\n    fi\n\n    # Create the draft release\n    local response\n    response=\"$(github_create_release -d -t \"${tag_name}\" -o \"${repo_owner}\" -r \"${draft_repo}\" )\" ||\n        failure \"Failed to create draft release on %s\" \"${repo_owner}/${draft_repo}\"\n\n    # Extract the release ID from the response\n    local release_id\n    release_id=\"$(printf \"%s\" \"${response}\" | jq -r '.id')\" ||\n        failure \"Failed to extract draft release ID from response for %s on %s\" \"${tag_name}\" \"${repo_owner}/${draft_repo}\"\n\n    github_upload_release_artifacts \"${draft_repo}\" \"${release_id}\" \"${artifacts}\"\n}\n\n# Create a GitHub release\n#\n# -b BODY - body of release\n# -c COMMITISH - commitish of release\n# -n NAME - name of the release\n# -o OWNER - repository owner (required)\n# -r REPO - repository name (required)\n# -t TAG_NAME - tag name for release (required)\n# -d - draft release\n# -p - prerelease\n# -g - generate release notes\n# -m - make release latest\n#\n# NOTE: Artifacts for release must be uploaded using `github_upload_release_artifacts`\nfunction github_create_release() {\n    local OPTIND opt owner repo tag_name\n    # Values that can be null\n    local body commitish name\n    # Values we default\n    local draft=\"false\"\n    local generate_notes=\"false\"\n    local make_latest=\"false\"\n    local prerelease=\"false\"\n\n    while getopts \":b:c:n:o:r:t:dpgm\" opt; do\n        case \"${opt}\" in\n            \"b\") body=\"${OPTARG}\" ;;\n            \"c\") commitish=\"${OPTARG}\" ;;\n            \"n\") name=\"${OPTARG}\" ;;\n            \"o\") owner=\"${OPTARG}\" ;;\n            \"r\") repo=\"${OPTARG}\" ;;\n            \"t\") tag_name=\"${OPTARG}\" ;;\n            \"d\") draft=\"true\" ;;\n            \"p\") prerelease=\"true\" ;;\n            \"g\") generate_notes=\"true\" ;;\n            \"m\") make_latest=\"true\" ;;\n            *) failure \"Invalid flag provided to github_create_release\" ;;\n        esac\n    done\n    shift $((OPTIND-1))\n\n    # Sanity check\n    if [ -z \"${owner}\" ]; then\n        failure \"Repository owner value is required for GitHub release\"\n    fi\n\n    if [ -z \"${repo}\" ]; then\n        failure \"Repository name is required for GitHub release\"\n    fi\n\n    if [ -z \"${tag_name}\" ] && [ \"${draft}\" != \"true\" ]; then\n        failure \"Tag name is required for GitHub release\"\n    fi\n\n    if [ \"${draft}\" = \"true\" ] && [ \"${prerelease}\" = \"true\" ]; then\n        failure \"Release cannot be both draft and prerelease\"\n    fi\n\n    # If no name is provided, use the tag name value\n    if [ -z \"${name}\" ]; then\n        name=\"${tag_name}\"\n    fi\n\n    # shellcheck disable=SC2016\n    local payload_template='{tag_name: $tag_name, draft: $draft, prerelease: $prerelease, generate_release_notes: $generate_notes, make_latest: $make_latest'\n    local jq_args=(\"-n\"\n                   \"--arg\" \"tag_name\" \"${tag_name}\"\n                   \"--arg\" \"make_latest\" \"${make_latest}\"\n                   \"--argjson\" \"draft\" \"${draft}\"\n                   \"--argjson\" \"generate_notes\" \"${generate_notes}\"\n                   \"--argjson\" \"prerelease\" \"${prerelease}\"\n                  )\n\n    if [ -n \"${commitish}\" ]; then\n        # shellcheck disable=SC2016\n        payload_template+=', target_commitish: $commitish'\n        jq_args+=(\"--arg\" \"commitish\" \"${commitish}\")\n    fi\n    if [ -n \"${name}\" ]; then\n        # shellcheck disable=SC2016\n        payload_template+=', name: $name'\n        jq_args+=(\"--arg\" \"name\" \"${name}\")\n    fi\n    if [ -n \"${body}\" ]; then\n        # shellcheck disable=SC2016\n        payload_template+=', body: $body'\n        jq_args+=(\"--arg\" \"body\" \"${body}\")\n    fi\n    payload_template+='}'\n\n    # Generate the payload\n    local payload\n    payload=\"$(jq \"${jq_args[@]}\" \"${payload_template}\" )\" ||\n        failure \"Could not generate GitHub release JSON payload\"\n\n    local target_repo=\"${owner}/${repo}\"\n    # Set repository to get correct token behavior on request\n    local repository_bak=\"${repository}\"\n    repository=\"${target_repo}\"\n\n    # Craft our request arguments\n    local req_args=(\"-X\" \"POST\" \"https://api.github.com/repos/${target_repo}/releases\" \"-d\" \"${payload}\")\n\n    # Create the draft release\n    local response\n    if ! response=\"$(github_request \"${req_args[@]}\")\"; then\n        failure \"Could not create github release on ${target_repo}\"\n    fi\n\n    # Restore the repository\n    repository=\"${repository_bak}\"\n\n    local rel_type\n    if [ \"${draft}\" = \"true\" ]; then\n        rel_type=\"draft release\"\n    elif [ \"${prerelease}\" = \"true\" ]; then\n        rel_type=\"prerelease\"\n    else\n        rel_type=\"release\"\n    fi\n\n    # Report new draft release was created\n    printf \"New %s '%s' created on '%s'\\n\" \"${rel_type}\" \"${tag_name}\" \"${target_repo}\" >&2\n\n    # Print the response\n    printf \"%s\" \"${response}\"\n}\n\n# Check if a github release exists by tag name\n# NOTE: This can be used for release and prerelease checks.\n#       Draft releases must use the github_draft_release_exists\n#       function.\n#\n# $1: repository name\n# $2: release tag name\nfunction github_release_exists() {\n    local release_repo=\"${1}\"\n    local release_name=\"${2}\"\n\n    if [ -z \"${release_repo}\" ]; then\n        failure \"Repository name required for release lookup\"\n    fi\n    if [ -z \"${release_name}\" ]; then\n        failure \"Release name required for release lookup\"\n    fi\n\n    # Override repository value to get correct token automatically\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${release_repo}\"\n\n    local result=\"1\"\n    if github_request \\\n        -H \"Accept: application/vnd.github+json\" \\\n        \"https://api.github.com/repos/${repository}/releases/tags/${release_name}\" > /dev/null; then\n        debug \"release '${release_name}' found in ${repository}\"\n        result=\"0\"\n    else\n        debug \"release '${release_name}' not found in ${repository}\"\n    fi\n\n    # Restore repository value\n    repository=\"${repository_bak}\"\n\n    return \"${result}\"\n}\n\n# Check if a github release exists using fuzzy match\n#\n# $1: repository name\n# $2: release name\nfunction github_release_exists_fuzzy() {\n    local release_repo=\"${1}\"\n    local release_name=\"${2}\"\n\n    if [ -z \"${release_repo}\" ]; then\n        failure \"Repository name required for draft release lookup\"\n    fi\n    if [ -z \"${release_name}\" ]; then\n        failure \"Release name required for draft release lookup\"\n    fi\n\n    # Override repository value to get correct token automatically\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${release_repo}\"\n\n    local page=$((1))\n    local matched_name\n\n    while [ -z \"${release_content}\" ]; do\n        local release_list\n        release_list=\"$(github_request \\\n            -H \"Content-Type: application/json\" \\\n            \"https://api.github.com/repos/${repository}/releases?per_page=100&page=${page}\")\" ||\n            failure \"Failed to request releases list for ${repository}\"\n\n        # If there's no more results, just bust out of the loop\n        if [ \"$(jq 'length' <( printf \"%s\" \"${release_list}\" ))\" -lt \"1\" ]; then\n            break\n        fi\n\n        local names name_list n matched_name\n        name_list=\"$(printf \"%s\" \"${release_list}\" | jq '.[] | .name')\" ||\n            failure \"Could not generate name list\"\n\n        # shellcheck disable=SC2206\n        names=( $name_list )\n        for n in \"${names[@]}\"; do\n            if [[ \"${n}\" =~ $release_name ]]; then\n                matched_name=\"${n}\"\n                break\n            fi\n        done\n\n        if [ -n \"${matched_name}\" ]; then\n            break\n        fi\n\n        ((page++))\n    done\n\n    # Restore the $repository value\n    repository=\"${repository_bak}\"\n\n    if [ -z \"${matched_name}\" ]; then\n        debug \"did not locate release named %s for %s\" \"${release_name}\" \"${repo_owner}/${release_repo}\"\n        return 1\n    fi\n\n    debug \"found release name %s in %s (pattern: %s)\" \"${matched_name}\" \"${repo_owner}/${release_repo}\" \"${release_name}\"\n    return 0\n}\n\n# Check if a draft release exists by name\n#\n# $1: repository name\n# $2: release name\nfunction github_draft_release_exists() {\n    local release_repo=\"${1}\"\n    local release_name=\"${2}\"\n\n    if [ -z \"${release_repo}\" ]; then\n        failure \"Repository name required for draft release lookup\"\n    fi\n    if [ -z \"${release_name}\" ]; then\n        failure \"Release name required for draft release lookup\"\n    fi\n\n    # Override repository value to get correct token automatically\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${release_repo}\"\n\n    local page=$((1))\n    local release_content\n\n    while [ -z \"${release_content}\" ]; do\n        local release_list\n        release_list=\"$(github_request \\\n            -H \"Content-Type: application/json\" \\\n            \"https://api.github.com/repos/${repository}/releases?per_page=100&page=${page}\")\" ||\n            failure \"Failed to request releases list for ${repository}\"\n\n        # If there's no more results, just bust out of the loop\n        if [ \"$(jq 'length' <( printf \"%s\" \"${release_list}\" ))\" -lt \"1\" ]; then\n            break\n        fi\n\n        query=\"$(printf '.[] | select(.name == \"%s\")' \"${release_name}\")\"\n\n        release_content=$(printf \"%s\" \"${release_list}\" | jq -r \"${query}\")\n\n        ((page++))\n    done\n\n    # Restore the $repository value\n    repository=\"${repository_bak}\"\n\n    if [ -z \"${release_content}\" ]; then\n        debug \"did not locate draft release named %s for %s\" \"${release_name}\" \"${repo_owner}/${release_repo}\"\n        return 1\n    fi\n\n    debug \"found draft release name %s in %s\" \"${release_name}\" \"${repo_owner}/${release_repo}\"\n    return 0\n}\n\n# Download artifact(s) from GitHub draft release. A draft release is not\n# attached to a tag and therefore is referenced by the release name directly.\n# The artifact pattern is simply a substring that is matched against the\n# artifact download URL. Artifact(s) will be downloaded to the working directory.\n#\n# $1: repository name\n# $2: release name\n# $3: artifact pattern (optional, all artifacts downloaded if omitted)\nfunction github_draft_release_assets() {\n    local release_repo_name=\"${1}\"\n    local release_name=\"${2}\"\n    local asset_pattern=\"${3}\"\n\n    if [ -z \"${release_repo_name}\" ]; then\n        failure \"Repository name is required for draft release asset fetching\"\n    fi\n    if [ -z \"${release_name}\" ]; then\n        failure \"Draft release name is required for draft release asset fetching\"\n    fi\n\n    # Override repository value to get correct token automatically\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${release_repo_name}\"\n\n    local page=$((1))\n    local release_content query\n    while [ -z \"${release_content}\" ]; do\n        local release_list\n        release_list=$(github_request -H \"Content-Type: application/json\" \\\n            \"https://api.github.com/repos/${repository}/releases?per_page=100&page=${page}\") ||\n            failure \"Failed to request releases list for ${repository}\"\n\n        # If there's no more results, just bust out of the loop\n        if [ \"$(jq 'length' <( printf \"%s\" \"${release_list}\" ))\" -lt \"1\" ]; then\n            debug \"did not locate draft release named %s in %s\" \"${release_name}\" \"${repository}\"\n            break\n        fi\n\n        query=\"$(printf '.[] | select(.name == \"%s\")' \"${release_name}\")\"\n        release_content=$(printf \"%s\" \"${release_list}\" | jq -r \"${query}\")\n\n        ((page++))\n    done\n\n    query=\".assets[]\"\n    if [ -n \"${asset_pattern}\" ]; then\n        debug \"apply pattern filter to draft assets: %s\" \"${asset_pattern}\"\n        query+=\"$(printf ' | select(.name | contains(\"%s\"))' \"${asset_pattern}\")\"\n    fi\n\n    local asset_list\n    asset_list=$(printf \"%s\" \"${release_content}\" | jq -r \"${query} | .url\") ||\n        failure \"Failed to detect asset in release (${release_name}) for ${repository}\"\n\n    local name_list\n    name_list=$(printf \"%s\" \"${release_content}\" | jq -r \"${query} | .name\") ||\n        failure \"Failed to detect asset in release (${release_name}) for ${repository}\"\n\n    debug \"draft release assets list: %s\" \"${name_list}\"\n\n    local assets asset_names\n    readarray -t assets <  <(printf \"%s\" \"${asset_list}\")\n    readarray -t asset_names < <(printf \"%s\" \"${name_list}\")\n\n    if [ \"${#assets[@]}\" -ne \"${#asset_names[@]}\" ]; then\n        failure \"Failed to match download assets with names in release list for ${repository}\"\n    fi\n\n    local idx\n    for ((idx=0; idx<\"${#assets[@]}\"; idx++ )); do\n        local asset=\"${assets[$idx]}\"\n        local artifact=\"${asset_names[$idx]}\"\n        github_request -o \"${artifact}\" \\\n            -H \"Accept: application/octet-stream\" \"${asset}\" ||\n            \"Failed to download asset in release (${release_name}) for ${repository} - ${artifact}\"\n\n        printf \"downloaded draft release asset at %s\\n\" \"${artifact}\" >&2\n    done\n\n    repository_bak=\"${repository}\" # restore repository value\n}\n\n# This function is identical to the github_draft_release_assets\n# function above with one caveat: it does not download the files.\n# Each file that would be downloaded is simply touched in the\n# current directory. This provides an easy way to check the\n# files that would be downloaded without actually downloading\n# them.\n#\n# An example usage of this can be seen in the vagrant package\n# building where we use this to enable building missing substrates\n# or packages on re-runs and only download the artifacts if\n# actually needed.\nfunction github_draft_release_asset_names() {\n    local release_reponame=\"${1}\"\n    local release_name=\"${2}\"\n    local asset_pattern=\"${3}\"\n\n    if [ -z \"${release_reponame}\" ]; then\n        failure \"Repository name is required for draft release assets names\"\n    fi\n\n    if [ -z \"${release_name}\" ]; then\n        failure \"Release name is required for draft release asset names\"\n    fi\n\n    # Override repository value to get correct token automatically\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${release_reponame}\"\n\n    local page=$((1))\n    local release_content query\n    while [ -z \"${release_content}\" ]; do\n        local release_list\n        release_list=$(github_request H \"Content-Type: application/json\" \\\n            \"https://api.github.com/repos/${repository}/releases?per_page=100&page=${page}\") ||\n            failure \"Failed to request releases list for ${repository}\"\n\n        # If there's no more results, just bust out of the loop\n        if [ \"$(jq 'length' <( printf \"%s\" \"${release_list}\" ))\" -lt \"1\" ]; then\n            debug \"did not locate draft release named %s in %s\" \"${release_name}\" \"${repository}\"\n            break\n        fi\n\n        query=\"$(printf '.[] | select(.name == \"%s\")' \"${release_name}\")\"\n        release_content=$(printf \"%s\" \"${release_list}\" | jq -r \"${query}\")\n\n        ((page++))\n    done\n\n    query=\".assets[]\"\n    if [ -n \"${asset_pattern}\" ]; then\n        debug \"apply pattern filter to draft assets: %s\" \"${asset_pattern}\"\n        query+=\"$(printf ' | select(.name | contains(\"%s\"))' \"${asset_pattern}\")\"\n    fi\n\n    local name_list\n    name_list=$(printf \"%s\" \"${release_content}\" | jq -r \"${query} | .name\") ||\n        failure \"Failed to detect asset in release (${release_name}) for ${repository}\"\n\n    debug \"draft release assets list: %s\" \"${name_list}\"\n\n    local asset_names\n    readarray -t asset_names < <(printf \"%s\" \"${name_list}\")\n\n    local idx\n    for ((idx=0; idx<\"${#asset_names[@]}\"; idx++ )); do\n        local artifact=\"${asset_names[$idx]}\"\n        touch \"${artifact}\" ||\n            failure \"Failed to touch release asset at path: %s\" \"${artifact}\"\n        printf \"touched draft release asset at %s\\n\" \"${artifact}\" >&2\n    done\n\n    repository_bak=\"${repository}\" # restore repository value\n}\n\n# Delete a github release by tag name\n# NOTE: Releases and prereleases can be deleted using this\n#       function. For draft releases use github_delete_draft_release\n#\n# $1: tag name of release\n# $2: repository name (optional, defaults to current repository name)\nfunction github_delete_release() {\n    local release_name=\"${1}\"\n    local release_repo=\"${2:-$repo_name}\"\n\n    if [ -z \"${release_name}\" ]; then\n        failure \"Release name is required for deletion\"\n    fi\n    if [ -z \"${release_repo}\" ]; then\n        failure \"Repository is required for release deletion\"\n    fi\n\n    # Override repository value to get correct token automatically\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${release_repo}\"\n\n    # Fetch the release first\n    local release_content\n    release_content=\"$(github_request \\\n        -H \"Accept: application/vnd.github+json\" \\\n        \"https://api.github.com/repos/${repository}/releases/tags/${release_name}\")\" ||\n        failure \"Failed to fetch release information for '${release_name}' in ${repository}\"\n\n    # Get the release id to reference in delete request\n    local rel_id\n    rel_id=\"$(jq -r '.id' <( printf \"%s\" \"${release_content}\" ) )\" ||\n        failure \"Failed to read release id for '${release_name}' in ${repository}\"\n\n    debug \"deleting github release '${release_name}' in ${repository} with id ${rel_id}\"\n\n    # Send the deletion request\n    github_request \\\n        -X \"DELETE\" \\\n        -H \"Accept: application/vnd.github+json\" \\\n        \"https://api.github.com/repos/${repository}/releases/${rel_id}\" > /dev/null ||\n        failure \"Failed to delete release '${release_name}' in ${repository}\"\n\n    # Restore repository value\n    repository=\"${repository_bak}\"\n}\n\n# Delete draft release with given name\n#\n# $1: name of draft release\n# $2: repository name (optional, defaults to current repository name)\nfunction github_delete_draft_release() {\n    local draft_name=\"${1}\"\n    local delete_repo=\"${2:-$repo_name}\"\n\n    if [ -z \"${draft_name}\" ]; then\n        failure \"Draft name is required for deletion\"\n    fi\n\n    if [ -z \"${delete_repo}\" ]; then\n        failure \"Repository is required for draft deletion\"\n    fi\n\n    # Override repository value to get correct token automatically\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${delete_repo}\"\n\n    local draft_ids=()\n    local page=$((1))\n    while true; do\n        local release_list list_length\n        release_list=$(github_request -H \"Content-Type: application/json\" \\\n            \"https://api.github.com/repos/${repository}/releases?per_page=100&page=${page}\") ||\n            failure \"Failed to request releases list for draft deletion on ${repository}\"\n        list_length=\"$(jq 'length' <( printf \"%s\" \"${release_list}\" ))\" ||\n            failure \"Failed to calculate release length for draft deletion on ${repository}\"\n\n        # If the list is empty then there are no more releases to process\n        if [ -z \"${list_length}\" ] || [ \"${list_length}\" -lt 1 ]; then\n            debug \"no releases returned for page %d in repository %s\" \"${page}\" \"${repository}\"\n            break\n        fi\n\n        local entry i release_draft release_id release_name\n        for (( i=0; i < \"${list_length}\"; i++ )); do\n            entry=\"$(jq \".[$i]\" <( printf \"%s\" \"${release_list}\" ))\" ||\n                failure \"Failed to read entry for draft deletion on ${repository}\"\n            release_draft=\"$(jq -r '.draft' <( printf \"%s\" \"${entry}\" ))\" ||\n                failure \"Failed to read entry draft for draft deletion on ${repository}\"\n            release_id=\"$(jq -r '.id' <( printf \"%s\" \"${entry}\" ))\" ||\n                failure \"Failed to read entry ID for draft deletion on ${repository}\"\n            release_name=\"$(jq -r '.name' <( printf \"%s\" \"${entry}\" ))\" ||\n                failure \"Failed to read entry name for draft deletion on ${repository}\"\n\n            # If the names don't match, skip\n            if [ \"${release_name}\" != \"${draft_name}\" ]; then\n                debug \"skipping release deletion, name mismatch (%s != %s)\" \"${release_name}\" \"${draft_name}\"\n                continue\n            fi\n\n            # If the release is not a draft, fail\n            if [ \"${release_draft}\" != \"true\" ]; then\n                debug \"skipping release '%s' (ID: %s) from '%s' - release is not a draft\" \"${draft_name}\" \"${release_id}\" \"${repository}\"\n                continue\n            fi\n\n            # If we are here, we found a match\n            draft_ids+=( \"${release_id}\" )\n        done\n        ((page++))\n    done\n\n    # If no draft ids were found, the release was not found\n    # so we can just return success\n    if [ \"${#draft_ids[@]}\" -lt \"1\" ]; then\n        debug \"no draft releases found matching name %s in %s\" \"${draft_name}\" \"${repository}\"\n        repository=\"${repository_bak}\" # restore repository value before return\n        return 0\n    fi\n\n    # Still here? Okay! Delete the draft(s)\n    local draft_id\n    for draft_id in \"${draft_ids[@]}\"; do\n        info \"Deleting draft release %s from %s (ID: %d)\\n\" \"${draft_name}\" \"${repository}\" \"${draft_id}\"\n        github_request -X DELETE \"https://api.github.com/repos/${repository}/releases/${draft_id}\" ||\n            failure \"Failed to prune draft release ${draft_name} from ${repository}\"\n    done\n\n    repository=\"${repository_bak}\" # restore repository value before return\n}\n\n# Delete prerelease with given name\n#\n# $1: tag name of prerelease\n# $2: repository name (optional, defaults to current repository name)\nfunction github_delete_prerelease() {\n    local tag_name=\"${1}\"\n    local delete_repo=\"${2:-$repo_name}\"\n\n    if [ -z \"${tag_name}\" ]; then\n        failure \"Tag name is required for deletion\"\n    fi\n\n    if [ -z \"${delete_repo}\" ]; then\n        failure \"Repository is required for prerelease deletion\"\n    fi\n\n    # Override repository value to get correct token automatically\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${delete_repo}\"\n\n    local prerelease\n    prerelease=$(github_request -H \"Content-Type: application/vnd.github+json\" \\\n        \"https://api.github.com/repos/${repository}/releases/tags/${tag_name}\") ||\n        failure \"Failed to get prerelease %s from %s\" \"${tag_name}\" \"${repository}\"\n    local prerelease_id\n    prerelease_id=\"$(jq -r '.id' <( printf \"%s\" \"${prerelease}\" ))\" ||\n        failure \"Failed to read prerelease ID for %s on %s\" \"${tag_name}\" \"${repository}\"\n    local is_prerelease\n    is_prerelease=\"$(jq -r '.prerelease' <( printf \"%s\" \"${prerelease}\" ))\" ||\n        failure \"Failed to read prerelease status for %s on %s\" \"${tag_name}\" \"${repository}\"\n\n    # Validate the matched release is a prerelease\n    if [ \"${is_prerelease}\" != \"true\" ]; then\n        failure \"Prerelease %s on %s is not marked as a prerelease, cannot delete\" \"${tag_name}\" \"${repository}\"\n    fi\n\n    info \"Deleting prerelease %s from repository %s\" \"${tag_name}\" \"${repository}\"\n    github_request -X DELETE \"https://api.github.com/repos/${repository}/releases/${prerelease_id}\" ||\n        failure \"Failed to delete prerelease %s from %s\" \"${tag_name}\" \"${repository}\"\n\n    repository=\"${repository_bak}\" # restore repository value before return\n}\n\n# Delete any draft releases that are older than the\n# given number of days\n#\n# $1: days\n# $2: repository name (optional, defaults to current repository name)\nfunction github_draft_release_prune() {\n    github_release_prune \"draft\" \"${@}\"\n}\n\n# Delete any prereleases that are older than the\n# given number of days\n#\n# $1: days\n# $2: repository name (optional, defaults to current repository name)\nfunction github_prerelease_prune() {\n    github_release_prune \"prerelease\" \"${@}\"\n}\n\n# Delete any releases of provided type that are older than the\n# given number of days\n#\n# $1: type (prerelease or draft)\n# $2: days\n# $3: repository name (optional, defaults to current repository name)\nfunction github_release_prune() {\n    local prune_type=\"${1}\"\n    if [ -z \"${prune_type}\" ]; then\n        failure \"Type is required for release pruning\"\n    fi\n    if [ \"${prune_type}\" != \"draft\" ] && [ \"${prune_type}\" != \"prerelease\" ]; then\n        failure \"Invalid release pruning type provided '%s' (supported: draft or prerelease)\" \"${prune_type}\"\n    fi\n\n    local days=\"${2}\"\n    if [ -z \"${days}\" ]; then\n        failure \"Number of days to retain is required for pruning\"\n    fi\n    if [[ \"${days}\" = *[!0123456789]* ]]; then\n        failure \"Invalid value provided for days to retain when pruning (%s)\" \"${days}\"\n    fi\n\n    local prune_repo=\"${3:-$repo_name}\"\n    if [ -z \"${prune_repo}\" ]; then\n        failure \"Repository name is required for pruning\"\n    fi\n\n    local prune_seconds now\n    now=\"$(date '+%s')\"\n    prune_seconds=$((\"${now}\"-(\"${days}\" * 86400)))\n\n    # Override repository value to get correct token automatically\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${prune_repo}\"\n\n    debug \"deleting %ss over %d days old from %s\" \"${prune_type}\" \"${days}\" \"${repository}\"\n\n    local page=$((1))\n    while true; do\n        local release_list list_length\n\n        release_list=$(github_request -H \"Accept: application/vnd.github+json\" \\\n            \"https://api.github.com/repos/${repository}/releases?per_page=100&page=${page}\") ||\n            failure \"Failed to request releases list for pruning on ${repository}\"\n\n        list_length=\"$(jq 'length' <( printf \"%s\" \"${release_list}\" ))\" ||\n            failure \"Failed to calculate release length for pruning on ${repository}\"\n\n        if [ -z \"${list_length}\" ] || [ \"${list_length}\" -lt \"1\" ]; then\n            debug \"releases listing page %d for %s is empty\" \"${page}\" \"${repository}\"\n            break\n        fi\n\n        local entry i release_type release_name release_id release_create date_check\n        for (( i=0; i < \"${list_length}\"; i++ )); do\n            entry=\"$(jq \".[${i}]\" <( printf \"%s\" \"${release_list}\" ))\" ||\n                failure \"Failed to read entry for pruning on %s\" \"${repository}\"\n            release_type=\"$(jq -r \".${prune_type}\" <( printf \"%s\" \"${entry}\" ))\" ||\n                failure \"Failed to read entry %s for pruning on %s\" \"${prune_type}\" \"${repository}\"\n            release_name=\"$(jq -r '.name' <( printf \"%s\" \"${entry}\" ))\" ||\n                failure \"Failed to read entry name for pruning on %s\" \"${repository}\"\n            release_id=\"$(jq -r '.id' <( printf \"%s\" \"${entry}\" ))\" ||\n                failure \"Failed to read entry ID for pruning on %s\" \"${repository}\"\n            release_create=\"$(jq -r '.created_at' <( printf \"%s\" \"${entry}\" ))\" ||\n                failure \"Failed to read entry created date for pruning on %s\" \"${repository}\"\n            date_check=\"$(date --date=\"${release_create}\" '+%s')\" ||\n                failure \"Failed to parse entry created date for pruning on %s\" \"${repository}\"\n\n            if [ \"${release_type}\" != \"true\" ]; then\n                debug \"Skipping %s on %s because release is not a %s\" \"${release_name}\" \"${repository}\" \"${prune_type}\"\n                continue\n            fi\n\n            if [ \"$(( \"${date_check}\" ))\" -lt \"${prune_seconds}\" ]; then\n                info \"Deleting release %s from %s\\n\" \"${release_name}\" \"${prune_repo}\"\n                github_request -X DELETE \"https://api.github.com/repos/${repository}/releases/${release_id}\" ||\n                    failure \"Failed to prune %s %s from %s\" \"${prune_type}\" \"${release_name}\" \"${repository}\"\n            fi\n        done\n        ((page++))\n    done\n\n    repository=\"${repository_bak}\" # restore the repository value\n}\n\n# Delete all but the latest N number of releases of the provided type\n#\n# $1: type (prerelease or draft)\n# $2: number of releases to retain\n# $3: repository name (optional, defaults to current repository name)\nfunction github_release_prune_retain() {\n    local prune_type=\"${1}\"\n    if [ -z \"${prune_type}\" ]; then\n        failure \"Type is required for release pruning\"\n    fi\n    if [ \"${prune_type}\" != \"draft\" ] && [ \"${prune_type}\" != \"prerelease\" ]; then\n        failure \"Invalid release pruning type provided '%s' (supported: draft or prerelease)\" \"${prune_type}\"\n    fi\n\n    local retain=\"${2}\"\n    if [ -z \"${retain}\" ]; then\n        failure \"Number of releases to retain is required for pruning\"\n    fi\n    if [[ \"${retain}\" = *[!0123456789]* ]]; then\n        failure \"Invalid value provided for number of releases to retain when pruning (%s)\" \"${days}\"\n    fi\n\n    local prune_repo=\"${3:-$repo_name}\"\n    if [ -z \"${prune_repo}\" ]; then\n        failure \"Repository name is required for pruning\"\n    fi\n\n    # Override repository value to get correct token automatically\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${prune_repo}\"\n\n    debug \"pruning all %s type releases except latest %d releases\" \"${prune_type}\" \"${retain}\"\n    local prune_list=()\n    local page=$((1))\n    while true; do\n        local release_list list_length\n\n        release_list=$(github_request -H \"Accept: application/vnd.github+json\" \\\n            \"https://api.github.com/repos/${repository}/releases?per_page=100&page=${page}&sort=created_at&direction=desc\") ||\n            failure \"Failed to request releases list for pruning on ${repository}\"\n        list_length=\"$(jq 'length' <( printf \"%s\" \"${release_list}\" ))\" ||\n            failure \"Failed to calculate release length for pruning on ${repository}\"\n        if [ -z \"${list_length}\" ] || [ \"${list_length}\" -lt \"1\" ]; then\n            debug \"releases listing page %d for %s is empty\" \"${page}\" \"${repository}\"\n            break\n        fi\n\n        local entry i release_type release_name release_id release_create date_check\n        for (( i=0; i < \"${list_length}\"; i++ )); do\n            entry=\"$(jq \".[${i}]\" <( printf \"%s\" \"${release_list}\" ))\" ||\n                failure \"Failed to read entry for pruning on %s\" \"${repository}\"\n            release_type=\"$(jq -r \".${prune_type}\" <( printf \"%s\" \"${entry}\" ))\" ||\n                failure \"Failed to read entry %s for pruning on %s\" \"${prune_type}\" \"${repository}\"\n            release_name=\"$(jq -r '.name' <( printf \"%s\" \"${entry}\" ))\" ||\n                failure \"Failed to read entry name for pruning on %s\" \"${repository}\"\n            release_id=\"$(jq -r '.id' <( printf \"%s\" \"${entry}\" ))\" ||\n                failure \"Failed to read entry ID for pruning on %s\" \"${repository}\"\n\n            if [ \"${release_type}\" != \"true\" ]; then\n                debug \"Skipping %s on %s because release is not a %s\" \"${release_name}\" \"${repository}\" \"${prune_type}\"\n                continue\n            fi\n\n            debug \"adding %s '%s' to prune list (ID: %s)\" \"${prune_type}\" \"${release_name}\" \"${release_id}\"\n            prune_list+=( \"${release_id}\" )\n        done\n        (( page++ ))\n    done\n\n    local prune_count=\"${#prune_list[@]}\"\n    local prune_trim=$(( \"${prune_count}\" - \"${retain}\" ))\n\n    # If there won't be any remaining items in the list, bail\n    if [ \"${prune_trim}\" -le 0 ]; then\n        debug \"no %ss in %s to prune\" \"${prune_type}\" \"${repository}\"\n        repository=\"${repository_bak}\" # restore the repository value\n        return 0\n    fi\n\n    # Trim down the list to what should be deleted\n    prune_list=(\"${prune_list[@]:$retain:$prune_trim}\")\n\n    # Now delete what is left in the list\n    local r_id\n    for r_id in \"${prune_list[@]}\"; do\n        debug \"deleting release (ID: %s) from %s\" \"${r_id}\" \"${repository}\"\n        github_request -X DELETE \"https://api.github.com/repos/${repository}/releases/${r_id}\" ||\n            failure \"Failed to prune %s %s from %s\" \"${prune_type}\" \"${r_id}\" \"${repository}\"\n    done\n\n    repository=\"${repository_bak}\" # restore the repository value\n}\n\n# Grab the correct github token to use for authentication. The\n# rules used for the token to return are as follows:\n#\n# * only $GITHUB_TOKEN is set: $GITHUB_TOKEN\n# * only $HASHIBOT_TOKEN is set: $HASHIBOT_TOKEN\n#\n# when both $GITHUB_TOKEN and $HASHIBOT_TOKEN are set:\n#\n# * $repository value matches $GITHUB_REPOSITORY: $GITHUB_TOKEN\n# * $repository value does not match $GITHUB_REPOSITORY: $HASHIBOT_TOKEN\n#\n# Will return `0` when a token is returned, `1` when no token is returned\nfunction github_token() {\n    local gtoken\n\n    # Return immediately if no tokens are available\n    if [ -z \"${GITHUB_TOKEN}\" ] && [ -z \"${HASHIBOT_TOKEN}\" ]; then\n        debug \"no github or hashibot token set\"\n        return 1\n    fi\n\n    # Return token if only one token exists\n    if [ -n \"${GITHUB_TOKEN}\" ] && [ -z \"${HASHIBOT_TOKEN}\" ]; then\n        debug \"only github token set\"\n        printf \"%s\\n\" \"${GITHUB_TOKEN}\"\n        return 0\n    elif [ -n \"${HASHIBOT_TOKEN}\" ] && [ -z \"${GITHUB_TOKEN}\" ]; then\n        debug \"only hashibot token set\"\n        printf \"%s\\n\" \"${HASHIBOT_TOKEN}\"\n        return 0\n    fi\n\n    # If the $repository matches the original $GITHUB_REPOSITORY use the local token\n    if [ \"${repository}\" = \"${GITHUB_REPOSITORY}\" ]; then\n        debug \"prefer github token \"\n        printf \"%s\\n\" \"${GITHUB_TOKEN}\"\n        return 0\n    fi\n\n    # Still here, then we send back that hashibot token\n    printf \"%s\\n\" \"${HASHIBOT_TOKEN}\"\n    return 0\n}\n\n# This function is used to make requests to the GitHub API. It\n# accepts the same argument list that would be provided to the\n# curl executable. It will check the response status and if a\n# 429 is received (rate limited) it will pause until the defined\n# rate limit reset time and then try again.\n#\n# NOTE: Informative information (like rate limit pausing) will\n# be printed to stderr. The response body will be printed to\n# stdout. Return value of the function will be the exit code\n# from the curl process.\nfunction github_request() {\n    local request_exit=0\n    local info_prefix=\"__info__\"\n    local info_tmpl=\"${info_prefix}:code=%{response_code}:header=%{size_header}:download=%{size_download}:file=%{filename_effective}\"\n    local raw_response_content\n\n    local curl_cmd=(\"curl\" \"-w\" \"${info_tmpl}\" \"-i\" \"-SsL\" \"--fail\")\n    local gtoken\n\n    # Only add the authentication token if we have one\n    if gtoken=\"$(github_token)\"; then\n        curl_cmd+=(\"-H\" \"Authorization: token ${gtoken}\")\n    fi\n\n    # Attach the rest of the arguments\n    curl_cmd+=(\"${@#}\")\n\n    debug \"initial request: %s\" \"${curl_cmd[*]}\"\n\n    # Make our request\n    raw_response_content=\"$(\"${curl_cmd[@]}\")\" || request_exit=\"${?}\"\n\n    # Define the status here since we will set it in\n    # the conditional below of something weird happens\n    local status\n\n    # Check if our response content starts with the info prefix.\n    # If it does, we need to extract the headers from the file.\n    if [[ \"${raw_response_content}\" = \"${info_prefix}\"* ]]; then\n        debug \"extracting request information from: %s\" \"${raw_response_content}\"\n        raw_response_content=\"${raw_response_content#\"${info_prefix}\":code=}\"\n        local response_code=\"${raw_response_content%%:*}\"\n        debug \"response http code: %s\" \"${response_code}\"\n        raw_response_content=\"${raw_response_content#*:header=}\"\n        local header_size=\"${raw_response_content%%:*}\"\n        debug \"response header size: %s\" \"${header_size}\"\n        raw_response_content=\"${raw_response_content#*:download=}\"\n        local download_size=\"${raw_response_content%%:*}\"\n        debug \"response file size: %s\" \"${download_size}\"\n        raw_response_content=\"${raw_response_content#*:file=}\"\n        local file_name=\"${raw_response_content}\"\n        debug \"response file name: %s\" \"${file_name}\"\n        if [ -f \"${file_name}\" ]; then\n            # Read the headers from the file and place them in the\n            # raw_response_content to be processed\n            local download_fd\n            exec {download_fd}<\"${file_name}\"\n            debug \"file descriptor created for header grab (source: %s): %q\" \"${file_name}\" \"${download_fd}\"\n            debug \"reading response header content from %s\" \"${file_name}\"\n            read -r -N \"${header_size}\" -u \"${download_fd}\" raw_response_content\n            # Close our descriptor\n            debug \"closing file descriptor: %q\" \"${download_fd}\"\n            exec {download_fd}<&-\n            # Now trim the headers from the file content\n            debug \"trimming response header content from %s\" \"${file_name}\"\n            tail -c \"${download_size}\" \"${file_name}\" > \"${file_name}.trimmed\" ||\n                failure \"Could not trim headers from downloaded file (%s)\" \"${file_name}\"\n            mv -f \"${file_name}.trimmed\" \"${file_name}\" ||\n                failure \"Could not replace downloaded file with trimmed file (%s)\" \"${file_name}\"\n        else\n            debug \"expected file not found (%s)\" \"${file_name}\"\n            status=\"${response_code}\"\n        fi\n    else\n        # Since the response wasn't written to a file, trim the\n        # info from the end of the response\n        if [[ \"${raw_response_content}\" != *\"${info_prefix}\"* ]]; then\n            debug \"github request response does not include information footer\"\n            failure \"Unexpected error encountered, partial GitHub response returned\"\n        fi\n        raw_response_content=\"${raw_response_content%\"${info_prefix}\"*}\"\n    fi\n\n    local ratelimit_reset\n    local ratelimit_remaining\n    local response_content=\"\"\n\n    # Read the response into lines for processing\n    local lines\n    mapfile -t lines < <( printf \"%s\" \"${raw_response_content}\" )\n\n    # Process the lines to extract out status and rate\n    # limit information. Populate the response_content\n    # variable with the actual response value\n    local i\n    for (( i=0; i < \"${#lines[@]}\"; i++ )); do\n        # The line will have a trailing `\\r` so just\n        # trim it off\n        local line=\"${lines[$i]%%$'\\r'*}\"\n        # strip any leading/trailing whitespace characters\n        read -rd '' line <<< \"${line}\"\n\n        if [ -z \"${line}\" ] && [[ \"${status}\" = \"2\"* ]]; then\n            local start=\"$(( i + 1 ))\"\n            local remain=\"$(( \"${#lines[@]}\" - \"${start}\" ))\"\n            local response_lines=(\"${lines[@]:$start:$remain}\")\n            response_content=\"${response_lines[*]}\"\n            break\n        fi\n\n        if [[ \"${line}\" == \"HTTP/\"* ]]; then\n            status=\"${line##* }\"\n            debug \"http status found: %d\" \"${status}\"\n        fi\n        if [[ \"${line}\" == \"x-ratelimit-reset\"* ]]; then\n            ratelimit_reset=\"${line##*ratelimit-reset: }\"\n            debug \"ratelimit reset time found: %s\" \"${ratelimit_reset}\"\n        fi\n        if [[ \"${line}\" == \"x-ratelimit-remaining\"* ]]; then\n            ratelimit_remaining=\"${line##*ratelimit-remaining: }\"\n            debug \"ratelimit requests remaining: %d\" \"${ratelimit_remaining}\"\n        fi\n    done\n\n    # If the status was not detected, force an error\n    if [ -z \"${status}\" ]; then\n        failure \"Failed to detect response status for GitHub request\"\n    fi\n\n    # If the status was a 2xx code then everything is good\n    # and we can return the response and be done\n    if [[ \"${status}\" = \"2\"* ]]; then\n        printf \"%s\" \"${response_content}\"\n        return 0\n    fi\n\n    # If we are being rate limited, print a notice and then\n    # wait until the rate limit will be reset\n    if [[ \"${status}\" = \"429\" ]] || [[ \"${status}\" = \"403\" ]]; then\n        debug \"request returned %d status, checking for rate limiting\" \"${status}\"\n\n        # If the ratelimit reset was not detected force an error\n        if [ -z \"${ratelimit_reset}\" ]; then\n            failure \"Failed to detect rate limit reset time for GitHub request\"\n        fi\n\n        # If there are requests still available against the ratelimt\n        # and the status is a 403, then it's an actual 403 and not\n        # a ratelimit\n        if [[ \"${status}\" = \"403\" ]] && [[ \"${ratelimit_remaining}\" -gt 0 ]]; then\n            failure \"Request failed with 403 status response\"\n        fi\n\n        debug \"rate limiting has been detected on request\"\n\n        local reset_date\n        reset_date=\"$(date --date=\"@${ratelimit_reset}\")\" ||\n            failure \"Failed to GitHub parse ratelimit reset timestamp (${ratelimit_reset})\"\n\n        local now\n        now=\"$( date '+%s' )\" || failure \"Failed to get current timestamp in ratelimit check\"\n        local reset_wait=\"$(( \"${ratelimit_reset}\" - \"${now}\" + 2))\"\n\n        printf \"GitHub rate limit encountered, reset at %s (waiting %d seconds)\\n\" \\\n            \"${reset_date}\" \"${reset_wait}\" >&2\n\n        sleep \"${reset_wait}\" || failure \"Pause for GitHub rate limited request retry failed\"\n\n        github_request \"${@}\"\n        return \"${?}\"\n    fi\n\n    # At this point we just need to return error information\n    printf \"GitHub request returned HTTP status: %d\\n\" \"${status}\" >&2\n    printf \"Response body: %s\\n\" \"${response_content}\" >&2\n\n    return \"${request_exit}\"\n}\n\n# Lock issues which have been closed for longer than\n# provided number of days. A date can optionally be\n# provided which will be used as the earliest date to\n# search. A message can optionally be provided which\n# will be added as a comment in the issue before locking.\n#\n# -d: number of days\n# -m: message to include when locking the issue (optional)\n# -s: date to begin searching from (optional)\nfunction lock_issues() {\n    local OPTIND opt days start since message\n    while getopts \":d:s:m:\" opt; do\n        case \"${opt}\" in\n            \"d\") days=\"${OPTARG}\" ;;\n            \"s\") start=\"${OPTARG}\" ;;\n            \"m\") message=\"${OPTARG}\" ;;\n            *) failure \"Invalid flag provided to lock_issues\" ;;\n        esac\n    done\n    shift $((OPTIND-1))\n\n    # If days where not provided, return error\n    if [ -z \"${days}\" ]; then\n        failure \"Number of days since closed required for locking issues\"\n    fi\n    # If a start date was provided, check that it is a format we can read\n    if [ -n \"${start}\" ]; then\n        if ! since=\"$(date --iso-8601=seconds --date=\"${start}\" 2> /dev/null)\"; then\n            failure \"$(printf \"Start date provided for issue locking could not be parsed (%s)\" \"${start}\")\"\n        fi\n    fi\n\n    debug \"locking issues that have been closed for at least %d days\" \"${days}\"\n\n    local req_args=()\n    # Start with basic setup\n    req_args+=(\"-H\" \"Accept: application/vnd.github+json\")\n    # Add authorization header\n    req_args+=(\"-H\" \"Authorization: token ${GITHUB_TOKEN}\")\n    # Construct our request endpoint\n    local req_endpoint=\"https://api.github.com/repos/${repository}/issues\"\n    # Page counter for requests\n    local page=$(( 1 ))\n    # Request arguments\n    local req_params=(\"per_page=20\" \"state=closed\")\n\n    # If we have a start time, include it\n    if [ -n \"${since}\" ]; then\n        req_params+=(\"since=${since}\")\n    fi\n\n    # Compute upper bound for issues we can close\n    local lock_seconds now\n    now=\"$(date '+%s')\"\n    lock_seconds=$((\"${now}\"-(\"${days}\" * 86400)))\n\n    while true; do\n        # Join all request parameters with '&'\n        local IFS_BAK=\"${IFS}\"\n        IFS=\"&\"\n        local all_params=(\"${req_params[*]}\" \"page=${page}\")\n        local params=\"${all_params[*]}\"\n        IFS=\"${IFS_BAK}\"\n\n        local issue_list issue_count\n        # Make our request to get a page of issues\n        issue_list=\"$(github_request \"${req_args[@]}\" \"${req_endpoint}?${params}\")\" ||\n            failure \"Failed to get repository issue list for ${repository}\"\n        issue_count=\"$(jq 'length' <( printf \"%s\" \"${issue_list}\" ))\" ||\n            failure \"Failed to compute count of issues in list for ${repository}\"\n\n        if [ -z \"${issue_count}\" ] || [ \"${issue_count}\" -lt 1 ]; then\n            break\n        fi\n\n        # Iterate through the list\n        local i\n        for (( i=0; i < \"${issue_count}\"; i++ )); do\n            # Extract the issue we are going to process\n            local issue\n            issue=\"$(jq \".[${i}]\" <( printf \"%s\" \"${issue_list}\" ))\" ||\n                failure \"Failed to extract issue from list for ${repository}\"\n\n            # Grab the ID of this issue\n            local issue_id\n            issue_id=\"$(jq -r '.id' <( printf \"%s\" \"${issue}\" ))\" ||\n                failure \"Failed to read ID of issue for ${repository}\"\n\n            # First check if issue is already locked\n            local issue_locked\n            issue_locked=\"$(jq -r '.locked' <( printf \"%s\" \"${issue}\" ))\" ||\n                failure \"Failed to read locked state of issue for ${repository}\"\n\n            if [ \"${issue_locked}\" == \"true\" ]; then\n                debug \"Skipping %s#%s because it is already locked\" \"${repository}\" \"${issue_id}\"\n                continue\n            fi\n\n            # Get the closed date\n            local issue_closed\n            issue_closed=\"$(jq -r '.closed_at' <( printf \"%s\" \"${issue}\" ))\" ||\n                failure \"Failed to read closed at date of issue for ${repository}\"\n\n            # Convert closed date to unix timestamp\n            local date_check\n            date_check=\"$( date --date=\"${issue_closed}\" '+%s' )\" ||\n                failure \"Failed to parse closed at date of issue for ${repository}\"\n\n            # Check if the issue is old enough to be locked\n            if [ \"$(( \"${date_check}\" ))\" -lt \"${lock_seconds}\" ]; then\n                printf \"Locking issue %s#%s\\n\" \"${repository}\" \"${issue_id}\" >&2\n\n                # If we have a comment to add before locking, do that now\n                if [ -n \"${message}\" ]; then\n                    local message_json\n                    message_json=$(jq -n \\\n                        --arg msg \"$(printf \"%b\" \"${message}\")\" \\\n                        '{body: $msg}'\n                        ) || failure \"Failed to create issue comment JSON content for ${repository}\"\n\n                    debug \"adding issue comment before locking on %s#%s\" \"${repository}\" \"${issue_id}\"\n\n                    github_request \"${req_args[@]}\" -X POST \"${req_endpoint}/${issue_id}/comments\" -d \"${message_json}\" ||\n                        failure \"Failed to create issue comment on ${repository}#${issue_id}\"\n                fi\n\n                # Lock the issue\n                github_request \"${req_args[@]}\" -X PUT \"${req_endpoint}/${issue_id}/lock\" -d '{\"lock_reason\":\"resolved\"}' ||\n                    failure \"Failed to lock issue ${repository}#${issue_id}\"\n            fi\n        done\n\n        ((page++))\n    done\n}\n\n# Send a repository dispatch to the defined repository\n#\n# $1: repository name\n# $2: event type (single word string)\n# $n: \"key=value\" pairs to build payload (optional)\n#\nfunction github_repository_dispatch() {\n    local drepo_name=\"${1}\"\n    local event_type=\"${2}\"\n\n    if [ -z \"${drepo_name}\" ]; then\n        failure \"Repository name is required for repository dispatch\"\n    fi\n\n    # shellcheck disable=SC2016\n    local payload_template='{\"vagrant-ci\": $vagrant_ci'\n    local jqargs=(\"--arg\" \"vagrant_ci\" \"true\")\n    local arg\n    for arg in \"${@:3}\"; do\n        local payload_key=\"${arg%%=*}\"\n        local payload_value=\"${arg##*=}\"\n        payload_template+=\", \\\"${payload_key}\\\": \\$${payload_key}\"\n        # shellcheck disable=SC2089\n        jqargs+=(\"--arg\" \"${payload_key}\" \"${payload_value}\")\n    done\n    payload_template+=\"}\"\n\n    # NOTE: we want the arguments to be expanded below\n    local payload\n    payload=$(jq -n \"${jqargs[@]}\" \"${payload_template}\" ) ||\n        failure \"Failed to generate repository dispatch payload\"\n\n    # shellcheck disable=SC2016\n    local msg_template='{event_type: $event_type, client_payload: $payload}'\n    local msg\n    msg=$(jq -n \\\n        --argjson payload \"${payload}\" \\\n        --arg event_type \"${event_type}\" \\\n        \"${msg_template}\" \\\n        ) || failure \"Failed to generate repository dispatch message\"\n\n    # Update repository value to get correct token\n    local repository_bak=\"${repository}\"\n    repository=\"${repo_owner}/${drepo_name}\"\n\n    github_request -X \"POST\" \\\n        -H 'Accept: application/vnd.github.everest-v3+json' \\\n        --data \"${msg}\" \\\n        \"https://api.github.com/repos/${repo_owner}/${drepo_name}/dispatches\" ||\n        failure \"Repository dispatch to ${repo_owner}/${drepo_name} failed\"\n\n    # Restore the repository value\n    repository=\"${repository_bak}\"\n}\n\n# Copy a function to a new name\n#\n# $1: Original function name\n# $2: Copy function name\nfunction copy_function() {\n    local orig=\"${1}\"\n    local new=\"${2}\"\n    local fn\n    fn=\"$(declare -f \"${orig}\")\" ||\n        failure \"Orignal function (${orig}) not defined\"\n    fn=\"${new}${fn#*\"${orig}\"}\"\n    eval \"${fn}\"\n}\n\n# Rename a function to a new name\n#\n# $1: Original function name\n# $2: New function name\nfunction rename_function() {\n    local orig=\"${1}\"\n    copy_function \"${@}\"\n    unset -f \"${orig}\"\n}\n\n# Cleanup wrapper so we get some output that cleanup is starting\nfunction _cleanup() {\n    debug \"* Running cleanup task...\"\n    # Always restore this value for cases where a failure\n    # happened within a function while this value was in\n    # a modified state\n    repository=\"${_repository_backup}\"\n    cleanup\n}\n\n# Stub cleanup method which can be redefined\n# within actual script\nfunction cleanup() {\n    debug \"** No cleanup tasks defined\"\n}\n\n# Only setup our cleanup trap and fail alias when not in testing\nif [ -z \"${BATS_TEST_FILENAME}\" ]; then\n    trap _cleanup EXIT\n    # This is a compatibility alias for existing scripts which\n    # use the common.sh library. BATS support defines a `fail`\n    # function so it has been renamed `failure` to prevent the\n    # name collision. When not running under BATS we enable the\n    # `fail` function so any scripts that have not been updated\n    # will not be affected.\n    copy_function \"failure\" \"fail\"\nfi\n\n# Make sure the CI bin directory exists\nif [ ! -d \"${ci_bin_dir}\" ]; then\n    wrap mkdir -p \"${ci_bin_dir}\" \\\n        \"Failed to create CI bin directory\"\nfi\n\n# Always ensure CI bin directory is in PATH\nif [[ \"${PATH}\" != *\"${ci_bin_dir}\"* ]]; then\n    export PATH=\"${PATH}:${ci_bin_dir}\"\nfi\n\n# Enable debugging. This needs to be enabled with\n# extreme caution when used on public repositories.\n# Output with debugging enabled will likely include\n# secret values which should not be publicly exposed.\n#\n# If repository is public, FORCE_PUBLIC_DEBUG environment\n# variable must also be set.\n\npriv_args=(\"-H\" \"Accept: application/json\")\n# If we have a token available, use it for the check query\nif [ -n \"${HASHIBOT_TOKEN}\" ]; then\n    priv_args+=(\"-H\" \"Authorization: token ${GITHUB_TOKEN}\")\nelif [ -n \"${GITHUB_TOKEN}\" ]; then\n    priv_args+=(\"-H\" \"Authorization: token ${HASHIBOT_TOKEN}\")\nfi\n\nif [ -n \"${GITHUB_ACTIONS}\" ]; then\n    priv_check=\"$(curl \"${priv_args[@]}\" -s \"https://api.github.com/repos/${GITHUB_REPOSITORY}\" | jq .private)\" ||\n        failure \"Repository visibility check failed\"\nfi\n\n# If the value wasn't true we unset it to indicate not private. The\n# repository might actually be private but we weren't supplied a\n# token (or one with correct permissions) so we fallback to the safe\n# assumption of not private.\nif [ \"${priv_check}\" != \"true\" ]; then\n    readonly is_public=\"1\"\n    readonly is_private=\"\"\nelse\n    # shellcheck disable=SC2034\n    readonly is_public=\"\"\n    # shellcheck disable=SC2034\n    readonly is_private=\"1\"\nfi\n\n# Check if we are running a job created by a tag. If so,\n# mark this as being a release job and set the release_version\nif [[ \"${GITHUB_REF}\" == *\"refs/tags/\"* ]]; then\n    export tag=\"${GITHUB_REF##*tags/}\"\n    if valid_release_version \"${tag}\"; then\n        readonly release=1\n        export release_version=\"${tag##*v}\"\n    else\n        readonly release\n    fi\nelse\n    # shellcheck disable=SC2034\n    readonly release\nfi\n\n# Seed an initial output file\noutput_file > /dev/null 2>&1\n"
  },
  {
    "path": ".ci/build",
    "content": "#!/usr/bin/env bash\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nroot=\"$( cd -P \"$( dirname \"$csource\" )/../\" && pwd )\"\n\n. \"${root}/.ci/load-ci.sh\"\n\nif [ \"${#}\" -ne 1 ]; then\n      printf \"Usage: %s ARTIFACTS_DIR\\n\" \"${0}\" >&2\n      exit 1\nfi\n\ndest=\"${1}\"\nmkdir -p \"${dest}\" ||\n     failure \"Could not create destination directory (%s)\" \"${dest}\"\npushd \"${dest}\"\ndest=\"$(pwd)\" || failure \"Could not read destination directory path\"\npopd\n\n# Move to root of project\npushd \"${root}\"\n\ninfo \"Building Vagrant RubyGem...\"\nwrap gem build ./*.gemspec \\\n     \"Failed to build Vagrant RubyGem\"\n\n# Get the path of the gem\nfiles=( vagrant*.gem )\ngem=\"${files[0]}\"\nif [ ! -f \"${gem}\" ]; then\n     debug \"could not locate gem in %s\" \"${files[*]}\"\n     failure \"Unable to locate built Vagrant RubyGem\"\nfi\n\nwrap mv \"${gem}\" \"${dest}\" \\\n     \"Failed to relocate Vagrant RubyGem\"\n\nprintf \"build-artifacts-path=%s\\n\" \"${dest}\"\n"
  },
  {
    "path": ".ci/dev-build",
    "content": "#!/usr/bin/env bash\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nroot=\"$( cd -P \"$( dirname \"$csource\" )/../\" && pwd )\"\n\n. \"${root}/.ci/load-ci.sh\"\n\nif [ \"${#}\" -ne 3 ]; then\n  printf \"Usage: %s BRANCH COMMIT_ID BUILD_TYPE\\n\" \"${0}\" >&2\n  exit 1\nfi\n\nbranch=\"${1}\"\nfull_sha=\"${2}\"\nbuild_type=\"${3}\"\n\nif [ -z \"${branch}\" ]; then\n  failure \"Branch variable is unset, required for dev build\"\nfi\nif [ -z \"${full_sha}\" ]; then\n  failure \"The full_sha variable is unexpectedly missing, cannot trigger dev build\"\nfi\nif [ -z \"${build_type}\" ]; then\n  failure \"The build type is required for triggering dev build\"\nfi\n\n# Trim the reference prefix if needed\nif [[ \"${branch}\" = *\"refs/heads\"* ]]; then\n  debug \"trimming branch reference value - %s\" \"${branch}\"\n  branch=\"${branch##*refs/heads/}\"\n  debug \"trimmed branch value - %s\" \"${branch}\"\nfi\n\ninfo \"Triggering development build %s (%s)\" \"${branch}\" \"${full_sha}\"\ngithub_repository_dispatch \"vagrant-builders\" \"${build_type}\" \"commit_id=${full_sha}\" \"branch=${branch}\"\n"
  },
  {
    "path": ".ci/generate-licenses",
    "content": "#!/usr/bin/env bash\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nroot=\"$( cd -P \"$( dirname \"$csource\" )/../\" && pwd )\"\n\n. \"${root}/.ci/load-ci.sh\"\n\nif [ \"${#}\" -ne 1 ]; then\n    printf \"Usage: %s LICENSE_DIR\\n\" \"${0}\"\n    exit 1\nfi\n\nlicense_dir=\"${1}\"\nif [ ! -d \"${license_dir}\" ]; then\n    mkdir -p \"${license_dir}\" ||\n        failure \"Unable to create license directory\"\nfi\n\npushd \"${license_dir}\"\nlicense_dir=\"$(pwd)\" || failure \"Could not read license directory path\"\npopd\n\n# Move to the root\npushd \"${root}\"\n\ninfo \"Generating Vagrant license files\"\n\nversion=\"$(< ./version.txt)\" ||\n    failure \"Unable to read version file\"\n\nlicense_date=\"$(date \"+%Y\")\" ||\n    failure \"Unable to generate year for license\"\n\nlicense_template=\"./templates/license/license.html.tmpl\"\nlicense_destination=\"${license_dir}/LICENSE.html\"\n\ndebug \"Updating license file: ${license_destination}\"\n\nif [ ! -f \"${license_template}\" ]; then\n    failure \"Unable to locate license template (${license_template})\"\nfi\n\nsed \"s/%VERSION%/${version}/\" \"${license_template}\" > \"${license_destination}\" ||\n    failure \"Unable to update version in ${license_destination}\"\nsed -i \"s/%YEAR%/${license_date}/\" \"${license_destination}\" ||\n    failure \"Unable to update year in ${license_destination}\"\n\nlicense_template=\"./templates/license/license.rtf.tmpl\"\nlicense_destination=\"${license_dir}/LICENSE.rtf\"\n\ndebug \"Updating license file: ${license_destination}\"\n\nif [ ! -f \"${license_template}\" ]; then\n    failure \"Unable to locate license template (${license_template})\"\nfi\n\nsed \"s/%VERSION%/${version}/\" \"${license_template}\" > \"${license_destination}\" ||\n    failure \"Unable to update version in ${license_destination}\"\nsed -i \"s/%YEAR%/${license_date}/\" \"${license_destination}\" ||\n    failure \"Unable to update year in ${license_destination}\"\n\n\nlicense_template=\"./templates/license/license.tmpl\"\nlicense_destination=\"${license_dir}/LICENSE.txt\"\n\ndebug \"Updating license file: ${license_destination}\"\n\nif [ ! -f \"${license_template}\" ]; then\n    failure \"Unable to locate license template (${license_template})\"\nfi\n\nsed \"s/%VERSION%/${version}/\" \"${license_template}\" > \"${license_destination}\" ||\n    failure \"Unable to update version in ${license_destination}\"\nsed -i \"s/%YEAR%/${license_date}/\" \"${license_destination}\" ||\n    failure \"Unable to update year in ${license_destination}\"\n"
  },
  {
    "path": ".ci/load-ci.sh",
    "content": "#!/usr/bin/env bash\n# Copyright IBM Corp. 2019, 2025\n# SPDX-License-Identifier: MPL-2.0\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nif ! root=\"$( cd -P \"$( dirname \"$csource\" )/../\" && pwd )\"; then\n    echo \"⛔ ERROR: Failed to determine root local directory ⛔\" >&2\n    exit 1\nfi\n\nexport root\nexport ci_bin_dir=\"${root}/.ci/.ci-utility-files\"\n\n# shellcheck source=/dev/null\nif ! source \"${ci_bin_dir}/common.sh\"; then\n    echo \"⛔ ERROR: Failed to source Vagrant CI common file ⛔\" >&2\n    exit 1\nfi\nexport PATH=\"${PATH}:${ci_bin_dir}\"\n\n# And we are done!\ndebug \"VagrantCI Loaded\"\n"
  },
  {
    "path": ".ci/nightly-build",
    "content": "#!/usr/bin/env bash\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nroot=\"$( cd -P \"$( dirname \"$csource\" )/../\" && pwd )\"\n\n. \"${root}/.ci/load-ci.sh\"\n\nif [ \"${#}\" -ne 1 ]; then\n  printf \"Usage: %s COMMIT_ID\\n\" \"${0}\" >&2\n  exit 1\nfi\n\nfull_sha=\"${1}\"\n\nif [ -z \"${full_sha}\" ]; then\n  failure \"The full_sha variable is unexpectedly missing, cannot trigger nightly build\"\nfi\n\ninfo \"Triggering nightly build %s (%s)\" \"${tag}\" \"${full_sha}\"\ngithub_repository_dispatch \"vagrant-builders\" \"nightlies\" \"commit_id=${full_sha}\"\n"
  },
  {
    "path": ".ci/release",
    "content": "#!/usr/bin/env bash\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nroot=\"$( cd -P \"$( dirname \"$csource\" )/../\" && pwd )\"\n\n. \"${root}/.ci/load-ci.sh\"\n\nif [ \"${#}\" -ne 2 ]; then\n  printf \"Usage: %s TAG COMMIT_ID\\n\" \"${0}\" >&2\n  exit 1\nfi\n\ntag=\"${1}\"\nfull_sha=\"${2}\"\n\nif [ -z \"${tag}\" ]; then\n  failure \"Tag variable is unset, required for release\"\nfi\nif [ -z \"${full_sha}\" ]; then\n  failure \"The full_sha variable is unexpectedly missing, cannot trigger release\"\nfi\n\n# Trim the prefix of the tag if it hasn't been\nif [[ \"${tag}\" = *\"refs/tags\"* ]]; then\n  debug \"trimming tag reference value - %s\" \"${tag}\"\n  tag=\"${tag##*refs/tags/}\"\n  debug \"trimmed tag value - %s\" \"${tag}\"\nfi\n\ninfo \"Triggering release %s (%s)\" \"${tag}\" \"${full_sha}\"\ngithub_repository_dispatch \"vagrant-builders\" \"hashicorp-release\" \"commit_id=${full_sha}\" \"tag=${tag}\"\n"
  },
  {
    "path": ".ci/release-initiator",
    "content": "#!/usr/bin/env bash\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nroot=\"$( cd -P \"$( dirname \"$csource\" )/../\" && pwd )\"\n\n. \"${root}/.ci/load-ci.sh\"\n\nif [ \"${#}\" -ne 1 ]; then\n  printf \"Usage: %s VERSION\\n\" \"${0}\" >&2\n  exit 1\nfi\n\nversion=\"${1}\"\n\ninfo \"Updating repository files for ${version} release\"\n\nif [[ \"${version}\" = \"v\"* ]]; then\n    failure \"Invalid version format, cannot start with 'v': %s\" \"${version}\"\nfi\n\nif ! valid_release_version \"${version}\"; then\n    failure \"Invalid version format provided: %s\" \"${version}\"\nfi\n\ndebug \"Configuring git\"\nhashibot_git\n\ndebug \"Updating version.txt with version value: %s\" \"${version}\"\nif [ ! -f \"version.txt\" ]; then\n    failure \"Unable to locate version.txt file\"\nfi\n\nprintf \"%s\" \"${version}\" > version.txt\n\ndebug \"Updating CHANGELOG.md\"\nif [ ! -f \"CHANGELOG.md\" ]; then\n    failure \"Unable to locate CHANGLOG.md file\"\nfi\n\ndatestamp=\"$(date \"+%B %d, %Y\")\" ||\n    failure \"Unable to generate date\"\n\nprintf \"## %s (%s)\\n\" \"${version}\" \"${datestamp}\" > .CHANGELOG.md.new\n\ngrep -v UNRELEASED < CHANGELOG.md >> .CHANGELOG.md.new ||\n    failure \"Unable to update CHANGELOG contents\"\n\nmv .CHANGELOG.md.new CHANGELOG.md ||\n    failure \"Unable to overwrite CHANGELOG file\"\n\nlicense_date=\"$(date \"+%Y\")\" ||\n    failure \"Unable to generate year for license\"\n\nlicense_template=\"./templates/license/license.tmpl\"\nlicense_destination=\"./LICENSE\"\n\ndebug \"Updating license file: ${license_destination}\"\n\nif [ ! -f \"${license_template}\" ]; then\n    failure \"Unable to locate license template (${license_template})\"\nfi\nif [ ! -f \"${license_destination}\" ]; then\n    failure \"Unable to locate license destination (${license_destination})\"\nfi\n\nsed \"s/%VERSION%/${version}/\" \"${license_template}\" > \"${license_destination}\" ||\n    failure \"Unable to update version in ${license_destination}\"\nsed -i \"s/%YEAR%/${license_date}/\" \"${license_destination}\" ||\n    failure \"Unable to update year in ${license_destination}\"\n\ndebug \"Updating download version in website source\"\n\nversion_file=\"./website/data/version.json\"\nif [ ! -f \"${version_file}\" ]; then\n    failure \"Unable to locate version data file (%s)\" \"${version_file}\"\nfi\n\nsed -i \"s/  \\\"VERSION\\\":.*,/  \\\"VERSION\\\": \\\"${version}\\\",/\" \"${version_file}\" ||\n    failure \"Unable to update version data file (%s)\" \"${version_file}\"\n\ndebug \"Commit version updates\"\n\n# display changes before commit\ngit status\n\ngit add version.txt CHANGELOG.md LICENSE \"${version_file}\" ||\n    failure \"Unable to stage updated release files for commit\"\n\ngit commit -m \"Release ${version}\" ||\n    failure \"Unable to commit updated files for release\"\n\nrelease_tag=\"v${version}\"\n\ndebug \"Creating new tag %s\" \"${release_tag}\"\n\ngit tag \"${release_tag}\"\n\n# Generate a new version for development\nversion_prefix=\"${version%.*}\"\npatch=\"${version##*.}\"\nnew_patch=$(( \"${patch}\" + 1 ))\ndev_version=\"${version_prefix}.${new_patch}.dev\"\n\ndebug \"Updating files for new development - %s\" \"${dev_version}\"\n\ndebug \"Updating version.txt with version value: %s\" \"${dev_version}\"\nprintf \"%s\\n\" \"${dev_version}\" > version.txt\n\ndebug \"Updating CHANGELOG\"\n\nprintf \"## %s (UNRELEASED)\\n\\nFEATURES:\\n\\nIMPROVEMENTS:\\n\\nBUG FIXES:\\n\\n\" \"${dev_version}\" > .CHANGELOG.md.new\ncat CHANGELOG.md >> .CHANGELOG.md.new\n\nmv .CHANGELOG.md.new CHANGELOG.md ||\n    failure \"Unable to overwrite CHANGELOG file\"\n\ndebug \"Updating LICENSE\"\n\nsed \"s/%VERSION%/${dev_version}/\" \"${license_template}\" > LICENSE ||\n    failure \"Unable to update LICENSE\"\n\ndebug \"Commit development version updates\"\n\n# display changes before commit\ngit status\n\ngit add version.txt CHANGELOG.md LICENSE ||\n    failure \"Unable to stage updated development files for commit\"\n\ngit commit -m \"Update files for new development ${dev_version}\" ||\n    failure \"Unable to commit updated files for development\"\n\n# Now that all changes are complete, push\ndebug \"Pushing all changes to origin\"\n\ngit push origin main ||\n    failure \"Unable to push changes to main\"\ngit push origin \"${release_tag}\" ||\n    failure \"Unable to push tag to main\"\n"
  },
  {
    "path": ".ci/spec/clean-packet.sh",
    "content": "#!/usr/bin/env bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nroot=\"$( cd -P \"$( dirname \"$csource\" )/../../\" && pwd )\"\n\n. \"${root}/.ci/load-ci.sh\"\n. \"${root}/.ci/spec/env.sh\"\n\npushd \"${root}\" > \"${output}\"\n\necho \"Cleaning up packet device...\"\n\nunset PACKET_EXEC_PERSIST\nunset PACKET_EXEC_PRE_BUILTINS\n# spec test configuration, defined by action runners, used by Vagrant on packet\nexport PKT_VAGRANT_HOST_BOXES=\"${VAGRANT_HOST_BOXES}\"\nexport PKT_VAGRANT_GUEST_BOXES=\"${VAGRANT_GUEST_BOXES}\"\n# other vagrant-spec options\nexport PKT_VAGRANT_HOST_MEMORY=\"${VAGRANT_HOST_MEMORY:-10000}\"\nexport PKT_VAGRANT_CWD=\"test/vagrant-spec/\"\nexport PKT_VAGRANT_VAGRANTFILE=Vagrantfile.spec\n###\n\nwrap_stream packet-exec run -- \"vagrant destroy -f\" \\\n                \"Vagrant failed to destroy remaining vagrant-spec guests during clean up\"\n\n\necho \"Finished destroying spec test hosts\"\n"
  },
  {
    "path": ".ci/spec/create-hosts.sh",
    "content": "#!/usr/bin/env bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nroot=\"$( cd -P \"$( dirname \"$csource\" )/../../\" && pwd )\"\n\n. \"${root}/.ci/load-ci.sh\"\n. \"${root}/.ci/spec/env.sh\"\n\npushd \"${root}\" > \"${output}\"\n\n# spec test configuration, defined by action runners, used by Vagrant on packet\nexport PKT_VAGRANT_HOST_BOXES=\"${VAGRANT_HOST_BOXES}\"\nexport PKT_VAGRANT_GUEST_BOXES=\"${VAGRANT_GUEST_BOXES}\"\n# other vagrant-spec options\nexport PKT_VAGRANT_HOST_MEMORY=\"${VAGRANT_HOST_MEMORY:-10000}\"\nexport PKT_VAGRANT_CWD=\"test/vagrant-spec/\"\nexport PKT_VAGRANT_VAGRANTFILE=Vagrantfile.spec\nexport PKT_VAGRANT_SPEC_PROVIDERS=\"${VAGRANT_SPEC_PROVIDERS}\"\n###\n\n# Grab vagrant-spec gem and place inside root dir of Vagrant repo\nwrap aws s3 cp \"${ASSETS_PRIVATE_BUCKET}/hashicorp/vagrant-spec/vagrant-spec.gem\" \"vagrant-spec.gem\" \\\n  \"Could not download vagrant-spec.gem from s3 asset bucket\"\n###\n\n# Grab vagrant installer and place inside root dir of Vagrant repo\nif [ -z \"${VAGRANT_PRERELEASE_VERSION}\" ]; then\n  INSTALLER_URL=`curl -s https://api.github.com/repos/hashicorp/vagrant-installers/releases | jq -r '.[0].assets[] | select(.name | contains(\"_x86_64.deb\")) | .browser_download_url'`\nelse\n  INSTALLER_URL=`curl -s https://api.github.com/repos/hashicorp/vagrant-installers/releases/tags/${VAGRANT_PRERELEASE_VERSION} | jq -r '.assets[] | select(.name | contains(\"_x86_64.deb\")) | .browser_download_url'`\nfi\n\nwrap curl -fLO ${INSTALLER_URL} \\\n  \"Could not download vagrant installers\"\n###\n\n# Run the job\n\necho \"Creating vagrant spec guests...\"\nwrap_stream packet-exec run -upload --  \"vagrant up --no-provision --provider vmware_desktop\" \\\n                                        \"Vagrant Acceptance host creation command failed\"\n\n\necho \"Finished bringing up vagrant spec guests\"\n"
  },
  {
    "path": ".ci/spec/create-packet.sh",
    "content": "#!/usr/bin/env bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\nexport PACKET_EXEC_PREFER_FACILITIES=\"${PACKET_EXEC_PREFER_FACILITIES:-iad2,dfw2,dfw1,ny5,ny7,ewr1,la4,lax1,lax2,tr2,ch3,ord1,ord4}\"\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nroot=\"$( cd -P \"$( dirname \"$csource\" )/../../\" && pwd )\"\n\n. \"${root}/.ci/load-ci.sh\"\n. \"${root}/.ci/spec/env.sh\"\n\npushd \"${root}\" > \"${output}\"\n\n# Ensure we have a packet device to connect\necho \"Creating packet device if needed...\"\n\npacket-exec info\n\nif [ $? -ne 0 ]; then\n    wrap_stream packet-exec create \\\n                \"Failed to create packet device\"\nfi\n\necho \"Finished creating spec test packet instance\"\n"
  },
  {
    "path": ".ci/spec/env.sh",
    "content": "#!/usr/bin/env bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\n# packet and job configuration\nexport SLACK_USERNAME=\"Vagrant\"\nexport SLACK_ICON=\"https://media.giphy.com/media/yIQ5glQeheYE0/200.gif\"\nexport SLACK_TITLE=\"Vagrant-Spec Test Runner\"\nexport SLACK_CHANNEL=\"CLYRTANRH\" # CLYRTANRH is ID of #team-vagrant-spam-channel\nexport PACKET_EXEC_DEVICE_NAME=\"${PACKET_EXEC_DEVICE_NAME:-spec-ci-boxes}\"\nexport PACKET_EXEC_DEVICE_SIZE=\"${PACKET_EXEC_DEVICE_SIZE:-c3.small.x86,c3.medium.x86}\"\nexport PACKET_EXEC_PREFER_FACILITIES=\"${PACKET_EXEC_PREFER_FACILITIES:-la4,dc10,dc13,ny7,pa4,md2}\"\nexport PACKET_EXEC_OPERATING_SYSTEM=\"${PACKET_EXEC_OPERATING_SYSTEM:-ubuntu_18_04}\"\nexport PACKET_EXEC_PRE_BUILTINS=\"${PACKET_EXEC_PRE_BUILTINS:-InstallVagrant,InstallVirtualBox,InstallVmware,InstallVagrantVmware}\"\nexport PACKET_EXEC_QUIET=\"1\"\nexport PACKET_EXEC_PERSIST=\"1\"\n# job_id is provided by common.sh\nexport PACKET_EXEC_REMOTE_DIRECTORY=\"${job_id}\"\nexport PKT_VAGRANT_CLOUD_TOKEN=\"${VAGRANT_CLOUD_TOKEN}\"\n\n# Pass Hashibot Credentials down to packet-exec run commands so they can fetch\n# private github repos during build\nexport PKT_HASHIBOT_USERNAME=\"${HASHIBOT_USERNAME}\"\nexport PKT_HASHIBOT_TOKEN=\"${HASHIBOT_TOKEN}\"\n###\n"
  },
  {
    "path": ".ci/spec/notify-success.sh",
    "content": "#!/usr/bin/env bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nroot=\"$( cd -P \"$( dirname \"$csource\" )/../../\" && pwd )\"\n\n. \"${root}/.ci/load-ci.sh\"\n. \"${root}/.ci/spec/env.sh\"\n\npushd \"${root}\" > \"${output}\"\n\nslack -m 'Tests have passed!'\n"
  },
  {
    "path": ".ci/spec/pull-log.sh",
    "content": "#!/usr/bin/env bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nroot=\"$( cd -P \"$( dirname \"$csource\" )/../../\" && pwd )\"\n\n. \"${root}/.ci/load-ci.sh\"\n. \"${root}/.ci/spec/env.sh\"\n\npushd \"${root}\" > \"${output}\"\n\n# Use same setup as run-tests.sh so `vagrant ssh` will work.\n\nunset PACKET_EXEC_PRE_BUILTINS\n\n# spec test configuration, defined by action runners, used by Vagrant on packet\nexport PKT_VAGRANT_HOST_BOXES=\"${VAGRANT_HOST_BOXES}\"\nexport PKT_VAGRANT_GUEST_BOXES=\"${VAGRANT_GUEST_BOXES}\"\n# other vagrant-spec options\nexport PKT_VAGRANT_HOST_MEMORY=\"${VAGRANT_HOST_MEMORY:-10000}\"\nexport PKT_VAGRANT_CWD=\"test/vagrant-spec/\"\nexport PKT_VAGRANT_VAGRANTFILE=Vagrantfile.spec\nexport PKT_VAGRANT_SPEC_PROVIDERS=\"${VAGRANT_SPEC_PROVIDERS}\"\nexport PKT_VAGRANT_DOCKER_IMAGES=\"${VAGRANT_DOCKER_IMAGES}\"\n\necho \"Pulling log...\"\npacket-exec run -download vagrant-spec.log \"vagrant ssh -c \\\"cat /tmp/vagrant-spec.log\\\" > vagrant-spec.log\"\n"
  },
  {
    "path": ".ci/spec/run-test.sh",
    "content": "#!/usr/bin/env bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nroot=\"$( cd -P \"$( dirname \"$csource\" )/../../\" && pwd )\"\n\n. \"${root}/.ci/load-ci.sh\"\n. \"${root}/.ci/spec/env.sh\"\n\npushd \"${root}\" > \"${output}\"\n\n# Assumes packet is already set up\nunset PACKET_EXEC_PRE_BUILTINS\n\n# spec test configuration, defined by action runners, used by Vagrant on packet\nexport PKT_VAGRANT_HOST_BOXES=\"${VAGRANT_HOST_BOXES}\"\nexport PKT_VAGRANT_GUEST_BOXES=\"${VAGRANT_GUEST_BOXES}\"\n# other vagrant-spec options\nexport PKT_VAGRANT_HOST_MEMORY=\"${VAGRANT_HOST_MEMORY:-10000}\"\nexport PKT_VAGRANT_CWD=\"test/vagrant-spec/\"\nexport PKT_VAGRANT_VAGRANTFILE=Vagrantfile.spec\nexport PKT_VAGRANT_SPEC_PROVIDERS=\"${VAGRANT_SPEC_PROVIDERS}\"\nexport PKT_VAGRANT_DOCKER_IMAGES=\"${VAGRANT_DOCKER_IMAGES}\"\n###\n# Run the job\n\necho \"Running vagrant spec tests...\"\n# Need to make memory customizable for windows hosts\nwrap_stream packet-exec run \"vagrant provision\" \\\n                \"Vagrant Acceptance testing command failed\"\n\necho \"Finished vagrant spec tests\"\n"
  },
  {
    "path": ".ci/sync.sh",
    "content": "#!/usr/bin/env bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\ncsource=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$csource\" ] ; do csource=\"$(readlink \"$csource\")\"; done\nroot=\"$( cd -P \"$( dirname \"$csource\" )/../\" && pwd )\"\n\n. \"${root}/.ci/load-ci.sh\"\n\nexport PATH=\"${PATH}:${root}/.ci\"\n\npushd \"${root}\" > \"${output}\"\n\nif [ \"${repo_name}\" = \"vagrant\" ]; then\n    remote_repository=\"hashicorp/vagrant-acceptance\"\nelse\n  fail \"This repository is not configured to sync vagrant to mirror repository\"\nfi\n\nwrap git config pull.rebase false \\\n     \"Failed to configure git pull strategy\"\n\necho \"Adding remote mirror repository '${remote_repository}'...\"\nwrap git remote add mirror \"https://${HASHIBOT_USERNAME}:${HASHIBOT_TOKEN}@github.com/${remote_repository}\" \\\n     \"Failed to add mirror '${remote_repository}' for sync\"\n\necho \"Updating configured remotes...\"\nwrap_stream git remote update mirror \\\n            \"Failed to update mirror repository (${remote_repository}) for sync\"\n\nrb=$(git branch -r --list \"mirror/${ident_ref}\")\n\nif [ \"${rb}\" != \"\" ]; then\n    echo \"Pulling ${ident_ref} from mirror...\"\n    wrap_stream git pull mirror \"${ident_ref}\" \\\n                \"Failed to pull ${ident_ref} from mirror repository (${remote_repository}) for sync\"\nfi\n\necho \"Pushing ${ident_ref} to mirror...\"\nwrap_stream git push mirror \"${ident_ref}\" \\\n            \"Failed to sync mirror repository (${remote_repository})\"\n"
  },
  {
    "path": ".copywrite.hcl",
    "content": "schema_version = 1\n\nproject {\n  license        = \"BUSL-1.1\"\n  copyright_year = 2024\n\n  header_ignore = [\n    \"internal/pkg/defaults/**\",\n    \"internal/pkg/spinner/**\",\n    \"internal/server/bindata_ui.go\",\n    \"internal/server/gen/**\",\n    \"lib/vagrant/protobufs/**\",\n    \"thirdparty/**\",\n  ]\n}\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @hashicorp/Vagrant\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nHashiCorp Community Guidelines apply to you when interacting with the community here on GitHub and contributing code.\n\nPlease read the full text at https://www.hashicorp.com/community-guidelines\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing to Vagrant\n\n**First:** We like to encourage you to contribute to the repository. If you're unsure or afraid of _anything_, just ask or submit the issue or pull request anyways. You won't be yelled at for giving your best effort. The worst that can happen is that you'll be politely asked to change something. We appreciate any sort of contributions, and don't want a wall of rules to get in the way of that.\n\nHowever, for those individuals who want a bit more guidance on the best way to contribute to the project, read on. This document will cover what we're looking for. By addressing all the points we're looking for, it raises the chances we can quickly merge or address your contributions.\n\nBefore opening a new issue or pull request, we do appreciate if you take some time to search for [possible duplicates](https://github.com/hashicorp/vagrant/issues?q=sort%3Aupdated-desc), or similar discussions on [HashiCorp Discuss](https://discuss.hashicorp.com/c/vagrant/24). On GitHub, you can scope searches by labels to narrow things down.\n\nTo ensure that the Vagrant community remains an open and safe space for everyone we also follow the [HashiCorp community guidelines](https://www.hashicorp.com/community-guidelines). When contributing to Vagrant, please respect these guidelines.\n\n## Issues\n\n### Reporting an Issue\n\n**Tip:** We have provided a [GitHub issue template](https://github.com/hashicorp/vagrant/blob/main/.github/ISSUE_TEMPLATE/bug-report.md). By respecting the proposed format and filling all the relevant sections, you'll strongly help the Vagrant collaborators to handle your request the best possible way.\n\n### Issue Lifecycle\n\n1. The issue is reported.\n2. The issue is verified and categorized by Vagrant collaborator(s). Categorization is done via GitHub tags for different dimensions (like issue type, affected components, pending actions, etc.)\n3. Unless it is critical, the issue is left for a period of time, giving outside contributors a chance to address the issue. Later, the issue may be assigned to a Vagrant collaborator and planned for a specific release [milestone](https://github.com/hashicorp/vagrant/milestones)\n4. The issue is addressed in a pull request or commit. The issue will be referenced in the commit message so that the code that fixes it is clearly linked.\n5. The issue is closed. Sometimes, valid issues will be closed to keep the issue tracker clean. The issue is still indexed and available for future viewers, or can be re-opened if necessary.\n6. The issue is locked. After about 30 days the issue will be locked. This is done to keep issue activity in open issues and encourge users to open a new issue if an old issue is being encountered again.\n\n## Pull Requests\n\nThank you for contributing! Here you'll find information on what to include in your Pull Request (“PR” for short) to ensure it is reviewed quickly, and possibly accepted.\n\nBefore starting work on a new feature or anything besides a minor bug fix, it is highly recommended to first initiate a discussion with the Vagrant community (either via a GitHub issue or [HashiCorp Discuss](https://discuss.hashicorp.com/c/vagrant/24)). This will save you from wasting days implementing a feature that could be rejected in the end.\n\nNo pull request template is provided on GitHub. The expected changes are often already described and validated in an existing issue, that obviously should be referenced. The Pull Request thread should be mainly used for the code review.\n\n**Tip:** Make it small! A focused PR gives you the best chance of having it accepted. Then, repeat if you have more to propose!\n\n### Vagrant Go\n\nThe Vagrant port to Go is currently in an alpha state and under heavy development. Please refer to [this issue](https://github.com/hashicorp/vagrant/issues/12819) before starting or submitting pull requests related to Vagrant Go.\n\n### Setup a development installation of Vagrant\n\n*A Vagrantfile is provided that should take care setting up a VM for running the rspec tests.* If you only need to run those tests and don't also want to run a development version of Vagrant from a host machine then it's recommended to use that.\n\nThere are a few prerequisites for setting up a development environment with Vagrant. Ensure you have the following installed on your machine:\n\n* git\n* bsdtar\n* curl\n\n#### Install Ruby\n\nIt's nice to have a way to control what version of ruby is installed, so you may want to install [rvm](https://rvm.io/rvm/install), [chruby](https://github.com/postmodern/chruby#install) or [rbenv](https://github.com/rbenv/rbenv#installation). For Windows [ruby installer](https://rubyinstaller.org/) is recommended.\n\n#### Setup Vagrant\nClone Vagrant's repository from GitHub into the directory where you keep code on your machine:\n\n```\n  $ git clone --recurse-submodules https://github.com/hashicorp/vagrant.git\n```\n\nNext, move into the newly created `./vagrant` directory.\n\n```\n  $ cd ./vagrant\n```\n\nAll commands will be run from this path. Now, run the `bundle` command to install the Ruby dependencies:\n\n```\n  $ bundle install\n```\n\nYou can now run Vagrant by running `bundle exec vagrant` from inside that directory.\n\n##### Setting up Vagrant-go\n\nAdd the generated `binstubs` to your `PATH`\n```\n  $ export PATH=/path/to/my/vagrant/binstubs\n```\n\nInstall go using the method of your choice.\n\nBuild the Vagrant go binary using `make`\n```\n  $ make\n```\n\nThis will generate a `./vagrant` binary in your project root.\n\n### How to prepare your pull request\n\nOnce you're confident that your upcoming changes will be accepted:\n\n* In your forked repository, create a topic branch for your upcoming patch.\n  * Usually this is based on the main branch.\n  * Checkout a new branch based on main; `git checkout -b my-contrib main`\n    Please avoid working directly on the `main` branch.\n* Make focused commits of logical units and describe them properly.\n* Avoid re-formatting of the existing code.\n* Check for unnecessary whitespace with `git diff --check` before committing.\n* Tests are required in each pull request. There are some exceptions like docs changes and dependency constraint updates.\n* Assure nothing is broken by running manual tests, and all the automated tests.\n\n### Running tests\n\nVagrant uses rspec to run tests. Once your Vagrant bundle is installed from Git repository, you can run the test suite with:\n\n    bundle exec rake\n\nThis will run the unit test suite, which should come back all green!\n\nIf you are developing Vagrant on a machine that already has a Vagrant package installation present, both will attempt to use the same folder for their configuration (location of this folder depends on system). This can cause errors when Vagrant attempts to load plugins. In this case, override the `VAGRANT_HOME` environment variable for your development version of Vagrant before running any commands to be some new folder within the project or elsewhere on your machine. For example, in Bash:\n\n    export VAGRANT_HOME=~/.vagrant-dev\n\nYou can now run Vagrant commands against the development version:\n\n    bundle exec vagrant\n\n### Acceptance Tests\n\nVagrant also comes with an acceptance test suite that does black-box\ntests of various Vagrant components. Note that these tests are **extremely\nslow** because actual VMs are spun up and down. The full test suite can\ntake hours. Instead, try to run focused component tests.\n\nTo run the acceptance test suite, first copy `vagrant-spec.config.example.rb`\nto `vagrant-spec.config.rb` and modify it to valid values. The places you\nshould fill in are clearly marked.\n\nNext, see the components that can be tested:\n\n```\n$ rake acceptance:components\ncli\nprovider/virtualbox/basic\n...\n```\n\nThen, run one of those components:\n\n```\n$ rake acceptance:run COMPONENTS=\"cli\"\n...\n```\n\n### Submit Changes\n\n* Push your changes to a topic branch in your fork of the repository.\n* Open a PR to the original repository and choose the right original branch you want to patch (main for most cases).\n* If not done in commit messages (which you really should do) please reference and update your issue with the code changes.\n* Even if you have write access to the repository, do not directly push or merge your own pull requests. Let another team member review your PR and approve.\n\n### Pull Request Lifecycle\n\n1. You are welcome to submit your PR for commentary or review before it is fully completed. Please prefix the title of your PR with \"[WIP]\" to indicate this. It's also a good idea to include specific questions or items you'd like feedback on.\n2. Sign the [HashiCorp CLA](#hashicorp-cla). If you haven't signed the CLA yet, a bot will ask you to do so. You only need to sign the CLA once. If you've already signed the CLA, the CLA status will be green automatically.\n3. The PR is categorized by Vagrant collaborator(s), applying GitHub tags similarly to issues triage.\n4. Once you believe your PR is ready to be merged, you can remove any\n  \"[WIP]\" prefix from the title and a Vagrant collaborator will review.\n5. One of the Vagrant collaborators will look over your contribution and either provide comments letting you know if there is anything left to do. We do our best to provide feedback in a timely manner, but it may take some time for us to respond.\n6. Once all outstanding comments have been addressed, your contribution will be merged! Merged PRs will be included in the next Vagrant release. The Vagrant contributors will take care of updating the CHANGELOG as they merge.\n7. We might decide that a PR should be closed. We'll make sure to provide clear reasoning when this happens.\n\n## HashiCorp CLA\n\nWe require all contributors to sign the [HashiCorp CLA](https://www.hashicorp.com/cla).\n\nIn simple terms, the CLA affirms that the work you're contributing is original, that you grant HashiCorp permission to use that work (including license to any patents necessary), and that HashiCorp may relicense your work for our commercial products if necessary. Note that this description is a summary and the specific legal terms should be read directly in the [CLA](https://www.hashicorp.com/cla).\n\nThe CLA does not change the terms of the standard open source license used by our software such as MPL2 or MIT. You are still free to use our projects within your own projects or businesses, republish modified source, and more. Please reference the appropriate license of this project to learn more.\n\nTo sign the CLA, open a pull request as usual. If you haven't signed the CLA yet, a bot will respond with a link asking you to sign the CLA. We cannot merge any pull request until the CLA is signed. You only need to sign the CLA once. If you've signed the CLA before, the bot will not respond to your PR and your PR will be allowed to merge.\n\n# Additional Resources\n\n* [HashiCorp Community Guidelines](https://www.hashicorp.com/community-guidelines)\n* [General GitHub documentation](https://help.github.com/)\n* [GitHub pull request documentation](https://help.github.com/send-pull-requests/)\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: Bug Report\nabout: Let us know about an unexpected error, a crash, or an incorrect behavior.\nlabels: \"waiting-intake\"\n---\n\n<!--\nPlease note that the Vagrant issue tracker is reserved for bug reports. For general usage questions, please use\nHashiCorp Discuss, https://discuss.hashicorp.com/c/vagrant/.\n\nWhen you submit a bug report, please provide the minimal configuration and required information necessary to reliably reproduce the issue. It\nshould include a basic Vagrantfile.\n\nThank you!\n-->\n\n### Debug output\n\n<!--\nProvide a link to a GitHub Gist containing the complete debug output, https://www.vagrantup.com/docs/other/debugging.html. \n\nThe debug output should\nbe very long. Do NOT paste the debug output in the issue.\n-->\n\n### Expected behavior\n\n<!--\nWhat should have happened?\n-->\n\n### Actual behavior\n\n<!--\nWhat actually happened?\n-->\n\n### Reproduction information \n\n#### Vagrant version\n\n<!--\nUse `vagrant -v` to collect the version information. If you are not running the latest version\nof Vagrant, please upgrade before submitting an issue.\n-->\n\n#### Host operating system\n\n<!--\nYour local system.\n-->\n\n#### Guest operating system\n\n<!--\nThe operating system of the virtual machine.\n-->\n\n#### Steps to reproduce\n\n1.\n2.\n3.\n\n#### Vagrantfile\n\n```ruby\n# Copy-paste your Vagrantfile here. Remove any sensitive information such as passwords, authentication tokens, or email addresses.\n```\n\n<!--\nAlways start with a minimal Vagrantfile and include only the relevant configuration\nto reproduce the reported behavior.\n-->\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: BUSL-1.1\n\ncontact_links:\n  - name: Ask a Question\n    url: https://discuss.hashicorp.com/c/vagrant\n    about: If you have a question or are looking for advice, please post on our Discuss forum.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/engineering-task.md",
    "content": "---\nname: Vagrant Engineering\nabout: For Vagrant Engineers to track tasks. \n---\n\n<!--\nThis template is intended for the Vagrant Engineering team to track tasks. \n\nUse the Bug Report issue template to request help when Vagrant is not working as expected and the feature request to suggest an enhancement. \n-->\n\n## Description \n2 - 3 sentences that define the solution or changes. \n\n## Use case (optional) \nHow will this affect the end user? \n\n## Supporting material \nAdd links to related issues or supporting documentation. \n\n<!--\nTo complete this issue, add labels and select a milestone. \n\n### Labels \n\n#### Estimated task complexity time-frame (does not guarantee a delivery date)\n* day\n* week\n* multi-week\n\nIf a task takes longer than 3 weeks, either consider if you can break it down into smaller pieces of work or update the issue with a new estimated time-frame with a summary checklist of tasks. \n\n#### Estimated user impact\nMost issues should categorized as minor or major. \n* Critical: fixes a bug that impacts production deployments\n* Major: addresses a frequent request, relieves a common pain point\n* Minor: trivial inconveniences that have robust workarounds \n  \n#### Task size\n\nSelect a task size label based on the estimated completion time and description (task-small, task-medium, task-large).\n\n### Milestone\n\nYour milestone selection will be considered a suggestion for the team to review during backlog grooming.\n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/vagrant-feature-request.md",
    "content": "---\nname: Vagrant Feature request\nabout: Suggest an idea or enhancement for Vagrant\ntitle: 'Enhancement Request: Your description here'\nlabels: ['enhancement', 'waiting-intake']\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/backport.yml",
    "content": "---\nname: Backport Assistant Runner\n\non:\n  pull_request_target:\n    types:\n      - closed\n      - labeled\n\njobs:\n  backport:\n    if: github.event.pull_request.merged\n    runs-on: ubuntu-latest\n    container: hashicorpdev/backport-assistant:0.2.3\n    steps:\n      - name: Backport changes to stable-website\n        run: |\n          backport-assistant backport -automerge\n        env:\n          BACKPORT_LABEL_REGEXP: \"backport/(?P<target>website)\"\n          BACKPORT_TARGET_TEMPLATE: \"stable-{{.target}}\"\n          GITHUB_TOKEN: ${{ secrets.HASHIBOT_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/check-legacy-links-format.yml",
    "content": "name: Legacy Link Format Checker\n\non:\n  push:\n    paths:\n      - \"website/content/**/*.mdx\"\n      - \"website/data/*-nav-data.json\"\n\njobs:\n  check-links:\n    uses: hashicorp/dev-portal/.github/workflows/docs-content-check-legacy-links-format.yml@475289345d312552b745224b46895f51cc5fc490\n    with:\n      repo-owner: \"hashicorp\"\n      repo-name: \"vagrant\"\n      commit-sha: ${{ github.sha }}\n      mdx-directory: \"website/content\"\n      nav-data-directory: \"website/data\"\n"
  },
  {
    "path": ".github/workflows/code.yml",
    "content": "on:\n  push:\n    branches:\n      - 'main'\n      - 'spec-test-*'\n\njobs:\n  sync-acceptance:\n    if: github.repository == 'hashicorp/vagrant'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Code Checkout\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n        with:\n          persist-credentials: false\n          fetch-depth: 0\n      - name: Sync Acceptance Testing Repository\n        run: ./.ci/sync.sh\n        working-directory: ${{github.workspace}}\n        env:\n          HASHIBOT_TOKEN: ${{ secrets.HASHIBOT_TOKEN }}\n          HASHIBOT_USERNAME: ${{ secrets.HASHIBOT_USERNAME }}\n          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}\n"
  },
  {
    "path": ".github/workflows/dev-appimage-build.yml",
    "content": "name: Appimage Vagrant Development Build\non:\n  push:\n    branches: 'dev-appimage-*'\n  workflow_dispatch:\n\njobs:\n  trigger-build:\n    if: github.repository == 'hashicorp/vagrant'\n    name: Trigger Vagrant Appimage Development Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Code Checkout\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - name: Trigger Development Build\n        run: ./.ci/dev-build \"${BRANCH}\" \"${COMMIT_ID}\" build-appimage\n        env:\n          HASHIBOT_TOKEN: ${{ secrets.HASHIBOT_TOKEN }}\n          BRANCH: ${{ github.ref_name }}\n          COMMIT_ID: ${{ github.sha }}\n"
  },
  {
    "path": ".github/workflows/dev-arch-build.yml",
    "content": "name: Arch Linux Vagrant Development Build\non:\n  push:\n    branches: 'dev-arch-*'\n  workflow_dispatch:\n\njobs:\n  trigger-build:\n    if: github.repository == 'hashicorp/vagrant'\n    name: Trigger Vagrant Arch Linux Development Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Code Checkout\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - name: Trigger Development Build\n        run: ./.ci/dev-build \"${BRANCH}\" \"${COMMIT_ID}\" build-arch\n        env:\n          HASHIBOT_TOKEN: ${{ secrets.HASHIBOT_TOKEN }}\n          BRANCH: ${{ github.ref_name }}\n          COMMIT_ID: ${{ github.sha }}\n"
  },
  {
    "path": ".github/workflows/dev-build.yml",
    "content": "name: Full Vagrant Development Build\non:\n  push:\n    branches: 'build-*'\n  workflow_dispatch:\n\njobs:\n  trigger-build:\n    if: github.repository == 'hashicorp/vagrant'\n    name: Trigger Vagrant Development Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Code Checkout\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - name: Trigger Development Build\n        run: ./.ci/dev-build \"${BRANCH}\" \"${COMMIT_ID}\" build\n        env:\n          HASHIBOT_TOKEN: ${{ secrets.HASHIBOT_TOKEN }}\n          BRANCH: ${{ github.ref_name }}\n          COMMIT_ID: ${{ github.sha }}\n"
  },
  {
    "path": ".github/workflows/dev-debs-build.yml",
    "content": "name: DEB Vagrant Development Build\non:\n  push:\n    branches: 'dev-debs-*'\n  workflow_dispatch:\n\njobs:\n  trigger-build:\n    if: github.repository == 'hashicorp/vagrant'\n    name: Trigger Vagrant DEB Development Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Code Checkout\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - name: Trigger Development Build\n        run: ./.ci/dev-build \"${BRANCH}\" \"${COMMIT_ID}\" build-debs\n        env:\n          HASHIBOT_TOKEN: ${{ secrets.HASHIBOT_TOKEN }}\n          BRANCH: ${{ github.ref_name }}\n          COMMIT_ID: ${{ github.sha }}\n"
  },
  {
    "path": ".github/workflows/dev-macos-build.yml",
    "content": "name: macOS Vagrant Development Build\non:\n  push:\n    branches: 'dev-macos-*'\n  workflow_dispatch:\n\njobs:\n  trigger-build:\n    if: github.repository == 'hashicorp/vagrant'\n    name: Trigger Vagrant macOS Development Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Code Checkout\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - name: Trigger Development Build\n        run: ./.ci/dev-build \"${BRANCH}\" \"${COMMIT_ID}\" build-macos\n        env:\n          HASHIBOT_TOKEN: ${{ secrets.HASHIBOT_TOKEN }}\n          BRANCH: ${{ github.ref_name }}\n          COMMIT_ID: ${{ github.sha }}\n"
  },
  {
    "path": ".github/workflows/dev-rpms-build.yml",
    "content": "name: RPM Vagrant Development Build\non:\n  push:\n    branches: 'dev-rpms-*'\n  workflow_dispatch:\n\njobs:\n  trigger-build:\n    if: github.repository == 'hashicorp/vagrant'\n    name: Trigger Vagrant RPM Development Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Code Checkout\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - name: Trigger Development Build\n        run: ./.ci/dev-build \"${BRANCH}\" \"${COMMIT_ID}\" build-rpms\n        env:\n          HASHIBOT_TOKEN: ${{ secrets.HASHIBOT_TOKEN }}\n          BRANCH: ${{ github.ref_name }}\n          COMMIT_ID: ${{ github.sha }}\n"
  },
  {
    "path": ".github/workflows/dev-windows-build.yml",
    "content": "name: Windows Vagrant Development Build\non:\n  push:\n    branches: 'dev-windows-*'\n  workflow_dispatch:\n\njobs:\n  trigger-build:\n    if: github.repository == 'hashicorp/vagrant'\n    name: Trigger Vagrant Windows Development Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Code Checkout\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - name: Trigger Development Build\n        run: ./.ci/dev-build \"${BRANCH}\" \"${COMMIT_ID}\" build-windows\n        env:\n          HASHIBOT_TOKEN: ${{ secrets.HASHIBOT_TOKEN }}\n          BRANCH: ${{ github.ref_name }}\n          COMMIT_ID: ${{ github.sha }}\n"
  },
  {
    "path": ".github/workflows/initiate-release.yml",
    "content": "name: Start Vagrant Release Process\non:\n  workflow_dispatch:\n    inputs:\n      release_version:\n        description: 'Release Version (example: 1.0.0)'\n        required: true\n        type: string\n\njobs:\n  start-release:\n    if: github.repository == 'hashicorp/vagrant'\n    name: Initiate Release\n    runs-on: ubuntu-latest\n    steps:\n      - name: Code Checkout\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n        with:\n          # NOTE: custom token is used so pushed tag will trigger release workflow\n          token: ${{ secrets.HASHIBOT_TOKEN }}\n      - name: Run initiator\n        run: ./.ci/release-initiator \"${VERSION}\"\n        env:\n          VERSION: ${{ inputs.release_version }}\n          HASHIBOT_TOKEN: ${{ secrets.HASHIBOT_TOKEN }}\n          HASHIBOT_USERNAME: ${{ vars.HASHIBOT_USERNAME }}\n          HASHIBOT_EMAIL: ${{ vars.HASHIBOT_EMAIL }}\n"
  },
  {
    "path": ".github/workflows/lock.yml",
    "content": "name: 'Lock Threads'\n\non:\n  schedule:\n    - cron: '50 1 * * *'\n\njobs:\n  lock:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: write\n    steps:\n      - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1\n        with:\n          github-token: ${{ github.token }}\n          issue-inactive-days: '30'\n          pr-inactive-days: '30'\n"
  },
  {
    "path": ".github/workflows/nightlies.yml",
    "content": "name: Vagrant Nightly Builds\non:\n  schedule:\n    - cron: 30 4 * * *\n\njobs:\n  trigger-nightly:\n    if: github.repository == 'hashicorp/vagrant'\n    name: Trigger Nightly Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Code Checkout\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - name: Trigger Nightly Build\n        run: ./.ci/nightly-build \"${COMMIT_ID}\"\n        env:\n          HASHIBOT_TOKEN: ${{ secrets.HASHIBOT_TOKEN }}\n          COMMIT_ID: ${{ github.sha }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Vagrant Release\non:\n  push:\n    tags: 'v*'\n\njobs:\n  trigger-release:\n    if: github.repository == 'hashicorp/vagrant'\n    name: Trigger Installers Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Code Checkout\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - name: Trigger Build\n        run: ./.ci/release \"${TAG}\" \"${COMMIT_ID}\"\n        env:\n          HASHIBOT_TOKEN: ${{ secrets.HASHIBOT_TOKEN }}\n          TAG: ${{ github.ref }}\n          COMMIT_ID: ${{ github.sha }}\n"
  },
  {
    "path": ".github/workflows/testing-skipped.yml",
    "content": "name: Vagrant Ruby Tests\non:\n  pull_request:\n    branches:\n      - main\n    ignored-paths:\n      - 'bin/**'\n      - 'lib/**'\n      - 'plugins/**'\n      - 'test/**'\n      - 'Gemfile'\n      - 'templates/**'\n      - 'vagrant.gemspec'\n      - 'Rakefile'\n\njobs:\n  unit-tests-ruby:\n    runs-on: ubuntu-latest\n    continue-on-error: true\n    strategy:\n      matrix:\n        ruby: [ '3.1', '3.2', '3.3', '3.4' ]\n    name: Vagrant unit tests on Ruby ${{ matrix.ruby }}\n    steps:\n      - name: Stubbed for skip\n        run: \"echo 'No testing required in changeset'\"\n"
  },
  {
    "path": ".github/workflows/testing.yml",
    "content": "name: Vagrant Ruby Tests\non:\n  push:\n    branches:\n      - main\n      - 'test-*'\n    paths:\n      - 'bin/**'\n      - 'lib/**'\n      - 'plugins/**'\n      - 'test/**'\n      - 'templates/**'\n      - 'Gemfile'\n      - 'vagrant.gemspec'\n      - 'Rakefile'\n  pull_request:\n    branches:\n      - main\n    paths:\n      - 'bin/**'\n      - 'lib/**'\n      - 'plugins/**'\n      - 'test/**'\n      - 'Gemfile'\n      - 'templates/**'\n      - 'vagrant.gemspec'\n      - 'Rakefile'\n\njobs:\n  unit-tests-ruby:\n    runs-on: ubuntu-latest\n    continue-on-error: true\n    strategy:\n      matrix:\n        ruby: [ '3.1', '3.2', '3.3', '3.4' ]\n    name: Vagrant unit tests on Ruby ${{ matrix.ruby }}\n    steps:\n      - name: Code Checkout\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - name: Setup Ruby\n        uses: ruby/setup-ruby@dffb23f65a78bba8db45d387d5ea1bbd6be3ef18 # v1.293.0\n        with:\n          ruby-version: ${{matrix.ruby}}\n          bundler-cache: true\n      - name: install dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install -yq libarchive-tools\n      - name: Run Tests\n        run: bundle exec rake test:unit\n"
  },
  {
    "path": ".gitignore",
    "content": "# OS-specific\n.DS_Store\n\n# Editor swapfiles\n.*.sw?\n*~\n\n# Vagrant stuff\nacceptance_config.yml\nboxes/*\n/.vagrant\n/website/.vagrant\n/website/build\n/vagrant-spec.config.rb\ntest/vagrant-spec/.vagrant/\n.vagrant/\n/vagrant\n/pkg\ndata.db\nvagrant-restore.db.lock\ncmd/vagrant/Vagrantfile\nbinstubs/\n\n# Bundler/Rubygems\n*.gem\n.bundle\npkg/*\n/Gemfile.lock\ntest/tmp/\n/exec\n.ruby-bundle\nvendor/bundle\n\n# Documentation\n_site/*\n.yardoc/\ndoc/\n\n# Python\n*.pyc\n\n# Rubinius\n*.rbc\n\n# IDE junk\n.idea/*\n*.iml\n.project\n.vscode\n\n# Ruby Managers\n.rbenv\n.rbenv-gemsets\n.ruby-gemset\n.ruby-version\n.rvmrc\n\n# Box storage for spec\ntest/vagrant-spec/boxes/*.box\n\nsimple_speed.py\ncover.out\n\n# nektos/act secrets file\n.secrets\n\n# delve debug binary\n__debug_bin\n\n# solargraph (ruby lsp) & rubocop\n.solargraph.yml\n.rubocop.yml\n\n# Ignore generated binaries\nbin/vagrant-go*\n\n# extension\ntmp*\nlib/vagrant/vagrant_ssl.so\n\n# direnv directory\n.direnv\n"
  },
  {
    "path": ".gitmodules",
    "content": ""
  },
  {
    "path": ".vimrc",
    "content": "\" tabstop settings\nset tabstop=2\nset softtabstop=2\nset shiftwidth=2\nset expandtab\n"
  },
  {
    "path": ".yardopts",
    "content": "-m markdown\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## 2.4.10.dev (UNRELEASED)\n\nFEATURES:\n\n- provider/virtualbox: Add support for LSI Logic SAS storage controller [GH-13692]\n\nIMPROVEMENTS:\n\n- provisioner/ansible: Add support for dynamically selecting ansible-playbook inventory argument option based on ansible-core version [GH-13740]\n- docs: Add deprecation notice for documentation under website directory [GH-13746]\n- docs: Fix typos and lingustic issues in various places [GH-13731]\n\nBUG FIXES:\n\n- core: Fix issue with missing translations when running vagrant login [GH-13747]\n- provider/hyperv: Preserve primary disk when resizing disks [GH-13748]\n\n## 2.4.9 (August 21, 2025)\n\nFEATURES:\n\n- provider/virtualbox: Add support for VirtualBox 7.2 [GH-13709]\n\nIMPROVEMENTS:\n\n- docs: Update the node version for the docs site [GH-13713]\n- docs: Remove outdated link for Vagrant Cloud [GH-13710]\n\nBUG FIXES:\n\n- provisioner/ansible: Fix OS version detection, when installing Ansible on RHEL-like operating systems [GH-13701]\n\n## 2.4.8 (August 05, 2025)\n\nIMPROVEMENTS:\n\n- core: Improve error message when no matching provider is found for a box [GH-13693]\n- core: Improve error message on box add failures [GH-13687]\n\nBUG FIXES:\n\n- core: Fix box add action when adding a box directly from a file [GH-13699]\n- provider/hyperv: Fix XML configuration parsing logic, and add additional checks for minimum amount of memory and CPU [GH-13691]\n- core: Fix guest network configuration when more than one network interface is present [GH-13686]\n\n## 2.4.7 (June 17, 2025)\n\nBUG FIXES:\n\n- guests/linux: Fix /etc/fstab clean up behavior [GH-13681]\n- provider/docker: Fix auto generated container names [GH-13678]\n- provider/hyperv: Fix import for XML based configuration [GH-13674]\n\n## 2.4.6 (May 21, 2025)\n\nIMPROVEMENTS:\n\n- core: Improve error messages on box add failures [GH-13660]\n- core: Only generate and attach ISO for cloud-init on first boot [GH-13666]\n- host/windows: Add basic oscdimg detection on Windows [GH-13668]\n- provider/hyperv: Enable cloud-init support [GH-13671]\n- provider/virtualbox: Allow link-local IPv6 addresses for hostonly [GH-12653]\n\nBUG FIXES:\n\n- command: Remove server mode checks [GH-13657]\n- core: Prevent cloud-init from regenerating and attaching ISO [GH-13666]\n- provider/hyperv: Extract machine ID if collection returned [GH-13669]\n- provider/hyperv: Fix import failure due to lack of resources [GH-13670]\n- provider/virtualbox: Fix VirtualBox private network setup [GH-13659]\n\n## 2.4.5 (April 23, 2025)\n\nFEATURES:\n\n- communicator/none: Add experimental none communicator [GH-13651]\n\nBUG FIXES:\n\n- core/bundler: Handle multiple versions for system specifications [GH-13652]\n\n## 2.4.4 (April 21, 2025)\n\nIMPROVEMENTS:\n\n- communicator/ssh: Update connect retry behavior, make configurable [GH-13628]\n- core: Better behavior outside installers [GH-13636]\n- guest/amazonlinux: Support networkd based configuration [GH-13626]\n- guest/arch: Update networking for recent versions [GH-13640]\n- guest/rhel: Fix networking setup in recent versions [GH-13625]\n- host/darwin: Remove HFS from ISO creation [GH-13561]\n- provider/hyperv: Add dvd disk support [GH-13642]\n- provider/hyperv: Update primary disk detection [GH-13643]\n- provider/virtualbox: Add VirtioSCSI storage controller support [GH-13587]\n- util/powershell: Prefer using pwsh when available [GH-13648]\n\nBUG FIXES:\n\n- command/box: Fix architecture constraints in outdated/updated command [GH-13601]\n- command/box: Fix architecture constraint on provider matches [GH-13647]\n- communicators/winrm: Catch IO::Timeout when waiting for communicator [GH-13606]\n- communicators/ssh: Catch IO::Timeout when waiting for communicator [GH-13606]\n- guest/alpine: Fix DHCP assigned default route behavior [GH-13633]\n- provider/docker: Handle variation in error text during image removal [GH-13564]\n- provider/virtualbox: Use interface name for hostonly configuration [GH-13644]\n- synced_folder/smb: Remove `nofail` mount option [GH-13645]\n\nVAGRANT-GO:\n\n- Removed with work archived to vagrant-go branch [GH-13622]\n\n## 2.4.3 (November 12, 2024)\n\nIMPROVEMENTS:\n\n- command/cloud: Support HCP authentication [GH-13540]\n\nBUG FIXES:\n\n- core: Relax constraint on logger dependency [GH-13532]\n\n## 2.4.2 (November 01, 2024)\n\nFEATURES:\n\n- provider/virtualbox: Add support for VirtualBox 7.1 [GH-13513]\n\nIMPROVEMENTS:\n\n- core: Always downcase type value when getting digest for checksum calculation [GH-13471]\n- core: Activate all runtime dependencies at startup [GH-13526]\n- guest/debian: Fix NFS install capability to prevent hang on install [GH-13411]\n- kernel_v2/config: Add warning for IPv6 address ending with :1 [GH-13362]\n- provider/docker: Properly match container ID when trailing output is present [GH-13475]\n- provider/docker: Support build with containerd storage [GH-13343]\n- provider/virtualbox: Allow paused state when booting vm [GH-13496]\n- provider/virtualbox: Handling warnings in output when detecting version [GH-13394]\n- synced_folder/nfs: Output mounting entry [GH-13383]\n- synced_folder/smb: Adjust ordering in mount entry output [GH-13383]\n\nBUG FIXES:\n\n- command/cloud: Fix provider upload [GH-13467]\n- host/bsd: Use nfsd update command instead of restart [GH-13490]\n- kernel_v2/config: Fix IP address check [GH-13494] \n- provider/docker: Prevent error if network configuration data is missing [GH-13337, GH-13373]\n- provider/docker: Fix docker-exec commands to pass kwargs correctly [GH-13488]\n- provider/docker: Fix docker to not rebuild image if it already exists [GH-13489]\n- provider/virtualbox: Prevent encoding errors within error translation [GH-13525]\n- provider/hyperv: Fix configure disks capability [GH-13346]\n- provisioner/ansible: Fix version detection [GH-13375]\n- provisioner/ansible: Support double digit versions [GH-13493]\n- provisioner/salt: Fix bootstrap script URLs [GH-13517]\n- synced_folder/nfs: Fix upstart detection [GH-13409]\n\nVAGRANT-GO:\n\n## 2.4.1 (January 19, 2024)\n\nFEATURES:\n\nIMPROVEMENTS:\n\n- communicator/ssh: Support ECDSA type keys for insecure key replacement [GH-13327]\n- communicator/ssh: Inspect guest for supported key types [GH-13334]\n- core: Update Ruby constraint to allow Ruby 3.3 [GH-13335]\n- core/bundler: Force strict dependencies for default gems [GH-13336]\n- provisioner/ansible: Support pip installation for RHEL >= 8 [GH-13326]\n- util/keypair: Add support for ECDSA keys [GH-13327]\n\nBUG FIXES:\n\n- command/plugin: Fix plugin extension installation on Windows [GH-13328]\n- communicator/ssh: Fix private key writing on Windows [GH-13329]\n- core: Fix Vagrant SSL helper detection on macOS [GH-13277]\n- core: Fix box collection sorting [GH-#13320]\n- util/platform: Fix architecture mapping for Windows [GH-13278]\n\nVAGRANT-GO:\n\n## 2.4.0 (October 16, 2023)\n\nFEATURES:\n\n- core: Add architecture support [GH-13239]\n\nIMPROVEMENTS:\n\n- communicator/ssh: Add key type detection on insecure key replacement [GH-13219] \n- core: Extract box files as sparse files [GH-13252]\n- keys: Add ed25519 insecure private key [GH-13219]\n- util/downloader: Perform best effort revocation checks on Windows [GH-13214]\n- util/keypair: Add support for generating ed25519 key pairs [GH-13219]\n\nBUG FIXES:\n\n- core: Fix extension installation path [GH-13215]\n- provider/virtualbox: Fix ipv6 static network configuration [GH-13241]\n\nVAGRANT-GO:\n\n- Add basic support for HCL based config [GH-13257]\n\n## 2.3.7 (June 15, 2023)\n\nIMPROVEMENTS:\n\n- command/ssh: Enable deprecated key types and algorithms [GH-13179]\n- core: Update user error message on failed extension installs [GH-13207]\n- core: Support loading legacy providers in OpenSSL 3 [GH-13178]\n- provisioner/salt: Verify bootstrap-salt download [GH-13166]\n\nBUG FIXES:\n\n- communicator/ssh: Remove keyboard-interactive auth method [GH-13194]\n- provisioner/salt: Fix usage on Windows guests [GH-13086]\n\nVAGRANT-GO:\n\n- Update data layer implementation [GH-12904]\n- Update dependencies [GH-13201]\n\n## 2.3.6 (May 19, 2023)\n\nBUG FIXES:\n\n- command/serve: Isolate proto constants to file for auto-loading [GH-13165]\n- core/util: Unlock file prior to deletion [GH-13159]\n- provider/docker: Attempt using docker command for bridge ip [GH-13153]\n- provider/virtualbox: Update preferred locale values for driver [GH-13160]\n\n## 2.3.5 (May 15, 2023)\n\nBUG FIXES:\n\n- communicator/ssh: Use netssh builtin keep alive functionality [GH-13069]\n- communicator/ssh: Update connection settings when using a password to connect ssh [GH-13052]\n- core:  Add a file mutex when downloading box files [GH-13057]\n- guest/arch: Support differentiating between Artix and Arch Linux [GH-13055]\n- host/windows: Get state of Windows feature \"Microsoft-Hyper-V-Hypervisor\" [GH-11933]\n- provider/docker: Ignore inactive docker containers when assigning ports [GH-13146]\n- provider/docker: Sync folders before preparing nfs settings [GH-13149]\n- provider/virtualbox: De-duplicate machine port forward info [GH-13056]\n- provider/virtualbox:  Remove check for hyperv being enabled when verifying virtualbox is usable on windows [GH-13090]\n- provider/virtualbox: Validate LANG value when possible [GH-13150]\n- provider/hyperv: Check for hyper-v feature \"EnhancedSessionTransportType\" [GH-12280]\n- provisioner/ansible: Fix installing Ansible provisioner with version and pip [GH-13054]\n- synced_folders/rsync: allow rsync-auto to also ignore relative paths [GH-13066]\n\nNOTE: Vagrant installer packages were updated to Ruby 3\n\n## 2.3.4 (December 9, 2022)\n\nIMPROVEMENTS:\n\n- host/darwin: Isolate loading incompatible libraries to support EOL platforms [GH-13022]\n- provider/virtualbox: Detect network type when not provided [GH-13024]\n\nBUG FIXES:\n\n- host/windows: Add fix for Powershell 7.3.0 [GH-13006]\n- provider/virtualbox: Adjust hostnet DHCP configuration, ignore invalid devices [GH-13004]\n- provisioner/ansible: Fix install package names on older debian (and derivatives) versions [GH-13017]\n\n## 2.3.3 (November 15, 2022) \n\nIMPROVEMENTS:\n\n- core: Bump net-ssh dependency to 7.0 and remove patches [GH-12979]\n- synced_folders/rsync: Include ssh `extra_args` value [GH-12973]\n\nBUG FIXES:\n\n- command/serve: Force root level namespace for Google constant [GH-12989]\n- guest/solaris: Fix insecure key authorized keys removal [GH-12740]\n- provider/virtualbox: Fix `:private_network` support for VirtualBox 7 on macOS [GH-12983]\n- provider/virtualbox: Prevent localization of command output [GH-12994]\n- provisioner/ansible: Update setup packages in debian capability [GH-12832]\n\n## 2.3.2 (October 18, 2022)\n\nFEATURES:\n\n- provider/virtualbox: Add support for VirtualBox 7.0 [GH-12947]\n\n## 2.3.1 (September 29, 2022)\n\nIMPROVEMENTS:\n\n- core: Raise error if required metadata.json box fields are not present [GH-12895]\n- core: Provider helpful error when box version format is invalid [GH-12911]\n\nBUG FIXES:\n\n- Fix flakiness when bringing up a machine that forwards ssh [GH-12909]\n- communicator/ssh: Fix `private_key_path` behavior when `keys_only` is disabled [GH-12885]\n- synced_folder/nfs: Update exports file creation [GH-12910]\n- util/downloader: Fix user agent [GH-12925]\n\nVAGRANT-GO:\n\n- Support secret interactive input [GH-12876]\n- Support terminal coloring [GH-12888]\n- Validate if requested provider is usable and append/prepend information to errors [GH-12898]\n- Raise error if required metadata.json box fields are not present [GH-12919]\n\n## 2.3.0 (August 5, 2022)\n\nFEATURES:\n\n- core: Introduce vagrant-go [GH-12819]\n\nIMPROVEMENTS:\n\n- core: Set rsa-sha2 in kex algorithm set to enable in key exchange [GH-12584]\n- core/bundler: Improve Gem spec selection when resolving [GH-12567]\n- push/heroku: Display output from push [GH-12646]\n\nBUG FIXES:\n\n- host/darwin: Fix `NameError` for version capability [GH-12581]\n- push/ftp:  Fix `VAGRANT_CWD` handling [GH-12645]\n- guests/redhat: Fix NFS shares on Rocky 9 guests [GH-12813]\n\n## 2.2.19 (November 5, 2021)\n\nIMPROVEMENTS:\n\n- guest/suse: Add fallback shutdown for versions without systemd [GH-12489]\n- provider/virtualbox: Validate VirtualBox hostonly network range [GH-12564]\n\nBUG FIXES\n\n- guest/atomic: Update detection to prevent matching on non-atomic guests [GH-12575]\n- guest/coreos: Fix configure network capability [GH-12575]\n- guest/windows: Fix directory creation with rsync [GH-11880]\n- host/windows: Properly handle spaces in path to SSH key [GH-12398]\n- provisioner/chef: Update install checks [GH-12555]\n\n## 2.2.18 (July 27, 2021)\n\nBUG FIXES:\n\n- core: Fix of plugin manager kwargs [GH-12452]\n- providers/docker: Pass in docker command opts as a map [GH-12449]\n- providers/hyperv: Fix network address detection [GH-12472]\n\n## 2.2.17 (July 7, 2021)\n\nFEATURES:\n\n- guest/rocky: Add guest support for Rocky Linux [GH-12440]\n\nIMPROVEMENTS:\n\n- command/package: Add --info flag to package command [GH-12304]\n- guest/debian: Retry network setup on debain [GH-12421]\n- guest/suse: Use systemctl poweroff in the background instead of shutdown [GH-12439]\n- guest/windows: Get powershell path in %WINDIR%/System32 [GH-12436]\n- host/windows: Check Domain and Application Directory contexts for credentials when validating SMB creds [GH-12428]\n- providers/hyper-v: Fix IP detection when multiple devices are attached [GH-12232]\n- provisioner/ansible: Detects new versions of ansible-4.0.0+ [GH-12391]\n- provisioner/ansible: Strip whitespace from ansible version [GH-12420]\n- provisioner/salt: Always use upstream Salt bootstrap script on Windows [GH-12127]\n- provisioner/salt: Use more conservative TLS settings to work on older .NET versions [GH-12413]\n- provisioner/shell: Buffer output to display full lines [GH-12437]\n- vagrant: Updates to support Ruby 3.0 [GH-12427]\n\nBUG FIXES:\n\n- command/cloud: Fix authentication middleware to prevent breaking local paths [GH-12419]\n- communicator/ssh: Fix net-ssh patches for RSA signatures [GH-12415]\n- core: Add box directly with authed urls [GH-12278]\n\n## 2.2.16 (April 29, 2021)\n\nIMPROVEMENTS:\n\n- guest/linux: Detect in process shutdown in reboot capability [GH-12302]\n- util/powershell: Support `pwsh` executable in new versions of powershell [GH-12335]\n\nBUG FIXES:\n\n- communicator/ssh: Properly handle authentication with RSA keys [GH-12298]\n- guest/fedora: Import guest detection module [GH-12275]\n- guest/linux: Fix SMB folder mount name capability call [GH-12281]\n- provider/docker: Properly handle updated buildkit build output [GH-12300]\n\n## 2.2.15 (March 30, 2021)\n\nIMPROVEMENTS:\n\n- command/cloud: Remove access token URL parameter by default [GH-12234]\n- command/cloud: Add VAGRANT_SERVER_ACCESS_TOKEN_BY_URL to revert access token behavior [GH-12252]\n- core: Bump vagrant_cloud dependency to 3.0.3 [GH-12200]\n- core: Bump listen gem version and remove ruby_dep [GH-12148]\n- core: Bump vagrant_cloud dependency to 3.0.4 [GH-12242]\n- core/bundler: Update resolution handling when outside of installer and bundler [GH-12225]\n- core/plugin: Provide friendlier error messages on install fail when possible [GH-12225]\n- guest/openwrt: Add support for OpenWrt guests [GH-11791]\n- guest/freebsd: FreeBSD updated ansible to py37-ansible [GH-12201]\n- provider/virtualbox: Get default dhcp ip from a matching host ip [GH-12211]\n- util/downloader: Prevent redirect notification for default store [GH-12235]\n\nBUG FIXES:\n\n- command/cloud: Automatically disable direct uploads when file is too large [GH-12250]\n- core: Make shell script for loop shell agnostic [GH-12205]\n- core: Raise error if downloading box metadata fails [GH-12189]\n- core: Apply download options to metadata requests [GH-12177]\n- core: Don't try to find \"\" by prefix in the machine index [GH-12188]\n- core: Don't count not created machines as declined when destroying [GH-12186]\n- core: Bump bcrypt_pbkdf version [GH-12216]\n- core: Remove all space from checksums [GH-12168]\n- core/bundler: Do not include default gems as pinned constraints [GH-12253]\n- core/synced_folders: Extract os friendly mount name for vbox shared folders [GH-12184]\n- guest/alpine: Check if interface exists before shutting it down [GH-12181]\n- guest/nixos: Fix network config for recent NixOS releases [GH-12152]\n- guest/fedora: Detect fedora using os-releases id [GH-12230]\n\n## 2.2.14 (November 20, 2020)\n\nIMPROVEMENTS:\n\n- host/windows: Update filesystem type matching on WSL2 [GH-12056]\n- provisioner/salt: Modernize Salt bootstrap script [GH-12135]\n\nBUG FIXES:\n\n- core: Track raw actions as they are applied to prevent multiple insertions [GH-12037]\n- core/bundler: Update solution file resolution to support prerelease matching [GH-12054]\n- guest/darwin: Mount vmware synced folders for big sur guests [GH-12053]\n\n## 2.2.13 (November 06, 2020)\n\nBUG FIXES:\n\n- core/bundler: Adjust request sets properly with non-development prerelease [GH-12025]\n\n## 2.2.12 (November 06, 2020)\n\nBUG FIXES:\n\n- core/bundler: Automatically enable prerelease dependency resolution [GH-12023]\n\nNOTE: This is a fix release to resolve an immediate issue with Vagrant\n      plugin functionality\n\n## 2.2.11 (November 05, 2020)\n\nIMPROVEMENTS:\n\n- command/cap: Add ability to specify target [GH-11965]\n- command/cloud: Add --force flag to `version release` command [GH-11912]\n- command/cloud: Updates to utilize the 3.0 version of vagrant_cloud [GH-11916]\n- core: Switch from unmaintained gem erubis to erubi [GH-11893]\n- core: Download Vagrant boxes using auth headers [GH-11835]\n- core: Remove dependency on mime gem [GH-11857]\n- core: Handle Errno::EALREADY exceptions on port check [GH-12008]\n- core: Fix missing hook/trigger insertion into action stack [GH-12014]\n- guest/linux: Make max reboot wait duration configurable [GH-12011]\n- guest/windows: Make max reboot wait duration configurable [GH-12011]\n- providers/virtualbox: Fix availability check of provider [GH-11936]\n- tests: Add integration tests for Docker provider [GH-11907]\n\nBUG FIXES:\n\n- core/synced_folders: Don't persist synced folders to fstab if guest is not reachable [GH-11900]\n- core: Don't try to recover machine without a uuid [GH-11863]\n- config/disks: Transform provider specific config to common form [GH-11939]\n- config/vm: Override synced-folder `:nfs` option [GH-11988]\n- contrib/zsh: Remove newline from beginning of completion script [GH-11963]\n- guests/arch: Install smbclient when setting up arch smb [GH-11923]\n- guest/linux: Check for /etc/fstab before trying to modify [GH-11897]\n- guest/linux: Create an /etc/fstab if does not exist [GH-11909]\n- guest/linux: Persist SMB mounts [GH-11846]\n- guest/debian: Set hostname in /etc/hosts as first step to changing hostname [GH-11885]\n- guest/rhel: Check for existence of network files before trying to update them [GH-11877]\n- guest/suse: Don't use hostnamectl to set hostname if not available on system [GH-11996]\n- tests: Remove rsync dependency from tests [GH-11889]\n\n## 2.2.10 (August 24, 2020)\n\nFEATURES:\n\n- hyperv/disks: Add ability to manage virtual disks for guests [GH-11541]\n\nIMPROVEMENTS:\n\n- core: Allow provisioners to be run when a communicator is not available [GH-11579]\n- core: Add `autocomplete` command that allows for install of bash or zsh autocomplete scripts [GH-11523]\n- core: Update to childprocess gem to 4.0.0 [GH-11717]\n- core: Add action to wait for cloud-init to finish running [GH-11773]\n- core: Update to net-ssh to 6.0 and net-sftp to 3.0 [GH-11621]\n- core: Optimize port in use check for faster validation [GH-11810]\n- core: Support for Ruby 2.7 [GH-11814]\n- core: Add synced folder capabilities for mount options and default fstab modification behavior [GH-11797]\n- guest/arch: Use systemd-networkd to configure networking for guests [GH-11400]\n- guest/haiku: Rsync install for rsync synced folders [GH-11614]\n- guest/solaris11: Add guest capability shell_expand_guest_path [GH-11759]\n- host/darwin: Add ability to build ISO [GH-11694]\n- hosts/linux: Add ability to build ISO [GH-11750]\n- hosts/windows: Add ability to build ISO [GH-11750]\n- providers/hyperv: Add support for SecureBootTemplate setting on import [GH-11756]\n- providers/hyperv: Add support for EnhancedSessionTransportType [GH-11014]\n- virtualbox/disks: Add ability to manage virtual dvds for guests [GH-11613]\n\nBUG FIXES:\n\n- core: Ensure MapCommandOptions class is required [GH-11629]\n- core: Fix `:all` special value on triggers [GH-11688]\n- core: Ensure network addresses have a valid netmask [GH-11679]\n- core: Recover local machine metadata in global index [GH-11656]\n- core: Print CLI help message is ambiguous option provided [GH-11746]\n- core: Update how `/etc/hosts` gets updated for darwin, freebsd and openbsd [GH-11719]\n- core: Capture `[3J` escape sequence [GH-11807]\n- core: Treat empty box value as invalid [GH-11618]\n- core: Allow forwarding ports to unknown addresses [GH-11810]\n- core: Scrub credentials as whole words [GH-11837]\n- commands/destroy: Add gracefull option to switch beween gracefully or forcefully shutting down a vm [GH-11628]\n- communicator/ssh: Raise an error for a nil exit status [GH-11721]\n- communicator/winrm: Check for nil return from querying for forwarded ports [GH-11831]\n- config/vm: Add option `allow_hosts_modification` to allow/disable Vagrant editing the guests `/etc/hosts` file [GH-11565]\n- config/vm: Add config option `hostname` to `config.vm.network` [GH-11566]\n- config/vm: Don't ignore NFS synced folders on Windows hosts [GH-11631]\n- host: Use regular port check for loopback addresses [GH-11654]\n- host: Allow windows and linux hosts to detach from rdp process [GH-11732]\n- host/windows: Properly register SMB password validation capability [GH-11795]\n- guests: Allow setting of hostname according to `hostname` option for multiple guests [GH-11704]\n- guest/alpine: Allow setting of hostname according to `hostname` option [GH-11718]\n- guest/esxi: Be more permissive with permissions of ssh directory [GH-11587]\n- guest/linux: Add virtual box shared folders to guest fstab [GH-11570]\n- guest/suse: Allow setting of hostname according to `hostname` option [GH-11567]\n- providers/docker: Ensure new containers don't grab existing bound ports [GH-11602]\n- providers/hyperv: Fix check for secure boot [GH-11809]\n- providers/virtualbox: Fix inability to create disk with same name across multiple guests [GH-11767]\n- provisioners/docker: Allow to specify docker image version using the `run` option [GH-11806]\n- provisioners/file: Allow creating empty folders [GH-11805]\n- provisioners/shell: Ensure Windows shell provisioner gets the correct file extension [GH-11644]\n- util/powershell: Use correct powershell executable for privileged commands [GH-11787]\n\n## 2.2.9 (May 07, 2020)\n\nBUG FIXES:\n\n- core/bundler: Properly handle plugin install with available specification [GH-11592]\n- provisioners/docker: Fix CentOS docker install and start service capabilities [GH-11581]\n- provisioners/podman: Seperate RHEL install from CentOS install [GH-11584]\n\n## 2.2.8 (May 04, 2020)\n\nFEATURES:\n\n- virtualbox/disks: Add ability to manage virtual disks for guests [GH-11349]\n\nIMPROVEMENTS:\n\n- bin/vagrant: Automatically include global options within commands [GH-11473]\n- bin/vagrant: Suppress Ruby warnings when not running pre-release version [GH-11446]\n- communicator/ssh: Add support for configuring SSH connect timeout [GH-11533]\n- core: Update childprocess gem [GH-11487]\n- core: Add cli option `--no-tty` [GH-11414]\n- core: Overhaul call stack modifications implementation for hooks and triggers [GH-11455]\n- core/bundler: Cache plugin solution sets to speed up startup times [GH-11363]\n- config/vm: Add`box_download_options` config to specify extra download options for a box [GH-11560]\n- guest/alpine: Add ansible provisioner guest support [GH-11411]\n- guest/linux: Update systemd? check to use sudo [GH-11398]\n- guest/linux: Use systemd if available to halt and reboot system [GH-11407]\n- guests/linux: Mount smb folders with `mfsymlinks` option by default [GH-11503]\n- guest/redhat: Add support for SMB [GH-11463]\n- guest/windows: Rescue all regular exceptions during reboot wait [GH-11428]\n- providers/docker: Support catching container name when using podman [GH-11356]\n- provisioners/docker: Support Centos8 [GH-11462]\n- provisioners/podman: Add Podman as a provisioner [GH-11472]\n- provisioners/salt: Allow specifying python_version [GH-11436]\n\nBUG FIXES:\n\n- communicators/winssh: Fix issues with Windows SSH communicator [GH-11430]\n- core/bundler: Activate vagrant specification when not active [GH-11445]\n- core/bundler: Properly resolve sets when Vagrant is in prerelease [GH-11571]\n- core/downloader: Always set `-q` flag as first option [GH-11366]\n- core/hooks: Update dynamic action hook implementation to prevent looping [GH-11427]\n- core/synced_folders: Validate type option if set [GH-11359]\n- guests/debian: Choose netplan renderer based on network configuration and installed tools [GH-11498]\n- host/darwin: Quote directories in /etc/exports [GH-11441]\n- host/linux: Ensure `/etc/exports` does not contain duplicate records [GH-10591]\n- host/windows: Check all interfaces for port conflict when host_ip: \"0.0.0.0\" [GH-11454]\n- providers/docker: Fix issue where Vagrant fails to remove image if it is in use [GH-11355]\n- providers/docker: Fix issue with getting correct docker image id from build output [GH-11461]\n- providers/hyperv: Prevent error when identity reference cannot be translated [GH-11425]\n- provider/hyperv: Use service id for manipulating vm integration services [GH-11499]\n- providers/virtualbox: Parse `list dhcpservers` output on VirtualBox 6.1 [GH-11404]\n- providers/virtualbox: Raise an error if guest IP ends in .1 [GH-11500]\n- provisioners/shell: Ensure windows shell provisioners always get an extension [GH-11517]\n- util/io: Fix encoding conversion errors [GH-11571]\n\n## 2.2.7 (January 27, 2020)\n\nIMPROVEMENTS:\n\n- guest/opensuse: Check for basename hostname prior to setting hostname [GH-11170]\n- host/linux: Check for modinfo in /sbin if it's not on PATH [GH-11178]\n- core: Show guest name in hostname error message [GH-11175]\n- provisioners/shell: Linux guests now support `reboot` option [GH-11194]\n- darwin/nfs: Put each NFS export on its own line [GH-11216]\n- contrib/bash: Add more completion flags to up command [GH-11223]\n- provider/virtualbox: Add VirtualBox provider support for version 6.1.x [GH-11250]\n- box/outdated: Allow to force check for box updates and ignore cached check [GH-11231]\n- guest/alpine: Update apk cache when installing rsync [GH-11220]\n- provider/virtualbox: Improve error message when machine folder is inaccessible [GH-11239]\n- provisioner/ansible_local: Add pip install method for arch guests [GH-11265]\n- communicators/winssh: Use Windows shell for `vagrant ssh -c` [GH-11258]\n\nBUG FIXES:\n\n- command/snapshot/save: Fix regression that prevented snapshot of all guests in environment [GH-11152]\n- core: Update UI to properly retain newlines when adding prefix [GH-11126]\n- core: Check if box update is available locally [GH-11188]\n- core: Ensure Vagrant::Errors are loaded in file_checksum util [GH-11183]\n- cloud/publish: Improve argument handling for missing arguments to command [GH-11184]\n- core: Get latest version for current provider during outdated check [GH-11192]\n- linux/nfs: avoid adding extra newlines to /etc/exports [GH-11201]\n- guest/darwin: Fix VMware synced folders on APFS [GH-11267]\n- guest/redhat: Ensure `nfs-server` is restarted when installing nfs client [GH-11212]\n- core: Do not validate checksums if options are empty string [GH-11211]\n- provider/docker: Enhance docker build method to match against buildkit output [GH-11205]\n- provisioner/ansible_local: Don't prompt for input when installing Ansible on Ubuntu and Debian [GH-11191]\n- provisioner/ansible_local: Ensure all guest caps accept all passed in arguments [GH-11265]\n- host/windows: Fix regression that prevented port collisions from being detected [GH-11244]\n- core/provisioner: Set top level provisioner name if set in a provisioner config [GH-11295]\n\n## 2.2.6 (October 14, 2019)\n\nFEATURES:\n\n- core/provisioners: Introduce new Provisioner options: before and after [GH-11043]\n- guest/alpine: Integrate the vagrant-alpine plugin into Vagrant core [GH-10975]\n\nIMPROVEMENTS:\n\n- command/box/prune: Allow prompt skip while preserving actively in use boxes [GH-10908]\n- command/cloud: Support providing checksum information with boxes [GH-11101]\n- dev: Fixed Vagrantfile for Vagrant development [GH-11012]\n- guest/alt: Improve handling for using network tools when setting hostname [GH-11000]\n- guest/suse: Add ipv6 network config templates for SUSE based distributions [GH-11013]\n- guest/windows: Retry on connection timeout errors for the reboot capability [GH-11093]\n- host/bsd: Use host resolve path capability to modify local paths if required [GH-11108]\n- host/darwin: Add host resolve path capability to provide real paths for firmlinks [GH-11108]\n- provisioners/chef: Update pkg install flags for chef on FreeBSD guests [GH-11075]\n- provider/hyperv: Improve error message when VMMS is not running [GH-10978]\n- provider/virtualbox: Raise additional errors for incomplete virtualbox installation on usable check [GH-10938]\n- util/filechecksum: Add support for more checksum types [GH-11101]\n\nBUG FIXES:\n\n- command/rsync-auto: Fix path watcher bug so that all subdirectories are synced when changed [GH-11089]\n- command/snapshot/save: Ensure VM id is passed to list snapshots for hyper-v provider [GH-11097]\n- core: Ensure proper paths are shown in config loading exceptions [GH-11056]\n- guest/suse: Use hostnamectl instead of hostname to set the hostname under SUSE [GH-11100]\n- provider/docker: Fix default provider validation if password is used [GH-11053]\n- provider/docker: Fix Docker providers usable? check [GH-11068]\n- provisioner/ansible_local: Ensure pip_install_cmd is finalized to emptry string [GH-11098]\n- provisioner/file: Ensure relative path for file provisioner source is relative to guest machines cwd [GH-11099]\n- provider/docker: Ensure docker build_args option is properly set in docker compose config yaml [GH-11106]\n- guest/suse: Update nfs & service daemon names for suse based hosts and guests [GH-11076]\n- provider/docker: Determine ip address prefix workaround for docker public networks [GH-11111]\n- provider/docker: Only return interfaces where addr is not nil for networks [GH-11116]\n\n## 2.2.5 (June 19, 2019)\n\nFEATURES:\n\n- providers/docker: Private and Public networking support [GH-10702]\n\nIMPROVEMENTS:\n\n- command/global-status: Provide machine-readable information [GH-10506]\n- command/snapshot: Separate snapshot names for guests when listing snapshots [GH-10828]\n- command/box/update: Ignore missing metadata files when updating all boxes [GH-10829]\n- core: Use consistent settings when unpacking boxes as root [GH-10707]\n- core: Write metadata.json file when packaging box [GH-10706]\n- core: Remove whitespace from id file on load [GH-10727]\n- core/bundler: Support resolution when installed within system [GH-10894]\n- guest/coreos:  Update network configuration and hostname setting [GH-10752]\n- guest/freebsd: Add proper VirtualBox share folders support for FreeBSD guests [GH-10717]\n- guest/freebsd: Add unmount share folder for  VirtualBox guests [GH-10761]\n- guest/freebsd: Simplify network interface listing when configuring networks [GH-10763]\n- providers/docker: Add usable? check to docker provider [GH-10890]\n- synced_folder/smb: Remove configuration information from synced folder data [GH-10811]\n\nBUG FIXES:\n\n- command/box/update: Ensure the right version is picked when updating specific boxes [GH-10810]\n- command/cloud: Properly set variable from CLI argument parsing for `username` field [GH-10726]\n- command/rsync_auto: Use relative paths to machines folder path for file path Listener [GH-10902]\n- communicator/ssh: Remove net/sftp loading to prevent loading errors [GH-10745]\n- contrib/bash: Search for running_vm_list only in `machines` folder [GH-10841]\n- core/bundler: Properly parse multiple constants when installing plugins [GH-10896]\n- core/environment: Support plugin configuration within box Vagrantfiles [GH-10889]\n- core/triggers: Fix typo in UI output [GH-10748]\n- core/triggers: Properly exit with abort option [GH-10824]\n- core/triggers: Ensure guest names are string when filtering trigger configs [GH-10854]\n- core/triggers: Abort after all running processes have completed when parallel is enabled [GH-10891]\n- guest/void: Fix NFS capability detection [GH-10713]\n- guest/bsd: Properly set BSD options order for /etc/exports [GH-10909]\n- host/windows: Fix rubygems error when host has directory named `c` [GH-10803]\n- provider/virtualbox: Ensure non-existent machines do not attempt to list snapshots [GH-10784]\n- provider/docker: Properly set docker-compose config file with volume names [GH-10820]\n- provisioner/ansible: Fix pip installer hardcoded curl get_pip.py piped to python [GH-10625]\n- provisioner/chef: Update chef install check for guests [GH-10917]\n- synced_folders/rsync: Remove rsync__excludes from command if array is empty [GH-10901]\n\n## 2.2.4 (February 27, 2019)\n\nFEATURES:\n\n- core/triggers: Introduce new option `:type` for actions, hooks, and commands [GH-10615]\n\nIMPROVEMENTS:\n\n- communicator/ssh: Update `#upload` behavior to work properly with new sshd path checks [GH-10698]\n- communicator/winrm: Update `#upload` behavior to match ssh communicator upload behavior [GH-10698]\n- guest/windows: Add reboot output to guest capability [GH-10638]\n- provisioner/file: Refactor path modification rules and allow communicator to handle details [GH-10698]\n\nBUG FIXES:\n\n- core: Fix format finalization of plugins in Vagrantfile [GH-10664]\n- core: Fix SIGINT behavior and prevent backtrace [GH-10666]\n- core: Change remaining box_client_cert refs to box_download_client_cert [GH-10622]\n- core: Move over AddAuthentication middleware and hooks  out of deprecated class [GH-10686]\n- guest/debian: Properly set DHCP for systemd-networkd ips [GH-10586]\n- guest/solaris11: Create interface if required before configuration [GH-10595]\n- installers/appimage: Use ld path with appimage libs on suffix [GH-10647]\n- providers/docker: Expand paths when comparing synced folders on reload [GH-10645]\n- providers/virtualbox: Fix import paths on Windows with VirtualBox 6 [GH-10629]\n- synced_folders/rsync: Properly clean up tmp folder created during rsync [GH-10690]\n\n## 2.2.3 (January 9, 2019)\n\nFEATURES:\n\n- host/void: Add host support for void linux [GH-10012]\n\nIMPROVEMENTS:\n\n- command/rsync-auto: Prevent crash on post-rsync command failure [GH-10515]\n- command/snapshot: Raise error for bad subcommand [GH-10470]\n- command/package: Ensure temp dir for package command is cleaned up [GH-10479]\n- command/powershell: Support running elevated commands [GH-10528]\n- communicator/ssh: Add `config` and `remote_user` options [GH-10496]\n- core: Display version update on stderr instead of stdout [GH-10482]\n- core: Add experimental feature flag [GH-10485]\n- core: Show box version during box outdated check [GH-10573]\n- guest/windows: Modify elevated username only on username failure [GH-10488]\n- host/windows: Prevent SMB setup commands from becoming too long [GH-10489]\n- host/windows: Automatically answer yes when pruning SMB shares [GH-10524]\n- provisioners/file: Show source and destination locations with file provisioner [GH-10570]\n- provisioners/salt: Validate that `install_type` is set if `version` is specified [GH-10474]\n- provisioners/salt: Update default install version [GH-10537]\n- provisioners/shell: Add `reboot` option for rebooting supported guest [GH-10532]\n- synced_folders/rsync: Support using rsync `--chown` option [GH-10529]\n- util/guest_inspection: Validate hostnamectl command works when detected [GH-10512]\n- util/platform: Use wslpath command for customized root on WSL [GH-10574]\n\nBUG FIXES:\n\n- command/cloud publish: Ensure box file exists before path expanding [GH-10468]\n- command/cloud publish: Catch InvalidVersion errors from vagrant_cloud client [GH-10513]\n- command/snapshot: Retain consistent provisioning behavior across all commands [GH-10490]\n- command/validate: Bypass install checks for validating configs with the `--ignore-provider` flag [GH-10467]\n- communicator/ssh: Fix garbage output detection [GH-10571]\n- guest/alt: Fix network configuration errors [GH-10527]\n- guest/coreos: Fix grep command for network interface of CoreOS guest [GH-10554]\n- guest/freebsd: Fix defaultrouter rcvar in static network template [GH-10469]\n- guest/redhat: Fix network configuration errors [GH-10527]\n- providers/virtualbox: Adjust version requirement for NIC warning [GH-10486]\n- util/powershell: Use correct Base64 encoding for encoded commands [GH-10487]\n\n## 2.2.2 (November 27, 2018)\n\nBUG FIXES:\n\n- providers/virtualbox: Update default_nic_type implementation and add warning [GH-10450]\n\n## 2.2.1 (November 15, 2018)\n\nFEATURES:\n\n- core/plugins: Add reset! method to communicator [GH-10399]\n- providers/virtualbox: Add support for VirtualBox 6.0 [GH-10379]\n\nIMPROVEMENTS:\n\n- command/validate: Allow validation of config while ignoring provider [GH-10351]\n- communicators/ssh: Prevent overly verbose output waiting for connection [GH-10321]\n- communicators/ssh: Support ed25519 keys [GH-10365]\n- communicators/ssh: Add reset! implementation [GH-10399]\n- communicators/winrm: Add reset! implementation [GH-10399]\n- core: Limit number of automatic box update checks [GH-10359]\n- host/windows: Remove PATH check in WSL detection [GH-10313]\n- providers/hyperv: Disable automatic checkpoints before deletion [GH-10406]\n- providers/virtualbox: Add `automount` flag if specified with synced_folder [GH-10326]\n- providers/virtualbox: Refactor host only network settings [GH-7699]\n- providers/virtualbox: Support setting default NIC type for network adapters [GH-10383]\n- providers/virtualbox: Update ssh_port helper to handle multiple matches [GH-10409]\n- provisioners/shell: Add :reset option to allow communicator reset [GH-10399]\n- synced_folders/smb: Allow for 'default' smb_username in prompt if set [GH-10319]\n- util/network_ip: Simplify `network_address` helper [GH-7693]\n- util/platform: Prevent hard failure during hyper-v enabled check [GH-10332]\n\nBUG FIXES:\n\n- command/login: Only show deprecation warning when command is invoked [GH-10374]\n- core: Fallback to Vagrantfile defined box information [GH-10368]\n- core/bundler: Update source ordering to properly resolve with new RubyGems [GH-10364]\n- core/triggers: Only split inline script if host is non-Windows [GH-10405]\n- communicator/winrm: Prepend computer name to username when running elevated commands [GH-10387]\n- guest/debian: Fix halting issue when setting hostname by restarting networking on guest [GH-10301, GH-10330]\n- guest/linux: Fix vagrant user access to docker after install [GH-10399]\n- guest/windows: Add reboot capability to fix hostname race condition [GH-10347]\n- guest/windows: Allow for reading key paths with spaces [GH-10389]\n- host/windows: Fix powershell to properly handle paths with spaces [GH-10390]\n- providers/docker: Deterministic host VM synced folder location for Docker VM [GH-10311]\n- providers/hyperv: Fix network vlan configuration script [GH-10366]\n- providers/hyperv: Properly output error message on failed guest import [GH-10404]\n- providers/hyperv: Fix typo in network configuration detection script [GH-10410]\n\n## 2.2.0 (October 16, 2018)\n\nFEATURES:\n\n- command/cloud: Introduce `vagrant cloud` subcommand to Vagrant [GH-10148]\n- command/upload: Add command for uploading files to guest [GH-10263]\n- command/winrm: Add command for executing guest commands via WinRM [GH-10263]\n- command/winrm-config: Add command for providing WinRM configuration [GH-10263]\n\nIMPROVEMENTS:\n\n- core: Ensure file paths are identical when checking for cwd [GH-10220]\n- core: Add config option `ignore_box_vagrantfile` for ignoring vagrantfile inside box [GH-10242]\n- core/triggers: Add abort option to core triggers [GH-10232]\n- core/triggers: Introduce `ruby` option for trigger [GH-10267]\n- contrib/bash: Add completion for snapshot names for vagrant snapshot restore|delete [GH-9054]\n- providers/docker: Build docker from git repo [GH-10221]\n- providers/hyperv: Update Hyper-V admin check and allow override via ENV variable [GH-10275]\n- providers/virtualbox: Allow base_mac to be optional [GH-10255]\n- provisioners/salt: bootstrap-salt.sh: use -s with curl [GH-9432]\n- provisioners/salt: remove leading space with bootstrap_options [GH-9431]\n\nBUG FIXES:\n\n- core/environment: Provide rgloader for local plugin installations [GH-10279]\n- contrib/sudoers/osx: Fix missing comma and add remove export alias [GH-10235]\n- guest/redhat: Update restart logic in redhat change_host_name cap [GH-10223]\n- guest/windows: Allow special characters in SMB password field [GH-10219]\n- providers/hyperv: Only use AutomaticCheckpointsEnabled when available [GH-10264]\n- providers/hyperv: Only use CheckpointType when available [GH-10265]\n- provisioners/ansible: Fix remote directory creation [GH-10259, GH-10258]\n- provisioners/puppet: Properly set env variables for puppet provisioner on windows [GH-10218]\n- provisioners/salt: Properly set salt pillar variables for windows guests [GH-10215]\n- synced_folders/rsync: Ensure unique tmp dirs for ControlPath with rsync [GH-10291]\n\n## 2.1.5 (September 12, 2018)\n\nIMPROVEMENTS:\n\n- core: Add `Vagrant.version?` helper method [GH-10191]\n- core: Scrub sensitive values from logger output [GH-10200]\n- core: Prevent multiple evaluations of Vagrantfile [GH-10199]\n- command/init: Support VAGRANT_DEFAULT_TEMPLATE env var [GH-10171]\n- command/powershell: Improve doc help string and fix winrm locales error [GH-10189]\n- contrib/bash: autocomplete running VM names for destroy subcommand [GH-10168]\n- guest/debian: Use `sudo` to determine if systemd is in use for hardened systems [GH-10198]\n- guest/openbsd: Add IPv6 network template for OpenBSD machines [GH-8912]\n- provisioners/salt: Allow non-windows hosts to pass along version [GH-10194]\n\nBUG FIXES:\n\n- core: Fix Vagrant.has_plugin? behavior before plugins are initialized [GH-10165]\n- core: Check verify_host_key for falsey or :never values when generating ssh config [GH-10182]\n- guest/linux: Filter out empty strings and loopback interfaces when constructing list of network interfaces [GH-10092]\n- provider/hyper-v: Check for automatic checkpoint support before configuring [GH-10181]\n\n## 2.1.4 (August 30, 2018)\n\nBUG FIXES:\n\n- core: Fix local plugin installation prompt answer parsing [GH-10154]\n- core: Reset internal environment after plugin loading [GH-10155]\n- host/windows: Fix SMB list parsing when extra fields are included [GH-10156]\n- provisioners/ansible_local: Fix umask setting permission bug [GH-10140]\n\n## 2.1.3 (August 29, 2018)\n\nFEATURES:\n\n- core: Support for project specific plugins [GH-10037]\n\nIMPROVEMENTS:\n\n- command/reload: Add `--force` flag to reload command [GH-10123]\n- communicator/winrm: Display warning if vagrant-winrm plugin is detected [GH-10076]\n- contrib/bash: Replace -VAGRANTSLASH- with literal slash in completion [GH-9987]\n- core: Show installed version of Vagrant when displaying version check [GH-9968]\n- core: Retain information of original box backing active guest [GH-10083]\n- core: Only write box info if provider supports box objects [GH-10126]\n- core: Update net-ssh dependency constraint to ~> 5.0.0 [GH-10066]\n- core/triggers: Catch and allow for non-standard exit codes with triggers `run` options [GH-10005]\n- core/triggers: Allow for spaces in `path` for trigger run option [GH-10118]\n- guest/debian: Isolate network interface configuration to individual files for systemd [GH-9889]\n- guest/redhat: Use libnfs-utils package if available [GH-9878]\n- provider/docker: Support Docker volume consistency for synced folders [GH-9811]\n- provider/hyperv: Disable synced folders on non-DrvFs file systems by default [GH-10001]\n- util/downloader: Support custom suffix on user agent string [GH-9966]\n- util/downloader: Prevent false positive matches on Location header [GH-10041]\n- util/subprocess: Force system library paths for executables external to AppImage [GH-10078]\n\nBUG FIXES:\n\n- core: Disable Vagrantfile loading with plugin commands [GH-10030]\n- core: Ensure the SecureRandom library is loaded for the trigger class [GH-10063]\n- core/triggers: Allow trigger run args option to be a single string [GH-10116]\n- util/powershell: Properly `join` commands from passed in array [GH-10115]\n- guest/solaris: Add back guest detection check for Solaris derived guests [GH-10081]\n- guest/windows: Be more explicit when invoking cmd.exe with mount_volume script [GH-9976]\n- host/linux: Fix sudo usage in NFS capability when modifying exports file [GH-10084]\n- host/windows: Remove localization dependency from SMB list generation [GH-10043]\n- provider/docker: Convert windows paths for volume mounts on docker driver [GH-10100]\n- provider/hyperv: Fix checkpoint configuration and properly disable automatic checkpoints by default [GH-9999]\n- provider/hyperv: Remove localization dependency from access check [GH-10000]\n- provider/hyperv: Enable ExposeVirtualizationExtensions only when available [GH-10079]\n- provider/virtualbox: Skip link-local when fixing IPv6 route [GH-9639, GH-10077]\n- push/ftp: Custom error when attempting to push too many files [GH-9952]\n- util/downloader: Prevent errors when Location header contains relative path [GH-10017]\n- util/guest_inspection: Prevent nmcli check from hanging when pty is enabled [GH-9926]\n- util/platform: Always force string type conversion on path [GH-9998]\n\n## 2.1.2 (June 26, 2018)\n\nIMPROVEMENTS:\n\n- commands/suspend: Introduce flag for suspending all machines [GH-9829]\n- commands/global-status: Improve message about removing stale entries [GH-9856]\n- provider/hyperv: Attempt to determine import failure cause [GH-9936]\n- provider/hyperv: Update implementation. Include support for modifications on reload [GH-9872]\n- provider/hyperv: Validate maxmemory configuration setting [GH-9932]\n- provider/hyperv: Enable provider within WSL [GH-9943]\n- provider/hyperv: Add Hyper-V accessibility check on data directory path [GH-9944]\n- provisioners/ansible_local: Improve installation from PPA on Ubuntu guests.\n    The compatibility is maintained only for active long-term support (LTS) versions,\n    i.e. Ubuntu 12.04 (Precise Pangolin) is no longer supported. [GH-9879]\n\nBUG FIXES:\n\n- communicator/ssh: Update ssh private key file permission handling on Windows [GH-9923, GH-9900]\n- core: Display plugin commands in help [GH-9808]\n- core: Ensure guestpath or name is set with synced_folder option and dont set guestpath if not provided [GH-9692]\n- guest/debian: Fix netplan generation when using DHCP [GH-9855]\n- guest/debain: Update priority of network configuration file when using networkd [GH-9867]\n- guest/ubuntu: Update netplan config generation to detect NetworkManager [GH-9824]\n- guest/ubuntu: Fix failing Ansible installation from PPA on Bionic Beaver (18.04 LTS) [GH-9796]\n- host/windows: Prevent processing of last SMB line when using net share [GH-9917]\n- provisioner/chef: Prevent node_name set on configuration with chef_apply [GH-9916]\n- provisioner/salt: Remove usage of masterless? config attribute [GH-9833]\n\n## 2.1.1 (May 7, 2018)\n\nIMPROVEMENTS:\n\n- guest/linux: Support builtin vboxsf module for shared folders [GH-9800]\n- host/windows: Update SMB capability to work without Get-SmbShare cmdlet [GH-9785]\n\nBUG FIXES:\n\n- core/triggers: Initialize internal trigger object for machine before initializing provider [GH-9784]\n- core/triggers: Ensure internal trigger fire does not get called if plugin installed [GH-9799]\n- provider/hyperv: Call import script with switchid instead of switchname [GH-9781]\n\n## 2.1.0 (May 3, 2018)\n\nFEATURES:\n\n- core: Integrate vagrant-triggers plugin functionality into core Vagrant [GH-9713]\n\nIMPROVEMENTS:\n\n- core: Improve messaging around not finding requested provider [GH-9735]\n- core: Disable exception reports by default [GH-9738]\n- core: Continue on if vagrant fails to parse metadata box for update [GH-9760]\n- hosts/linux: Support RDP capability within WSL [GH-9758]\n- hosts/windows: Add SMB default mount options capability and set default version to 2.0 [GH-9734]\n- provider/hyperv: Include neighbor check for MAC on guest IP detection [GH-9737]\n- provider/virtualbox: Do not require VirtualBox availability within WSL [GH-9759]\n- provisioner/chef_zero: Support arrays for data_bags_path [GH-9669]\n- util/downloader: Don't raise error if response is HTTP 416 [GH-9729]\n- util/platform: Update Hyper-V enabled check [GH-9746]\n\nBUG FIXES:\n\n- communicators/ssh: Log error and proceed on Windows private key permissions [GH-9769]\n- middleware/authentication: Prevent URL modification when no changes are required [GH-9730]\n- middleware/authentication: Ignore URLs which cannot be parsed [GH-9739]\n- provider/hyperv: Reference switches by ID instead of name [GH-9747]\n- provider/docker: Use Util::SafeExec if docker-exec is run with `-t` option [GH-9761]\n- provisioner/chef: Trim drive letter from path on Windows [GH-9766]\n- provisioner/puppet: Properly finalize structured_facts config option [GH-9720]\n- util/platform: Fix original WSL to Windows path for \"root\" directory [GH-9696]\n\n## 2.0.4 (April 20, 2018)\n\nFEATURES:\n\n- core: Vagrant aliases [GH-9504]\n\nIMPROVEMENTS:\n\n- communicators/ssh: Update file permissions when generating new key pairs [GH-9676]\n- core: Make resolv-replace usage opt-in instead of opt-out [GH-9644]\n- core: Suppress error messages from checkpoint runs [GH-9645]\n- guests/coreos: Identify operating systems closely related to CoreOS [GH-9600]\n- guests/debian: Adjust network configuration file prefix to 50- [GH-9646]\n- guests/photon: Less specific string grep to fix PhotonOS 2.0 detection [GH-9528]\n- guests/windows: Fix slow timeout when updating windows hostname [GH-9578]\n- hosts/windows: Make powershell version detection timeout configurable [GH-9506]\n- providers/virtualbox: Improve network collision error message [GH-9685]\n- provisioner/chef_solo: Improve Windows drive letter removal hack for remote paths[GH-9490]\n- provisioner/chef_zero: File path expand all chef_zero config path options [GH-9690]\n- provisioner/puppet: Puppet structured facts toyaml on provisioner [GH-9670]\n- provisioner/salt: Add master_json_config & minion_json_config options [GH-9420]\n- util/platform: Warn on ArgumentError exceptions from encoding [GH-9506]\n\nBUG FIXES:\n\n- commands/package: Fix uninitialized constant error [GH-9654]\n- communicators/winrm: Fix command filter to properly parse commands [GH-9673]\n- hosts/windows: Properly respect the VAGRANT_PREFER_SYSTEM_BIN environment variable [GH-9503]\n- hosts/windows: Fix virtualbox shared folders path for windows guests [GH-8099]\n- guests/freebsd: Fix typo in command that manages configuring networks [GH-9705]\n- util/checkpoint_client: Respect VAGRANT_CHECKPOINT_DISABLE environment variable [GH-9659]\n- util/platform: Use `--version` instead of `version` for WSL validation [GH-9674]\n\n## 2.0.3 (March 15, 2018)\n\nIMPROVEMENTS:\n\n  - guests/solaris: More explicit Solaris 11 and inherit SmartOS from Solaris [GH-9398]\n  - hosts/windows: Add support for latest WSL release [GH-9525, GH-9300]\n  - plugins/login: Update middleware to re-map hosts and warn on custom server [GH-9499]\n  - providers/hyper-v: Exit if Hyper-V is enabled and VirtualBox provider is used [GH-9456]\n  - provisioners/salt: Change to a temporary directory before downloading script files [GH-9351]\n  - sycned_folders/nfs: Default udp to false when using version 4 [GH-8828]\n  - util/downloader: Notify on host redirect [GH-9344]\n\nBUG FIXES:\n\n  - core: Use provider override when specifying box_version [GH-9502]\n  - guests/debian: Renew DHCP lease on hostname change [GH-9405]\n  - guests/debian: Point hostname to 127.0.1.1 in /etc/hosts [GH-9404]\n  - guests/debian: Update systemd? check for guest inspection [GH-9459]\n  - guests/debian: Use ip route in dhcp template [GH-8730]\n  - guests/gentoo: Disable if/netplugd when setting up a static ip on a gentoo guest using openrc [GH-9261]\n  - guests/openbsd: Atomically apply new hostname.if(5) [GH-9265]\n  - hosts/windows: Fix halt problem when determining powershell version on old powershells [GH-9470]\n  - hosts/windows: Convert to windows path if on WSL during vbox export [GH-9518]\n  - providers/virtualbox: Fix hostonly matching not respecting :name argument [GH-9302]\n  - util/credential_scrubber: Ignore empty strings [GH-9472, GH-9462]\n\n## 2.0.2 (January 29, 2018)\n\nFEATURES:\n\n  - core: Provide mechanism for removing sensitive data from output [GH-9276]\n  - core: Relax Ruby constraints to include 2.5 [GH-9363]\n  - core: Hide sensitive values in output [GH-9369]\n  - command/init: Support custom Vagrantfile templates [GH-9202]\n  - guests: Add support for the Haiku operating system [GH-7805, GH-9245]\n  - synced_folders/smb: Add support for macOS hosts [GH-9294]\n  - vagrant-spec: Update vagrant-spec to include Windows platforms and updated linux boxes [GH-9183]\n\nIMPROVEMENTS:\n\n  - config/ssh: Deprecate :paranoid in favor of :verify_host_key [GH-9341]\n  - core: Add optional timestamp prefix on log output [GH-9269]\n  - core: Print more helpful error message for NameEror exceptions in Vagrantfiles [GH-9252]\n  - core: Update checkpoint implementation to announce updates and support notifications [GH-9380]\n  - core: Use Ruby's Resolv by default [GH-9394]\n  - docs: Include virtualbox 5.2.x as supported in docs [GH-9237]\n  - docs: Improve how to pipe debug log on powershell [GH-9330]\n  - guests/amazon: Improve guest detection [GH-9307]\n  - guests/debian: Update guest configure networks [GH-9338]\n  - guests/dragonflybsd: Base guest on FreeBSD to inherit more functionality [GH-9205]\n  - guests/linux: Improve NFS service name detection and interactions [GH-9274]\n  - guests/linux: Support mount option overrides for SMB mounts [GH-9366]\n  - guests/linux: Use `ip` for reading guest address if available [GH-9315]\n  - guests/solaris: Improve guest detection for alternatives [GH-9295]\n  - hosts/windows: Check credentials during SMB prepare [GH-9365]\n  - providers/hyper-v: Ensure Hyper-V cmdlets are fully qualified [GH-8863]\n  - middleware/authentication: Add app.vagrantup.com to allowed hosts [GH-9145]\n  - provisioners/shell: Support hiding environment variable values in output [GH-9367]\n  - providers/virtualbox: Add a clean error message for invalid IP addresses [GH-9275]\n  - providers/virtualbox: Introduce flag for SharedFoldersEnableSymlinksCreate setting [GH-9354]\n  - providers/virtualbox: Provide warning for SharedFoldersEnableSymlinksCreate setting [GH-9389]\n  - provisioners/salt: Fixes timeout issue in salt bootstrapping for windows [GH-8992]\n  - synced_folders/smb: Update Windows implementation [GH-9294]\n  - util/ssh: Attempt to locate local ssh client before attempting installer provided [GH-9400]\n\nBUG FIXES:\n\n  - commands/box: Show all box providers with `update outdated --global` [GH-9347]\n  - commands/destroy: Exit 0 if vagrant destroy finds no running vms [GH-9251]\n  - commands/package: Fix --output path with specified folder [GH-9131]\n  - guests/suse: Do not use full name when setting hostname [GH-9212]\n  - providers/hyper-v: Fix enable virtualization extensions on import [GH-9255]\n  - provisioners/ansible(both): Fix broken 'ask_sudo_pass' option [GH-9173]\n\n## 2.0.1 (November 2, 2017)\n\nFEATURES:\n\n  - core: Introduce Ruby 2.4 to Vagrant [GH-9102]\n  - providers/virtualbox: Virtualbox 5.2 support [GH-8955]\n\nIMPROVEMENTS:\n\n  - command/destroy: Introduce parallel destroy for certain providers [GH-9127]\n  - communicators/winrm: Include APIPA check within ready check [GH-8997]\n  - core: Clear POSIXLY_CORRECT when using optparse [GH-8685]\n  - docs: Add auto_start_action and auto_stop_action to docs. [GH-9029]\n  - docs: Fix typo in box format doc [GH-9100]\n  - provisioners/chef: Handle chef provisioner reboot request [GH-8874]\n  - providers/salt: Support Windows Salt Minions greater than 2016.x.x [GH-8926]\n  - provisioners/salt: Add wget to bootstrap_salt options when fetching installer file [GH-9112]\n  - provisioners/shell: Use ui.detail for displaying output [GH-8983]\n  - util/downloader: Use CURL_CA_BUNDLE environment variable [GH-9135]\n\nBUG FIXES:\n\n  - communicators/ssh: Retry on Errno::EPIPE exceptions [GH-9065]\n  - core: Rescue more exceptions when checking if port is open [GH-8517]\n  - guests/solaris11: Inherit from Solaris guest and keep solaris11 specific methods [GH-9034]\n  - guests/windows: Split out cygwin path helper for msys2/cygwin paths and ensure cygpath exists [GH-8972]\n  - guests/windows: Specify expected shell when executing on guest (fixes einssh communicator usage) [GH-9012]\n  - guests/windows: Include WinSSH Communicator when using insert_public_key [GH-9105]\n  - hosts/windows: Check for vagrant.exe when validating versions within WSL [GH-9107, GH-8962]\n  - providers/docker: Isolate windows check within executor to handle running through VM [GH-8921]\n  - providers/hyper-v: Properly invoke Auto stop action [GH-9000]\n  - provisioners/puppet: Fix winssh communicator support in puppet provisioner [GH-9014]\n  - virtualbox/synced_folders: Allow synced folders to contain spaces in the guest path [GH-8995]\n\n## 2.0.0 (September 7, 2017)\n\nIMPROVEMENTS:\n\n  - commands/login: Add support for two-factor authentication [GH-8935]\n  - commands/ssh-config: Properly display windows path if invoked from msys2 or\n      cygwin [GH-8915]\n  - guests/alt: Add support for ALT Linux [GH-8746]\n  - guests/kali: Fix file permissions on guest plugin ruby files [GH-8950]\n  - hosts/linux: Provide common systemd detection for services interaction, fix NFS\n      host interactions [GH-8938]\n  - providers/salt: Remove duplicate stdout, stderr output from salt [GH-8767]\n  - providers/salt: Introduce salt_call_args and salt_args option for salt provisioner\n      [GH-8927]\n  - providers/virtualbox: Improving resilience of some VirtualBox commands [GH-8951]\n  - provisioners/ansible(both): Add the compatibility_mode option, with auto-detection\n      enabled by default [GH-8913, GH-6570]\n  - provisioners/ansible: Add the version option to the host-based provisioner\n      [GH-8913, GH-8914]\n  - provisioners/ansible(both): Add the become and become_user options with deprecation\n      of sudo and sudo_user options [GH-8913, GH-6570]\n  - provisioners/ansible: Add the ask_become_pass option with deprecation of the\n      ask_sudo_pass option [GH-8913, GH-6570]\n\nBUG FIXES:\n\n  - guests/shell_expand_guest_path : Properly expand guest paths that include relative\n      path alias [GH-8918]\n  - hosts/linux: Remove duplicate export folders before writing /etc/exports [GH-8945]\n  - provisioners/ansible(both): Add single quotes to the inventory host variables, only\n      when necessary [GH-8597]\n  - provisioners/ansible(both): Add the \"all:vars\" section to the inventory when defined\n      in `groups` option [GH-7730]\n  - provisioners/ansible_local: Extra variables are no longer truncated when a dollar ($)\n      character is present [GH-7735]\n  - provisioners/file: Align file provisioner functionality on all platforms [GH-8939]\n  - util/ssh: Properly quote key path for IdentityFile option to allow for spaces [GH-8924]\n\nBREAKING CHANGES:\n\n  - Both Ansible provisioners are now capable of automatically setting the compatibility_mode that\n      best fits with the Ansible version in use. You may encounter some compatibility issues when\n      upgrading. If you were using Ansible 2.x and referring to the _ssh-prefixed variables present\n      in the generated inventory (e.g. `ansible_ssh_host`). In this case, you can fix your Vagrant\n      setup by setting compatibility_mode = \"1.8\", or by migrating to the new variable names (e.g.\n      ansible_host).\n\n## 1.9.8 (August 23, 2017)\n\nIMPROVEMENTS:\n\n  - bash: Add box prune to contrib bash completion [GH-8806]\n  - commands/login: Ask for description of Vagrant Cloud token [GH-8876]\n  - commands/validate: Improve functionality of the validate command [GH-8889]n\n  - core: Updated Vagrants rspec gem to 3.5.0 [GH-8850]\n  - core: Validate powershell availability and version before use [GH-8839]\n  - core: Introduce extra_args setting for ssh configs [GH-8895]\n  - docs: Align contrib/sudoers file for ubuntu linux with docs [GH-8842]\n  - provider/hyperv: Prefer IPv4 guest address [GH-8831, GH-8759]\n  - provisioners/chef: Add config option omnibus_url for chef provisioners [GH-8682]\n  - provisioners/chef: Improve exception handling around missing folder paths [GH-8775]\n\nBUG FIXES:\n\n  - box/update: Add force flag for box upgrade command [GH-8871]\n  - commands/rsync-auto: Ensure relative dirs are still rsync'd if defined [GH-8781]\n  - commands/up: Disable install providers when using global id on vagrant up [GH-8910]\n  - communicators/winssh: Fix public key insertion to retain ACL [GH-8790]\n  - core: Update util/ssh to use `-o` for identity files [GH-8786]\n  - guests/freebsd: Fix regex for listing network devices on some FreeBSD boxes. [GH-8760]\n  - hosts/windows: Prevent control characters in version check for WSL [GH-8902, GH-8901]\n  - providers/docker: Split String type links into Array when using compose [GH-8837, GH-8821]\n  - providers/docker: Expand relative volume paths correctly [GH-8838, GH-8822]\n  - providers/docker: Error when compose option enabled with force_host_vm [GH-8911]\n  - provisioners/ansible: Update to use `-o` for identity files [GH-8786]\n  - provisioners/file: Ensure remote folder exists prior to scp file or folder [GH-8880]\n  - provisioners/salt: Fix error case when github is unreachable for installer [GH-8864]\n  - provisioners/shell: Allow frozen string scripts [GH-8875]\n  - provisioners/puppet: Remove `--manifestdir` flag from puppet apply in provisioner [GH-8797]\n  - synced_folders/rsync: Correctly format IPv6 host [GH-8840, GH-8809]\n\n## 1.9.7 (July 7, 2017)\n\nFEATURES:\n\n  - core: Add support for preferred providers [GH-8558]\n\nIMPROVEMENTS:\n\n  - guests/bsd: Invoke `tee` with explicit path [GH-8740]\n  - guests/smartos: Guest updates for host name and nfs capabilities [GH-8695]\n  - guests/windows: Add public key capabilities for WinSSH communicator [GH-8761]\n  - hosts/windows: Log command exec encoding failures and use original string on failure [GH-8820]\n  - providers/virtualbox: Filter machine IPs when preparing NFS settings [GH-8819]\n\nBUG FIXES:\n\n  - communicators/winssh: Make script upload directory configurable [GH-8761]\n  - core: Update cygwin detection to prevent PATH related errors [GH-8749, GH-6788]\n  - core: Fix URI parsing of box names to prevent errors [GH-8762, GH-8758]\n  - provider/docker: Only rsync-auto current working dir with docker provider [GH-8756]\n\n## 1.9.6 (June 28, 2017)\n\nIMPROVEMENTS:\n\n  - commands/snapshot: Enforce unique snapshot names and introduce `--force` flag [GH-7810]\n  - commands/ssh: Introduce tty flag for `vagrant ssh -c` [GH-6827]\n  - core: Warn about vagrant CWD changes for a machine [GH-3921]\n  - core: Allow Compression and DSAAuthentication ssh flags to be configurable [GH-8693]\n  - core/box: Warn if user sets box as url [GH-7118]\n  - core/bundler: Enforce stict constraints on vendored libraries [GH-8692]\n  - guests/kali: Add support for guest [GH-8553]\n  - guests/smartos: Update halt capability and add public key insert and remove capabilities [GH-8618]\n  - provisioners/ansible: Fix SSH keys only behavior to be consistent with Vagrant [GH-8467]\n  - providers/docker: Add post install provisioner for docker setup [GH-8722]\n  - snapshot/delete: Improve error message when given snapshot doesn't exist [GH-8653]\n  - snapshot/list: Raise exception if provider does not support snapshots [GH-8619]\n  - snapshot/restore: Improve error message when given snapshot doesn't exist [GH-8653]\n  - snapshot/save: Raise exception if provider does not support snapshots [GH-8619]\n\nBUG FIXES:\n\n  - communicators/ssh: Move `none` cipher to end of default cipher list in Net::SSH [GH-8661]\n  - core: Add unique identifier to provisioner objects [GH-8680]\n  - core: Stop config loader from loading dupe config if home and project dir are equal [GH-8707]\n  - core/bundler: Impose constraints on update and allow system plugins to properly update [GH-8729]\n  - guests/linux: Strip whitespace from GID [GH-8666, GH-8664]\n  - guests/solaris: Do not use UNC style path for shared folders from windows hosts [GH-7723]\n  - guests/windows: Fix directory creation when using rsync for synced folders [GH-8588]\n  - hosts/windows: Force common encoding when running system commands [GH-8725]\n  - providers/docker: Fix check for docker-compose [GH-8659, GH-8660]\n  - providers/docker: Fix SSH under docker provider [GH-8706]\n  - providers/hyperv: Fix box import [GH-8678, GH-8677]\n  - provisioners/ansible_local: Catch pip_args in FreeBSD's and SUSE's ansible_install [GH-8676]\n  - provisioners/salt: Fix minion ID configuration [GH-7865, GH-7454]\n  - snapshot/restore: Exit 1 if vm has not been created when command is invoked [GH-8653]\n\n## 1.9.5 (May 15, 2017)\n\nFEATURES:\n\n  - hosts/windows: Support running within WSL [GH-8570, GH-8582]\n\nIMPROVEMENTS:\n\n  - communicators/ssh: Retry on aborted connections [GH-8526, GH-8520]\n  - communicators/winssh: Enabling shared folders and networking setup [GH-8567]\n  - core: Remove nokogiri dependency and constraint [GH-8571]\n  - guests: Do not modify existing /etc/hosts content [GH-8506, GH-7794]\n  - guests/redhat: Update network configuration capability to properly handle NM [GH-8531]\n  - hosts/windows: Check for elevated shell for Hyper-V [GH-8548, GH-8510]\n  - hosts/windows: Fix invalid share names on Windows guests from Windows hosts [GH-8433]\n  - providers: Return errors from docker/hyperv on ssh when not available [GH-8565, GH-8508]\n  - providers/docker: Add support for driving provider with docker-compose [GH-8576]\n\nBUG FIXES:\n\n  - guests/debian: Fix use_dhcp_assigned_default_route [GH-8577, GH-8575]\n  - provisioners/shell: Fix Windows batch file provisioning [GH-8539, GH-8535]\n  - providers/docker: Fall back to old style for SSH info lookup [GH-8566, GH-8552]\n  - providers/hyperv: Fix import script [GH-8529]\n  - providers/hyperv: Use string comparison for conditional checks in import scripts [GH-8568, GH-8444]\n\n## 1.9.4 (April 24, 2017)\n\nFEATURES:\n\n  - command/validate: Add Vagrantfile validation command [GH-8264, GH-8151]\n  - communicators/winssh: Add WinSSH communicator for Win32-OpenSSH [GH-8485]\n  - provider/hyperv: Support integration services configuration [GH-8379, GH-8378]\n\nIMPROVEMENTS:\n\n  - core: Update internal dependencies [GH-8329, GH-8456]\n  - core/bundler: Warn when plugin require fails instead of generating hard failure [GH-8400, GH-8392]\n  - core/bundler: Error when configured plugin sources are unavailable [GH-8442]\n  - guests/elementary: Add support for new guest \"Elementary OS\" [GH-8472]\n  - guests/esxi: Add public_key capability [GH-8310]\n  - guests/freebsd: Add chef_install and chef_installed? capabilities [GH-8443]\n  - guests/gentoo: Add support for systemd in network configuration [GH-8407, GH-8406]\n  - guests/windows: Support mounting synced folders via SSH on windows [GH-7425, GH-6220]\n  - hosts/windows: Improve user permission detection [GH-7797]\n  - provider/docker: Improve IP and port detection [GH-7840, GH-7651]\n  - provider/docker: Do not force docker host VM on Darwin or Windows [GH-8437, GH-7895]\n  - provisioners/ansible_local: Add `pip_args` option to define additional parameters when installing Ansible via pip [GH-8170, GH-8405]\n  - provisioners/ansible_local: Add `:pip_args_only` install mode to allow full custom pip installations [GH-8405]\n  - provisioners/salt: Update minion version installed to 2016.11.3 [GH-8448]\n\nBUG FIXES:\n\n  - command/box: Remove extraneous sort from box list prior to display [GH-8422]\n  - command/box: Properly handle local paths with spaces for box add [GH-8503, GH-6825]\n  - command/up: Prevent other provider installation when explicitly defined [GH-8393, GH-8389]\n  - communicators/ssh: Do not yield empty output data [GH-8495, GH-8259]\n  - core: Provide fallback and retry when 0.0.0.0 is unavailable during port check [GH-8399, GH-8395]\n  - core: Support port checker methods that do not expect inclusion of host_ip [GH-8497, GH-8423]\n  - core/bundler: Check if source is local path and prevent addition to remote sources [GH-8401]\n  - core/ui: Prevent deadlock detection errors [GH-8414, GH-8125]\n  - guests/debian: Remove hardcoded device name in interface template [GH-8336, GH-7960]\n  - guests/linux: Fix SMB mount capability [GH-8410, GH-8404]\n  - hosts/windows: Fix issues with Windows encoding [GH-8385, GH-8380, GH-8212, GH-8207, GH-7516]\n  - hosts/windows: Fix UNC path generation when UNC path is provided [GH-8504]\n  - provisioners/salt: Allow Salt version to match 2 digit month [GH-8428]\n  - provisioners/shell: Properly handle remote paths on Windows that include spaces [GH-8498, GH-7234]\n\n## 1.9.3 (March 21, 2017)\n\nIMPROVEMENTS:\n\n  - command/plugin: Remove requirement for paths with no spaces [GH-7967]\n  - core: Support host_ip for forwarded ports [GH-7035, GH-8350]\n  - core: Include disk space hint in box install failure message [GH-8089]\n  - core/bundler: Allow vagrant constraint matching in prerelease mode [GH-8341]\n  - provisioner/docker: Include /bin/docker as valid path [GH-8390]\n  - provider/hyperv: Support enabling Hyper-V nested virtualization [GH-8325, GH-7738]\n\nBUG FIXES:\n\n  - communicator/winrm: Prevent inaccurate WinRM address [GH-7983, GH-8073]\n  - contrib/bash: Handle path spaces in bash completion [GH-8337]\n  - core: Fix box sorting on find and list [GH-7956, GH-8334]\n  - core/bundler: Force path as preferred source on install [GH-8327]\n  - core/provision: Update \"never\" behavior to match documentation [GH-8366, GH-8016]\n  - plugins/push: Isolate deprecation to Atlas strategy only\n  - plugins/synced_folders: Give UID/GID precedence if found within mount options\n      [GH-8122, GH-8064, GH-7859]\n\n## 1.9.2 (February 27, 2017)\n\nFEATURES:\n\n  - providers/hyperv: Support packaging of Hyper-V boxes [GH-7867]\n  - util/command_deprecation: Add utility module for command deprecation [GH-8300]\n  - util/subprocess: Add #stop and #running? methods [GH-8270]\n\nIMPROVEMENTS:\n\n  - commands/expunge: Display default value on prompt and validate input [GH-8192, GH-8171]\n  - communicator/winrm: Refactor WinRM communicator to use latest WinRM\n      gems and V2 API [GH-8102]\n  - core: Scrub URL credentials from output when adding boxes [GH-8194, GH-8117]\n  - providers/hyperv: Prefer VMCX over XML configuration when VMCX is supported [GH-8119]\n\nBUG FIXES:\n\n  - command/init: Include box version when using minimal option [GH-8283, GH-8282]\n  - command/package: Fix SecureRandom constant error [GH-8159]\n  - communicator/ssh: Remove any STDERR output prior to command execution [GH-8291, GH-8288]\n  - core/bundler: Prevent pristine warning messages [GH-8191, GH-8190, GH-8147]\n  - core/bundler: Fix local installations of pre-release plugins [GH-8252, GH-8253]\n  - core/bundler: Prefer user defined source when installing plugins [GH-8273, GH-8210]\n  - core/environment: Prevent persisting original environment variable if name is empty\n      [GH-8198, GH-8126]\n  - core/environment: Fix gems_path location [GH-8248]\n  - core/environment: Properly expand dotfile path [GH-8196, GH-8108]\n  - guests/arch: Fix configuring multiple network interfaces [GH-8165]\n  - guests/linux: Fix guest detection for names with spaces [GH-8092]\n  - guests/redhat: Fix network interface configuration [GH-8148]\n\nDEPRECATIONS:\n\n  - command/push: Disable push command [GH-8300]\n\n## 1.9.1 (December 7, 2016)\n\nIMPROVEMENTS:\n\n  - core: Disable Vagrantfile loading when running plugin commands [GH-8066]\n  - guests/redhat: Detect and restart NetworkManager service if in use [GH-8052, GH-7994]\n\nBUG FIXES:\n\n  - core: Detect load failures within install solution sets and retry [GH-8068]\n  - core: Prevent interactive shell on plugin uninstall [GH-8086, GH-8087]\n  - core: Remove bundler usage from Util::Env [GH-8090, GH-8094]\n  - guests/linux: Prevent stderr output on init version check for synced folders [GH-8051]\n\n## 1.9.0 (November 28, 2016)\n\nFEATURES:\n\n  - commands/box: Add `prune` subcommand for removing outdated boxes [GH-7978]\n  - core: Remove Bundler integration for handling internal plugins [GH-7793, GH-8000, GH-8011, GH-8031]\n  - providers/hyperv: Add support for Hyper-V binary configuration format\n      [GH-7854, GH-7706, GH-6102]\n  - provisioners/shell: Support MD5/SHA1 checksum validation of remote scripts [GH-7985, GH-6323]\n\nIMPROVEMENTS:\n\n  - commands/plugin: Retain name sorted output when listing plugins [GH-8028]\n  - communicator/ssh: Support custom environment variable export template\n      [GH-7976, GH-6747]\n  - provisioners/ansible(both): Add `config_file` option to point the location of an\n      `ansible.cfg` file via ANSIBLE_CONFIG environment variable [GH-7195, GH-7918]\n  - synced_folders: Support custom naming and disable auto-mount [GH-7980, GH-6836]\n\nBUG FIXES:\n\n  - guests/linux: Do not match interfaces with special characters when sorting [GH-7989, GH-7988]\n  - provisioner/salt: Fix Hash construction for constant [GH-7986, GH-7981]\n\n## 1.8.7 (November 4, 2016)\n\nIMPROVEMENTS:\n\n  - guests/linux: Place ethernet devices at start of network devices list [GH-7848]\n  - guests/linux: Provide more consistent guest detection [GH-7887, GH-7827]\n  - guests/openbsd: Validate guest rsync installation success [GH-7929, GH-7898]\n  - guests/redhat: Include Virtuozzo Linux 7 within flavor identification [GH-7818]\n  - guests/windows: Allow vagrant to start Windows Nano without provisioning [GH-7831]\n  - provisioners/ansible_local: Change the Ansible binary detection mechanism [GH-7536]\n  - provisioners/ansible(both): Add the `playbook_command` option [GH-7881]\n  - provisioners/puppet: Support custom environment variables [GH-7931, GH-7252, GH-2270]\n  - util/safe_exec: Use subprocess for safe_exec on Windows [GH-7802]\n  - util/subprocess: Allow closing STDIN [GH-7778]\n\nBUG FIXES:\n\n  - communicators/winrm: Prevent connection leakage [GH-7712]\n  - core: Prevent duplicate provider priorities [GH-7756]\n  - core: Allow Numeric type for box version [GH-7874, GH-6960]\n  - core: Provide friendly error when user environment is too large [GH-7889, GH-7857]\n  - guests: Remove `set -e` usage for better shell compatibility [GH-7921, GH-7739]\n  - guests/linux: Fix incorrectly configured private network [GH-7844, GH-7848]\n  - guests/linux: Properly order network interfaces\n      [GH-7866, GH-7876, GH-7858, GH-7876]\n  - guests/linux: Only emit upstart event if initctl is available [GH-7813]\n  - guests/netbsd: Fix rsync installation [GH-7922, GH-7901]\n  - guests/photon: Fix networking setup [GH-7808, GH-7873]\n  - guests/redhat: Properly configure network and restart service [GH-7751]\n  - guests/redhat: Prevent NetworkManager from managing devices on initial start [GH-7926]\n  - hosts/linux: Fix race condition in writing /etc/exports file for NFS configuration\n      [GH-7947, GH-7938] - Thanks to Aron Griffis (@agriffis) for identifying this issue\n  - plugins/rsync: Escape exclude paths [GH-7928, GH-7910]\n  - providers/docker: Remove --interactive flag when pty is true [GH-7688]\n  - provisioners/ansible_local: Use enquoted path for file/directory existence checks\n  - provisioners/salt: Synchronize configuration defaults with documentation [GH-7907, GH-6624]\n  - pushes/atlas: Fix atlas push on Windows platform [GH-6938, GH-7802]\n\n## 1.8.6 (September 27, 2016)\n\nIMPROVEMENTS:\n\n  - Add detection for DragonFly BSD [GH-7701]\n  - Implement auto_start and auto_stop actions for Hyper-V [GH-7647]\n  - communicators/ssh: Remove any content prepended to STDOUT [GH-7676, GH-7613]\n\nBUG FIXES:\n\n  - commands/package: Provide machine data directory for base box package\n      [GH-5070, GH-7725]\n  - core: Fix windows path formatting [GH-6598]\n  - core: Fixes for ssh-agent interactions [GH-7703, GH-7621, GH-7398]\n  - core: Support VAGRANT_DOTFILE_PATH relative to the Vagrantfile [GH-7623]\n  - guests: Prevent ssh disconnect errors on halt command [GH-7675]\n  - guests/bsd: Remove Darwin matching [GH-7701]\n  - guests/linux: Fix SSH key permissions [GH-7610, GH-7611]\n  - guests/linux: Always sort discovered network interfaces [GH-7705, GH-7668]\n  - guests/linux: Fixes for user and group ID lookups for virtualbox shared folders\n      [GH-7616, GH-7662, GH-7720]\n  - guests/openbsd: Add custom halt capability [GH-7701]\n  - guests/ubuntu: Fix detection on older guests [GH-7632, GH-7524, GH-7625]\n  - hosts/arch: Detect NFS server by service name on arch [GH-7630, GH-7629]\n  - hosts/darwin: Fix generated RDP configuration file [GH-7698]\n  - provisioners/ansible: Add support for `ssh.proxy_command` setting [GH-7752]\n  - synced_folders/nfs: Display warning when configured for NFSv4 and UDP [GH-7740]\n  - synced_folders/rsync: Properly ignore excluded files within synced directory\n      from `chown` command. [GH-5256, GH-7726]\n\n## 1.8.5 (July 18, 2016)\n\nFEATURES:\n\n  - core: Provide a way to globally disable box update checks with the\n      environment variable `VAGRANT_BOX_UPDATE_CHECK_DISABLE`. Setting this\n      to any non-empty value will instruct Vagrant to not look for box updates\n      when running `vagrant up`. Setting this environment variable has no\n      effect on the `vagrant box` commands.\n\nIMPROVEMENTS:\n\n  - guests/arch: Support installing synced folder clients [GH-7519]\n  - guests/darwin: Allow ipv6 static networks [GH-7491]\n  - providers/virtualbox: Add support for 5.1 [GH-7574]\n\nBUG FIXES:\n\n  - core: Bump listen gem and Ruby version to improve rsync performance\n      [GH-7453, GH-7441]\n  - core: Check process stdout when detecting if a hyperv admin\n      [GH-7465, GH-7467]\n  - core: Ensure removal of temporary directory when box download fails\n      [GH-7496, GH-7499]\n  - core: Fix regression for installing plugins from path [GH-7505, GH-7493]\n  - core: Skip checking conflicts on disabled ports [GH-7587]\n  - core: Idempotent write-out for state file [GH-7550]\n  - core/guests: Create common BSD guest for shared logic\n  - core/guests: Ignore empty output from `/sbin/ip`\n      [GH-7539, GH-7537, GH-7533, GH-7605]\n  - synced_folders/nfs: Shellescape rsync paths\n      [GH-7540, GH-7605]\n  - synced_folders/nfs: Ensure retries take place [GH-6360, GH-7605]\n  - synced_folders/rsync: Shellescape rsync paths\n      [GH-7580, GH-6690, GH-7579, GH-7605]\n  - synced_folders/rsync: Translate Windows paths\n      [GH-7012, GH-6702, GH-6568, GH-7046]\n  - guests/bsd: Consolidate core logic for mounting NFS folders\n      [GH-7480, GH-7474, GH-7466]\n  - guests/bsd: Consolidate core logic for public key management [GH-7481]\n  - guests/bsd: Consolidate core logic for halting [GH-7484]\n  - guests/centos: Use `ip` instead of `ifconfig` to detect network interfaces\n      [GH-7460]\n  - guests/debian: Ensure newline when inserting public key [GH-7456]\n  - guests/linux: Ensure NFS retries during mounting [GH-7492]\n  - guests/redhat: Use `/sbin/ip` to list and configure networks for\n      compatability with older versions of CentOS [GH-7482]\n  - guests/redhat: Ensure newline when inserting public key [GH-7598, GH-7605]\n  - guests/ubuntu: Use /etc/os-release to detect [GH-7524]\n  - guests/ubuntu: Use short hostname [GH-7488, GH-7605]\n  - providers/hyperv: Fix version check and catch statement [GH-7447, GH-7487]\n\n## 1.8.4 (June 13, 2016)\n\nBUG FIXES:\n\n  - core: Fix bundler plugin issue and version constraint [GH-7418, GH-7415]\n  - providers/virtualbox: Use 8 network interfaces (due to Windows limitation)\n      [GH-7417, GH-7419]\n  - provisioners/ansible(both): Honor \"galaxy_roles_path\" option when running\n      ansible-playbook [GH-7269, GH-7420]\n  - provisioners/ansible_local: Add quotes around \"ansible-galaxy\" arguments\n      [GH-7420]\n\nIMPROVEMENTS:\n\n  - guests/redhat: Add CloudLinux detection [GH-7428, GH-7427]\n\n## 1.8.3 (June 10, 2016)\n\nBREAKING CHANGES:\n\n  - The `winrm` communicator now shares the same upload behavior as the `ssh`\n      communicator. This change should have no impact to most vagrant operations\n      but may break behavior when uploading directories to an existing\n      destination target. The `file` provisioner should be the only builtin\n      provisioner affected by this change. When uploading a directory and the\n      destination directory exists on the endpoint, the source base directory\n      will be created below the destination directory on the endpoint and the\n      source directory contents will be unzipped to that location. Prior to this\n      release, the contents of the source directory would be unzipped to an\n      existing destination directory without creating the source base directory.\n      This new behavior is more consistent with SCP and other well known shell copy commands.\n  - The Chef provisioner's `channel` default value has changed from \"current\" to\n      \"stable\". The \"current\" channel includes nightly releases and should be\n      opt-in only. Note that users wishing to download the Chef Development Kit\n      will need to opt into the \"current\" channel until Chef Software promotes\n      into the \"stable\" channel.\n  - The Arch Linux host capability for NFS removed support for rc.d in favor or\n      systemd which has been present since 2012. Please see GH-7181 for more\n      information.\n\nFEATURES:\n\n  - provider/docker: Allow non-linux users to opt-out of the host VM to run\n      Docker containers by setting `config.force_host_vm = false` in the\n      Vagrantfile. This is especially useful for customers who wish to use\n      the beta builds for Mac and Windows, dlite, or a custom provider.\n      [GH-7277, GH-7298, 8c11b53]\n  - provider/docker: New command: `docker-exec` allows attaching to an\n      already-running container.\n      [GH-7377, GH-6566, GH-5193, GH-4904, GH-4057, GH-4179, GH-4903]\n\nIMPROVEMENTS:\n\n  - core/downloader: increase box resume download limit to 24h\n      [GH-7352, GH-7272]\n  - core/package: run validations prior to packaging [GH-7353, GH-7351]\n  - core/action: make `start` (\"vagrant up\") run provisioners [GH-4467, GH-4421]\n  - commands/all: Make it clear that machine IDs can be specified\n      [GH-7356, GH-7228]\n  - commands/init: Add support for specifying the box version [GH-7363, GH-5004]\n  - commands/login: Print a warning with both the environment variable and\n      local login token are present [GH-7206, GH-7219]\n  - communicators/winrm: Upgrade to latest WinRM gems [GH-6922]\n  - provisioners/ansible_local: Allow to install Ansible from pip,\n      with version selection capability [GH-6654, GH-7167]\n  - provisioners/ansible_local: Use `provisioning_path` as working directory\n      for `ansible-galaxy` execution\n  - provisioners/ansible(both provisioners): Add basic config\n      validators/converters on `raw_arguments` and `raw_ssh_args` options\n      [GH-7103]\n  - provisioners/chef: Add the ability to install on SUSE [GH-6806]\n  - provisioners/chef: Support legacy solo mode [GH-7327]\n  - provisioners/docker: Restart container if newer image is available\n      [GH-7358, GH-6620]\n  - hosts/arch: Remove sysvinit and assume systemd [GH-7181]\n  - hosts/linux: Do not use a pager with systemctl commands [GH-7270]\n  - hosts/darwin: Add `extra_args` support for RDP [GH-5523, GH-6602]\n  - hosts/windows: Use SafeExec to capture history in Powershell [GH-6749]\n  - guests/amazon: Add detection [GH-7395, GH-7254]\n  - guests/freebsd: Add quotes around hostname [GH-6867]\n  - guests/fedora: Add support for ipv6 static networks [GH-7275, GH-7276]\n  - guests/tinycore: Add support for shared folders [GH-6977, GH-6968]\n  - guests/trisquel: Add initial support [GH-6842, GH-6843]\n  - guests/windows: Add support for automatic login (no password prompting)\n      [GH-5670]\n  - core: Add `--no-delete` and provisioning flags to snapshot restore/pop\n      [GH-6879]\n  - providers/docker: Allow TCP and UDP ports on the same number [GH-7365,\n      GH-5527]\n  - providers/hyperv: Add support for differencing disk [GH-7090]\n  - providers/hyperv: Add support for snapshots [GH-7110]\n  - providers/hyperv: Reinstate compatibility with PS 4 [GH-7108]\n  - providers/virtualbox: Add linked clone support for Virtualbox 1.4 [GH-7050]\n  - synced_folders/nfs: Read static and dynamic IPs [GH-7290, GH-7289]\n\nBUG FIXES:\n\n  - core: Bump nokogiri version to fix windows bug [GH-6766, GH-6848]\n  - core: Revert a change made to the output of the identify file [GH-6962,\n      GH-6929, GH-6589]\n  - core: Fix login command behind a proxy [GH-6898, GH-6899]\n  - core: Fix support for regular expressions on multi-machine `up`\n      [GH-6908, GH-6909]\n  - core: Allow boxes to use pre-release versions [GH-6892, GH-6893]\n  - core: Rescue `Errno:ENOTCONN` waiting for port to be open [GH-7182, GH-7184]\n  - core: Properly authenticate metadata box URLs [GH-6776, GH-7158]\n  - core: Do not run provisioners if already run on resume [GH-7059, GH-6787]\n  - core: Implement better tracking of tempfiles and tmpdirs to identify file\n      leaks [GH-7355]\n  - core: Allow SSH forwarding on Windows [GH-7287, GH-7202]\n  - core: Allow customizing `keys_only` SSH option [GH-7360, GH-4275]\n  - core: Allow customizing `paranoid` SSH option [GH-7360, GH-4275]\n  - command/box_update: Do not update the same box twice [GH-6042, GH-7379]\n  - command/init: Remove unnecessary `sudo` from generated Vagrantfile\n      [GH-7369, GH-7295]\n  - docs & core: Be consistent about the \"2\" in the Vagrantfile version\n      [GH-6961, GH-6963]\n  - guests/all: Refactor guest capabilities to run in a single command -\n      **please see GH-7393 for the complete list of changes!**\n  - guests/arch: Restart network after configuration [GH-7120, GH-7119]\n  - guests/debian: Do not return an error if ifdown fails [GH-7159,\n      GH-7155, GH-6871]\n  - guests/freebsd: Use `pkg` to install rsync [GH-6760]\n  - guests/freebsd: Use `netif` to configure networks [GH-5852, GH-7093]\n  - guests/coreos: Detect all interface names [GH-6608, GH-6610]\n  - providers/hyperv: Only specify Hyper-V if the parameter is support\n      [GH-7101, GH-7098]\n  - providers/virtualbox: Set maximum network adapters to 36 [GH-7293, GH-7286]\n  - providers/virtualbox: Do not fail when master VM from linked clone is\n      missing [GH-7126, GH-6742]\n  - providers/virtualbox: Use scoped overrides in preparing NFS\n      [GH-7387, GH-7386]\n  - provisioners/ansible: Fix a race condition in the concurrent generations of\n      the ansible inventory file, while running `vagrant up --parallel`\n      [GH-6526, GH-7190]\n  - provisioners/ansible_local: Don't quote the Ansible arguments defined in the\n      `raw_arguments` option [GH-7103]\n  - provisioners/ansible_local: Format json `extra_vars` with double quotes\n      [GH-6726, GH-7103]\n  - provisioners/ansible_local: Fix errors in absolute paths to playbook or\n      galaxy resources when running on a Windows host [GH-6740, GH-6757]\n  - provisioners/ansible_local: Change the way to verify `ansible-galaxy`\n      presence, to avoid a non-zero status code with Ansible 2.0 [GH-6793]\n  - provisioners/ansible(both provisioners): The Ansible configuration files\n      detection is only executed by the `provision` action [GH-6763, GH-6984]\n  - provisioners/chef: Do not use double sudo when installing\n      [GGH-6805, GH-6804]\n  - provisioners/chef: Change the default channel to \"stable\" (previously it\n      was \"current\") [GH-7001, GH-6979]\n  - provisioners/chef: Default node_name to hostname if present\n      [GH-7063, GH-7153]\n  - provisioners/docker: Fix -no-trunc command option [GH-7085]\n  - provisioners/docker: Allow provisioning when container name is specified\n      [GH-7074, GH-7086]\n  - provisioners/puppet: Use `where.exe` to locate puppet binary\n      [GH-6912, GH-6876]\n  - provisioners/salt: Move masterless config to apply to all platforms\n      [GH-7207, Gh-6924, GH-6915]\n  - pushes/ftp: Create parent directories when uploading [GH-7154, GH-6316]\n  - synced_folders/smb: Do not interpolate configuration file [GH-6906]\n\n## 1.8.1 (December 21, 2015)\n\nBUG FIXES:\n\n  - core: Don't create \".bundle\" directory in pwd [GH-6717]\n  - core: Fix exception on installing VirtualBox [GH-6713]\n  - core: Do not convert standalone drive letters such as \"D:\" to\n      UNC paths [GH-6598]\n  - core: Fix a crash in parsing the config in some cases with network\n      configurations [GH-6730]\n  - core: Clean up temporarily files created by bundler\n    [GH-7354, GH-6301, GH-3469, GH-6231]\n  - commands/up: Smarter logic about what provider to install, avoiding\n      situations where VirtualBox was installed over the correct provider [GH-6731]\n  - guests/debian: Fix Docker install [GH-6722]\n  - provisioners/chef: convert chef version to a string before comparing for\n    the command builder [GH-6709, GH-6711]\n  - provisioners/shell: convert env var values to strings [GH-6714]\n\n## 1.8.0 (December 21, 2015)\n\nFEATURES:\n\n  - **New Command: `vagrant powershell`**: For machines that support it,\n    this will open a PowerShell prompt.\n  - **New Command: `vagrant port`**: For machines that support it, this will\n    display the list of forwarded ports from the guest to the host.\n  - **Linked Clones**: VirtualBox and VMware providers now support\n    linked clones for very fast (millisecond) imports on up. [GH-4484]\n  - **Snapshots**: The `vagrant snapshot` command can be used to checkpoint\n    and restore point-in-time snapshots.\n  - **IPv6 Private Networks**: Private networking now supports IPv6. This\n    only works with VirtualBox and VMware at this point. [GH-6342]\n  - New provisioner: `ansible_local` to execute Ansible from the guest\n    machine. [GH-2103]\n\nBREAKING CHANGES:\n\n  - The `ansible` provisioner now can override the effective ansible remote user\n    (i.e. `ansible_ssh_user` setting) to always correspond to the vagrant ssh\n    username. This change is enabled by default, but we expect this to affect\n    only a tiny number of people as it corresponds to the common usage.\n    If you however use multiple remote usernames in your Ansible plays, tasks,\n    or custom inventories, you can simply set the option `force_remote_user` to\n    false to make Vagrant behave the same as before.\n  - provisioners/salt: the \"config_dir\" option has been removed. It has no\n      effect in Vagrant 1.8. [GH-6073]\n\nIMPROVEMENTS:\n\n  - core: allow removal of all box versions with `--all` flag [GH-3462]\n  - core: prune entries from global status on non-existent cwd [GH-6535]\n  - core: networking: allow specifying a DHCP IP [GH-6325]\n  - core: run provisioner cleanup tasks before powering off the VM [GH-6553]\n  - core: only run provisioner cleanup tasks if they're implemented [GH-6603]\n      This improves UX, but wasn't a bug before.\n  - command/plugin: Add `--plugin-clean-sources` flag to reset plugin install\n      sources, primarily for corp firewalls. [GH-4738]\n  - command/rsync-auto: SSH connection is cached for faster sync times [GH-6399]\n  - command/up: provisioners are run on suspend resume [GH-5815]\n  - communicators/ssh: allow specifying host environment variables to forward\n    to guests [GH-4132, GH-6562]\n  - communicators/winrm: Configurable execution time limit [GH-6213]\n  - providers/virtualbox: cache version lookup, which caused significant\n      slowdown on some Windows hosts [GH-6552]\n  - providers/virtualbox: add `public_address` capability for virtualbox\n    [GH-6583, GH-5978]\n  - provisioners/chef: perform cleanup tasks on the guest instead of the host\n  - provisioners/chef: automatically generate a node_name if one was not given\n    [GH-6555]\n  - provisioners/chef: install Chef automatically on Windows [GH-6557]\n  - provisioners/chef: allow the user to specify the Chef product (such as\n    the Chef Development Kit) [GH-6557]\n  - provisioners/chef: allow data_bags_path to be an array [GH-5988, GH-6561]\n  - provisioners/shell: Support interactive mode for elevated PowerShell\n      scripts [GH-6185]\n  - provisioners/shell: add `env` option [GH-6588, GH-6516]\n  - provisioners/ansible+ansible_local: add support for ansible-galaxy [GH-2718]\n  - provisioners/ansible+ansible_local: add support for group and host variables\n      in the generated inventory [GH-6619]\n  - provisioners/ansible+ansible_local: add support for alphanumeric patterns\n      for groups in the generated inventory [GH-3539]\n  - provisioners/ansible: add support for WinRM settings [GH-5086]\n  - provisioners/ansible: add new `force_remote_user` option to control whether\n    `ansible_ssh_user` parameter should be applied or not [GH-6348]\n  - provisioners/ansible: show a warning when running from a Windows Host [GH-5292]\n  - pushes/local-exec: add support for specifying script args [GH-6661, GH-6660]\n  - guests/slackware: add support for networking [GH-6514]\n\nBUG FIXES:\n\n  - core: Ctrl-C weirdness fixed where it would exit parent process\n      before Vagrant finished cleaning up [GH-6085]\n  - core: DHCP network configurations don't warn on IP addresses ending\n      in \".1\" [GH-6150]\n  - core: only append `access_token` when it does not exist in the URL\n    [GH-6395, GH-6534]\n  - core: use the correct private key when packaging a box [GH-6406]\n  - core: fix crash when using invalid box checksum type [GH-6327]\n  - core: don't check for metadata if the download URL is not HTTP [GH-6540]\n  - core: don't make custom dotfile path if there is no Vagrantfile [GH-6542]\n  - core: more robust check for admin privs on Windows [GH-5616]\n  - core: properly detect when HTTP server doesn't support byte ranges and\n      retry from scratch [GH-4479]\n  - core: line numbers show properly in Vagrantfile syntax errors\n      on Windows [GH-6445]\n  - core: catch errors setting env vars on Windows [GH-6017]\n  - core: remove cached synced folders when they're removed from the\n      Vagrantfile [GH-6567]\n  - core: use case-insensitive comparison for box checksum validations\n    [GH-6648, GH-6650]\n  - commands/box: add command with `~` paths on Windows works [GH-5747]\n  - commands/box: the update command supports CA settings [GH-4473]\n  - commands/box: removing all versions and providers of a box will properly\n      clean all directories in `~/.vagrant.d/boxes` [GH-3570]\n  - commands/box: outdated global won't halt on metadata download failure [GH-6453]\n  - commands/login: respect environment variables in `vagrant login` command\n    [GH-6590, GH-6422]\n  - commands/package: when re-packaging a packaged box, preserve the\n      generated SSH key [GH-5780]\n  - commands/plugin: retry plugin install automatically a few times to\n      avoid network issues [GH-6097]\n  - commands/rdp: prefer `xfreerdp` if it is available on Linux [GH-6475]\n  - commands/up: the `--provision-with` flag works with provisioner names [GH-5981]\n  - communicator/ssh: fix potential crash case with PTY [GH-6225]\n  - communicator/ssh: escape IdentityFile path [GH-6428, GH-6589]\n  - communicator/winrm: respect `boot_timeout` setting [GH-6229]\n  - communicator/winrm: execute scheduled tasks immediately on Windows XP\n      since elevation isn't required [GH-6195]\n  - communicator/winrm: Decouple default port forwarding rules for \"winrm\" and\n      \"winrm-ssl\" [GH-6581]\n  - communicator/winrm: Hide progress bars from PowerShell v5 [GH-6309]\n  - guests/arch: enable network device after setting it up [GH-5737]\n  - guests/darwin: advanced networking works with more NICs [GH-6386]\n  - guests/debian: graceful shutdown works properly with newer releases [GH-5986]\n  - guests/fedora: Preserve `localhost` entry when changing hostname [GH-6203]\n  - guests/fedora: Use dnf if it is available [GH-6288]\n  - guests/linux: when replacing a public SSH key, use POSIX-compliant\n      sed flags [GH-6565]\n  - guests/suse: DHCP network interfaces properly configured [GH-6502]\n  - hosts/slackware: Better detection of NFS [GH-6367]\n  - providers/hyper-v: support generation 2 VMs [GH-6372]\n  - providers/hyper-v: support VMs with more than one NIC [GH-4346]\n  - providers/hyper-v: check if user is in the Hyper-V admin group if\n      they're not a Windows admin [GH-6662]\n  - providers/virtualbox: ignore \"Unknown\" status bridge interfaces [GH-6061]\n  - providers/virtualbox: only fix ipv6 interfaces that are in use\n      [GH-6586, GH-6552]\n  - provisioners/ansible: use quotes for the `ansible_ssh_private_key_file`\n    value in the generated inventory [GH-6209]\n  - provisioners/ansible: use quotes when passing the private key files via\n      OpenSSH `-i` command line arguments [GH-6671]\n  - provisioners/ansible: don't show the `ansible-playbook` command when verbose\n    option is an empty string\n  - provisioners/chef: fix `nodes_path` for Chef Zero [GH-6025, GH-6049]\n  - provisioners/chef: do not error when the `node_name` is unset\n    [GH-6005, GH-6064, GH-6541]\n  - provisioners/chef: only force the formatter on Chef 11 or higher\n    [GH-6278, GH-6556]\n  - provisioners/chef: require `nodes_path` to be set for Chef Zero\n    [GH-6110, GH-6559]\n  - provisioners/puppet: apply provisioner uses correct default manifests\n    with environments. [GH-5987]\n  - provisioners/puppet: remove broken backticks [GH-6404]\n  - provisioners/puppet: find Puppet binary properly on Windows [GH-6259]\n  - provisioners/puppet-server: works with Puppet Collection 1 [GH-6389]\n  - provisioners/salt: call correct executables on Windows [GH-5999]\n  - provisioners/salt: log level and colorize works for masterless [GH-6474]\n  - push/local-exec: use subprocess on windows when fork does not exist\n    [GH-5307, GH-6563]\n  - push/heroku: use current branch [GH-6554]\n  - synced\\_folders/rsync: on Windows, replace all paths with Cygwin\n      paths since all rsync implementations require this [GH-6160]\n  - synced\\_folders/smb: use credentials files to allow for more characters\n      in password [GH-4230]\n\nPLUGIN AUTHOR CHANGES:\n\n  - installer: Upgrade to Ruby 2.2.3\n\n## 1.7.4 (July 17, 2015)\n\nBUG FIXES:\n\n  - communicators/winrm: catch timeout errors [GH-5971]\n  - communicators/ssh: use the same SSH args for `vagrant ssh` with and without\n    a command [GH-4986, GH-5928]\n  - guests/fedora: networks can be configured without nmcli [GH-5931]\n  - guests/fedora: biosdevname can return 4 or 127 [GH-6139]\n  - guests/redhat: systemd detection should happen on guest [GH-5948]\n  - guests/ubuntu: setting hostname fixed in 12.04 [GH-5937]\n  - hosts/linux: NFS can be configured without `$TMP` set on the host [GH-5954]\n  - hosts/linux: NFS will sudo copying back to `/etc/exports` [GH-5957]\n  - providers/docker: Add `pull` setting, default to false [GH-5932]\n  - providers/virtualbox: remove UNC path conversion on Windows since it\n      caused mounting regressions [GH-5933]\n  - provisioners/puppet: Windows Puppet 4 paths work correctly [GH-5967]\n  - provisioners/puppet: Fix config merging errors [GH-5958]\n  - provisioners/salt: fix \"dummy config\" error on bootstrap [GH-5936]\n\n## 1.7.3 (July 10, 2015)\n\nFEATURES:\n\n  - **New guest: `atomic`* - Project Atomic is supported as a guest\n  - providers/virtualbox: add support for 5.0 [GH-5647]\n\nIMPROVEMENTS:\n\n  - core: add password authentication to rdp_info hash [GH-4726]\n  - core: improve error message when packaging fails [GH-5399]\n  - core: improve message when adding a box from a file path [GH-5395]\n  - core: add support for network gateways [GH-5721]\n  - core: allow redirecting stdout and stderr in the UI [GH-5433]\n  - core: update version of winrm-fs to 0.2.0 [GH-5738]\n  - core: add option to enabled trusted http(s) redirects [GH-4422]\n  - core: capture additional information such as line numbers during\n    Vagrantfile loading [GH-4711, GH-5769]\n  - core: add .color? to UI objects to see if they support color [GH-5771]\n  - core: ignore hidden directories when searching for boxes [GH-5748, GH-5785]\n  - core: use `config.ssh.sudo_command` to customize the sudo command\n      format [GH-5573]\n  - core: add `Vagrant.original_env` for Vagrant and plugins to restore or\n      inspect the original environment when Vagrant is being run from the\n      installer [GH-5910]\n  - guests/darwin: support inserting generated key [GH-5204]\n  - guests/darwin: support mounting SMB shares [GH-5750]\n  - guests/fedora: support Fedora 21 [GH-5277]\n  - guests/fedora: add capabilities for nfs and flavor [GH-5770, GH-4847]\n  - guests/linux: specify user's domain as separate parameter [GH-3620, GH-5512]\n  - guests/redhat: support Scientific Linux 7 [GH-5303]\n  - guests/photon: initial support [GH-5612]\n  - guests/solaris,solaris11: support inserting generated key [GH-5182]\n      [GH-5290]\n  - providers/docker: images are pulled prior to starting [GH-5249]\n  - provisioners/ansible: store the first ssh private key in the auto-generated inventory [GH-5765]\n  - provisioners/chef: add capability for checking if Chef is installed on Windows [GH-5669]\n  - provisioners/docker: restart containers if arguments have changed [GH-3055, GH-5924]\n  - provisioners/puppet: add support for Puppet 4 and configuration options [GH-5601]\n  - provisioners/puppet: add support for `synced_folder_args` in apply [GH-5359]\n  - provisioners/salt: add configurable `config_dir` [GH-3138]\n  - provisioners/salt: add support for masterless configuration [GH-3235]\n  - provisioners/salt: provider path to missing file in errors [GH-5637]\n  - provisioners/salt: add ability to run salt orchestrations [GH-4371]\n  - provisioners/salt: update to 2015.5.2 [GH-4152, GH-5437]\n  - provisioners/salt: support specifying version to install [GH-5892]\n  - provisioners/shell: add :name attribute to shell provisioner [GH-5607]\n  - providers/docker: supports file downloads with the file provisioner [GH-5651]\n  - providers/docker: support named Dockerfile [GH-5480]\n  - providers/docker: don't remove image on reload so that build cache can\n      be used fully [GH-5905]\n  - providers/hyperv: select a Hyper-V switch based on a `network_name` [GH-5207]\n  - providers/hyperv: allow configuring VladID [GH-5539]\n  - providers/virtualbox: regexp supported for bridge configuration [GH-5320]\n  - providers/virtualbox: handle a list of bridged NICs [GH-5691]\n  - synced_folders/rsync: allow showing rsync output in debug mode [GH-4867]\n  - synced_folders/rsync: set `rsync__rsync_path` to specify the remote\n      command used to execute rsync [GH-3966]\n\nBUG FIXES:\n\n  - core: push configurations are validated with global configs [GH-5130]\n  - core: remove executable permissions on internal file [GH-5220]\n  - core: check name and version in `has_plugin?` [GH-5218]\n  - core: do not create duplicates when defining two private network addresses [GH-5325]\n  - core: update ssh to check for Plink [GH-5604]\n  - core: do not report plugins as installed when plugins are disabled [GH-5698, GH-5430]\n  - core: Only take files when packaging a box to avoid duplicates [GH-5658, GH-5657]\n  - core: escape curl urls and authentication [GH-5677]\n  - core: fix crash if a value is missing for CLI arguments [GH-5550]\n  - core: retry SSH key generation for transient RSA errors [GH-5056]\n  - core: `ssh.private_key_path` will override the insecure key [GH-5632]\n  - core: restore the original environment when shelling out to subprocesses\n      outside of the installer [GH-5912]\n  - core/cli: fix box checksum validation [GH-4665, GH-5221]\n  - core/windows: allow Windows UNC paths to allow more than 256\n      characters [GH-4815]\n  - command/rsync-auto: don't crash if rsync command fails [GH-4991]\n  - communicators/winrm: improve error handling significantly and improve\n      the error messages shown to be more human-friendly. [GH-4943]\n  - communicators/winrm: remove plaintext passwords from files after\n      provisioner is complete [GH-5818]\n  - hosts/nfs: allow colons (`:`) in NFS IDs [GH-5222]\n  - guests/darwin: remove dots from LocalHostName [GH-5558]\n  - guests/debian: Halt works properly on Debian 8. [GH-5369]\n  - guests/fedora: recognize future fedora releases [GH-5730]\n  - guests/fedora: reload iface connection by NetworkManager [GH-5709]\n  - guests/fedora: do not use biosdevname if it is not installed [GH-5707]\n  - guests/freebsd: provide an argument to the backup file [GH-5516, GH-5517]\n  - guests/funtoo: fix incorrect path in configure networks [GH-4812]\n  - guests/linux: fix edge case exception where no home directory\n      is available on guest [GH-5846]\n  - guests/linux: copy NFS exports to tmpdir to do edits to guarantee\n      permissions are available [GH-5773]\n  - guests/openbsd: output newline after inserted public key [GH-5881]\n  - guests/tinycore: fix change hostname functionality [GH-5623]\n  - guests/ubuntu: use `hostnamectl` to set hostname on Ubuntu Vivid [GH-5753]\n  - guests/windows: Create rsync folder prior to rsync-ing. [GH-5282]\n  - guests/windows: Changing hostname requires reboot again since\n      the non-reboot code path was crashing Windows server. [GH-5261]\n  - guests/windows: ignore virtual NICs [GH-5478]\n  - hosts/windows: More accurately get host IP address in VPNs. [GH-5349]\n  - plugins/login: allow users to login with a token [GH-5145]\n  - providers/docker: Build image from `/var/lib/docker` for more disk\n      space on some systems. [GH-5302]\n  - providers/docker: Fix crash that could occur in some scenarios when\n      the host VM path changed.\n  - providers/docker: Fix crash that could occur on container destroy\n      with VirtualBox shared folders [GH-5143]\n  - providers/hyperv: allow users to configure memory, cpu count, and vmname [GH-5183]\n  - providers/hyperv: import respects secure boot. [GH-5209]\n  - providers/hyperv: only set EFI secure boot for gen 2 machines [GH-5538]\n  - providers/virtualbox: read netmask from dhcpservers [GH-5233]\n  - providers/virtualbox: Fix exception when VirtualBox version is empty. [GH-5308]\n  - providers/virtualbox: Fix exception when VBoxManage.exe can't be run\n      on Windows [GH-1483]\n  - providers/virtualbox: Error if another user is running after a VM is\n      created to avoid issue with VirtualBox \"losing\" the VM [GH-5895]\n  - providers/virtualbox: The \"name\" setting on private networks will\n      choose an existing hostonly network [GH-5389]\n  - provisioners/ansible: fix SSH settings to support more than 5 ssh keys [GH-5017]\n  - provisioners/ansible: increase ansible connection timeout to 30 seconds [GH-5018]\n  - provisioners/ansible: disable color if Vagrant is not colored [GH-5531, GH-5532]\n  - provisioners/ansible: only show ansible-playbook command when `verbose` option is enabled [GH-5803]\n  - provisioners/ansible: fix a race condition in the inventory file generation [GH-5551]\n  - provisioners/docker: use `service` to restart Docker instead of upstart [GH-5245, GH-5577]\n  - provisioners/docker: Only add docker user to group if exists. [GH-5315]\n  - provisioners/docker: Use https for repo [GH-5749]\n  - provisioners/docker: `apt-get update` before installing linux kernel\n      images to get the correct version [GH-5860]\n  - provisioners/chef: Fix shared folders missing error [GH-5199]\n  - provisioners/chef: Use `command -v` to check for binary instead of\n      `which` since that doesn't exist on some systems. [GH-5170]\n  - provisioners/chef-zero: support more chef-zero/local mode attributes [GH-5339]\n  - provisioners/chef: use windows-specific paths in Chef provisioners [GH-5913]\n  - provisioners/docker: use docker.com instead of docker.io [GH-5216]\n  - provisioners/docker: use `--restart` instead of `-r` on daemon [GH-4477]\n  - provisioners/file: validation of source is relative to Vagrantfile [GH-5252]\n  - pushes/atlas: send additional box metadata [GH-5283]\n  - pushes/local-exec: fix \"text file busy\" error for inline [GH-5695]\n  - pushes/ftp: improve check for remote directory existence [GH-5549]\n  - synced\\_folders/rsync: add `IdentitiesOnly=yes` to the rsync command. [GH-5175]\n  - synced\\_folders/smb: use correct `password` option [GH-5805]\n  - synced\\_folders/smb: prever IPv4 over IPv6 address to mount [GH-5798]\n  - virtualbox/config: fix misleading error message for private_network [GH-5536, GH-5418]\n\n## 1.7.2 (January 6, 2015)\n\nBREAKING CHANGES:\n\n  - If you depended on the paths that Chef/Puppet provisioners use to\n    store cookbooks (ex. \"/tmp/vagrant-chef-1\"), these will no longer be\n    correct. Without this change, Chef/Puppet didn't work at all with\n    `vagrant provision`. We expect this to affect only a minor number of\n    people, since it's not something that was ever documented or recommended\n    by Vagrant, or even meant to be supported.\n\nFEATURES:\n\n  - provisioners/salt: add support for grains [GH-4895]\n\nIMPROVEMENTS:\n\n  - commands/reload,up: `--provision-with` implies `--provision` [GH-5085]\n\nBUG FIXES:\n\n  - core: private boxes still referencing vagrantcloud.com will have\n      their vagrant login access token properly appended\n  - core: push plugin configuration is properly validated\n  - core: restore box packaging functionality\n  - commands/package: fix crash\n  - commands/push: push lookups are by user-defined name, not push\n      strategy name [GH-4975]\n  - commands/push: validate the configuration\n  - communicators/winrm: detect parse errors in PowerShell and error\n  - guests/arch: fix network configuration due to poor line breaks. [GH-4964]\n  - guests/solaris: Merge configurations properly so configs can be set\n      in default Vagrantfiles. [GH-5092]\n  - installer: SSL cert bundle contains 1024-bit keys, fixing SSL verification\n      for a lot of sites.\n  - installer: vagrant executable properly `cygpaths` the SSL bundle path\n      for Cygwin\n  - installer: Nokogiri (XML lib used by Vagrant and dependencies) linker\n      dependencies fixed, fixing load issues on some platforms\n  - providers/docker: Symlinks in shared folders work. [GH-5093]\n  - providers/hyperv: VM start errors turn into proper Vagrant errors. [GH-5101]\n  - provisioners/chef: fix missing shared folder error [GH-4988]\n  - provisioners/chef: remove Chef version check from solo.rb generation and\n      make `roles_path` populate correctly\n  - provisioners/chef: fix bad invocation of `with_clean_env` [GH-5021]\n  - pushes/atlas: support more verbose logging\n  - pushes/ftp: expand file paths relative to the Vagrantfile\n  - pushes/ftp: improved debugging output\n  - pushes/ftp: create parent directories if they do not exist on the remote\n      server\n\n## 1.7.1 (December 11, 2014)\n\nIMPROVEMENTS:\n\n  - provisioners/ansible: Use Docker proxy if needed. [GH-4906]\n\nBUG FIXES:\n\n  - providers/docker: Add support of SSH agent forwarding. [GH-4905]\n\n## 1.7.0 (December 9, 2014)\n\nBREAKING CHANGES:\n\n  - provisioners/ansible: `raw_arguments` has now highest priority\n  - provisioners/ansible: only the `ssh` connection transport is supported\n      (`paramiko` can be enabled with `raw_arguments` at your own risks)\n\nFEATURES:\n\n  - **Vagrant Push**: Vagrant can now deploy! `vagrant push` is a single\n      command to deploy your application. Deploy to Heroku, FTP, or\n      HashiCorp's commercial product Atlas. New push strategies can be\n      added with plugins.\n  - **Named provisioners**: Provisioners can now be named. This name is used\n      for output as well as `--provision-with` for better control.\n  - Default provider logic improved: Providers in `config.vm.provider` blocks\n      in your Vagrantfile now have higher priority than plugins. Earlier\n      providers are chosen before later ones. [GH-3812]\n  - If the default insecure keypair is used, Vagrant will automatically replace\n      it with a randomly generated keypair on first `vagrant up`. [GH-2608]\n  - Vagrant Login is now part of Vagrant core\n  - Chef Zero provisioner: Use Chef 11's \"local\" mode to run recipes against an\n      in-memory Chef Server\n  - Chef Apply provisioner: Specify inline Chef recipes and recipe snippets\n      using the Chef Apply provisioner\n\nIMPROVEMENTS:\n\n  - core: `has_plugin?` function now takes a second argument which is a\n      version constraint requirement. [GH-4650]\n  - core: \".vagrantplugins\" file in the same folder as your Vagrantfile\n      will be loaded for defining inline plugins. [GH-3775]\n  - commands/plugin: Plugin list machine-readable output contains the plugin\n      name as the target for versions and other info. [GH-4506]\n  - env/with_cleanenv: New helper for plugin developers to use when shelling out\n      to another Ruby environment\n  - guests/arch: Support predictable network interface naming. [GH-4468]\n  - guests/suse: Support NFS client install, rsync setup. [GH-4492]\n  - guests/tinycore: Support changing host names. [GH-4469]\n  - guests/tinycore: Support DHCP-based networks. [GH-4710]\n  - guests/windows: Hostname can be set without reboot. [GH-4687]\n  - providers/docker: Build output is now shown. [GH-3739]\n  - providers/docker: Can now start containers from private repositories\n      more easily. Vagrant will login for you if you specify auth. [GH-4042]\n  - providers/docker: `stop_timeout` can be used to modify the `docker stop`\n      timeout. [GH-4504]\n  - provisioners/chef: Automatically install Chef when using a Chef provisioner.\n  - provisioners/ansible: Always show Ansible command executed when Vagrant log\n      level is debug (even if ansible.verbose is false)\n  - synced\\_folders/nfs: Won't use `sudo` to write to /etc/exports if there\n      are write privileges. [GH-2643]\n  - synced\\_folders/smb: Credentials from one SMB will be copied to the rest. [GH-4675]\n\nBUG FIXES:\n\n  - core: Fix cases where sometimes SSH connection would hang.\n  - core: On a graceful halt, force halt if capability \"insert public key\"\n      is missing. [GH-4684]\n  - core: Don't share `/vagrant` if any \".\" folder is shared. [GH-4675]\n  - core: Fix SSH private key permissions more aggressively. [GH-4670]\n  - core: Custom Vagrant Cloud server URL now respected in more cases.\n  - core: On downloads, don't continue downloads if the remote server\n      doesn't support byte ranges. [GH-4479]\n  - core: Box downloads recognize more complex content types that include\n      \"application/json\" [GH-4525]\n  - core: If all sub-machines are `autostart: false`, don't start any. [GH-4552]\n  - core: Update global-status state in more cases. [GH-4513]\n  - core: Only delete machine state if the machine is not created in initialize\n  - commands/box: `--cert` flag works properly. [GH-4691]\n  - command/docker-logs: Won't crash if container is removed. [GH-3990]\n  - command/docker-run: Synced folders will be attached properly. [GH-3873]\n  - command/rsync: Sync to Docker containers properly. [GH-4066]\n  - guests/darwin: Hostname sets bonjour name and local host name. [GH-4535]\n  - guests/freebsd: NFS mounting can specify the version. [GH-4518]\n  - guests/linux: More descriptive error message if SMB mount fails. [GH-4641]\n  - guests/rhel: Hostname setting on 7.x series works properly. [GH-4527]\n  - guests/rhel: Installing NFS client works properly on 7.x [GH-4499]\n  - guests/solaris11: Static IP address preserved after restart. [GH-4621]\n  - guests/ubuntu: Detect with `lsb_release` instead of `/etc/issue`. [GH-4565]\n  - hosts/windows: RDP client shouldn't map all drives by default. [GH-4534]\n  - providers/docker: Create args works. [GH-4526]\n  - providers/docker: Nicer error if package is called. [GH-4595]\n  - providers/docker: Host IP restriction is forwarded through. [GH-4505]\n  - providers/docker: Protocol is now honored in direct `ports settings.\n  - providers/docker: Images built using `build_dir` will more robustly\n      capture the final image. [GH-4598]\n  - providers/docker: NFS synced folders now work. [GH-4344]\n  - providers/docker: Read the created container ID more robustly.\n  - providers/docker: `vagrant share` uses correct IP of proxy VM if it\n      exists. [GH-4342]\n  - providers/docker: `vagrant_vagrantfile` expands home directory. [GH-4000]\n  - providers/docker: Fix issue where multiple identical proxy VMs would\n      be created. [GH-3963]\n  - providers/docker: Multiple links with the same name work. [GH-4571]\n  - providers/virtualbox: Show a human-friendly error if VirtualBox didn't\n      clean up an existing VM. [GH-4681]\n  - providers/virtualbox: Detect case when VirtualBox reports 0.0.0.0 as\n      IP address and don't allow it. [GH-4671]\n  - providers/virtualbox: Show more descriptive error if VirtualBox is\n      reporting an empty version. [GH-4657]\n  - provisioners/ansible: Force `ssh` (OpenSSH) connection by default [GH-3396]\n  - provisioners/ansible: Don't use or modify `~/.ssh/known_hosts` file by default,\n      similarly to native vagrant commands [GH-3900]\n  - provisioners/ansible: Use intermediate Docker host when needed. [GH-4071]\n  - provisioners/docker: Get GPG key over SSL. [GH-4597]\n  - provisioners/docker: Search for docker binary in multiple places. [GH-4580]\n  - provisioners/salt: Highstate works properly with a master. [GH-4471]\n  - provisioners/shell: Retry getting SSH info a few times. [GH-3924]\n  - provisioners/shell: PowerShell scripts can have args. [GH-4548]\n  - synced\\_folders/nfs: Don't modify NFS exports file if no exports. [GH-4619]\n  - synced\\_folders/nfs: Prune exports for file path IDs. [GH-3815]\n\nPLUGIN AUTHOR CHANGES:\n\n  - `Machine#action` can be called with the option `lock: false` to not\n      acquire a machine lock.\n  - `Machine#reload` will now properly trigger the `machine_id_changed`\n      callback on providers.\n\n## 1.6.5 (September 4, 2014)\n\nBUG FIXES:\n\n  - core: forward SSH even if WinRM is used. [GH-4437]\n  - communicator/ssh: Fix crash when pty is enabled with SSH. [GH-4452]\n  - guests/redhat: Detect various RedHat flavors. [GH-4462]\n  - guests/redhat: Fix typo causing crash in configuring networks. [GH-4438]\n  - guests/redhat: Fix typo causing hostnames to not set. [GH-4443]\n  - providers/virtualbox: NFS works when using DHCP private network. [GH-4433]\n  - provisioners/salt: Fix error when removing non-existent bootstrap script\n      on Windows. [GH-4614]\n\n## 1.6.4 (September 2, 2014)\n\nBACKWARDS INCOMPATIBILITIES:\n\n  - commands/docker-run: Started containers are now deleted after run.\n      Specify the new `--no-rm` flag to retain the original behavior. [GH-4327]\n  - providers/virtualbox: Host IO cache is no longer enabled by default\n      since it causes stale file issues. Please enable manually if you\n      require this. [GH-3934]\n\nIMPROVEMENTS:\n\n  - core: Added `config.vm.box_server_url` setting to point at a\n     Vagrant Cloud instance. [GH-4282]\n  - core: File checksumming performance has been improved by at least\n      100%. Memory requirements have gone down by half. [GH-4090]\n  - commands/docker-run: Add the `--no-rm` flag. Containers are\n      deleted by default. [GH-4327]\n  - commands/plugin: Better error output is shown when plugin installation\n      fails.\n  - commands/reload: show post up message [GH-4168]\n  - commands/rsync-auto: Add `--poll` flag. [GH-4392]\n  - communicators/winrm: Show stdout/stderr if command fails. [GH-4094]\n  - guests/nixos: Added better NFS support. [GH-3983]\n  - providers/hyperv: Accept VHD disk format. [GH-4208]\n  - providers/hyperv: Support generation 2 VMs. [GH-4324]\n  - provisioners/docker: More verbose output. [GH-4377]\n  - provisioners/salt: Get proper exit codes to detect failed runs. [GH-4304]\n\nBUG FIXES:\n\n  - core: Downloading box files should resume in more cases since the\n      temporary file is preserved in more cases. [GH-4301]\n  - core: Windows is not detected as NixOS in some cases. [GH-4302]\n  - core: Fix encoding issues with Windows. There are still some outlying\n      but this fixes a few. [GH-4159]\n  - core: Fix crash case when destroying with an invalid provisioner. [GH-4281]\n  - core: Box names with colons work on Windows. [GH-4100]\n  - core: Cleanup all temp files. [GH-4103]\n  - core: User curlrc is not loaded, preventing strange download issues.\n      [GH-4328]\n  - core: VM names may no longer contain brackets, since they cause\n      issues with some providers. [GH-4319]\n  - core: Use \"-f\" to `rm` files in case pty is true. [GH-4410]\n  - core: SSH key doesn't have to be owned by our user if we're running\n      as root. [GH-4387]\n  - core: \"vagrant provision\" will cause \"vagrant up\" to properly not\n      reprovision. [GH-4393]\n  - commands/box/add: \"Content-Type\" header is now case-insensitive when\n      looking for metadata type. [GH-4369]\n  - commands/docker-run: Named docker containers no longer conflict. [GH-4294]\n  - commands/package: base package won't crash with exception [GH-4017]\n  - commands/rsync-auto: Destroyed machines won't raise exceptions. [GH-4031]\n  - commands/ssh: Extra args are passed through to Docker container. [GH-4378]\n  - communicators/ssh: Nicer error if remote unexpectedly disconnects. [GH-4038]\n  - communicators/ssh: Clean error when max sessions is hit. [GH-4044]\n  - communicators/ssh: Fix many issues around PTY-enabled output parsing.\n      [GH-4408]\n  - communicators/winrm: Support `mkdir` [GH-4271]\n  - communicators/winrm: Properly escape double quotes. [GH-4309]\n  - communicators/winrm: Detect failed commands that aren't CLIs. [GH-4383]\n  - guests/centos: Fix issues when NFS client is installed by restarting\n      NFS [GH-4088]\n  - guests/debian: Deleting default route on DHCP networks can fail. [GH-4262]\n  - guests/fedora: Fix networks on Fedora 20 with libvirt. [GH-4104]\n  - guests/freebsd: Rsync install for rsync synced folders work on\n      FreeBSD 10. [GH-4008]\n  - guests/freebsd: Configure vtnet devices properly [GH-4307]\n  - guests/linux: Show more verbose error when shared folder mount fails.\n      [GH-4403]\n  - guests/redhat: NFS setup should use systemd for RH7+ [GH-4228]\n  - guests/redhat: Detect RHEL 7 (and CentOS) and install Docker properly. [GH-4402]\n  - guests/redhat: Configuring networks on EL7 works. [GH-4195]\n  - guests/redhat: Setting hostname on EL7 works. [GH-4352]\n  - guests/smartos: Use `pfexec` for rsync. [GH-4274]\n  - guests/windows: Reboot after hostname change. [GH-3987]\n  - hosts/arch: NFS works with latest versions. [GH-4224]\n  - hosts/freebsd: NFS exports are proper syntax. [GH-4143]\n  - hosts/gentoo: NFS works with latest versions. [GH-4418]\n  - hosts/windows: RDP command works without crash. [GH-3962]\n  - providers/docker: Port on its own will choose random host port. [GH-3991]\n  - providers/docker: The proxy VM Vagrantfile can be in the same directory\n      as the main Vagrantfile. [GH-4065]\n  - providers/virtualbox: Increase network device limit to 36. [GH-4206]\n  - providers/virtualbox: Error if can't detect VM name. [GH-4047]\n  - provisioners/cfengine: Fix default Yum repo URL. [GH-4335]\n  - provisioners/chef: Chef client cleanup should work. [GH-4099]\n  - provisioners/puppet: Manifest file can be a directory. [GH-4169]\n  - provisioners/puppet: Properly escape facter variables for PowerShell\n      on Windows guests. [GH-3959]\n  - provisioners/puppet: When provisioning fails, don't repeat all of\n      stdout/stderr. [GH-4303]\n  - provisioners/salt: Update salt minion version on Windows. [GH-3932]\n  - provisioners/shell: If args is an array and contains numbers, it no\n      longer crashes. [GH-4234]\n  - provisioners/shell: If fails, the output/stderr isn't repeated\n      again. [GH-4087]\n\n## 1.6.3 (May 29, 2014)\n\nFEATURES:\n\n  - **New Guest:** NixOS - Supports changing host names and setting\n      networks. [GH-3830]\n\nIMPROVEMENTS:\n\n  - core: A CA path can be specified in the Vagrantfile, not just\n      a file, when using a custom CA. [GH-3848]\n  - commands/box/add: `--capath` flag added for custom CA path. [GH-3848]\n  - commands/halt: Halt in reverse order of up, like destroy. [GH-3790]\n  - hosts/linux: Uses rdesktop to RDP into machines if available. [GH-3845]\n  - providers/docker: Support for UDP forwarded ports. [GH-3886]\n  - provisioners/salt: Works on Windows guests. [GH-3825]\n\nBUG FIXES:\n\n  - core: Provider plugins more easily are compatible with global-status\n      and should show less stale data. [GH-3808]\n  - core: When setting a synced folder, it will assume it is not disabled\n      unless explicitly specified. [GH-3783]\n  - core: Ignore UDP forwarded ports for collision detection. [GH-3859]\n  - commands/package: Package with `--base` for VirtualBox doesn't\n      crash. [GH-3827]\n  - guests/solaris11: Fix issue with public network and DHCP on newer\n      Solaris releases. [GH-3874]\n  - guests/windows: Private networks with static IPs work when there\n      is more than one. [GH-3818]\n  - guests/windows: Don't look up a forwarded port for WinRM if we're\n      not accessing the local host. [GH-3861]\n  - guests/windows: Fix errors with arg lists that are too long over\n      WinRM in some cases. [GH-3816]\n  - guests/windows: Powershell exits with proper exit code, fixing\n  -   issues where non-zero exit codes weren't properly detected. [GH-3922]\n  - hosts/windows: Don't execute mstsc using PowerShell since it doesn't\n      exit properly. [GH-3837]\n  - hosts/windows: For RDP, don't remove the Tempfile right away. [GH-3875]\n  - providers/docker: Never do graceful shutdown, always use\n      `docker stop`. [GH-3798]\n  - providers/docker: Better error messaging when SSH is not ready\n      direct to container. [GH-3763]\n  - providers/docker: Don't port map SSH port if container doesn't\n      support SSH. [GH-3857]\n  - providers/docker: Proper SSH info if using native driver. [GH-3799]\n  - providers/docker: Verify host VM has SSH ready. [GH-3838]\n  - providers/virtualbox: On Windows, check `VBOX_MSI_INSTALL_PATH`\n      for VBoxManage path as well. [GH-3852]\n  - provisioners/puppet: Fix setting facter vars with Windows\n      guests. [GH-3776]\n  - provisioners/puppet: On Windows, run in elevated prompt. [GH-3903]\n  - guests/darwin: Respect mount options for NFS. [GH-3791]\n  - guests/freebsd: Properly register the rsync_pre capability\n  - guests/windows: Certain executed provisioners won't leave output\n      and exit status behind. [GH-3729]\n  - synced\\_folders/rsync: `rsync__chown` can be set to `false` to\n      disable recursive chown after sync. [GH-3810]\n  - synced\\_folders/rsync: Use a proper msys path if not in\n      Cygwin. [GH-3804]\n  - synced\\_folders/rsync: Don't append args infinitely, clear out\n      arg list on each run. [GH-3864]\n\nPLUGIN AUTHOR CHANGES:\n\n  - Providers can now implement the `rdp_info` provider capability\n      to get proper info for `vagrant rdp` to function.\n\n## 1.6.2 (May 12, 2014)\n\nIMPROVEMENTS:\n\n  - core: Automatically forward WinRM port if communicator is\n      WinRM. [GH-3685]\n  - command/rdp: Args after \"--\" are passed directly through to the\n      RDP client. [GH-3686]\n  - providers/docker: `build_args` config to specify extra args for\n      `docker build`. [GH-3684]\n  - providers/docker: Can specify options for the build dir synced\n      folder when a host VM is in use. [GH-3727]\n  - synced\\_folders/nfs: Can tell Vagrant not to handle exporting\n      by setting `nfs_export: false` [GH-3636]\n\nBUG FIXES:\n\n  - core: Hostnames can be one character. [GH-3713]\n  - core: Don't lock machines on SSH actions. [GH-3664]\n  - core: Fixed crash when adding a box from Vagrant Cloud that was the\n      same name as a real directory. [GH-3732]\n  - core: Parallelization is more stable, doesn't crash due to to\n      bad locks. [GH-3735]\n  - commands/package: Don't double included files in package. [GH-3637]\n  - guests/linux: Rsync chown ignores symlinks. [GH-3744]\n  - provisioners/shell: Fix shell provisioner config validation when the\n    `binary` option is set to false [GH-3712]\n  - providers/docker: default proxy VM won't use HGFS [GH-3687]\n  - providers/docker: fix container linking [GH-3719]\n  - providers/docker: Port settings expose to host properly. [GH-3723]\n  - provisioners/puppet: Separate module paths with ';' on Windows. [GH-3731]\n  - synced\\_folders\\rsync: Copy symlinks as real files. [GH-3734]\n  - synced\\_folders/rsync: Remove non-portable '-v' flag from chown. [GH-3743]\n\n## 1.6.1 (May 7, 2014)\n\nIMPROVEMENTS:\n\n  - **New guest: Linux Mint** is now properly detected. [GH-3648]\n\nBUG FIXES:\n\n  - core: Global control works from directories that don't have a\n      Vagrantfile.\n  - core: Plugins that define config methods that collide with Ruby Kernel/Object\n  -   methods are merged properly. [GH-3670]\n  - commands/docker-run: `--help` works. [GH-3698]\n  - commands/package: `--base` works without crashing for VirtualBox.\n  - commands/reload: If `--provision` is specified, force provisioning. [GH-3657]\n  - guests/redhat: Fix networking issues with CentOS. [GH-3649]\n  - guests/windows: Human error if WinRM not in use to configure networks. [GH-3651]\n  - guests/windows: Puppet exit code 2 doesn't cause Windows to raise\n      an error. [GH-3677]\n  - providers/docker: Show proper error message when on Linux. [GH-3654]\n  - providers/docker: Proxy VM works properly even if default provider\n      environmental variable set to \"docker\" [GH-3662]\n  - providers/docker: Put sync folders in `/var/lib/docker` because\n      it usually has disk space. [GH-3680]\n  - synced\\_folders/rsync: Create the directory before syncing.\n\n## 1.6.0 (May 6, 2014)\n\nBACKWARDS INCOMPATIBILITIES:\n\n  - Deprecated: `halt_timeout` and `halt_check_interval` settings for\n      SmartOS, Solaris, and Solaris11 guests. These will be fully\n      removed in 1.7. A warning will be shown if they're in use in\n      1.6.\n\nFEATURES:\n\n  - **New guest: Windows**. Vagrant now fully supports Windows as a guest\n      VM. WinRM can be used for communication (or SSH), and the shell\n      provisioner, Chef, and Puppet all work with Windows VMs.\n  - **New command: global-status**. This command shows the state of every\n      created Vagrant environment on the system for that logged in user.\n  - **New command: rdp**. This command connects to the running machine\n      via the Remote Desktop Protocol.\n  - **New command: version**. This outputs the currently installed version\n      as well as the latest version of Vagrant available.\n  - **New provider: Docker**. This provider will back your development\n      environments with Docker containers. If you're not on Linux, it will\n      automatically spin up a VM for you on any provider. You can even\n      specify a specific Vagrantfile to use as the Docker container host.\n  - Control Vagrant environments from any directory. Using the UUIDs given\n      in `vagrant global-status`, you can issue commands from anywhere on\n      your machine, not just that environment's directory. Example:\n      `vagrant destroy UUID` from anywhere.\n  - Can now specify a `post_up_message` in your Vagrantfile that is shown\n      after a `vagrant up`. This is useful for putting some instructions of how\n      to use the development environment.\n  - Can configure provisioners to run \"once\" or \"always\" (defaults to \"once\"),\n      so that subsequent `vagrant up` or `reload` calls will always run a\n      provisioner. [GH-2421]\n  - Multi-machine environments can specify an \"autostart\" option (default\n      to true). `vagrant up` starts all machines that have enabled autostart.\n  - Vagrant is smarter about choosing a default provider. If\n    `VAGRANT_DEFAULT_PROVIDER` is set, it still takes priority, but otherwise\n    Vagrant chooses a \"best\" provider.\n\nIMPROVEMENTS:\n\n  - core: Vagrant locks machine access to one Vagrant process at a time.\n      This will protect against two simultaneous `up` actions happening\n      on the same environment.\n  - core: Boxes can be compressed with LZMA now as well.\n  - commands/box/remove: Warns if the box appears to be in use by an\n      environment. Can be forced with `--force`.\n  - commands/destroy: Exit codes changes. 0 means everything succeeded.\n      1 means everything was declined. 2 means some were declined. [GH-811]\n  - commands/destroy: Doesn't require box to exist anymore. [GH-1629]\n  - commands/init: force flag. [GH-3564]\n  - commands/init: flag for minimal Vagrantfile creation (no comments). [GH-3611]\n  - commands/rsync-auto: Picks up and syncs provisioner folders if\n      provisioners are backed by rsync.\n  - commands/rsync-auto: Detects when new synced folders were added and warns\n      user they won't be synced until `vagrant reload`.\n  - commands/ssh-config: Works without a target in multi-machine envs [GH-2844]\n  - guests/freebsd: Support for virtio interfaces. [GH-3082]\n  - guests/openbsd: Support for virtio interfaces. [GH-3082]\n  - guests/redhat: Networking works for upcoming RHEL7 release. [GH-3643]\n  - providers/hyperv: Implement `vagrant ssh -c` support. [GH-3615]\n  - provisioners/ansible: Support for Ansible Vault. [GH-3338]\n  - provisioners/ansible: Show Ansible command executed. [GH-3628]\n  - provisioners/salt: Colorize option. [GH-3603]\n  - provisioners/salt: Ability to specify log level. [GH-3603]\n  - synced\\_folders: nfs: Improve sudo commands used to make them\n      sudoers friendly. Examples in docs. [GH-3638]\n\nBUG FIXES:\n\n  - core: Adding a box from a network share on Windows works again. [GH-3279]\n  - commands/plugin/install: Specific versions are now locked in.\n  - commands/plugin/install: If insecure RubyGems.org is specified as a\n      source, use that. [GH-3610]\n  - commands/rsync-auto: Interrupt exits properly. [GH-3552]\n  - commands/rsync-auto: Run properly on Windows. [GH-3547]\n  - communicators/ssh: Detect if `config.ssh.shell` is invalid. [GH-3040]\n  - guests/debian: Can set hostname if hosts doesn't contain an entry\n      already for 127.0.1.1 [GH-3271]\n  - guests/linux: For `read_ip_address` capability, set `LANG=en` so\n      it works on international systems. [GH-3029]\n  - providers/virtualbox: VirtualBox detection works properly again on\n      Windows when the `VBOX_INSTALL_PATH` has multiple elements. [GH-3549]\n  - providers/virtualbox: Forcing MAC address on private network works\n      properly again. [GH-3588]\n  - provisioners/chef-solo: Fix Chef version checking to work with prerelease\n      versions. [GH-3604]\n  - provisioners/salt: Always copy keys and configs on provision. [GH-3536]\n  - provisioners/salt: Install args should always be present with bootstrap.\n  - provisioners/salt: Overwrite keys properly on subsequent provisions [GH-3575]\n  - provisioners/salt: Bootstrap uses raw GitHub URL rather than subdomain. [GH-3583]\n  - synced\\_folders/nfs: Acquires a process-level lock so exports don't\n      collide with Vagrant running in parallel.\n  - synced\\_folders/nfs: Implement usability check so that hosts that\n      don't support NFS get an error earlier. [GH-3625]\n  - synced\\_folders/rsync: Add UserKnownHostsFile option to not complain. [GH-3511]\n  - synced\\_folders/rsync: Proxy command is used properly if set. [GH-3553]\n  - synced\\_folders/rsync: Owner/group settings are respected. [GH-3544]\n  - synced\\_folders/smb: Passwords with symbols work. [GH-3642]\n\nPLUGIN AUTHOR CHANGES:\n\n  - **New host capability:** \"rdp\\_client\". This capability gets the RDP connection\n      info and must launch the RDP client on the system.\n  - core: The \"Call\" middleware now merges the resulting middleware stack\n      into the current stack, rather than running it as a separate stack.\n      The result is that ordering is preserved.\n  - core: The \"Message\" middleware now takes a \"post\" option that will\n      output the message on the return-side of the middleware stack.\n  - core: Forwarded port collision repair works when Vagrant is run in\n      parallel with other Vagrant processes. [GH-2966]\n  - provider: Providers can now specify that boxes are optional. This lets\n      you use the provider without a `config.vm.box`. Useful for providers like\n      AWS or Docker.\n  - provider: A new class-level `usable?` method can be implemented on the\n      provider implementation. This returns or raises an error when the\n      provider is not usable (i.e. VirtualBox isn't installed for VirtualBox)\n  - synced\\_folders: New \"disable\" method for removing synced folders from\n      a running machine.\n\n## 1.5.4 (April 21, 2014)\n\nIMPROVEMENTS:\n\n  - commands/box/list: Doesn't parse Vagrantfile. [GH-3502]\n  - providers/hyperv: Implement the provision command. [GH-3494]\n\nBUG FIXES:\n\n  - core: Allow overriding of the default SSH port. [GH-3474]\n  - commands/box/remove: Make output nicer. [GH-3470]\n  - commands/box/update: Show currently installed version. [GH-3467]\n  - command/rsync-auto: Works properly on Windows.\n  - guests/coreos: Fix test for Docker daemon running.\n  - guests/linux: Fix test for Docker provisioner whether Docker is\n      running.\n  - guests/linux: Fix regression where rsync owner/group stopped\n      working. [GH-3485]\n  - provisioners/docker: Fix issue where we weren't waiting for Docker\n      to properly start before issuing commands. [GH-3482]\n  - provisioners/shell: Better validation of master config path, results\n      in no more stack traces at runtime. [GH-3505]\n\n## 1.5.3 (April 14, 2014)\n\nIMPROVEMENTS:\n\n  - core: 1.5 upgrade code gives users a chance to quit. [GH-3212]\n  - commands/rsync-auto: An initial sync is done before watching folders. [GH-3327]\n  - commands/rsync-auto: Exit immediately if there are no paths to watch.\n      [GH-3446]\n  - provisioners/ansible: custom vars/hosts files can be added in\n      .vagrant/provisioners/ansible/inventory/ directory [GH-3436]\n\nBUG FIXES:\n\n  - core: Randomize some filenames internally to improve the parallelism\n      of Vagrant. [GH-3386]\n  - core: Don't error if network problems cause box update check to\n      fail [GH-3391]\n  - core: `vagrant` on Windows cmd.exe doesn't always exit with exit\n      code zero. [GH-3420]\n  - core: Adding a box from a network share has nice error on Windows. [GH-3279]\n  - core: Setting an ID on a provisioner now works. [GH-3424]\n  - core: All synced folder paths containing symlinks are fully\n      expanded before sharing. [GH-3444]\n  - core: Windows no longer sees \"process not started\" errors rarely.\n  - commands/box/repackage: Works again. [GH-3372]\n  - commands/box/update: Update should check for updates from latest\n      version. [GH-3452]\n  - commands/package: Nice error if includes contain symlinks. [GH-3200]\n  - commands/rsync-auto: Don't crash if the machine can't be communicated\n      to. [GH-3419]\n  - communicators/ssh: Throttle connection attempt warnings if the warnings\n      are the same. [GH-3442]\n  - guests/coreos: Docker provisioner works. [GH-3425]\n  - guests/fedora: Fix hostname setting. [GH-3382]\n  - guests/fedora: Support predictable network interface names for\n      public/private networks. [GH-3207]\n  - guests/linux: Rsync folders have proper group if owner not set. [GH-3223]\n  - guests/linux: If SMB folder mounting fails, the password will no\n      longer be shown in plaintext in the output. [GH-3203]\n  - guests/linux: SMB mount works with passwords with symbols. [GH-3202]\n  - providers/hyperv: Check for PowerShell features. [GH-3398]\n  - provisioners/docker: Don't automatically generate container name with\n      a forward slash. [GH-3216]\n  - provisioners/shell: Empty shell scripts don't cause errors. [GH-3423]\n  - synced\\_folders/smb: Only set the chmod properly by default on Windows\n      if it isn't already set. [GH-3394]\n  - synced\\_folders/smb: Sharing folders with odd characters like parens\n      works properly now. [GH-3405]\n\n## 1.5.2 (April 2, 2014)\n\nIMPROVEMENTS:\n\n  - **New guest:** SmartOS\n  - core: Change wording from \"error\" to \"warning\" on SSH retry output\n    to convey actual meaning.\n  - commands/plugin: Listing plugins now has machine-readable output. [GH-3293]\n  - guests/omnios: Mount NFS capability [GH-3282]\n  - synced\\_folders/smb: Verify PowerShell v3 or later is running. [GH-3257]\n\nBUG FIXES:\n\n  - core: Vagrant won't collide with newer versions of Bundler [GH-3193]\n  - core: Allow provisioner plugins to not have a config class. [GH-3272]\n  - core: Removing a specific box version that doesn't exist doesn't\n      crash Vagrant. [GH-3364]\n  - core: SSH commands are forced to be ASCII.\n  - core: private networks with DHCP type work if type parameter is\n      a string and not a symbol. [GH-3349]\n  - core: Converting to cygwin path works for folders with spaces. [GH-3304]\n  - core: Can add boxes with spaces in their path. [GH-3306]\n  - core: Prerelease plugins installed are locked to that prerelease\n      version. [GH-3301]\n  - core: Better error message when adding a box with a malformed version. [GH-3332]\n  - core: Fix a rare issue where vagrant up would complain it couldn't\n      check version of a box that doesn't exist. [GH-3326]\n  - core: Box version constraint can't be specified with old-style box. [GH-3260]\n  - commands/box: Show versions when listing. [GH-3316]\n  - commands/box: Outdated check can list local boxes that are newer. [GH-3321]\n  - commands/status: Machine readable output contains the target. [GH-3218]\n  - guests/arch: Reload udev rules after network change. [GH-3322]\n  - guests/debian: Changing host name works properly. [GH-3283]\n  - guests/suse: Shutdown works correctly on SLES [GH-2775]\n  - hosts/linux: Don't hardcode `exportfs` path. Now searches the PATH. [GH-3292]\n  - providers/hyperv: Resume command works properly. [GH-3336]\n  - providers/virtualbox: Add missing translation for stopping status. [GH-3368]\n  - providers/virtualbox: Host-only networks set cableconnected property\n      to \"yes\" [GH-3365]\n  - provisioners/docker: Use proper flags for 0.9. [GH-3356]\n  - synced\\_folders/rsync: Set chmod flag by default on Windows. [GH-3256]\n  - synced\\_folders/smb: IDs of synced folders are hashed to work better\n      with VMware. [GH-3219]\n  - synced\\_folders/smb: Properly remove existing folders with the\n      same name. [GH-3354]\n  - synced\\_folders/smb: Passwords with symbols now work. [GH-3242]\n  - synced\\_folders/smb: Exporting works for non-english locale Windows\n      machines. [GH-3251]\n\n## 1.5.1 (March 13, 2014)\n\nIMPROVEMENTS:\n\n  - guests/tinycore: Will now auto-install rsync.\n  - synced\\_folders/rsync: rsync-auto will not watch filesystem for\n    excluded paths. [GH-3159]\n\nBUG FIXES:\n\n  - core: V1 Vagrantfiles can upgrade provisioners properly. [GH-3092]\n  - core: Rare EINVAL errors on box adding are gone. [GH-3094]\n  - core: Upgrading the home directory for Vagrant 1.5 uses the Vagrant\n    temp dir. [GH-3095]\n  - core: Assume a box isn't metadata if it exceeds 20 MB. [GH-3107]\n  - core: Asking for input works even in consoles that don't support\n    hiding input. [GH-3119]\n  - core: Adding a box by path in Cygwin on Windows works. [GH-3132]\n  - core: PowerShell scripts work when they're in a directory with\n    spaces. [GH-3100]\n  - core: If you add a box path that doesn't exist, error earlier. [GH-3091]\n  - core: Validation on forwarded ports to make sure they're between\n    0 and 65535. [GH-3187]\n  - core: Downloads with user/password use the curl `-u` flag. [GH-3183]\n  - core: `vagrant help` no longer loads the Vagrantfile. [GH-3180]\n  - guests/darwin: Fix an exception when configuring networks. [GH-3143]\n  - guests/linux: Only chown folders/files in rsync if they don't\n    have the proper owner. [GH-3186]\n  - hosts/linux: Unusual sed delimiter to avoid conflicts. [GH-3167]\n  - providers/virtualbox: Make more internal interactions with VBoxManage\n    retryable to avoid spurious VirtualBox errors. [GH-2831]\n  - providers/virtualbox: Import progress works again on Windows.\n  - provisioners/ansible: Request SSH info within the provision method,\n    when we know its available. [GH-3111]\n  - synced\\_folders/rsync: owner/group settings work. [GH-3163]\n\n## 1.5.0 (March 10, 2014)\n\nBREAKING CHANGES:\n\n  - provisioners/ansible: the machine name (taken from Vagrantfile) is now\n    set as default limit to ensure that vagrant provision steps only\n    affect the expected machine.\n\nDEPRECATIONS:\n\n  - provisioners/chef-solo: The \"nfs\" setting has been replaced by\n    `synced_folder_type`. The \"nfs\" setting will be removed in the next\n    version.\n  - provisioners/puppet: The \"nfs\" setting has been replaced by\n    `synced_folder_type`. The \"nfs\" setting will be removed in the next\n    version.\n\nFEATURES:\n\n  - **New provider:** Hyper-V. If you're on a Windows machine with Hyper-V\n    enabled, Vagrant can now manage Hyper-V virtual machines out of the box.\n  - **New guest:** Funtoo (change host name and networks supported)\n  - **New guest:** NetBSD\n  - **New guest:** TinyCore Linux. This allows features such as networking,\n    halting, rsync and more work with Boot2Docker.\n  - **New synced folder type:** rsync - Does a one-time one-directional sync\n    to the guest machine. New commands `vagrant rsync` and `vagrant rsync-auto`\n    can resync the folders.\n  - **New synced folder type:** SMB- Allows bi-directional folder syncing\n    using SMB on Windows hosts with any guest.\n  - Password-based SSH authentication. This lets you use almost any off-the-shelf\n    virtual machine image with Vagrant. Additionally, Vagrant will automatically\n    insert a keypair into the machine.\n  - Plugin versions can now be constrained to a range of versions. Example:\n    `vagrant plugin install foo --plugin-version \"> 1.0, < 1.1\"`\n  - Host-specific operations now use a \"host capabilities\" system much like\n    guests have used \"guest capabilities\" for a few releases now. This allows\n    plugin developers to create pluggable host-specific capabilities and makes\n    further integrating Vagrant with new operating systems even easier.\n  - You can now override provisioners within sub-VM configuration and\n    provider overrides. See documentation for more info. [GH-1113]\n  - providers/virtualbox: Provider-specific configuration `cpus` can be used\n    to set the number of CPUs on the VM [GH-2800]\n  - provisioners/docker: Can now build images using `docker build`. [GH-2615]\n\nIMPROVEMENTS:\n\n  - core: Added \"error-exit\" type to machine-readable output which contains\n    error information that caused a non-zero exit status. [GH-2999]\n  - command/destroy: confirmation will re-ask question if bad input. [GH-3027]\n  - guests/solaris: More accurate Solaris >= 11, < 11 detection. [GH-2824]\n  - provisioners/ansible: Generates a single inventory file, rather than\n    one per machine. See docs for more info. [GH-2991]\n  - provisioners/ansible: SSH forwarding support. [GH-2952]\n  - provisioners/ansible: Multiple SSH keys can now be attempted [GH-2952]\n  - provisioners/ansible: Disable SSH host key checking by default,\n    which improves the experience. We believe this is a sane default\n    for ephemeral dev machines.\n  - provisioners/chef-solo: New config `synced_folder_type` replaces the\n    `nfs` option. This can be used to set the synced folders the provisioner\n    needs to any type. [GH-2709]\n  - provisioners/chef-solo: `roles_paths` can now be an array of paths in\n    Chef 11.8.0 and newer. [GH-2975]\n  - provisioners/docker: Can start a container without daemonization.\n  - provisioners/docker: Started containers are given names. [GH-3051]\n  - provisioners/puppet: New config `synced_folder_type` replaces the\n    `nfs` option. This can be used to set the synced folders the provisioner\n    needs to any type. [GH-2709]\n  - commands/plugin: `vagrant plugin update` will now update all installed\n    plugins, respecting any constraints set.\n  - commands/plugin: `vagrant plugin uninstall` can now uninstall multiple\n    plugins.\n  - commands/plugin: `vagrant plugin install` can now install multiple\n    plugins.\n  - hosts/redhat: Recognize Korora OS. [GH-2869]\n  - synced\\_folders/nfs: If the guest supports it, NFS clients will be\n    automatically installed in the guest.\n\nBUG FIXES:\n\n  - core: If an exception was raised while attempting to connect to SSH\n    for the first time, it would get swallowed. It is properly raised now.\n  - core: Plugin installation does not fail if your local gemrc file has\n    syntax errors.\n  - core: Plugins that fork within certain actions will no longer hang\n    indefinitely. [GH-2756]\n  - core: Windows checks home directory permissions more correctly to\n    warn of potential issues.\n  - core: Synced folders set to the default synced folder explicitly won't\n    be deleted. [GH-2873]\n  - core: Static IPs can end in \".1\". A warning is now shown. [GH-2914]\n  - core: Adding boxes that have directories in them works on Windows.\n  - core: Vagrant will not think provisioning is already done if\n    the VM is manually deleted outside of Vagrant.\n  - core: Box file checksums of large files works properly on Windows.\n    [GH-3045]\n  - commands/box: Box add `--force` works with `--provider` flag. [GH-2757]\n  - commands/box: Listing boxes with machine-readable output crash is gone.\n  - commands/plugin: Plugin installation will fail if dependencies conflict,\n    rather than at runtime.\n  - commands/ssh: When using `-c` on Windows, no more TTY errors.\n  - commands/ssh-config: ProxyCommand is included in output if it is\n    set. [GH-2950]\n  - guests/coreos: Restart etcd after configuring networks. [GH-2852]\n  - guests/linux: Don't chown VirtualBox synced folders if mounting\n    as readonly. [GH-2442]\n  - guests/redhat: Set hostname to FQDN, per the documentation for RedHat.\n    [GH-2792]\n  - hosts/bsd: Don't invoke shell for NFS sudo calls. [GH-2808]\n  - hosts/bsd: Sort NFS exports to avoid false validation errors. [GH-2927]\n  - hosts/bsd: No more checkexports NFS errors if you're sharing the\n    same directory. [GH-3023]\n  - hosts/gentoo: Look for systemctl in `/usr/bin` [GH-2858]\n  - hosts/linux: Properly escape regular expression to prune NFS exports,\n    allowing VMware to work properly. [GH-2934]\n  - hosts/opensuse: Start NFS server properly. [GH-2923]\n  - providers/virtualbox: Enabling internal networks by just setting \"true\"\n    works properly. [GH-2751]\n  - providers/virtualbox: Make more internal interactions with VBoxManage\n    retryable to avoid spurious VirtualBox errors. [GH-2831]\n  - providers/virtualbox: Config validation catches invalid keys. [GH-2843]\n  - providers/virtualbox: Fix network adapter configuration issue if using\n    provider-specific config. [GH-2854]\n  - providers/virtualbox: Bridge network adapters always have their\n    \"cable connected\" properly. [GH-2906]\n  - provisioners/chef: When chowning folders, don't follow symlinks.\n  - provisioners/chef: Encrypted data bag secrets also in Chef solo are\n    now uploaded to the provisioning path to avoid perm issues. [GH-2845]\n  - provisioners/chef: Encrypted data bag secret is removed from the\n    machine before and after provisioning also with Chef client. [GH-2845]\n  - provisioners/chef: Set `encrypted_data_bag_secret` on the VM to `nil`\n    if the secret is not specified. [GH-2984]\n  - provisioners/chef: Fix loading of the custom configure file. [GH-876]\n  - provisioners/docker: Only add SSH user to docker group if the user\n    isn't already in it. [GH-2838]\n  - provisioners/docker: Configuring autostart works properly with\n    the newest versions of Docker. [GH-2874]\n  - provisioners/puppet: Append default module path to the module paths\n    always. [GH-2677]\n  - provisioners/salt: Setting pillar data doesn't require `deep_merge`\n    plugin anymore. [GH-2348]\n  - provisioners/salt: Options can now set install type and install args.\n    [GH-2766]\n  - provisioners/salt: Fix case when salt would say \"options only allowed\n    before install arguments\" [GH-3005]\n  - provisioners/shell: Error if script is encoded incorrectly. [GH-3000]\n  - synced\\_folders/nfs: NFS entries are pruned on every `vagrant up`,\n    if there are any to prune. [GH-2738]\n\n## 1.4.3 (January 2, 2014)\n\nBUG FIXES:\n\n  - providers/virtualbox: `vagrant package` works properly again. [GH-2739]\n\n## 1.4.2 (December 31, 2013)\n\nIMPROVEMENTS:\n\n  - guests/linux: emit upstart event when NFS folders are mounted. [GH-2705]\n  - provisioners/chef-solo: Encrypted data bag secret is removed from the\n    machine after provisioning. [GH-2712]\n\nBUG FIXES:\n\n  - core: Ctrl-C no longer raises \"trap context\" exception.\n  - core: The version for `Vagrant.configure` can now be an int. [GH-2689]\n  - core: `Vagrant.has_plugin?` tries to use plugin's gem name before\n    registered plugin name [GH-2617]\n  - core: Fix exception if an EOFError was somehow raised by Ruby while\n    checking a box checksum. [GH-2716]\n  - core: Better error message if your plugin state file becomes corrupt\n    somehow. [GH-2694]\n  - core: Box add will fail early if the box already exists. [GH-2621]\n  - hosts/bsd: Only run `nfsd checkexports` if there is an exports file.\n    [GH-2714]\n  - commands/plugin: Fix exception that could happen rarely when installing\n    a plugin.\n  - providers/virtualbox: Error when packaging if the package already exists\n    _before_ the export is done. [GH-2380]\n  - providers/virtualbox: NFS with static IP works even if VirtualBox\n    guest additions aren't installed (regression). [GH-2674]\n  - synced\\_folders/nfs: sudo will only ask for password one at a time\n    when using a parallel provider [GH-2680]\n\n## 1.4.1 (December 18, 2013)\n\nIMPROVEMENTS:\n\n  - hosts/bsd: check NFS exports file for issues prior to exporting\n  - provisioners/ansible: Add ability to use Ansible groups in generated\n    inventory [GH-2606]\n  - provisioners/docker: Add support for using the provisioner with RedHat\n    based guests [GH-2649]\n  - provisioners/docker: Remove \"Docker\" prefix from Client and Installer\n    classes [GH-2641]\n\nBUG FIXES:\n\n  - core: box removal of a V1 box works\n  - core: `vagrant ssh -c` commands are now executed in the context of\n    a login shell (regression). [GH-2636]\n  - core: specifying `-t` or `-T` to `vagrant ssh -c` as extra args\n    will properly enable/disable a TTY for OpenSSH. [GH-2618]\n  - commands/init: Error if can't write Vagrantfile to directory. [GH-2660]\n  - guests/debian: fix `use_dhcp_assigned_default_route` to work properly.\n    [GH-2648]\n  - guests/debian,ubuntu: fix change\\_host\\_name for FQDNs with trailing\n    dots [GH-2610]\n  - guests/freebsd: configuring networks in the guest works properly\n    [GH-2620]\n  - guests/redhat: fix configure networks bringing down interfaces that\n    don't exist. [GH-2614]\n  - providers/virtualbox: Don't override NFS exports for all VMs when\n    coming up. [GH-2645]\n  - provisioners/ansible: Array arguments work for raw options [GH-2667]\n  - provisioners/chef-client: Fix node/client deletion when node\\_name is not\n    set. [GH-2345]\n  - provisioners/chef-solo: Force remove files to avoid cases where\n    a prompt would be shown to users. [GH-2669]\n  - provisioners/puppet: Don't prepend default module path for Puppet\n    in case Puppet is managing its own paths. [GH-2677]\n\n## 1.4.0 (December 9, 2013)\n\nFEATURES:\n\n  - New provisioner: Docker. Install Docker, pull containers, and run\n    containers easier than ever.\n  - Machine readable output. Vagrant now has machine-friendly output by\n    using the `--machine-readable` flag.\n  - New plugin type: synced folder implementation. This allows new ways of\n    syncing folders to be added as plugins to Vagrant.\n  - The `Vagrant.require_version` function can be used at the top of a Vagrantfile\n    to enforce a minimum/maximum Vagrant version.\n  - Adding boxes via `vagrant box add` and the Vagrantfile both support\n    providing checksums of the box files.\n  - The `--debug` flag can be specified on any command now to get debug-level\n    log output to ease reporting bugs.\n  - You can now specify a memory using `vb.memory` setting with VirtualBox.\n  - Plugin developers can now hook into `environment_plugins_loaded`, which is\n    executed after plugins are loaded but before Vagrantfiles are parsed.\n  - VirtualBox internal networks are now supported. [GH-2020]\n\nIMPROVEMENTS:\n\n  - core: Support resumable downloads [GH-57]\n  - core: owner/group of shared folders can be specified by integers. [GH-2390]\n  - core: the VAGRANT\\_NO\\_COLOR environmental variable may be used to enable\n    `--no-color` mode globally. [GH-2261]\n  - core: box URL and add date is tracked and shown if `-i` flag is\n    specified for `vagrant box list` [GH-2327]\n  - core: Multiple SSH keys can be specified with `config.ssh.private_key_path`\n    [GH-907]\n  - core: `config.vm.box_url` can be an array of URLs. [GH-1958]\n  - commands/box/add: Can now specify a custom CA cert for verifying\n    certs from a custom CA. [GH-2337]\n  - commands/box/add: Can now specify a client cert when downloading a\n    box. [GH-1889]\n  - commands/init: Add `--output` option for specifying output path, or\n    \"-\" for stdin. [GH-1364]\n  - commands/provision: Add `--no-parallel` option to disable provider\n    parallelization if the provider supports it. [GH-2404]\n  - commands/ssh: SSH compression is enabled by default. [GH-2456]\n  - commands/ssh: Inline commands specified with \"-c\" are now executed\n    using OpenSSH rather than pure-Ruby SSH. It is MUCH faster, and\n    stdin works!\n  - communicators/ssh: new configuration `config.ssh.pty` is a boolean for\n    whether you want ot use a PTY for provisioning.\n  - guests/linux: emit upstart event `vagrant-mounted` if upstart is\n    available. [GH-2502]\n  - guests/pld: support changing hostname [GH-2543]\n  - providers/virtualbox: Enable symlinks for VirtualBox 4.1. [GH-2414]\n  - providers/virtualbox: default VM name now includes milliseconds with\n    a random number to try to avoid conflicts in CI environments. [GH-2482]\n  - providers/virtualbox: customizations via VBoxManage are retried, avoiding\n    VirtualBox flakiness [GH-2483]\n  - providers/virtualbox: NFS works with DHCP host-only networks now. [GH-2560]\n  - provisioners/ansible: allow files for extra vars [GH-2366]\n  - provisioners/puppet: client cert and private key can now be specified\n    for the puppet server provisioner. [GH-902]\n  - provisioners/puppet: the manifests path can be in the VM. [GH-1805]\n  - provisioners/shell: Added `keep_color` option to not automatically color\n    output based on stdout/stderr. [GH-2505]\n  - provisioners/shell: Arguments can now be an array of args. [GH-1949]\n  - synced\\_folders/nfs: Specify `nfs_udp` to false to disable UDP based\n    NFS folders. [GH-2304]\n\nBUG FIXES:\n\n  - core: Make sure machine IDs are always strings. [GH-2434]\n  - core: 100% CPU spike when waiting for SSH is fixed. [GH-2401]\n  - core: Command lookup works on systems where PATH is not valid UTF-8 [GH-2514]\n  - core: Human-friendly error if box metadata.json becomes corrupted. [GH-2305]\n  - core: Don't load Vagrantfile on `vagrant plugin` commands, allowing\n    Vagrantfiles that use plugins to work. [GH-2388]\n  - core: global flags are ignored past the \"--\" on the CLI. [GH-2491]\n  - core: provisioning will properly happen if `up` failed. [GH-2488]\n  - guests/freebsd: Mounting NFS folders works. [GH-2400]\n  - guests/freebsd: Uses `sh` by default for shell. [GH-2485]\n  - guests/linux: upstart events listening for `vagrant-mounted` won't\n    wait for jobs to complete, fixing issues with blocking during\n    vagrant up [GH-2564]\n  - guests/redhat: `DHCP_HOSTNAME` is set to the hostname, not the FQDN. [GH-2441]\n  - guests/redhat: Down interface before messing up configuration file\n    for networking. [GH-1577]\n  - guests/ubuntu: \"localhost\" is preserved when changing hostnames.\n    [GH-2383]\n  - hosts/bsd: Don't set mapall if maproot is set in NFS. [GH-2448]\n  - hosts/gentoo: Support systemd for NFS startup. [GH-2382]\n  - providers/virtualbox: Don't start new VM if VirtualBox has transient\n    failure during `up` from suspended. [GH-2479]\n  - provisioners/chef: Chef client encrypted data bag secrets are now\n    uploaded to the provisioning path to avoid perm issues. [GH-1246]\n  - provisioners/chef: Create/chown the cache and backup folders. [GH-2281]\n  - provisioners/chef: Verify environment paths exist in config\n    validation step. [GH-2381]\n  - provisioners/puppet: Multiple puppet definitions in a Vagrantfile\n    work correctly.\n  - provisioners/salt: Bootstrap on FreeBSD systems work. [GH-2525]\n  - provisioners/salt: Extra args for bootstrap are put in the proper\n    location. [GH-2558]\n\n## 1.3.5 (October 15, 2013)\n\nFEATURES:\n\n  - VirtualBox 4.3 is now supported. [GH-2374]\n  - ESXi is now a supported guest OS. [GH-2347]\n\nIMPROVEMENTS:\n\n  - guests/redhat: Oracle Linux is now supported. [GH-2329]\n  - provisioners/salt: Support running overstate. [GH-2313]\n\nBUG FIXES:\n\n  - core: Fix some places where \"no error message\" errors were being\n    reported when in fact there were errors. [GH-2328]\n  - core: Disallow hyphens or periods for starting hostnames. [GH-2358]\n  - guests/ubuntu: Setting hostname works properly. [GH-2334]\n  - providers/virtualbox: Retryable VBoxManage commands are properly\n    retried. [GH-2365]\n  - provisioners/ansible: Verbosity won't be blank by default. [GH-2320]\n  - provisioners/chef: Fix exception raised during Chef client node\n    cleanup. [GH-2345]\n  - provisioners/salt: Correct master seed file name. [GH-2359]\n\n## 1.3.4 (October 2, 2013)\n\nFEATURES:\n\n  - provisioners/shell: Specify the `binary` option as true and Vagrant won't\n    automatically replace Windows line endings with Unix ones.  [GH-2235]\n\nIMPROVEMENTS:\n\n  - guests/suse: Support installing CFEngine. [GH-2273]\n\nBUG FIXES:\n\n  - core: Don't output `\\e[0K` anymore on Windows. [GH-2246]\n  - core: Only modify `DYLD_LIBRARY_PATH` on Mac when executing commands\n    in the installer context. [GH-2231]\n  - core: Clear `DYLD_LIBRARY_PATH` on Mac if the subprocess is executing\n    a setuid or setgid script. [GH-2243]\n  - core: Defined action hook names can be strings now. They are converted\n    to symbols internally.\n  - guests/debian: FQDN is properly set when setting the hostname. [GH-2254]\n  - guests/linux: Fix poor chown command for mounting VirtualBox folders.\n  - guests/linux: Don't raise exception right away if mounting fails, allow\n    retries. [GH-2234]\n  - guests/redhat: Changing hostname changes DHCP_HOSTNAME. [GH-2267]\n  - hosts/arch: Vagrant won't crash on Arch anymore. [GH-2233]\n  - provisioners/ansible: Extra vars are converted to strings. [GH-2244]\n  - provisioners/ansible: Output will show up on a task-by-task basis. [GH-2194]\n  - provisioners/chef: Propagate disabling color if Vagrant has no color\n    enabled. [GH-2246]\n  - provisioners/chef: Delete from chef server exception fixed. [GH-2300]\n  - provisioners/puppet: Work with restrictive umask. [GH-2241]\n  - provisioners/salt: Remove bootstrap definition file on each run in\n    order to avoid permissions issues. [GH-2290]\n\n## 1.3.3 (September 18, 2013)\n\nBUG FIXES:\n\n  - core: Fix issues with dynamic linker not finding symbols on OS X. [GH-2219]\n  - core: Properly clean up machine directories on destroy. [GH-2223]\n  - core: Add a timeout to waiting for SSH connection and server headers\n    on SSH. [GH-2226]\n\n## 1.3.2 (September 17, 2013)\n\nIMPROVEMENTS:\n\n  - provisioners/ansible: Support more verbosity levels, better documentation.\n    [GH-2153]\n  - provisioners/ansible: Add `host_key_checking` configuration. [GH-2203]\n\nBUG FIXES:\n\n  - core: Report the proper invalid state when waiting for the guest machine\n    to be ready\n  - core: `Guest#capability?` now works with strings as well\n  - core: Fix NoMethodError in the new `Vagrant.has_plugin?` method [GH-2189]\n  - core: Convert forwarded port parameters to integers. [GH-2173]\n  - core: Don't spike CPU to 100% while waiting for machine to boot. [GH-2163]\n  - core: Increase timeout for individual SSH connection to 60 seconds. [GH-2163]\n  - core: Call realpath after creating directory so NFS directory creation\n    works. [GH-2196]\n  - core: Don't try to be clever about deleting the machine state\n    directory anymore. Manually done in destroy actions. [GH-2201]\n  - core: Find the root Vagrantfile only if Vagrantfile is a file, not\n    a directory. [GH-2216]\n  - guests/linux: Try `id -g` in addition to `getent` for mounting\n    VirtualBox shared folders [GH-2197]\n  - hosts/arch: NFS exporting works properly, no exceptions. [GH-2161]\n  - hosts/bsd: Use only `sudo` for writing NFS exports. This lets NFS\n    exports work if you have sudo privs but not `su`. [GH-2191]\n  - hosts/fedora: Fix host detection encoding issues. [GH-1977]\n  - hosts/linux: Fix NFS export problems with `no_subtree_check`. [GH-2156]\n  - installer/mac: Vagrant works properly when a library conflicts from\n    homebrew. [GH-2188]\n  - installer/mac: deb/rpm packages now have an epoch of 1 so that new\n    installers don't appear older. [GH-2179]\n  - provisioners/ansible: Default output level is now verbose again. [GH-2194]\n  - providers/virtualbox: Fix an issue where destroy middlewares weren't\n    being properly called. [GH-2200]\n\n## 1.3.1 (September 6, 2013)\n\nBUG FIXES:\n\n  - core: Fix various issues where using the same options hash in a\n    Vagrantfile can cause errors.\n  - core: `VAGRANT_VAGRANTFILE` env var only applies to the project\n    Vagrantfile name. [GH-2130]\n  - core: Fix an issue where the data directory would be deleted too\n    quickly in a multi-VM environment.\n  - core: Handle the case where we get an EACCES cleaning up the .vagrant\n    directory.\n  - core: Fix exception on upgrade warnings from V1 to V2. [GH-2142]\n  - guests/coreos: Proper IP detection. [GH-2146]\n  - hosts/linux: NFS exporting works properly again. [GH-2137]\n  - provisioners/chef: Work even with restrictive umask on user. [GH-2121]\n  - provisioners/chef: Fix environment validation to be less restrictive.\n  - provisioners/puppet: No more \"shared folders cannot be found\" error.\n    [GH-2134]\n  - provisioners/puppet: Work with restrictive umask on user by testing\n    for folders with sudo. [GH-2121]\n\n## 1.3.0 (September 5, 2013)\n\nBACKWARDS INCOMPATIBILITY:\n\n  - `config.ssh.max_tries` is gone. Instead of maximum tries, Vagrant now\n    uses a simple overall timeout value `config.vm.boot_timeout` to wait for\n    the machine to boot up.\n  - `config.vm.graceful_halt_retry_*` settings are gone. Instead, a single\n    timeout is now used to wait for a graceful halt to work, specified\n    by `config.vm.graceful_halt_timeout`.\n  - The ':extra' flag to shared folders for specifying arbitrary mount\n    options has been replaced with the `:mount_options` flag, which is now\n    an array of mount options.\n  - `vagrant up` will now only run provisioning by default the first time\n   it is run. Subsequent `reload` or `up` will need to explicitly specify\n   the `--provision` flag to provision. [GH-1776]\n\nFEATURES:\n\n  - New command: `vagrant plugin update` to update specific installed plugins.\n  - New provisioner: File provisioner. [GH-2112]\n  - New provisioner: Salt provisioner. [GH-1626]\n  - New guest: Mac OS X guest support. [GH-1914]\n  - New guest: CoreOS guest support. Change host names and configure networks on\n    CoreOS. [GH-2022]\n  - New guest: Solaris 11 guest support. [GH-2052]\n  - Support for environments in the Chef-solo provisioner. [GH-1915]\n  - Provisioners can now define \"cleanup\" tasks that are executed on\n    `vagrant destroy`. [GH-1302]\n  - Chef Client provisioner will now clean up the node/client using\n    `knife` if configured to do so.\n  - `vagrant up` has a `--no-destroy-on-error` flag that will not destroy\n    the VM if a fatal error occurs. [GH-2011]\n  - NFS: Arbitrary mount options can be specified using the\n   `mount_options` option on synced folders. [GH-1029]\n  - NFS: Arbitrary export options can be specified using\n   `bsd__nfs_options` and `linux__nfs_options`. [GH-1029]\n  - Static IP can now be set on public networks. [GH-1745]\n  - Add `Vagrant.has_plugin?` method for use in Vagrantfile to check\n    if a plugin is installed. [GH-1736]\n  - Support for remote shell provisioning scripts [GH-1787]\n\nIMPROVEMENTS:\n\n  - core: add `--color` to any Vagrant command to FORCE color output. [GH-2027]\n  - core: \"config.vm.host_name\" works again, just an alias to hostname.\n  - core: Reboots via SSH are now handled gracefully (without exception).\n  - core: Mark `disabled` as true on forwarded port to disable. [GH-1922]\n  - core: NFS exports are now namespaced by user ID, so pruning NFS won't\n    remove exports from other users. [GH-1511]\n  - core: \"vagrant -v\" no longer loads the Vagrantfile\n  - commands/box/remove: Fix stack trace that happens if no provider\n    is specified. [GH-2100]\n  - commands/plugin/install: Post install message of a plugin will be\n    shown if available. [GH-1986]\n  - commands/status: cosmetic improvement to better align names and\n    statuses [GH-2016]\n  - communicators/ssh: Support a proxy_command. [GH-1537]\n  - guests/openbsd: support configuring networks, changing host name,\n    and mounting NFS. [GH-2086]\n  - guests/suse: Supports private/public networks. [GH-1689]\n  - hosts/fedora: Support RHEL as a host. [GH-2088]\n  - providers/virtualbox: \"post-boot\" customizations will run directly\n    after boot, and before waiting for SSH. [GH-2048]\n  - provisioners/ansible: Many more configuration options. [GH-1697]\n  - provisioners/ansible: Ansible `inventory_path` can be a directory now. [GH-2035]\n  - provisioners/ansible: Extra verbose option by setting `config.verbose`\n    to `extra`. [GH-1979]\n  - provisioners/ansible: `inventory_path` will be auto-generated if not\n    specified. [GH-1907]\n  - provisioners/puppet: Add `nfs` option to puppet provisioner. [GH-1308]\n  - provisioners/shell: Set the `privileged` option to false to run\n    without sudo. [GH-1370]\n\nBUG FIXES:\n\n  - core: Clean up \".vagrant\" folder more effectively.\n  - core: strip newlines off of ID file values [GH-2024]\n  - core: Multiple forwarded ports with different protocols but the same\n    host port can be specified. [GH-2059]\n  - core: `:nic_type` option for private networks is respected. [GH-1704]\n  - commands/up: provision-with validates the provisioners given. [GH-1957]\n  - guests/arch: use systemd way of setting host names. [GH-2041]\n  - guests/debian: Force bring up eth0. Fixes hangs on setting hostname.\n   [GH-2026]\n  - guests/ubuntu: upstart events are properly emitted again. [GH-1717]\n  - hosts/bsd: Nicer error if can't read NFS exports. [GH-2038]\n  - hosts/fedora: properly detect later CentOS versions. [GH-2008]\n  - providers/virtualbox: VirtualBox 4.2 now supports up to 36\n    network adapters. [GH-1886]\n  - provisioners/ansible: Execute ansible with a cwd equal to the\n    path where the Vagrantfile is. [GH-2051]\n  - provisioners/all: invalid config keys will be properly reported. [GH-2117]\n  - provisioners/ansible: No longer report failure on every run. [GH-2007]\n  - provisioners/ansible: Properly handle extra vars with spaces. [GH-1984]\n  - provisioners/chef: Formatter option works properly. [GH-2058]\n  - provisioners/chef: Create/chown the provisioning folder before\n    reading contents. [GH-2121]\n  - provisioners/puppet: mount synced folders as root to avoid weirdness\n  - provisioners/puppet: Run from the correct working directory. [GH-1967]\n    with Puppet. [GH-2015]\n  - providers/virtualbox: Use `getent` to get the group ID instead of\n    `id` in case the name doesn't have a user. [GH-1801]\n  - providers/virtualbox: Will only set the default name of the VM on\n    initial `up`. [GH-1817]\n\n## 1.2.7 (July 28, 2013)\n\nBUG FIXES:\n\n  - On Windows, properly convert synced folder host path to a string\n    so that separator replacement works properly.\n  - Use `--color=false` for no color in Puppet to support older\n    versions properly. [GH-2000]\n  - Make sure the hostname configuration is a string. [GH-1999]\n  - cURL downloads now contain a user agent which fixes some\n    issues with downloading Vagrant through proxies. [GH-2003]\n  - `vagrant plugin install` will now always properly show the actual\n    installed gem name. [GH-1834]\n\n## 1.2.6 (July 26, 2013)\n\nBUG FIXES:\n\n  - Box collections with multiple formats work properly by converting\n    the supported formats to symbols. [GH-1990]\n\n## 1.2.5 (July 26, 2013)\n\nFEATURES:\n\n  - `vagrant help <command>` now works. [GH-1578]\n  - Added `config.vm.box_download_insecure` to allow the box_url setting\n    to point to an https site that won't be validated. [GH-1712]\n  - VirtualBox VBoxManage customizations can now be specified to run\n    pre-boot (the default and existing functionality, pre-import,\n    or post-boot. [GH-1247]\n  - VirtualBox no longer destroys unused network interfaces by default.\n    This didn't work across multi-user systems and required admin privileges\n    on Windows, so it has been disabled by default. It can be enabled using\n    the VirtualBox provider-specific `destroy_unused_network_interfaces`\n    configuration by setting it to true. [GH-1324]\n\nIMPROVEMENTS:\n\n  - Remote commands that fail will now show the stdout/stderr of the\n    command that failed. [GH-1203]\n  - Puppet will run without color if the UI is not colored. [GH-1344]\n  - Chef supports the \"formatter\" configuration for setting the\n    formatter. [GH-1250]\n  - VAGRANT_DOTFILE_PATH environmental variable reintroduces the\n    functionality removed in 1.1 from \"config.dotfile_name\" [GH-1524]\n  - Vagrant will show an error if VirtualBox 4.2.14 is running.\n  - Added provider to BoxNotFound error message. [GH-1692]\n  - If Ansible fails to run properly, show an error message. [GH-1699]\n  - Adding a box with the `--provider` flag will now allow a box for\n    any of that provider's supported formats.\n  - NFS mounts enable UDP by default, resulting in higher performance.\n    (Because mount is over local network, packet loss is not an issue)\n   [GH-1706]\n\nBUG FIXES:\n\n  - `box_url` now handles the case where the provider doesn't perfectly\n    match the provider in use, but the provider supports it. [GH-1752]\n  - Fix uninitialized constant error when configuring Arch Linux network. [GH-1734]\n  - Debian/Ubuntu change hostname works properly if eth0 is configured\n    with hot-plugging. [GH-1929]\n  - NFS exports with improper casing on Mac OS X work properly. [GH-1202]\n  - Shared folders overriding '/vagrant' in multi-VM environments no\n    longer all just use the last value. [GH-1935]\n  - NFS export fsid's are now 32-bit integers, rather than UUIDs. This\n    lets NFS exports work with Linux kernels older than 2.6.20. [GH-1127]\n  - NFS export allows access from all private networks on the VM. [GH-1204]\n  - Default VirtualBox VM name now contains the machine name as defined\n    in the Vagrantfile, helping differentiate multi-VM. [GH-1281]\n  - NFS works properly on CentOS hosts. [GH-1394]\n  - Solaris guests actually shut down properly. [GH-1506]\n  - All provisioners only output newlines when the provisioner sends a\n    newline. This results in the output looking a lot nicer.\n  - Sharing folders works properly if \".profile\" contains an echo. [GH-1677]\n  - `vagrant ssh-config` IdentityFile is only wrapped in quotes if it\n    contains a space. [GH-1682]\n  - Shared folder target path can be a Windows path. [GH-1688]\n  - Forwarded ports don't auto-correct by default, and will raise an\n    error properly if they collide. [GH-1701]\n  - Retry SSH on ENETUNREACH error. [GH-1732]\n  - NFS is silently ignored on Windows. [GH-1748]\n  - Validation so that private network static IP does not end in \".1\" [GH-1750]\n  - With forward agent enabled and sudo being used, Vagrant will automatically\n    discover and set `SSH_AUTH_SOCK` remotely so that forward agent\n    works properly despite misconfigured sudoers. [GH-1307]\n  - Synced folder paths on Windows containing '\\' are replaced with\n    '/' internally so that they work properly.\n  - Unused config objects are finalized properly. [GH-1877]\n  - Private networks work with Fedora guests once again. [GH-1738]\n  - Default internal encoding of strings in Vagrant is now UTF-8, allowing\n    detection of Fedora to work again (which contained a UTF-8 string). [GH-1977]\n\n## 1.2.4 (July 16, 2013)\n\nFEATURES:\n\n  - Chef solo and client provisioning now support a `custom_config_path`\n    setting that accepts a path to a Ruby file to load as part of Chef\n    configuration, allowing you to override any setting available. [GH-876]\n  - CFEngine provisioner: you can now specify the package name to install,\n    so CFEngine enterprise is supported. [GH-1920]\n\nIMPROVEMENTS:\n\n  - `vagrant box remove` works with only the name of the box if that\n    box exists only backed by one provider. [GH-1032]\n  - `vagrant destroy` returns exit status 1 if any of the confirmations\n    are declined. [GH-923]\n  - Forwarded ports can specify a host IP and guest IP to bind to. [GH-1121]\n  - You can now set the \"ip\" of a private network that uses DHCP. This will\n    change the subnet and such that the DHCP server uses.\n  - Add `file_cache_path` support for chef_solo. [GH-1897]\n\nBUG FIXES:\n\n  - VBoxManage or any other executable missing from PATH properly\n    reported. Regression from 1.2.2. [GH-1928]\n  - Boxes downloaded as part of `vagrant up` are now done so _prior_ to\n    config validation. This allows Vagrantfiles to references files that\n    may be in the box itself. [GH-1061]\n  - Chef removes dna.json and encrypted data bag secret file prior to\n    uploading. [GH-1111]\n  - NFS synced folders exporting sub-directories of other exported folders now\n    works properly. [GH-785]\n  - NFS shared folders properly dereference symlinks so that the real path\n    is used, avoiding mount errors [GH-1101]\n  - SSH channel is closed after the exit status is received, potentially\n    eliminating any SSH hangs. [GH-603]\n  - Fix regression where VirtualBox detection wasn't working anymore. [GH-1918]\n  - NFS shared folders with single quotes in their name now work properly. [GH-1166]\n  - Debian/Ubuntu request DHCP renewal when hostname changes, which will\n    fix issues with FQDN detecting. [GH-1929]\n  - SSH adds the \"DSAAuthentication=yes\" option in case that is disabled\n    on the user's system. [GH-1900]\n\n## 1.2.3 (July 9, 2013)\n\nFEATURES:\n\n  - Puppet provisioner now supports Hiera by specifying a `hiera_config_path`.\n  - Added a `working_directory` configuration option to the Puppet apply\n    provisioner so you can specify the working directory when `puppet` is\n    called, making it friendly to Hiera data and such. [GH-1670]\n  - Ability to specify the host IP to bind forwarded ports to. [GH-1785]\n\nIMPROVEMENTS:\n\n  - Setting hostnames works properly on OmniOS. [GH-1672]\n  - Better VBoxManage error detection on Windows systems. This avoids\n    some major issues where Vagrant would sometimes \"lose\" your VM. [GH-1669]\n  - Better detection of missing VirtualBox kernel drivers on Linux\n    systems. [GH-1671]\n  - More precise detection of Ubuntu/Debian guests so that running Vagrant\n    within an LXC container works properly now.\n  - Allow strings in addition to symbols to more places in V1 configuration\n    as well as V2 configuration.\n  - Add `ARPCHECK=0` to RedHat OS family network configuration. [GH-1815]\n  - Add SSH agent forwarding sample to initial Vagrantfile. [GH-1808]\n  - VirtualBox: Only configure networks if there are any to configure.\n    This allows linux's that don't implement this capability to work with\n    Vagrant. [GH-1796]\n  - Default SSH forwarded port now binds to 127.0.0.1 so only local\n    connections are allowed. [GH-1785]\n  - Use `netctl` for Arch Linux network configuration. [GH-1760]\n  - Improve fedora host detection regular expression. [GH-1913]\n  - SSH shows a proper error on EHOSTUNREACH. [GH-1911]\n\nBUG FIXES:\n\n  - Ignore \"guest not ready\" errors when attempting to graceful halt and\n    carry on checks whether the halt succeeded. [GH-1679]\n  - Handle the case where a roles path for Chef solo isn't properly\n    defined. [GH-1665]\n  - Finding V1 boxes now works properly again to avoid \"box not found\"\n    errors. [GH-1691]\n  - Setting hostname on SLES 11 works again. [GH-1781]\n  - `config.vm.guest` properly forces guests again. [GH-1800]\n  - The `read_ip_address` capability for linux properly reads the IP\n    of only the first network interface. [GH-1799]\n  - Validate that an IP is given for a private network. [GH-1788]\n  - Fix uninitialized constant error for Gentoo plugin. [GH-1698]\n\n## 1.2.2 (April 23, 2013)\n\nFEATURES:\n\n  - New `DestroyConfirm` built-in middleware for providers so they can\n    more easily conform to the `destroy` action.\n\nIMPROVEMENTS:\n\n  - No longer an error if the Chef run list is empty. It is now\n    a warning. [GH-1620]\n  - Better locking around handling the `box_url` parameter for\n    parallel providers.\n  - Solaris guest is now properly detected on SmartOS, OmniOS, etc. [GH-1639]\n  - Guest addition version detection is more robust, attempting other\n    routes to get the version, and also retrying a few times. [GH-1575]\n\nBUG FIXES:\n\n  - `vagrant package --base` works again. [GH-1615]\n  - Box overrides specified in provider config overrides no longer\n    fail to detect the box. [GH-1617]\n  - In a multi-machine environment, a box not found won't be downloaded\n    multiple times. [GH-1467]\n  - `vagrant box add` with a file path now works correctly on Windows\n    when a drive letter is specified.\n  - DOS line endings are converted to Unix line endings for the\n    shell provisioner automatically. [GH-1495]\n\n## 1.2.1 (April 17, 2013)\n\nFEATURES:\n\n  - Add a `--[no-]parallel` flag to `vagrant up` to enable/disable\n    parallelism. Vagrant will parallelize by default.\n\nIMPROVEMENTS:\n\n  - Get rid of arbitrary 4 second sleep when connecting via SSH. The\n    issue it was attempting to work around may be gone now.\n\nBUG FIXES:\n\n  - Chef solo run list properly set. [GH-1608]\n  - Follow 30x redirects when downloading boxes. [GH-1607]\n  - Chef client config defaults are done properly. [GH-1609]\n  - VirtualBox mounts shared folders with the proper owner/group. [GH-1611]\n  - Use the Mozilla CA cert bundle for cURL so SSL validation works\n    properly.\n\n## 1.2.0 (April 16, 2013)\n\nBACKWARDS INCOMPATIBILITIES:\n\n  - WINDOWS USERS: Vagrant now defaults to using the 'USERPROFILE' environmental\n    variable for the home directory if it is set. This means that the default\n    location for the Vagrant home directory is now `%USERPROFILE%/.vagrant.d`.\n    On Cygwin, this will cause existing Cygwin users to \"lose\" their boxes.\n    To work around this, either set `VAGRANT_HOME` to your Cygwin \".vagrant.d\"\n    folder or move your \".vagrant.d\" folder to `USERPROFILE`. The latter is\n    recommended for long-term support.\n  - The constant `Vagrant::Environment::VAGRANT_HOME` was removed in favor of\n    `Vagrant::Environment#default_vagrant_home`.\n\nFEATURES:\n\n  - Providers can now parallelize! If they explicitly support it, Vagrant\n    will run \"up\" and other commands in parallel. For providers such AWS,\n    this means that your instances will come up in parallel. VirtualBox\n    does not support this mode.\n  - Box downloads are now done via `curl` rather than Ruby's built-in\n    HTTP library. This results in massive speedups, support for SSL\n    verification, FTP downloads, and more.\n  - `config.vm.provider` now takes an optional second parameter to the block,\n    allowing you to override any configuration value. These overrides are\n    applied last, and therefore override any other configuration value.\n    Note that while this feature is available, the \"Vagrant way\" is instead\n    to use box manifests to ensure that the \"box\" for every provider matches,\n    so these sorts of overrides are unnecessary.\n  - A new \"guest capabilities\" system to replace the old \"guest\" system.\n    This new abstraction allows plugins to define \"capabilities\" that\n    certain guest operating systems can implement. This allows greater\n    flexibility in doing guest-specific behavior.\n  - Ansible provisioner support. [GH-1465]\n  - Providers can now support multiple box formats by specifying the\n    `box_format:` option.\n  - CFEngine provisioner support.\n  - `config.ssh.default` settings introduced to set SSH defaults that\n    providers can still override. [GH-1479]\n\nIMPROVEMENTS:\n\n  - Full Windows support in cmd.exe, PowerShell, Cygwin, and MingW based\n    environments.\n  - By adding the \"disabled\" boolean flag to synced folders you can disable\n    them altogether. [GH-1004]\n  - Specify the default provider with the `VAGRANT_DEFAULT_PROVIDER`\n    environmental variable. [GH-1478]\n  - Invalid settings are now caught and shown in a user-friendly way. [GH-1484]\n  - Detect PuTTY Link SSH client on Windows and show an error. [GH-1518]\n  - `vagrant ssh` in Cygwin won't output DOS path file warnings.\n  - Add `--rtcuseutc on` as a sane default for VirtualBox. [GH-912]\n  - SSH will send keep-alive packets every 5 seconds by default to\n    keep connections alive. Can be disabled with `config.ssh.keep_alive`. [GH-516]\n  - Show a message on `vagrant up` if the machine is already running. [GH-1558]\n  - \"Running provisioner\" output now shoes the provisioner shortcut name,\n    rather than the less-than-helpful class name.\n  - Shared folders with the same guest path will overwrite each other. No\n    more shared folder IDs.\n  - Shell provisioner outputs script it is running. [GH-1568]\n  - Automatically merge forwarded ports that share the same host\n    port.\n\nBUG FIXES:\n\n  - The `:mac` option for host-only networks is respected. [GH-1536]\n  - Don't preserve modified time when untarring boxes. [GH-1539]\n  - Forwarded port auto-correct will not auto-correct to a port\n    that is also in use.\n  - Cygwin will always output color by default. Specify `--no-color` to\n    override this.\n  - Assume Cygwin has a TTY for asking for input. [GH-1430]\n  - Expand Cygwin paths to Windows paths for calls to VBoxManage and\n    for VirtualBox shared folders.\n  - Output the proper clear line text for shells in Cygwin when\n    reporting dynamic progress.\n  - When using `Builder` instances for hooks, the builders will be\n    merged for the proper before/after chain. [GH-1555]\n  - Use the Vagrant temporary directory again for temporary files\n    since they can be quite large and were messing with tmpfs. [GH-1442]\n  - Fix issue parsing extra SSH args in `vagrant ssh` in multi-machine\n    environments. [GH-1545]\n  - Networks come back up properly on RedHat systems after reboot. [GH-921]\n  - `config.ssh` settings override all detected SSH settings (regression). [GH-1479]\n  - `ssh-config` won't raise an exception if the VirtualBox machine\n    is not created. [GH-1562]\n  - Multiple machines defined in the same Vagrantfile with the same\n    name will properly merge.\n  - More robust hostname checking for RedHat. [GH-1566]\n  - Cookbook path existence for Chef is no longer an error, so that\n    things like librarian and berkshelf plugins work properly. [GH-1570]\n  - Chef solo provisioner uses proper SSH username instead of hardcoded\n    config. [GH-1576]\n  - Shell provisioner takes ownership of uploaded files properly so\n    that they can also be manually executed later. [GH-1576]\n\n## 1.1.6 (April 3, 2013)\n\nBUG FIXES:\n\n  - Fix SSH re-use connection logic to drop connection if an\n    error occurs.\n\n## 1.1.5 (April 2, 2013)\n\nIMPROVEMENTS:\n\n  - More robust SSH connection close detection.\n  - Don't load `vagrant plugin` installed plugins when in a Bundler\n    environment. This happens during plugin development. This will make\n    Vagrant errors much quieter when developing plugins.\n  - Vagrant will detect Bundler environments, make assumptions that you're\n    developing plugins, and will quiet its error output a bit.\n  - More comprehensive synced folder configuration validation.\n  - VBoxManage errors now show the output from the command so that\n    users can potentially know what is wrong.\n\nBUG FIXES:\n\n  - Proper error message if invalid provisioner is used. [GH-1515]\n  - Don't error on graceful halt if machine just shut down very\n    quickly. [GH-1505]\n  - Error message if private key for SSH isn't owned by the proper\n    user. [GH-1503]\n  - Don't error too early when `config.vm.box` is not properly set.\n  - Show a human-friendly error if VBoxManage is not found (exit\n    status 126). [GH-934]\n  - Action hook prepend/append will only prepend or append once.\n  - Retry SSH on Errno::EACCES.\n  - Show an error if an invalid network type is used.\n  - Don't share Chef solo folder if it doesn't exist on host.\n\n## 1.1.4 (March 25, 2013)\n\nBUG FIXES:\n\n  - Default forwarded port adapter for VirtualBox should be 1.\n\n## 1.1.3 (March 25, 2013)\n\nIMPROVEMENTS:\n\n  - Puppet apply provisioner now retains the default module path\n    even while specifying custom module paths. [GH-1207]\n  - Re-added DHCP support for host-only networks. [GH-1466]\n  - Ability to specify a plugin version, plugin sources, and\n    pre-release versions using `--plugin-version`, `--plugin-source`,\n    and `--plugin-prerelease`. [GH-1461]\n  - Move VirtualBox guest addition checks to after the machine\n    boots. [GH-1179]\n  - Removed `Vagrant::TestHelpers` because it doesn't really work anymore.\n  - Add PLX linux guest support. [GH-1490]\n\nBUG FIXES:\n\n  - Attempt to re-establish SSH connection on `Net::SSH::Disconnect`\n  - Allow any value that can convert to a string for `Vagrant.plugin`\n  - Chef solo `recipe_url` works properly again. [GH-1467]\n  - Port collision detection works properly in VirtualBox with\n    auto-corrected ports. [GH-1472]\n  - Fix obscure error when temp directory is world writable when\n    adding boxes.\n  - Improved error handling around network interface detection for\n    VirtualBox [GH-1480]\n\n## 1.1.2 (March 18, 2013)\n\nBUG FIXES:\n\n  - When not specifying a cookbooks_path for chef-solo, an error won't\n    be shown if \"cookbooks\" folder is missing.\n  - Fix typo for exception when no host-only network with NFS. [GH-1448]\n  - Use UNSET_VALUE/nil with args on shell provisioner by default since\n    `[]` was too truthy. [GH-1447]\n\n## 1.1.1 (March 18, 2013)\n\nIMPROVEMENTS:\n\n  - Don't load plugins on any `vagrant plugin` command, so that errors\n    are avoided. [GH-1418]\n  - An error will be shown if you forward a port to the same host port\n    multiple times.\n  - Automatically convert network, provider, and provisioner names to\n    symbols internally in case people define them as strings.\n  - Using newer versions of net-ssh and net-scp. [GH-1436]\n\nBUG FIXES:\n\n  - Quote keys to StringBlockEditor so keys with spaces, parens, and\n    so on work properly.\n  - When there is no route to host for SSH, re-establish a new connection.\n  - `vagrant package` once again works, no more nil error. [GH-1423]\n  - Human friendly error when \"metadata.json\" is missing in a box.\n  - Don't use the full path to the manifest file with the Puppet provisioner\n    because it exposes a bug with Puppet path lookup on VMware.\n  - Fix bug in VirtualBox provider where port forwarding just didn't work if\n    you attempted to forward to a port under 1024. [GH-1421]\n  - Fix cross-device box adds for Windows. [GH-1424]\n  - Fix minor issues with defaults of configuration of the shell\n    provisioner.\n  - Fix Puppet server using \"host_name\" instead of \"hostname\" [GH-1444]\n  - Raise a proper error if no hostonly network is found for NFS with\n    VirtualBox. [GH-1437]\n\n## 1.1.0 (March 14, 2013)\n\nBACKWARDS INCOMPATIBILITIES:\n\n  - Vagrantfiles from 1.0.x that _do not use_ any plugins are fully\n    backwards compatible. If plugins are used, they must be removed prior\n    to upgrading. The new plugin system in place will avoid this issue in\n    the future.\n  - Lots of changes introduced in the form of a new configuration version and\n    format, but this is _opt-in_. Old Vagrantfile format continues to be supported,\n    as promised. To use the new features that will be introduced throughout\n    the 1.x series, you'll have to upgrade at some point.\n  - The .vagrant file is no longer supported and has been replaced by\n    a .vagrant directory. Running vagrant will automatically upgrade\n    to the new style directory format, after which old versions of\n    Vagrant will not be able to see or control your VM.\n\nFEATURES:\n\n  - Groundwork for **providers**, alternate backends for Vagrant that\n    allow Vagrant to power systems other than VirtualBox. Much improvement\n    and change will come to this throughout the 1.x lifecycle. The API\n    will continue to change, features will be added, and more. Specifically,\n    a revamped system for handling shared folders gracefully across providers\n    will be introduced in a future release.\n  - New plugin system which adds much more structure and stability to\n    the overall API. The goal of this system is to make it easier to write\n    powerful plugins for Vagrant while providing a backwards-compatible API\n    so that plugins will always _load_ (though they will almost certainly\n    not be _functional_ in future versions of Vagrant).\n  - Plugins are now installed and managed using the `vagrant plugin` interface.\n  - Allow \"file://\" URLs for box URLs. [GH-1087]\n  - Emit \"vagrant-mount\" upstart event when NFS shares are mounted. [GH-1118]\n  - Add a VirtualBox provider config `auto_nat_dns_proxy` which when set to\n    false will not attempt to automatically manage NAT DNS proxy settings\n    with VirtualBox. [GH-1313]\n  - `vagrant provision` accepts the `--provision-with` flag [GH-1167]\n  - Set the name of VirtualBox machines with `virtualbox.name` in the\n    VirtualBox provider config. [GH-1126]\n  - `vagrant ssh` will execute an `ssh` binary on Windows if it is on\n    your PATH. [GH-933]\n  - The environmental variable `VAGRANT_VAGRANTFILE` can be used to\n    specify an alternate Vagrantfile filename.\n\nIMPROVEMENTS / BUG FIXES:\n\n  - Vagrant works much better in Cygwin environments on Windows by\n    properly resolving Cygwin paths. [GH-1366]\n  - Improve the SSH \"ready?\" check by more gracefully handling timeouts. [GH-841]\n  - Human friendly error if connection times out for HTTP downloads. [GH-849]\n  - Detect when the VirtualBox installation is incomplete and error. [GH-846]\n  - Detect when kernel modules for VirtualBox need to be installed on Gentoo\n    systems and report a user-friendly error. [GH-710]\n  - All `vagrant` commands that can take a target VM name can take one even\n    if you're not in a multi-VM environment. [GH-894]\n  - Hostname is set before networks are setup to avoid very slow `sudo`\n    speeds on CentOS. [GH-922]\n  - `config.ssh.shell` now includes the flags to pass to it, such as `-l` [GH-917]\n  - The check for whether a port is open or not is more complete by\n    catching ENETUNREACH errors. [GH-948]\n  - SSH uses LogLevel FATAL so that errors are still shown.\n  - Sending a SIGINT (Ctrl-C) very early on when executing `vagrant` no\n    longer results in an ugly stack trace.\n  - Chef JSON configuration output is now pretty-printed to be\n    human readable. [GH-1146]\n    that SSHing succeeds when booting a machine.\n  - VMs in the \"guru meditation\" state can be destroyed now using\n    `vagrant destroy`.\n  - Fix issue where changing SSH key permissions didn't properly work. [GH-911]\n  - Fix issue where Vagrant didn't properly detect VBoxManage on Windows\n    if VBOX_INSTALL_PATH contained multiple paths. [GH-885]\n  - Fix typo in setting host name for Gentoo guests. [GH-931]\n  - Files that are included with `vagrant package --include` now properly\n    preserve file attributes on earlier versions of Ruby. [GH-951]\n  - Multiple interfaces now work with Arch linux guests. [GH-957]\n  - Fix issue where subprocess execution would always spin CPU of Ruby\n    process to 100%. [GH-832]\n  - Fix issue where shell provisioner would sometimes never end. [GH-968]\n  - Fix issue where puppet would reorder module paths. [GH-964]\n  - When console input is asked for (destroying a VM, bridged interfaces, etc.),\n    keystrokes such as ctrl-D and ctrl-C are more gracefully handled. [GH-1017]\n  - Fixed bug where port check would use \"localhost\" on systems where\n    \"localhost\" is not available. [GH-1057]\n  - Add missing translation for \"saving\" state on VirtualBox. [GH-1110]\n  - Proper error message if the remote end unexpectedly resets the connection\n    while downloading a box over HTTP. [GH-1090]\n  - Human-friendly error is raised if there are permission issues when\n    using SCP to upload files. [GH-924]\n  - Box adding doesn't use `/tmp` anymore which can avoid some cross-device\n    copy issues. [GH-1199]\n  - Vagrant works properly in folders with strange characters. [GH-1223]\n  - Vagrant properly handles \"paused\" VirtualBox machines. [GH-1184]\n  - Better behavior around permissions issues when copying insecure\n    private key. [GH-580]\n\n## 1.0.7 (March 13, 2013)\n\n  - Detect if a newer version of Vagrant ran and error if it did,\n    because we're not forward-compatible.\n  - Check for guest additions version AFTER booting. [GH-1179]\n  - Quote IdentityFile in `ssh-config` so private keys with spaces in\n    the path work. [GH-1322]\n  - Fix issue where multiple Puppet module paths can be re-ordered [GH-964]\n  - Shell provisioner won't hang on Windows anymore due to unclosed\n    tempfile. [GH-1040]\n  - Retry setting default VM name, since it sometimes fails first time. [GH-1368]\n  - Support setting hostname on Suse [GH-1063]\n\n## 1.0.6 (December 21, 2012)\n\n  - Shell provisioner outputs proper line endings on Windows [GH-1164]\n  - SSH upload opens file to stream which fixes strange upload issues.\n  - Check for proper exit codes for Puppet, since multiple exit codes\n    can mean success. [GH-1180]\n  - Fix issue where DNS doesn't resolve properly for 12.10. [GH-1176]\n  - Allow hostname to be a substring of the box name for Ubuntu [GH-1163]\n  - Use `puppet agent` instead of `puppetd` to be Puppet 3.x\n    compatible. [GH-1169]\n  - Work around bug in VirtualBox exposed by bug in OS X 10.8 where\n    VirtualBox executables couldn't handle garbage being injected into\n    stdout by OS X.\n\n## 1.0.5 (September 18, 2012)\n\n  - Work around a critical bug in VirtualBox 4.2.0 on Windows that\n    causes Vagrant to not work. [GH-1130]\n  - Plugin loading works better on Windows by using the proper\n    file path separator.\n  - NFS works on Fedora 16+. [GH-1140]\n  - NFS works with newer versions of Arch hosts that use systemd. [GH-1142]\n\n## 1.0.4 (September 13, 2012)\n\n  - VirtualBox 4.2 driver. [GH-1120]\n  - Correct `ssh-config` help to use `--host`, not `-h`.\n  - Use \"127.0.0.1\" instead of \"localhost\" for port checking to fix problem\n    where \"localhost\" is not properly setup. [GH-1057]\n  - Disable read timeout on Net::HTTP to avoid `rbuf_fill` error. [GH-1072]\n  - Retry SSH on `EHOSTUNREACH` errors.\n  - Add missing translation for \"saving\" state. [GH-1110]\n\n## 1.0.3 (May 1, 2012)\n\n  - Don't enable NAT DNS proxy on machines where resolv.conf already points\n    to localhost. This allows Vagrant to work once again with Ubuntu\n    12.04. [GH-909]\n\n## 1.0.2 (March 25, 2012)\n\n  - Provisioners will still mount folders and such if `--no-provision` is\n    used, so that `vagrant provision` works. [GH-803]\n  - Nicer error message if an unsupported SSH key type is used. [GH-805]\n  - Gentoo guests can now have their host names changed. [GH-796]\n  - Relative paths can be used for the `config.ssh.private_key_path`\n    setting. [GH-808]\n  - `vagrant ssh` now works on Solaris, where `IdentitiesOnly` was not\n    an available option. [GH-820]\n  - Output works properly in the face of broken pipes. [GH-819]\n  - Enable Host IO Cache on the SATA controller by default.\n  - Chef-solo provisioner now supports encrypted data bags. [GH-816]\n  - Enable the NAT DNS proxy by default, allowing your DNS to continue\n    working when you switch networks. [GH-834]\n  - Checking for port forwarding collisions also checks for other applications\n    that are potentially listening on that port as well. [GH-821]\n  - Multiple VM names can be specified for the various commands now. For\n    example: `vagrant up web db service`. [GH-795]\n  - More robust error handling if a VM fails to boot. The error message\n    is much clearer now. [GH-825]\n\n## 1.0.1 (March 11, 2012)\n\n  - Installers are now bundled with Ruby 1.9.3p125. Previously they were\n    bundled with 1.9.3p0. This actually fixes some IO issues with Windows.\n  - Windows installer now outputs a `vagrant` binary that will work in msys\n    or Cygwin environments.\n  - Fix crashing issue which manifested itself in multi-VM environments.\n  - Add missing `rubygems` require in `environment.rb` to avoid\n    possible load errors. [GH-781]\n  - `vagrant destroy` shows a nice error when called without a\n    TTY (and hence can't confirm). [GH-779]\n  - Fix an issue with the `:vagrantfile_name` option to `Vagrant::Environment`\n    not working properly. [GH-778]\n  - `VAGRANT_CWD` environmental variable can be used to set the CWD to\n    something other than the current directory.\n  - Downloading boxes from servers that don't send a content-length\n    now works properly. [GH-788]\n  - The `:facter` option now works for puppet server. [GH-790]\n  - The `--no-provision` and `--provision-with` flags are available to\n    `vagrant reload` now.\n  - `:openbsd` guest which supports only halting at the moment. [GH-773]\n  - `ssh-config -h` now shows help, instead of assuming a host is being\n    specified. For host, you can still use `--host`. [GH-793]\n\n## 1.0.0 (March 6, 2012)\n\n  - `vagrant gem` should now be used to install Vagrant plugins that are\n    gems. This installs the gems to a private gem folder that Vagrant adds\n    to its own load path. This isolates Vagrant-related gems from system\n    gems.\n  - Plugin loading no longer happens right when Vagrant is loaded, but when\n    a Vagrant environment is loaded. I don't anticipate this causing any\n    problems but it is a backwards incompatible change should a plugin\n    depend on this (but I don't see any reason why they would).\n  - `vagrant destroy` now asks for confirmation by default. This can be\n    overridden with the `--force` flag. [GH-699]\n  - Fix issue with Puppet config inheritance. [GH-722]\n  - Fix issue where starting a VM on some systems was incorrectly treated\n    as failing. [GH-720]\n  - It is now an error to specify the packaging `output` as a directory. [GH-730]\n  - Unix-style line endings are used properly for guest OS. [GH-727]\n  - Retry certain VirtualBox operations, since they intermittently fail.\n    [GH-726]\n  - Fix issue where Vagrant would sometimes \"lose\" a VM if an exception\n    occurred. [GH-725]\n  - `vagrant destroy` destroys virtual machines in reverse order. [GH-739]\n  - Add an `fsid` option to Linux NFS exports. [GH-736]\n  - Fix edge case where an exception could be raised in networking code. [GH-742]\n  - Add missing translation for the \"guru meditation\" state. [GH-745]\n  - Check that VirtualBox exists before certain commands. [GH-746]\n  - NIC type can be defined for host-only network adapters. [GH-750]\n  - Fix issue where re-running chef-client would sometimes cause\n    problems due to file permissions. [GH-748]\n  - FreeBSD guests can now have their hostnames changed. [GH-757]\n  - FreeBSD guests now support host only networking and bridged networking. [GH-762]\n  - `VM#run_action` is now public so plugin-devs can hook into it.\n  - Fix crashing bug when attempting to run commands on the \"primary\"\n    VM in a multi-VM environment. [GH-761]\n  - With puppet you can now specify `:facter` as a dictionary of facts to\n    override what is generated by Puppet. [GH-753]\n  - Automatically convert all arguments to `customize` to strings.\n  - openSUSE host system. [GH-766]\n  - Fix subprocess IO deadlock which would occur on Windows. [GH-765]\n  - Fedora 16 guest support. [GH-772]\n\n## 0.9.7 (February 9, 2012)\n\n  - Fix regression where all subprocess IO simply didn't work with\n    Windows. [GH-721]\n\n## 0.9.6 (February 7, 2012)\n\n  - Fix strange issue with inconsistent childprocess reads on JRuby. [GH-711]\n  - `vagrant ssh` does a direct `exec()` syscall now instead of going through\n    the shell. This makes it so things like shell expansion oddities no longer\n    cause problems. [GH-715]\n  - Fix crashing case if there are no ports to forward.\n  - Fix issue surrounding improper configuration of host only networks on\n    RedHat guests. [GH-719]\n  - NFS should work properly on Gentoo. [GH-706]\n\n## 0.9.5 (February 5, 2012)\n\n  - Fix crashing case when all network options are `:auto_config false`.\n    [GH-689]\n  - Type of network adapter can be specified with `:nic_type`. [GH-690]\n  - The NFS version can be specified with the `:nfs_version` option\n    on shared folders. [GH-557]\n  - Greatly improved FreeBSD guest and host support. [GH-695]\n  - Fix instability with RedHat guests and host only and bridged networks.\n    [GH-698]\n  - When using bridged networking, only list the network interfaces\n    that are up as choices. [GH-701]\n  - More intelligent handling of the `certname` option for puppet\n    server. [GH-702]\n  - You may now explicitly set the network to bridge to in the Vagrantfile\n    using the `:bridge` parameter. [GH-655]\n\n## 0.9.4 (January 28, 2012)\n\n  - Important internal changes to middlewares that make plugin developer's\n    lives much easier. [GH-684]\n  - Match VM names that have parens, brackets, etc.\n  - Detect when the VirtualBox kernel module is not loaded and error. [GH-677]\n  - Set `:auto_config` to false on any networking option to not automatically\n    configure it on the guest. [GH-663]\n  - NFS shared folder guest paths can now contain shell expansion characters\n    such as `~`.\n  - NFS shared folders with a `:create` flag will have their host folders\n    properly created if they don't exist. [GH-667]\n  - Fix the precedence for Arch, Ubuntu, and FreeBSD host classes so\n    they are properly detected. [GH-683]\n  - Fix issue where VM import sometimes made strange VirtualBox folder\n    layouts. [GH-669]\n  - Call proper `id` command on Solaris. [GH-679]\n  - More accurate VBoxManage error detection.\n  - Shared folders can now be marked as transient using the `:transient`\n    flag. [GH-688]\n\n## 0.9.3 (January 24, 2012)\n\n  - Proper error handling for not enough arguments to `box` commands.\n  - Fix issue causing crashes with bridged networking. [GH-673]\n  - Ignore host only network interfaces that are \"down.\" [GH-675]\n  - Use \"printf\" instead of \"echo\" to determine shell expanded files paths\n    which is more generally POSIX compliant. [GH-676]\n\n## 0.9.2 (January 20, 2012)\n\n  - Support shell expansions in shared folder guest paths again. [GH-656]\n  - Fix issue where Chef solo always expected the host to have a\n    \"cookbooks\" folder in their directory. [GH-638]\n  - Fix `forward_agent` not working when outside of blocks. [GH-651]\n  - Fix issue causing custom guest implementations to not load properly.\n  - Filter clear screen character out of output on SSH.\n  - Log output now goes on `stderr`, since it is utility information.\n  - Get rid of case where a `NoMethodError` could be raised while\n    determining VirtualBox version. [GH-658]\n  - Debian/Ubuntu uses `ifdown` again, instead of `ifconfig xxx down`, since\n    the behavior seems different/wrong.\n  - Give a nice error if `:vagrant` is used as a JSON key, since Vagrant\n    uses this. [GH-661]\n  - If there is only one bridgeable interface, use that without asking\n    the user. [GH-655]\n  - The shell will have color output if ANSICON is installed on Windows. [GH-666]\n\n## 0.9.1 (January 18, 2012)\n\n  - Use `ifconfig device down` instead of `ifdown`. [GH-649]\n  - Clearer invalid log level error. [GH-645]\n  - Fix exception raised with NFS `recover` method.\n  - Fix `ui` `NoMethodError` exception in puppet server.\n  - Fix `vagrant box help` on Ruby 1.8.7. [GH-647]\n\n## 0.9.0 (January 17, 2012)\n\n  - VirtualBox 4.0 support backported in addition to supporting VirtualBox 4.1.\n  - `config.vm.network` syntax changed so that the first argument is now the type\n    of argument. Previously where you had `config.vm.network \"33.33.33.10\"` you\n    should now put `config.vm.network :hostonly, \"33.33.33.10\"`. This is in order\n    to support bridged networking, as well.\n  - `config.vm.forward_port` no longer requires a name parameter.\n  - Bridged networking. `config.vm.network` with `:bridged` as the option will\n    setup a bridged network.\n  - Host only networks can be configured with DHCP now. Specify `:dhcp` as\n    the IP and it will be done.\n  - `config.vm.customize` now takes a command to send to `VBoxManage`, so any\n    arbitrary command can be sent. The older style of passing a block no longer\n    works and Vagrant will give a proper error message if it notices this old-style\n    being used.\n  - `config.ssh.forwarded_port_key` is gone. Vagrant no longer cares about\n    forwarded port names for any reason. Please use `config.ssh.guest_port`\n    (more below).\n  - `config.ssh.forwarded_port_destination` has been replaced by\n    `config.ssh.guest_port` which more accurately reflects what it is\n    used for. Vagrant will automatically scan forwarded ports that match the\n    guest port to find the SSH port.\n  - Logging. The entire Vagrant source has had logging sprinkled throughout\n    to make debugging issues easier. To enable logging, set the VAGRANT_LOG\n    environmental variable to the log level you wish to see. By default,\n    logging is silent.\n  - `system` renamed to `guest` throughout the source. Any `config.vm.system`\n    configurations must be changed to `config.vm.guest`\n  - Puppet provisioner no longer defaults manifest to \"box.pp.\" Instead, it\n    is now \"default.pp\"\n  - All Vagrant commands that take a VM name in a Multi-VM environment\n    can now be given a regular expression. If the name starts and ends with a \"/\"\n    then it is assumed to be a regular expression. [GH-573]\n  - Added a \"--plain\" flag to `vagrant ssh` which will cause Vagrant to not\n    perform any authentication. It will simply `ssh` into the proper IP and\n    port of the virtual machine.\n  - If a shared folder now has a `:create` flag set to `true`, the path on the\n    host will be created if it doesn't exist.\n  - Added `--force` flag to `box add`, which will overwrite any existing boxes\n    if they exist. [GH-631]\n  - Added `--provision-with` to `up` which configures what provisioners run,\n    by shortcut. [GH-367]\n  - Arbitrary mount options can be passed with `:extra` to any shared\n    folders. [GH-551]\n  - Options passed after a `--` to `vagrant ssh` are now passed directly to\n    `ssh`. [GH-554]\n  - Ubuntu guests will now emit a `vagrant-mounted` upstart event after shared\n    folders are mounted.\n  - `attempts` is a new option on chef client and chef solo provisioners. This\n    will run the provisioner multiple times until erroring about failing\n    convergence. [GH-282]\n  - Removed Thor as a dependency for the command line interfaces. This resulted\n    in general speed increases across all command line commands.\n  - Linux uses `shutdown -h` instead of `halt` to hopefully more consistently\n    power off the system. [GH-575]\n  - Tweaks to SSH to hopefully be more reliable in coming up.\n  - Helpful error message when SCP is unavailable in the guest. [GH-568]\n  - Error message for improperly packaged box files. [GH-198]\n  - Copy insecure private key to user-owned directory so even\n    `sudo` installed Vagrant installations work. [GH-580]\n  - Provisioner stdout/stderr is now color coded based on stdout/stderr.\n    stdout is green, stderr is red. [GH-595]\n  - Chef solo now prompts users to run a `reload` if shared folders\n    are not found on the VM. [GH-253]\n  - \"--no-provision\" once again works for certain commands. [GH-591]\n  - Resuming a VM from a saved state will show an error message if there\n    would be port collisions. [GH-602]\n  - `vagrant ssh -c` will now exit with the same exit code as the command\n    run. [GH-598]\n  - `vagrant ssh -c` will now send stderr to stderr and stdout to stdout\n    on the host machine, instead of all output to stdout.\n  - `vagrant box add` path now accepts unexpanded shell paths such as\n    `~/foo` and will properly expand them. [GH-633]\n  - Vagrant can now be interrupted during the \"importing\" step.\n  - NFS exports will no longer be cleared when an expected error occurs. [GH-577]\n\n## 0.8.10 (December 10, 2011)\n\n  - Revert the SSH tweaks made in 0.8.8. It affected stability\n\n## 0.8.8 (December 1, 2011)\n\n  - Mount shared folders shortest to longest to avoid mounting\n    subfolders first. [GH-525]\n  - Support for basic HTTP auth in the URL for boxes.\n  - Solaris support for host only networks. [GH-533]\n  - `vagrant init` respects `Vagrant::Environment` cwd. [GH-528]\n  - `vagrant` commands will not output color when stdout is\n    not a TTY.\n  - Fix issue where `box_url` set with multiple VMs could cause issues. [GH-564]\n  - Chef provisioners no longer depend on a \"v-root\" share being\n    available. [GH-556]\n  - NFS should work for FreeBSD hosts now. [GH-510]\n  - SSH executed methods respect `config.ssh.max_tries`. [GH-508]\n  - `vagrant box add` now respects the \"no_proxy\" environmental variable.\n    [GH-502]\n  - Tweaks that should make \"Waiting for VM to boot\" slightly more\n    reliable.\n  - Add comments to Vagrantfile to make it detected as Ruby file for\n    `vi` and `emacs`. [GH-515]\n  - More correct guest addition version checking. [GH-514]\n  - Chef solo support on Windows is improved. [GH-542]\n  - Put encrypted data bag secret into `/tmp` by default so that\n    permissions are almost certainly guaranteed. [GH-512]\n\n## 0.8.7 (September 13, 2011)\n\n  - Fix regression with remote paths from chef-solo. [GH-431]\n  - Fix issue where Vagrant crashes if `.vagrant` file becomes invalid. [GH-496]\n  - Issue a warning instead of an error for attempting to forward a port\n    <= 1024. [GH-487]\n\n## 0.8.6 (August 28, 2011)\n\n  - Fix issue with download progress not properly clearing the line. [GH-476]\n  - NFS should work properly on Fedora. [GH-450]\n  - Arguments can be specified to the `shell` provisioner via the `args` option. [GH-475]\n  - Vagrant behaves much better when there are \"inaccessible\" VMs. [GH-453]\n\n## 0.8.5 (August 15, 2011)\n\nNote: 0.8.3 and 0.8.4 was yanked due to RubyGems encoding issue.\n\n - Fix SSH `exec!` to inherit proper `$PATH`. [GH-426]\n - Chef client now accepts an empty (`nil`) run list again. [GH-429]\n - Fix incorrect error message when running `provision` on halted VM. [GH-447]\n - Checking guest addition versions now ignores OSE. [GH-438]\n - Chef solo from a remote URL fixed. [GH-431]\n - Arch linux support: host only networks and changing the host name. [GH-439] [GH-448]\n - Chef solo `roles_path` and `data_bags_path` can only be single paths. [GH-446]\n - Fix `virtualbox_not_detected` error message to require 4.1.x. [GH-458]\n - Add shortname (`hostname -s`) for hostname setting on RHEL systems. [GH-456]\n - `vagrant ssh -c` output no longer has a prefix and respects newlines\n   from the output. [GH-462]\n\n## 0.8.2 (July 22, 2011)\n\n  - Fix issue with SSH disconnects not reconnecting.\n  - Fix chef solo simply not working with roles/data bags. [GH-425]\n  - Multiple chef solo provisioners now work together.\n  - Update Puppet provisioner so no deprecation warning is shown. [GH-421]\n  - Removed error on \"provisioner=\" in config, as this has not existed\n    for some time now.\n  - Add better validation for networking.\n\n## 0.8.1 (July 20, 2011)\n\n  - Repush of 0.8.0 to fix a Ruby 1.9.2 RubyGems issue.\n\n## 0.8.0 (July 20, 2011)\n\n  - VirtualBox 4.1 support _only_. Previous versions of VirtualBox\n    are supported by earlier versions of Vagrant.\n  - Performance optimizations in `virtualbox` gem. Huge speed gains.\n  - `:chef_server` provisioner is now `:chef_client`. [GH-359]\n  - SSH connection is now cached after first access internally,\n    speeding up `vagrant up`, `reload`, etc. quite a bit.\n  - Actions which modify the VM now occur much more quickly,\n    greatly speeding up `vagrant up`, `reload`, etc.\n  - SUSE host only networking support. [GH-369]\n  - Show nice error message for invalid HTTP responses for HTTP\n    downloader. [GH-403]\n  - New `:inline` option for shell provisioner to provide inline\n    scripts as a string. [GH-395]\n  - Host only network now properly works on multiple adapters. [GH-365]\n  - Can now specify owner/group for regular shared folders. [GH-350]\n  - `ssh_config` host name will use VM name if given. [GH-332]\n  - `ssh` `-e` flag changed to `-c` to align with `ssh` standard\n    behavior. [GH-323]\n  - Forward agent and forward X11 settings properly appear in\n    `ssh_config` output. [GH-105]\n  - Chef JSON can now be set with `chef.json =` instead of the old\n    `merge` technique. [GH-314]\n  - Provisioner configuration is no longer cleared when the box\n    needs to be downloaded during an `up`. [GH-308]\n  - Multiple Chef provisioners no longer overwrite cookbook folders. [GH-407]\n  - `package` won't delete previously existing file. [GH-408]\n  - Vagrantfile can be lowercase now. [GH-399]\n  - Only one copy of Vagrant may be running at any given time. [GH-364]\n  - Default home directory for Vagrant moved to `~/.vagrant.d` [GH-333]\n  - Specify a `forwarded_port_destination` for SSH configuration and\n    SSH port searching will fall back to that if it can't find any\n    other port. [GH-375]\n\n## 0.7.8 (July 19, 2011)\n\n  - Make sure VirtualBox version check verifies that it is 4.0.x.\n\n## 0.7.7 (July 12, 2011)\n\n  - Fix crashing bug with Psych and Ruby 1.9.2. [GH-411]\n\n## 0.7.6 (July 2, 2011)\n\n  - Run Chef commands in a single command. [GH-390]\n  - Add `nfs` option for Chef to mount Chef folders via NFS. [GH-378]\n  - Add translation for `aborted` state in VM. [GH-371]\n  - Use full paths with the Chef provisioner so that restart cookbook will\n    work. [GH-374]\n  - Add \"--no-color\" as an argument and no colorized output will be used. [GH-379]\n  - Added DEVICE option to the RedHat host only networking entry, which allows\n    host only networking to work even if the VM has multiple NICs. [GH-382]\n  - Touch the network configuration file for RedHat so that the `sed` works\n    with host only networking. [GH-381]\n  - Load prerelease versions of plugins if available.\n  - Do not load a plugin if it depends on an invalid version of Vagrant.\n  - Encrypted data bag support in Chef server provisioner. [GH-398]\n  - Use the `-H` flag to set the proper home directory for `sudo`. [GH-370]\n\n## 0.7.5 (May 16, 2011)\n\n  - `config.ssh.port` can be specified and takes highest precedence if specified.\n    Otherwise, Vagrant will still attempt to auto-detect the port. [GH-363]\n  - Get rid of RubyGems deprecations introduced with RubyGems 1.8.x\n  - Search in pre-release gems for plugins as well as release gems.\n  - Support for Chef-solo `data_bags_path` [GH-362]\n  - Can specify path to Chef binary using `binary_path` [GH-342]\n  - Can specify additional environment data for Chef using `binary_env` [GH-342]\n\n## 0.7.4 (May 12, 2011)\n\n  - Chef environments support (for Chef 0.10) [GH-358]\n  - Suppress the \"added to known hosts\" message for SSH [GH-354]\n  - Ruby 1.8.6 support [GH-352]\n  - Chef proxy settings now work for chef server [GH-335]\n\n## 0.7.3 (April 19, 2011)\n\n  - Retry all SSH on Net::SSH::Disconnect in case SSH is just restarting. [GH-313]\n  - Add NFS shared folder support for Arch linux. [GH-346]\n  - Fix issue with unknown terminal type output for sudo commands.\n  - Forwarded port protocol can now be set as UDP. [GH-311]\n  - Chef server file cache path and file backup path can be configured. [GH-310]\n  - Setting hostname should work on Debian now. [GH-307]\n\n## 0.7.2 (February 8, 2011)\n\n  - Update JSON dependency to 1.5.1, which works with Ruby 1.9 on\n    Windows.\n  - Fix sudo issues on sudo < 1.7.0 (again).\n  - Fix race condition in SSH, which specifically manifested itself in\n    the chef server provisioner. [GH-295]\n  - Change sudo shell to use `bash` (configurable). [GH-301]\n  - Can now set mac address of host only network. [GH-294]\n  - NFS shared folders with spaces now work properly. [GH-293]\n  - Failed SSH commands now show output in error message. [GH-285]\n\n## 0.7.1 (January 28, 2011)\n\n  - Change error output with references to VirtualBox 3.2 to 4.0.\n  - Internal SSH through net-ssh now uses `IdentitiesOnly` thanks to\n    upstream net-ssh fix.\n  - Fix issue causing warnings to show with `forwardx11` enabled for SSH. [GH-279]\n  - FreeBSD support for host only networks, NFS, halting, etc. [GH-275]\n  - Make SSH commands which use sudo compatible with sudo < 1.7.0. [GH-278]\n  - Fix broken puppet server provisioner which called a nonexistent\n    method.\n  - Default SSH host changed from `localhost` to `127.0.0.1` since\n    `localhost` is not always loopback.\n  - New `shell` provisioner which simply uploads and executes a script as\n    root on the VM.\n  - Gentoo host only networking no longer fails if already setup. [GH-286]\n  - Set the host name of your guest OS with `config.vm.host_name` [GH-273]\n  - `vagrant ssh-config` now outputs the configured `config.ssh.host`\n\n## 0.7.0 (January 19, 2011)\n\n  - VirtualBox 4.0 support. Support for VirtualBox 3.2 is _dropped_, since\n    the API is so different. Stay with the 0.6.x series if you have VirtualBox\n    3.2.x.\n  - Puppet server provisioner. [GH-262]\n  - Use numeric uid/gid in mounting shared folders to increase portability. [GH-252]\n  - HTTP downloading follows redirects. [GH-163]\n  - Downloaders have clearer output to note what they're doing.\n  - Shared folders with no guest path are not automounted. [GH-184]\n  - Boxes downloaded during `vagrant up` reload the Vagrantfile config, which\n    fixes a problem with box settings not being properly loaded. [GH-231]\n  - `config.ssh.forward_x11` to enable the ForwardX11 SSH option. [GH-255]\n  - Vagrant source now has a `contrib` directory where contributions of miscellaneous\n    addons for Vagrant will be added.\n  - Vagrantfiles are now loaded only once (instead of 4+ times) [GH-238]\n  - Ability to move home vagrant dir (~/.vagrant) by setting VAGRANT_HOME\n    environmental variable.\n  - Removed check and error for the \"OSE\" version of VirtualBox, since with\n    VirtualBox 4 this distinction no longer exists.\n  - Ability to specify proxy settings for chef. [GH-169]\n  - Helpful error message shown if NFS mounting fails. [GH-135]\n  - Gentoo guests now support host only networks. [GH-240]\n  - RedHat (CentOS included) guests now support host only networks. [GH-260]\n  - New Vagrantfile syntax for enabling and configuring provisioners. This\n    change is not backwards compatible. [GH-265]\n  - Provisioners are now RVM-friendly, meaning if you installed chef or puppet\n    with an RVM managed Ruby, Vagrant now finds then. [GH-254]\n  - Changed the unused host only network destroy mechanism to check for\n    uselessness after the VM is destroyed. This should result in more accurate\n    checks.\n  - Networks are no longer disabled upon halt/destroy. With the above\n    change, its unnecessary.\n  - Puppet supports `module_path` configuration to mount local modules directory\n    as a shared folder and configure puppet with it. [GH-270]\n  - `ssh-config` now outputs `127.0.0.1` as the host instead of `localhost`.\n\n## 0.6.9 (December 21, 2010)\n\n  - Puppet provisioner. [GH-223]\n  - Solaris system configurable to use `sudo`.\n  - Solaris system registered, so it can be set with `:solaris`.\n  - `vagrant package` include can be a directory name, which will cause the\n    contents to be recursively copied into the package. [GH-241]\n  - Arbitrary options to puppet binary can be set with `config.puppet.options`. [GH-242]\n  - BSD hosts use proper GNU sed syntax for clearing NFS shares. [GH-243]\n  - Enumerate VMs in a multi-VM environment in order they were defined. [GH-244]\n  - Check for VM boot changed to use `timeout` library, which works better with Windows.\n  - Show special error if VirtualBox not detected on 64-bit Windows.\n  - Show error to Windows users attempting to use host only networking since\n    it doesn't work yet.\n\n## 0.6.8 (November 30, 2010)\n\n  - Network interfaces are now up/down in distinct commands instead of just\n    restarting \"networking.\" [GH-192]\n  - Add missing translation for chef binary missing. [GH-203]\n  - Fix default settings for Opscode platform and comments. [GH-213]\n  - Blank client name for chef server now uses FQDN by default, instead of \"client\" [GH-214]\n  - Run list can now be nil, which will cause it to sync with chef server (when\n    chef server is enabled). [GH-214]\n  - Multiple NFS folders now work on linux. [GH-215]\n  - Add translation for state \"stuck\" which is very rare. [GH-218]\n  - virtualbox gem dependency minimum raised to 0.7.6 to verify FFI < 1.0.0 is used.\n  - Fix issue where box downloading from `vagrant up` didn't reload the box collection. [GH-229]\n\n## 0.6.7 (November 3, 2010)\n\n  - Added validation to verify that a box is specified.\n  - Proper error message when box is not found for `config.vm.box`. [GH-195]\n  - Fix output of `vagrant status` with multi-vm to be correct. [GH-196]\n\n## 0.6.6 (October 14, 2010)\n\n  - `vagrant status NAME` works once again. [GH-191]\n  - Conditional validation of Vagrantfile so that some commands don't validate. [GH-188]\n  - Fix \"junk\" output for ssh-config. [GH-189]\n  - Fix port collision handling with greater than two VMs. [GH-185]\n  - Fix potential infinite loop with root path if bad CWD is given to environment.\n\n## 0.6.5 (October 8, 2010)\n\n  - Validations on base MAC address to avoid situation described in GH-166, GH-181\n    from ever happening again.\n  - Properly load sub-VM configuration on first-pass of config loading. Solves\n    a LOT of problems with multi-VM. [GH-166] [GH-181]\n  - Configuration now only validates on final Vagrantfile proc, so multi-VM\n    validates correctly.\n  - A nice error message is given if \".vagrant\" is a directory and therefore\n    can't be accessed. [GH-172]\n  - Fix plugin loading in a Rails 2.3.x project. [GH-176]\n\n## 0.6.4 (October 4, 2010)\n\n  - Default VM name is now properly the parent folder of the working directory\n    of the environment.\n  - Added method to `TestHelpers` to assist with testing new downloaders.\n  - `up --no-provision` works again. This disables provisioning during the\n    boot process.\n  - Action warden doesn't do recovery process on `SystemExit` exceptions,\n    allowing the double ctrl-C to work properly again. [related to GH-166]\n  - Initial Vagrantfile is now heavily commented with various available\n    options. [GH-171]\n  - Box add checks if a box already exists before the download. [GH-170]\n  - NFS no longer attempts to clean exports file if VM is not created,\n    which was causing a stack trace during recovery. [related to GH-166]\n  - Basic validation added for Chef configuration (both solo and server).\n  - Top config class is now available in all `Vagrant::Config::Base`\n    subclasses, which is useful for config validation.\n  - Subcommand help shows proper full command in task listing. [GH-168]\n  - SSH gives error message if `ssh` binary is not found. [GH-161]\n  - SSH gives proper error message if VM is not running. [GH-167]\n  - Fix some issues with undefined constants in command errors.\n\n## 0.6.1, 0.6.2, 0.6.3 (September 27, 2010)\n\nA lot of quick releases which all were to fix issues with Ruby 1.8.7\ncompatibility.\n\n## 0.6.0 (September 27, 2010)\n\n  - VM name now defaults to the name of the containing folder, plus a timestamp.\n    This should make it easier to identify VMs in the VirtualBox GUI.\n  - Exposed Vagrant test helpers in `Vagrant::TestHelpers` for plugins to easily\n    test themselves against Vagrant environments.\n  - **Plugins** have landed. Plugins are simply gems which have a `vagrant_init.rb`\n    file somewhere in their load path. Please read the documentation on\n    vagrantup.com before attempting to create a plugin (which is very easy)\n    for more information on how it all works and also some guidelines.\n  - `vagrant package` now takes a `--vagrantfile` option to specify a\n    Vagrantfile to package. The `--include` approach for including a Vagrantfile\n    no longer works (previously built boxes will continue to work).\n  - `vagrant package` has new logic with regards to the `--include` option\n    depending on if the file path is relative or absolute (they can be\n    intermixed):\n      * _Relative_ paths are copied directly into the box, preserving\n        their path. So `--include lib/foo` would be in the box as \"lib/foo\"\n      * _Absolute_ paths are simply copied files into the root of the\n        box. So `--include /lib/foo` would be in the box as \"foo\"\n  - \"vagrant_main\" is no longer the default run list. Instead, chef\n    run list starts empty. It is up to you to specify all recipes in\n    the Vagrantfile now.\n  - Fixed various issues with certain action middleware not working if\n    the VM was not created.\n  - SSH connection is retried 5 times if there is a connection refused.\n    Related to GH-140.\n  - If `http_proxy` environmental variable is set, it will be used as the proxy\n    box adding via http.\n  - Remove `config.ssh.password`. It hasn't been used for a few versions\n    now and was only kept around to avoid exceptions in Vagrantfiles.\n  - Configuration is now validated so improper input can be found in\n    Vagrantfiles.\n  - Fixed issue with not detecting Vagrantfile at root directory (\"/\").\n  - Vagrant now gives a nice error message if there is a syntax error\n    in any Vagrantfile. [GH-154]\n  - The format of the \".vagrant\" file which stores persisted VMs has\n    changed. This is **backwards incompatible**. Will provide an upgrade\n    utility prior to 0.6 launch.\n  - Every [expected] Vagrant error now exits with a clean error message\n    and a unique exit status, and raises a unique exception (if you're\n    scripting Vagrant).\n  - Added I18n gem dependency for pulling strings into clean YML files.\n    Vagrant is now localizable as a side effect! Translations welcome.\n  - Fixed issue with \"Waiting for cleanup\" message appearing twice in\n    some cases. [GH-145]\n  - Converted CLI to use Thor. As a tradeoff, there are some backwards\n    incompatibilities:\n      * `vagrant package` - The `--include` flag now separates filenames\n        by spaces, instead of by commas. e.g. `vagrant package --include x y z`\n      * `vagrant ssh` - If you specify a command to execute using the `--execute`\n        flag, you may now only specify one command (before you were able to\n        specify an arbitrary amount). e.g. `vagrant ssh -e \"echo hello\"`\n      * `vagrant ssh-config` has become `vagrant ssh_config` due to a limitation\n        in Thor.\n\n## 0.5.4 (September 7, 2010)\n\n  - Fix issue with the \"exec failed\" by running on Tiger as well.\n  - Give an error when downloading a box which already exists prior\n    to actually downloading the box.\n\n## 0.5.3 (August 23, 2010)\n\n  - Add erubis as a dependency since its rendering of `erb` is sane.\n  - Fixed poorly formatted Vagrantfile after `vagrant init`. [GH-142]\n  - Fixed NFS not working properly with multiple NFS folders.\n  - Fixed chef solo provision to work on Windows. It was expanding a linux\n    path which prepended a drive letter onto it.\n\n## 0.5.2 (August 3, 2010)\n\n  - `vagrant up` can be used as a way to resume the VM as well (same as\n    `vagrant resume`). [GH-134]\n  - Sudo uses \"-E\" flag to preserve environment for chef provisioners.\n    This fixes issues with CentOS. [GH-133]\n  - Added \"IdentitiesOnly yes\" to options when `vagrant ssh` is run to\n    avoid \"Too Many Authentication Failures\" error. [GH-131]\n  - Fix regression with `package` not working. [GH-132]\n  - Added ability to specify box url in `init`, which populates the\n    Vagrantfile with the proper `config.vm.box_url`.\n\n## 0.5.1 (July 31, 2010)\n\n  - Allow specifying cookbook paths which exist only on the VM in `config.chef.cookbooks_path`.\n    This is used for specifying cookbook paths when `config.chef.recipe_url` is used. [GH-130]\n    See updated chef solo documentation for more information on this.\n  - No longer show \"Disabling host only networks...\" if no host only networks\n    are destroyed. Quiets `destroy`, `halt`, etc output a bit.\n  - Updated getting started guide to be more up to date and generic. [GH-125]\n  - Fixed error with doing a `vagrant up` when no Vagrantfile existed. [GH-128]\n  - Fixed NFS erroring when NFS wasn't even enabled if `/etc/exports` doesn't\n    exist. [GH-126]\n  - Fixed `vagrant resume` to properly resume a suspended VM. [GH-122]\n  - Fixed `halt`, `destroy`, `reload` to where they failed if the VM was\n    in a saved state. [GH-123]\n  - Added `config.chef.recipe_url` which allows you to specify a URL to\n    a gzipped tar file for chef solo to download cookbooks. See the\n    [chef-solo docs](https://docs.chef.io/chef_solo.html) for more information.\n    [GH-121]\n  - Added `vagrant box repackage` which repackages boxes which have\n    been added. This is useful in case you want to redistribute a base\n    box you have but may have lost the actual \"box\" file. [GH-120]\n\n## Previous\n\nThe changelog began with version 0.5.1 so any changes prior to that\ncan be seen by checking the tagged releases and reading git commit\nmessages.\n"
  },
  {
    "path": "Gemfile",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nsource \"https://rubygems.org\"\n\ngemspec\n\nif File.exist?(File.expand_path(\"../../vagrant-spec\", __FILE__))\n  gem 'vagrant-spec', path: \"../vagrant-spec\"\nelse\n  gem 'vagrant-spec', git: \"https://github.com/hashicorp/vagrant-spec.git\", branch: :main\nend\n"
  },
  {
    "path": "LICENSE",
    "content": "License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.\n\"Business Source License\" is a trademark of MariaDB Corporation Ab.\n\nParameters\n\nLicensor:             International Business Machines Corporation (IBM).\nLicensed Work:        Vagrant 2.4.3 or later. The Licensed Work is (c) 2024\n                      IBM Corp.\nAdditional Use Grant: You may make production use of the Licensed Work, provided\n                      Your use does not include offering the Licensed Work to third\n                      parties on a hosted or embedded basis in order to compete with\n                      IBM Corp’s paid version(s) of the Licensed Work. For purposes\n                      of this license:\n\n                      A \"competitive offering\" is a Product that is offered to third\n                      parties on a paid basis, including through paid support\n                      arrangements, that significantly overlaps with the capabilities\n                      of IBM Corp's paid version(s) of the Licensed Work. If Your\n                      Product is not a competitive offering when You first make it\n                      generally available, it will not become a competitive offering\n                      later due to IBM Corp releasing a new version of the Licensed\n                      Work with additional capabilities. In addition, Products that\n                      are not provided on a paid basis are not competitive.\n\n                      \"Product\" means software that is offered to end users to manage\n                      in their own environments or offered as a service on a hosted\n                      basis.\n\n                      \"Embedded\" means including the source code or executable code\n                      from the Licensed Work in a competitive offering. \"Embedded\"\n                      also means packaging the competitive offering in such a way\n                      that the Licensed Work must be accessed or downloaded for the\n                      competitive offering to operate.\n\n                      Hosting or using the Licensed Work(s) for internal purposes\n                      within an organization is not considered a competitive\n                      offering. IBM Corp considers your organization to include all\n                      of your affiliates under common control.\n\n                      For binding interpretive guidance on using IBM Corp products\n                      under the Business Source License, please visit our FAQ.\n                      (https://www.hashicorp.com/license-faq)\nChange Date:          Four years from the date the Licensed Work is published.\nChange License:       MPL 2.0\n\nFor information about alternative licensing arrangements for the Licensed Work,\nplease contact licensing@hashicorp.com.\n\nNotice\n\nBusiness Source License 1.1\n\nTerms\n\nThe Licensor hereby grants you the right to copy, modify, create derivative\nworks, redistribute, and make non-production use of the Licensed Work. The\nLicensor may make an Additional Use Grant, above, permitting limited production use.\n\nEffective on the Change Date, or the fourth anniversary of the first publicly\navailable distribution of a specific version of the Licensed Work under this\nLicense, whichever comes first, the Licensor hereby grants you rights under\nthe terms of the Change License, and the rights granted in the paragraph\nabove terminate.\n\nIf your use of the Licensed Work does not comply with the requirements\ncurrently in effect as described in this License, you must purchase a\ncommercial license from the Licensor, its affiliated entities, or authorized\nresellers, or you must refrain from using the Licensed Work.\n\nAll copies of the original and modified Licensed Work, and derivative works\nof the Licensed Work, are subject to this License. This License applies\nseparately for each version of the Licensed Work and the Change Date may vary\nfor each version of the Licensed Work released by Licensor.\n\nYou must conspicuously display this License on each original or modified copy\nof the Licensed Work. If you receive the Licensed Work in original or\nmodified form from a third party, the terms and conditions set forth in this\nLicense apply to your use of that work.\n\nAny use of the Licensed Work in violation of this License will automatically\nterminate your rights under this License for the current and all other\nversions of the Licensed Work.\n\nThis License does not grant you any right in any trademark or logo of\nLicensor or its affiliates (provided that you may use a trademark or logo of\nLicensor as expressly required by this License).\n\nTO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON\nAN \"AS IS\" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,\nEXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND\nTITLE.\n"
  },
  {
    "path": "README.md",
    "content": "# Vagrant\n\n- Website: [https://www.vagrantup.com/](https://www.vagrantup.com/)\n- Source: [https://github.com/hashicorp/vagrant](https://github.com/hashicorp/vagrant)\n- HashiCorp Discuss: [https://discuss.hashicorp.com/c/vagrant/24](https://discuss.hashicorp.com/c/vagrant/24)\n\nVagrant is a tool for building and distributing development environments.\n\nDevelopment environments managed by Vagrant can run on local virtualized\nplatforms such as VirtualBox or VMware, in the cloud via AWS or OpenStack,\nor in containers such as with Docker or raw LXC.\n\nVagrant provides the framework and configuration format to create and\nmanage complete portable development environments. These development\nenvironments can live on your computer or in the cloud, and are portable\nbetween Windows, Mac OS X, and Linux.\n\n## Quick Start\n\nPackage dependencies: Vagrant requires `bsdtar` and `curl` to be available on\nyour system PATH to run successfully.\n\nFor the quick-start, we'll bring up a development machine on\n[VirtualBox](https://www.virtualbox.org/) because it is free and works\non all major platforms. Vagrant can, however, work with almost any\nsystem such as [OpenStack](https://www.openstack.org/), [VMware](https://www.vmware.com/), [Docker](https://docs.docker.com/), etc.\n\nFirst, make sure your development machine has\n[VirtualBox](https://www.virtualbox.org/)\ninstalled. After this,\n[download and install the appropriate Vagrant package for your OS](https://www.vagrantup.com/downloads.html).\n\nTo build your first virtual environment:\n\n    vagrant init hashicorp/bionic64\n    vagrant up\n\nNote: The above `vagrant up` command will also trigger Vagrant to download the\n`bionic64` box via the specified URL. Vagrant only does this if it detects that\nthe box doesn't already exist on your system.\n\n## Getting Started Guide\n\nTo learn how to build a fully functional development environment, follow the\n[getting started guide](https://www.vagrantup.com/docs/getting-started).\n\n## Installing from Source\n\nIf you want the bleeding edge version of Vagrant, we try to keep main pretty stable\nand you're welcome to give it a shot. Please review the installation page [here](https://www.vagrantup.com/docs/installation/source).\n\n## Contributing to Vagrant\n\nPlease take time to read the [HashiCorp Community Guidelines](https://www.hashicorp.com/community-guidelines) and the [Vagrant Contributing Guide](https://github.com/hashicorp/vagrant/blob/main/.github/CONTRIBUTING.md).\n\nThen you're good to go!\n"
  },
  {
    "path": "RELEASE.md",
    "content": "# Releasing Vagrant\n\nThis documents how to release Vagrant. Various steps in this document will\nrequire privileged access to private systems, so this document is only\ntargeted at Vagrant core members who have the ability to cut a release.\n\n1. Go to the [release initiator workflow](https://github.com/hashicorp/vagrant/actions/workflows/initiate-release.yml)\n\n1. Trigger a new run with the version to be released (it should not include a `v` prefix, for example: `1.0.0`)\n\n1. After release is complete, update [Checkpoint](https://checkpoint.hashicorp.com/control)\n"
  },
  {
    "path": "Rakefile",
    "content": "require 'rubygems'\nrequire 'bundler/setup'\nrequire \"rake/extensiontask\"\n\n# Immediately sync all stdout so that tools like buildbot can\n# immediately load in the output.\n$stdout.sync = true\n$stderr.sync = true\n\nRake::ExtensionTask.new \"vagrant/vagrant_ssl\" do |ext|\nend\n\n# Load all the rake tasks from the \"tasks\" folder. This folder\n# allows us to nicely separate rake tasks into individual files\n# based on their role, which makes development and debugging easier\n# than one monolithic file.\ntask_dir = File.expand_path(\"../tasks\", __FILE__)\nDir[\"#{task_dir}/**/*.rake\"].each do |task_file|\n  load task_file\nend\n\ntask default: \"test:unit\"\n"
  },
  {
    "path": "Vagrantfile",
    "content": "# This Vagrantfile can be used to develop Vagrant. Note that VirtualBox\n# doesn't run in VirtualBox so you can't actually _run_ Vagrant within\n# the VM created by this Vagrantfile, but you can use it to develop the\n# Ruby, run unit tests, etc.\n\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"hashicorp/bionic64\"\n  config.vm.hostname = \"vagrant\"\n  config.ssh.shell = \"bash -c 'BASH_ENV=/etc/profile exec bash'\"\n\n  [\"vmware_desktop\", \"virtualbox\", \"hyperv\"].each do |provider|\n    config.vm.provider provider do |v, override|\n      v.memory = \"2048\"\n    end\n  end\n\n  # We split apart `install_rvm` from `setup_tests` because rvm says to\n  # logout and log back in just after installing RVM.\n  # https://github.com/rvm/ubuntu_rvm#3-reboot\n  config.vm.provision \"shell\", path: \"scripts/install_rvm\"\n\n  config.vm.provision \"shell\", path: \"scripts/setup_tests\"\n\n  config.push.define \"www\", strategy: \"local-exec\" do |push|\n    push.script = \"scripts/website_push_www.sh\"\n  end\n\n  config.push.define \"docs\", strategy: \"local-exec\" do |push|\n    push.script = \"scripts/website_push_docs.sh\"\n  end\nend\n"
  },
  {
    "path": "bin/vagrant",
    "content": "#!/usr/bin/env ruby\n\n# Trap interrupts to quit cleanly. This will be overridden at some point\n# by Vagrant. This is made to catch any interrupts while Vagrant is\n# initializing which have historically resulted in stack traces.\nSignal.trap(\"INT\") { abort }\n\n# Disable exception reporting by default if available\nif Thread.respond_to?(:report_on_exception=)\n  Thread.report_on_exception = false\nend\n\n# Split arguments by \"--\" if it's there, we'll recombine them later\nargv = ARGV.dup\nargv_extra = []\n\n# These will be the options that are passed to initialize the Vagrant\n# environment.\nopts = {}\n\nif idx = argv.index(\"--\")\n  argv_extra = argv.slice(idx+1, argv.length-2)\n  argv = argv.slice(0, idx)\nend\n\nrequire_relative \"../lib/vagrant/version\"\n# Fast path the version of Vagrant\nif argv.include?(\"-v\") || argv.include?(\"--version\")\n  puts \"Vagrant #{Vagrant::VERSION}\"\n  exit 0\nend\n\n# Disable plugin loading for commands where plugins are not required. This will\n# also disable loading of the Vagrantfile if it available as the environment\n# is not required for these commands\nargv.each_index do |i|\n  arg = argv[i]\n\n  if !arg.start_with?(\"-\")\n    if arg == \"box\" && argv[i+1] == \"list\"\n      opts[:vagrantfile_name] = \"\"\n      ENV['VAGRANT_NO_PLUGINS'] = \"1\"\n    end\n\n    # Do not load plugins when performing plugin operations\n    if arg == \"plugin\"\n      if argv.none?{|a| a == \"--local\" } && !ENV[\"VAGRANT_LOCAL_PLUGINS_LOAD\"]\n        opts[:vagrantfile_name] = \"\"\n      end\n      ENV['VAGRANT_NO_PLUGINS'] = \"1\"\n      # Only initialize plugins when listing installed plugins\n      if argv[i+1] != \"list\"\n        ENV['VAGRANT_DISABLE_PLUGIN_INIT'] = \"1\"\n      end\n    end\n\n    break\n  end\nend\n\n# Set logging level to `debug`. This is done before loading 'vagrant', as it\n# sets up the logging system.\nif argv.include?(\"--debug\")\n  argv.delete(\"--debug\")\n  ENV[\"VAGRANT_LOG\"] = \"debug\"\nend\n\n# Enable log timestamps if requested\nif argv.include?(\"--timestamp\")\n  argv.delete(\"--timestamp\")\n  ENV[\"VAGRANT_LOG_TIMESTAMP\"] = \"1\"\nend\n\n# Convenience flag to enable debug with timestamps\nif argv.include?(\"--debug-timestamp\")\n  argv.delete(\"--debug-timestamp\")\n  ENV[\"VAGRANT_LOG\"] = \"debug\"\n  ENV[\"VAGRANT_LOG_TIMESTAMP\"] = \"1\"\nend\n\n# Stdout/stderr should not buffer output\n$stdout.sync = true\n$stderr.sync = true\n\n# Before we start activate all our dependencies\n# so we can provide correct resolutions later\nbuiltin_specs = []\n\nvagrant_spec = Gem::Specification.find_all_by_name(\"vagrant\").detect do |spec|\n  spec.version == Gem::Version.new(Vagrant::VERSION)\nend\n\ndep_activator = proc do |spec|\n  spec.runtime_dependencies.each do |dep|\n    gem(dep.name, *dep.requirement.as_list)\n    dep_spec = Gem::Specification.find_all_by_name(dep.name).detect(&:activated?)\n    if dep_spec\n      builtin_specs << dep_spec\n      dep_activator.call(dep_spec)\n    end\n  end\nend\n\nif vagrant_spec\n  dep_activator.call(vagrant_spec)\nend\n\nenv = nil\nbegin\n  require 'vagrant'\n  require 'vagrant/bundler'\n  require 'vagrant/cli'\n  require 'vagrant/util/platform'\n  require 'vagrant/util/experimental'\n\n  # Set our list of builtin specs\n  Vagrant::Bundler.instance.builtin_specs = builtin_specs\n\n  # Schedule the cleanup of things\n  at_exit(&Vagrant::Bundler.instance.method(:deinit))\n\n  # If this is not a pre-release disable verbose output\n  if !Vagrant.prerelease?\n    $VERBOSE = nil\n  end\n\n  # Add any option flags defined within this file here\n  # so they are automatically propagated to all commands\n  Vagrant.add_default_cli_options(proc { |o|\n    o.on(\"--[no-]color\", \"Enable or disable color output\")\n    o.on(\"--machine-readable\", \"Enable machine readable output\")\n    o.on(\"-v\", \"--version\", \"Display Vagrant version\")\n    o.on(\"--debug\", \"Enable debug output\")\n    o.on(\"--timestamp\", \"Enable timestamps on log output\")\n    o.on(\"--debug-timestamp\", \"Enable debug output with timestamps\")\n    o.on(\"--no-tty\", \"Enable non-interactive output\")\n  })\n\n  # Create a logger right away\n  logger = Log4r::Logger.new(\"vagrant::bin::vagrant\")\n  logger.info(\"`vagrant` invoked: #{ARGV.inspect}\")\n\n  # Disable color in a few cases:\n  #\n  #   * --no-color is anywhere in our arguments\n  #   * STDOUT is not a TTY\n  #   * The terminal doesn't support colors (Windows)\n  #\n  if argv.include?(\"--no-color\") || ENV[\"VAGRANT_NO_COLOR\"]\n    # Delete the argument from the list so that it doesn't\n    # cause any invalid arguments down the road.\n    argv.delete(\"--no-color\")\n\n    opts[:ui_class] = Vagrant::UI::Basic\n  elsif !Vagrant::Util::Platform.terminal_supports_colors?\n    opts[:ui_class] = Vagrant::UI::Basic\n  elsif !$stdout.tty? && !Vagrant::Util::Platform.cygwin?\n    # Cygwin always reports STDOUT is not a TTY, so we only disable\n    # colors if its not a TTY AND its not Cygwin.\n    opts[:ui_class] = Vagrant::UI::Basic\n  end\n\n  # Also allow users to force colors.\n  if argv.include?(\"--color\") || ENV[\"VAGRANT_FORCE_COLOR\"]\n    argv.delete(\"--color\")\n    opts[:ui_class] = Vagrant::UI::Colored\n  end\n\n  # Highest precedence is if we have enabled machine-readable output\n  if argv.include?(\"--machine-readable\")\n    argv.delete(\"--machine-readable\")\n    opts[:ui_class] = Vagrant::UI::MachineReadable\n  end\n\n  # Setting to enable/disable showing progress bars\n  if argv.include?(\"--no-tty\")\n    argv.delete(\"--no-tty\")\n    opts[:ui_class] = Vagrant::UI::NonInteractive\n  end\n\n  # Default to colored output\n  opts[:ui_class] ||= Vagrant::UI::Colored\n\n  # Recombine the arguments\n  if !argv_extra.empty?\n    argv << \"--\"\n    argv += argv_extra\n  end\n\n  # Create the environment, which is the cwd of wherever the\n  # `vagrant` command was invoked from\n  logger.debug(\"Creating Vagrant environment\")\n  env = Vagrant::Environment.new(opts)\n\n  # If we are running with the Windows Subsystem for Linux do\n  # some extra setup to allow access to Vagrant managed machines\n  # outside the subsystem\n  if Vagrant::Util::Platform.wsl?\n    recreate_env = Vagrant::Util::Platform.wsl_init(env, logger)\n    if recreate_env\n      logger.info(\"Re-creating Vagrant environment due to WSL modifications.\")\n      env = Vagrant::Environment.new(opts)\n    end\n  end\n\n  # If not being run from the installer, check if expected tools\n  # are available.\n  if !Vagrant.in_installer?\n    missing_tools = Vagrant.detect_missing_tools\n\n    if !missing_tools.empty?\n      env.ui.warn(\n        I18n.t(\"vagrant.general.not_in_installer\", tools: missing_tools.sort.join(\", \")) + \"\\n\",\n        prefix: false\n      )\n    end\n  end\n\n  # Acceptable experimental flag values include:\n  #\n  # Unset  - Disables experimental features\n  # 0      - Disables experimental features\n  # 1      - Enables all features\n  # String - Enables one or more features, separated by commas\n  if Vagrant::Util::Experimental.enabled?\n    experimental = Vagrant::Util::Experimental.features_requested\n    ui = Vagrant::UI::Prefixed.new(env.ui, \"vagrant\")\n    logger.debug(\"Experimental flag is enabled\")\n    if Vagrant::Util::Experimental.global_enabled?\n      ui.warn(I18n.t(\"vagrant.general.experimental.all\"), bold: true, prefix: true, channel: :error)\n    else\n      ui.warn(I18n.t(\"vagrant.general.experimental.features\", features: experimental.join(\", \")), bold: true, prefix: true, channel: :error)\n    end\n  end\n\n  begin\n    # Execute the CLI interface, and exit with the proper error code\n    exit_status = env.cli(argv)\n  ensure\n    # Unload the environment so cleanup can be done\n    env.unload\n  end\n\n  # Exit with the exit status from our CLI command\n  exit(exit_status)\nrescue Exception => e\n  # It is possible for errors to happen in Vagrant's initialization. In\n  # this case, we don't have access to this class yet, so we check for it.\n  raise if !defined?(Vagrant) || !defined?(Vagrant::Errors)\n  raise if !e.is_a?(Vagrant::Errors::VagrantError)\n\n  require 'log4r'\n  logger = Log4r::Logger.new(\"vagrant::bin::vagrant\")\n  logger.error(\"Vagrant experienced an error! Details:\")\n  logger.error(e.inspect)\n  logger.error(e.message)\n  logger.error(e.backtrace.join(\"\\n\"))\n\n  if env\n    opts = { prefix: false }\n    env.ui.error(e.message, **opts) if e.message\n    env.ui.machine(\"error-exit\", e.class.to_s, e.message.to_s)\n  else\n    $stderr.puts \"Vagrant failed to initialize at a very early stage:\\n\\n\"\n    $stderr.puts e.message\n  end\n\n  exit e.status_code if e.respond_to?(:status_code)\n  exit 255 # An error occurred with no status code defined\nend\n"
  },
  {
    "path": "builtin/README.md",
    "content": "# Built-in Plugins\n\nThis directory contains all the \"built-in\" plugins. These are real plugins,\nthey dogfood the full plugin SDK, do not depend on any internal packages,\nand they are executed via subprocess just like a real plugin would be.\n\nThe difference is that these plugins are linked directly into the single\ncommand binary. We do this currently for ease of development of the project.\nIn the future we will split these out into standalone repositories and\nbinaries.\n"
  },
  {
    "path": "builtin/configvagrant/main.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage configvagrant\n\nimport (\n\t\"github.com/hashicorp/go-argmapper\"\n\t\"github.com/hashicorp/go-hclog\"\n\tsdk \"github.com/hashicorp/vagrant-plugin-sdk\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/core\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/terminal\"\n)\n\nvar CommandOptions = []sdk.Option{\n\tsdk.WithComponents(\n\t\t&Config{},\n\t),\n\tsdk.WithComponent(&Command{}, &component.CommandOptions{Primary: false}),\n\tsdk.WithName(\"configvagrant\"),\n}\n\ntype Vagrant struct {\n\tSensitive     []string `hcl:\"sensitive,optional\" json:\",omitempty\"`\n\tHost          *string  `hcl:\"host,optional\" json:\"host,omitempty\"`\n\tFinalizedInfo *string  `hcl:\"finalized_info,optional\" json:\"finalized_info,omitempty\"`\n\tPlugins       []Plugin `hcl:\"plugins,block\" json:\"plugins,omitempty\"`\n}\n\ntype Plugin struct {\n\tName string `hcl:\"name,label\"`\n\n\tEntryPoint *string  `hcl:\"entry_point,optional\" json:\"entry_point,omitempty\"`\n\tSources    []string `hcl:\"sources,optional\" json:\"source,omitempty\"`\n\tVersion    *string  `hcl:\"version,optional\" json:\"version,omitempty\"`\n}\n\ntype Config struct{}\n\nfunc (c *Config) Register() (*component.ConfigRegistration, error) {\n\treturn &component.ConfigRegistration{\n\t\tIdentifier: \"vagrants\",\n\t}, nil\n}\n\nfunc (c *Config) InitFunc() any {\n\treturn c.Init\n}\n\nfunc (c *Config) Init(in *component.ConfigData) (*component.ConfigData, error) {\n\treturn in, nil\n}\n\nfunc (c *Config) StructFunc() interface{} {\n\treturn c.Struct\n}\n\nfunc (c *Config) Struct() *Vagrant {\n\treturn &Vagrant{}\n}\n\nfunc (c *Config) MergeFunc() interface{} {\n\treturn c.Merge\n}\n\nfunc (c *Config) Merge(\n\tinput struct {\n\t\targmapper.Struct\n\t\tBase    *Vagrant\n\t\tOverlay *Vagrant\n\t\tLog     hclog.Logger\n\t},\n) (*Vagrant, error) {\n\tlog := input.Log\n\tlog.Info(\"merging config values in vagrants namespace\",\n\t\t\"base\", input.Base, \"overlay\", input.Overlay)\n\n\tresult := input.Base\n\tif input.Overlay.Host != nil {\n\t\tresult.Host = input.Overlay.Host\n\t}\n\n\tfor _, s := range input.Overlay.Sensitive {\n\t\tresult.Sensitive = append(result.Sensitive, s)\n\t}\n\n\tlog.Info(\"merged config value for vagrants namespace\", \"config\", result)\n\n\treturn result, nil\n}\n\nfunc (c *Config) FinalizeFunc() interface{} {\n\treturn c.Finalize\n}\n\nfunc (c *Config) Finalize(l hclog.Logger, conf *Vagrant) (*Vagrant, error) {\n\tl.Warn(\"checking current content\", \"host\", conf.Host)\n\tif conf.Host != nil {\n\t\tl.Warn(\"checking current value\", \"host\", *conf.Host)\n\t}\n\tinfo := \"go plugin finalization test content\"\n\tconf.FinalizedInfo = &info\n\treturn conf, nil\n}\n\ntype Command struct{}\n\nfunc (c *Command) ExecuteFunc(_ []string) interface{} {\n\treturn c.Execute\n}\n\nfunc (c *Command) Execute(ui terminal.UI, p core.Project) int32 {\n\tui.Output(\"Checking for our defined config...\")\n\tv, err := p.Vagrantfile()\n\tif err != nil {\n\t\tui.Output(\"Failed to get Vagrantfile instance: %s\", err)\n\t\treturn 1\n\t}\n\tui.Output(\"Our vagrantfile value is: %#v\", v)\n\tconf, err := v.GetConfig(\"vagrants\")\n\tif err != nil {\n\t\tui.Output(\"failed to get configuration for 'vagrants' namespace: %q\", err)\n\t\treturn 1\n\t}\n\n\tui.Output(\"We got something here!\")\n\tui.Output(\"Config defined host: %s\", conf.Data[\"host\"])\n\n\tif _, ok := conf.Data[\"finalized\"]; !ok {\n\t\tui.Output(\"ERROR: finalized data expected and not found in config!\")\n\t}\n\n\tif _, ok := conf.Data[\"merged\"]; !ok {\n\t\tui.Output(\"ERROR: merged data expected and not found in config!\")\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (c *Command) CommandInfoFunc() interface{} {\n\treturn c.CommandInfo\n}\n\nfunc (c *Command) CommandInfo() *component.CommandInfo {\n\treturn &component.CommandInfo{\n\t\tName: \"configvagrant\",\n\t\tHelp: \"I display config\",\n\t}\n}\n"
  },
  {
    "path": "builtin/httpdownloader/downloader/downloader.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage downloader\n\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/hashicorp/go-retryablehttp\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n)\n\n// Type is an enum of all the available http methods\ntype HTTPMethod int64\n\nconst (\n\tGET HTTPMethod = iota\n\tDELETE\n\tHEAD\n\tPOST\n\tPUT\n)\n\ntype Downloader struct {\n\tconfig DownloaderConfig\n}\n\ntype DownloaderConfig struct {\n\tDest           string\n\tHeaders        http.Header\n\tMethod         HTTPMethod\n\tRetryCount     int\n\tRequestBody    []byte\n\tSrc            string\n\tUrlQueryParams map[string]string\n}\n\nfunc (d *Downloader) InitFunc() any {\n\treturn d.Init\n}\n\nfunc (d *Downloader) Init(in *component.ConfigData) (*component.ConfigData, error) {\n\treturn in, nil\n}\n\nfunc (d *Downloader) StructFunc() any {\n\treturn d.Struct\n}\n\nfunc (d *Downloader) MergeFunc() any {\n\treturn d.Merge\n}\n\nfunc (d *Downloader) FinalizeFunc() any {\n\treturn d.Finalize\n}\n\nfunc (d *Downloader) Struct() *DownloaderConfig {\n\treturn &DownloaderConfig{}\n}\n\nfunc (d *Downloader) Merge(val *component.ConfigMerge) *component.ConfigData {\n\treturn val.Base\n}\n\nfunc (d *Downloader) Finalize(val *component.ConfigData) *component.ConfigData {\n\treturn val\n}\n\nfunc (d *Downloader) Register() (*component.ConfigRegistration, error) {\n\treturn &component.ConfigRegistration{\n\t\tIdentifier: \"downloader\",\n\t}, nil\n}\n\nfunc (d *Downloader) DownloadFunc() interface{} {\n\treturn d.Download\n}\n\nfunc (d *Downloader) Download() (err error) {\n\tclient := retryablehttp.NewClient()\n\tclient.RetryMax = d.config.RetryCount\n\tvar req *retryablehttp.Request\n\n\t// Create request with request body if one is provided\n\tif d.config.RequestBody != nil {\n\t\treq, err = retryablehttp.NewRequest(\n\t\t\td.config.Method.String(), d.config.Src, bytes.NewBuffer(d.config.RequestBody),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\t// If no request body is provided then create an empty request\n\t\treq, err = retryablehttp.NewRequest(\n\t\t\td.config.Method.String(), d.config.Src, nil,\n\t\t)\n\t}\n\n\t// Add query params if provided\n\tif d.config.UrlQueryParams != nil {\n\t\tq := req.URL.Query()\n\t\tfor k, v := range d.config.UrlQueryParams {\n\t\t\tq.Add(k, v)\n\t\t}\n\t\treq.URL.RawQuery = q.Encode()\n\t}\n\n\t// Set headers\n\treq.Header = d.config.Headers\n\t// Add headers to redirects\n\tclient.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {\n\t\tfor key, val := range via[0].Header {\n\t\t\treq.Header[key] = val\n\t\t}\n\t\treturn err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\tdata, err := ioutil.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = os.WriteFile(d.config.Dest, data, 0644)\n\treturn\n}\n\nvar (\n\t_ component.Downloader = (*Downloader)(nil)\n\t_ component.Config     = (*Downloader)(nil)\n)\n"
  },
  {
    "path": "builtin/httpdownloader/downloader/httpmethod_string.go",
    "content": "// Code generated by \"stringer -type=HTTPMethod -linecomment ./downloader\"; DO NOT EDIT.\n\npackage downloader\n\nimport \"strconv\"\n\nfunc _() {\n\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n\t// Re-run the stringer command to generate them again.\n\tvar x [1]struct{}\n\t_ = x[GET-0]\n\t_ = x[DELETE-1]\n\t_ = x[HEAD-2]\n\t_ = x[POST-3]\n\t_ = x[PUT-4]\n}\n\nconst _HTTPMethod_name = \"GETDELETEHEADPOSTPUT\"\n\nvar _HTTPMethod_index = [...]uint8{0, 3, 9, 13, 17, 20}\n\nfunc (i HTTPMethod) String() string {\n\tif i < 0 || i >= HTTPMethod(len(_HTTPMethod_index)-1) {\n\t\treturn \"HTTPMethod(\" + strconv.FormatInt(int64(i), 10) + \")\"\n\t}\n\treturn _HTTPMethod_name[_HTTPMethod_index[i]:_HTTPMethod_index[i+1]]\n}\n"
  },
  {
    "path": "builtin/httpdownloader/main.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage httpdownloader\n\nimport (\n\tsdk \"github.com/hashicorp/vagrant-plugin-sdk\"\n\t\"github.com/hashicorp/vagrant/builtin/httpdownloader/downloader\"\n)\n\n//go:generate stringer -type=HTTPMethod -linecomment ./downloader\n\nvar PluginOptions = []sdk.Option{\n\tsdk.WithComponents(\n\t\t&downloader.Downloader{},\n\t),\n\tsdk.WithName(\"httpdownloader\"),\n}\n"
  },
  {
    "path": "builtin/myplugin/command/command.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage command\n\nimport (\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\tplugincore \"github.com/hashicorp/vagrant-plugin-sdk/core\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/docs\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/terminal\"\n)\n\ntype Subcommand interface {\n\tCommandInfo() (*component.CommandInfo, error)\n}\n\ntype CommandConfig struct {\n}\n\n// Command is the Command implementation for myplugin.\ntype Command struct {\n\tconfig CommandConfig\n}\n\nfunc (c *Command) ConfigSet(v interface{}) error {\n\treturn nil\n}\n\nfunc (c *Command) CommandFunc() interface{} {\n\treturn nil\n}\n\nfunc (c *Command) Config() (interface{}, error) {\n\treturn &c.config, nil\n}\n\nfunc (c *Command) Documentation() (*docs.Documentation, error) {\n\tdoc, err := docs.New(docs.FromConfig(&CommandConfig{}))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn doc, nil\n}\n\n// ExecuteFunc implements component.Command\nfunc (c *Command) ExecuteFunc(cliArgs []string) interface{} {\n\tif len(cliArgs) < 2 {\n\t\treturn c.Execute\n\t}\n\tn := cliArgs[1]\n\tswitch n {\n\tcase \"info\":\n\t\treturn c.ExecuteInfo\n\tcase \"dothing\":\n\t\treturn c.ExecuteDoThing\n\tcase \"interactive\":\n\t\treturn c.ExecuteInteractive\n\tcase \"host\":\n\t\treturn c.ExecuteHost\n\t}\n\n\treturn c.Execute\n}\n\nfunc (c *Command) ExecuteInfo(trm terminal.UI, env plugincore.Project) int32 {\n\treturn (&Info{Command: c}).Execute(trm, env)\n}\n\nfunc (c *Command) ExecuteDoThing(trm terminal.UI, params *component.CommandParams) int32 {\n\treturn (&DoThing{Command: c}).Execute(trm, params)\n}\n\nfunc (c *Command) ExecuteInteractive(trm terminal.UI, params *component.CommandParams) int32 {\n\treturn (&Interactive{Command: c}).Execute(trm)\n}\n\nfunc (c *Command) ExecuteHost(trm terminal.UI, env plugincore.Project) int32 {\n\treturn (&Host{Command: c}).Execute(trm, env)\n}\n\n// CommandInfoFunc implements component.Command\nfunc (c *Command) CommandInfoFunc() interface{} {\n\treturn c.CommandInfo\n}\n\nfunc (c *Command) CommandInfo() *component.CommandInfo {\n\treturn &component.CommandInfo{\n\t\tName:        \"myplugin\",\n\t\tHelp:        c.Help(),\n\t\tSynopsis:    c.Synopsis(),\n\t\tFlags:       c.Flags(),\n\t\tSubcommands: c.subcommandsInfo(),\n\t}\n}\n\nfunc (c *Command) Synopsis() string {\n\treturn \"I don't do much, just hanging around\"\n}\n\nfunc (c *Command) Help() string {\n\treturn \"I'm here for testing, try running some subcommands\"\n}\n\nfunc (c *Command) Flags() component.CommandFlags {\n\treturn []*component.CommandFlag{\n\t\t{\n\t\t\tLongName:     \"hehe\",\n\t\t\tShortName:    \"\",\n\t\t\tDescription:  \"a test flag for strings\",\n\t\t\tDefaultValue: \"a default message\",\n\t\t\tType:         component.FlagString,\n\t\t},\n\t}\n}\n\nfunc (c *Command) Execute(trm terminal.UI, params *component.CommandParams) int32 {\n\ttrm.Output(\"You gave me the flag: \" + params.Flags[\"hehe\"].(string))\n\n\ttrm.Output(c.Help())\n\ttrm.Output(\"My subcommands are: \")\n\tfor _, cmd := range c.subcommandsInfo() {\n\t\ttrm.Output(\"    \" + cmd.Name)\n\t}\n\treturn 0\n}\n\nfunc (c *Command) subcommandsInfo() (r []*component.CommandInfo) {\n\tfor _, cmd := range c.subcommands() {\n\t\tv, _ := cmd.CommandInfo()\n\t\tr = append(r, v)\n\t}\n\treturn\n}\n\nfunc (c *Command) subcommands() map[string]Subcommand {\n\treturn map[string]Subcommand{\n\t\t\"info\":        &Info{Command: c},\n\t\t\"dothing\":     &DoThing{Command: c},\n\t\t\"interactive\": &Interactive{Command: c},\n\t\t\"host\":        &Host{Command: c},\n\t}\n}\n\nvar (\n\t_ component.Command = (*Command)(nil)\n)\n"
  },
  {
    "path": "builtin/myplugin/command/dothing.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage command\n\nimport (\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/docs\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/localizer\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/terminal\"\n\t\"github.com/hashicorp/vagrant/builtin/myplugin/locales\"\n\t\"golang.org/x/text/language\"\n)\n\n// DoThing is a Command implementation for myplugin\n// It is a subcommand of myplugin\ntype DoThing struct {\n\t*Command\n}\n\nfunc (c *DoThing) ConfigSet(v interface{}) error {\n\treturn nil\n}\n\nfunc (c *DoThing) CommandFunc() interface{} {\n\treturn nil\n}\n\nfunc (c *DoThing) Config() (interface{}, error) {\n\treturn &c.config, nil\n}\n\nfunc (c *DoThing) Documentation() (*docs.Documentation, error) {\n\tdoc, err := docs.New(docs.FromConfig(&CommandConfig{}))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn doc, nil\n}\n\n// ExecuteFunc implements component.Command\nfunc (c *DoThing) ExecuteFunc([]string) interface{} {\n\treturn c.Execute\n}\n\n// CommandInfoFunc implements component.Command\nfunc (c *DoThing) CommandInfoFunc() interface{} {\n\treturn c.CommandInfo\n}\n\nfunc (c *DoThing) CommandInfo() (*component.CommandInfo, error) {\n\treturn &component.CommandInfo{\n\t\tName:     \"dothing\",\n\t\tHelp:     c.Help(),\n\t\tSynopsis: c.Synopsis(),\n\t\tFlags:    c.Flags(),\n\t}, nil\n}\n\nfunc (c *DoThing) Synopsis() string {\n\treturn \"Really important *stuff*\"\n}\n\nfunc (c *DoThing) Help() string {\n\treturn \"Usage: vagrant myplugin dothing\"\n}\n\nfunc (c *DoThing) Flags() component.CommandFlags {\n\treturn []*component.CommandFlag{\n\t\t{\n\t\t\tLongName:     \"booltest\",\n\t\t\tShortName:    \"b\",\n\t\t\tDescription:  \"test flag for bools\",\n\t\t\tDefaultValue: \"true\",\n\t\t\tType:         component.FlagBool,\n\t\t},\n\t\t{\n\t\t\tLongName:     \"stringflag\",\n\t\t\tShortName:    \"s\",\n\t\t\tDescription:  \"test flag for strings\",\n\t\t\tDefaultValue: \"a default message value\",\n\t\t\tType:         component.FlagString,\n\t\t},\n\t}\n}\n\nfunc (c *DoThing) Execute(trm terminal.UI, params *component.CommandParams) int32 {\n\tlocaleDataEN, err := locales.Asset(\"locales/assets/en.json\")\n\tif err != nil {\n\t\treturn 1\n\t}\n\tlocaleDataES, err := locales.Asset(\"locales/assets/es.json\")\n\tif err != nil {\n\t\treturn 1\n\t}\n\td := []localizer.LocaleData{\n\t\t{\n\t\t\tLocaleData: localeDataEN,\n\t\t\tLocalePath: \"locales/assets/en.json\",\n\t\t\tLanguages:  []language.Tag{language.English, language.AmericanEnglish, language.BritishEnglish},\n\t\t},\n\t\t{\n\t\t\tLocaleData: localeDataES,\n\t\t\tLocalePath: \"locales/assets/es.json\",\n\t\t\tLanguages:  []language.Tag{language.Spanish},\n\t\t},\n\t}\n\tl, err := localizer.NewPluginLocalizer(d...)\n\tif err != nil {\n\t\treturn 1\n\t}\n\tmsg, err := l.LocalizeMsg(\"dothing\", nil)\n\tif err != nil {\n\t\ttrm.Output(err.Error())\n\t\treturn 1\n\t}\n\ttrm.Output(msg, terminal.WithColor(\"magenta\"))\n\treturn 0\n}\n\nvar (\n\t_ component.Command = (*DoThing)(nil)\n)\n"
  },
  {
    "path": "builtin/myplugin/command/host.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage command\n\nimport (\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/core\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/terminal\"\n)\n\n// Info is a Command implementation for myplugin.\n// It is a subcommand of myplugin\ntype Host struct {\n\t*Command\n}\n\n// ExecuteFunc implements component.Command\nfunc (c *Host) ExecuteFunc(cliArgs []string) interface{} {\n\treturn c.Execute\n}\n\n// CommandInfoFunc implements component.Command\nfunc (c *Host) CommandInfoFunc() interface{} {\n\treturn c.CommandInfo\n}\n\nfunc (c *Host) CommandInfo() (*component.CommandInfo, error) {\n\treturn &component.CommandInfo{\n\t\tName:     \"host\",\n\t\tHelp:     c.Help(),\n\t\tSynopsis: c.Synopsis(),\n\t\tFlags:    c.Flags(),\n\t}, nil\n}\n\nfunc (c *Host) Synopsis() string {\n\treturn \"runs host capability\"\n}\n\nfunc (c *Host) Help() string {\n\treturn c.Synopsis()\n}\n\nfunc (c *Host) Flags() component.CommandFlags {\n\treturn []*component.CommandFlag{}\n}\n\nfunc (c *Host) Execute(trm terminal.UI, project core.Project) int32 {\n\ttrm.Output(\"Attempting to run capability on host plugin\")\n\n\th, err := project.Host()\n\tif err != nil {\n\t\ttrm.Output(\"ERROR: %s\", err)\n\t\treturn 1\n\t}\n\n\ttrm.Output(\"have host plugin to run against\")\n\n\tif r, err := h.HasCapability(\"write_hello\"); !r {\n\t\ttrm.Output(\"No write_hello capability found (%s)\", err)\n\t\treturn 1\n\t}\n\n\ttrm.Output(\"host plugin has write_hello capability to run\")\n\n\tresult, err := h.Capability(\"write_hello\", trm)\n\tif err != nil {\n\t\ttrm.Output(\"Error running capability: %s\", err)\n\t\treturn 1\n\t}\n\n\ttrm.Output(\"Result: %#v\", result)\n\n\treturn 0\n}\n\nvar (\n\t_ component.Command = (*Host)(nil)\n)\n"
  },
  {
    "path": "builtin/myplugin/command/info.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage command\n\nimport (\n\t\"strings\"\n\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\tplugincore \"github.com/hashicorp/vagrant-plugin-sdk/core\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/docs\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/terminal\"\n)\n\n// Info is a Command implementation for myplugin.\n// It is a subcommand of myplugin\ntype Info struct {\n\t*Command\n}\n\nfunc (c *Info) ConfigSet(v interface{}) error {\n\treturn nil\n}\n\nfunc (c *Info) CommandFunc() interface{} {\n\treturn nil\n}\n\nfunc (c *Info) Config() (interface{}, error) {\n\treturn &c.config, nil\n}\n\nfunc (c *Info) Documentation() (*docs.Documentation, error) {\n\tdoc, err := docs.New(docs.FromConfig(&CommandConfig{}))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn doc, nil\n}\n\n// ExecuteFunc implements component.Command\nfunc (c *Info) ExecuteFunc(cliArgs []string) interface{} {\n\treturn c.Execute\n}\n\n// CommandInfoFunc implements component.Command\nfunc (c *Info) CommandInfoFunc() interface{} {\n\treturn c.CommandInfo\n}\n\nfunc (c *Info) CommandInfo() (*component.CommandInfo, error) {\n\treturn &component.CommandInfo{\n\t\tName:     \"info\",\n\t\tHelp:     c.Help(),\n\t\tSynopsis: c.Synopsis(),\n\t\tFlags:    c.Flags(),\n\t}, nil\n}\n\nfunc (c *Info) Synopsis() string {\n\treturn \"Output some project information!\"\n}\n\nfunc (c *Info) Help() string {\n\treturn \"Output some project information!\"\n}\n\nfunc (c *Info) Flags() component.CommandFlags {\n\treturn []*component.CommandFlag{}\n}\n\nfunc (c *Info) Execute(trm terminal.UI, p plugincore.Project) int32 {\n\tmn, _ := p.TargetNames()\n\ttrm.Output(\"\\nMachines in this project\")\n\ttrm.Output(strings.Join(mn[:], \"\\n\"))\n\n\tcwd, _ := p.CWD()\n\tdatadir, _ := p.DataDir()\n\tvagrantfileName, _ := p.VagrantfileName()\n\thome, _ := p.Home()\n\tlocalDataPath, _ := p.LocalData()\n\tdefaultPrivateKeyPath, _ := p.DefaultPrivateKey()\n\n\ttrm.Output(\"\\nEnvironment information\")\n\ttrm.Output(\"Working directory: \" + cwd.String())\n\ttrm.Output(\"Data directory: \" + datadir.DataDir().String())\n\ttrm.Output(\"Vagrantfile name: \" + vagrantfileName)\n\ttrm.Output(\"Home directory: \" + home.String())\n\ttrm.Output(\"Local data directory: \" + localDataPath.String())\n\ttrm.Output(\"Default private key path: \" + defaultPrivateKeyPath.String())\n\n\treturn 0\n}\n\nvar (\n\t_ component.Command = (*Info)(nil)\n)\n"
  },
  {
    "path": "builtin/myplugin/command/interactive.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage command\n\nimport (\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/docs\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/terminal\"\n)\n\n// Info is a Command implementation for myplugin.\n// It is a subcommand of myplugin\ntype Interactive struct {\n\t*Command\n}\n\nfunc (c *Interactive) ConfigSet(v interface{}) error {\n\treturn nil\n}\n\nfunc (c *Interactive) CommandFunc() interface{} {\n\treturn nil\n}\n\nfunc (c *Interactive) Config() (interface{}, error) {\n\treturn &c.config, nil\n}\n\nfunc (c *Interactive) Documentation() (*docs.Documentation, error) {\n\tdoc, err := docs.New(docs.FromConfig(&CommandConfig{}))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn doc, nil\n}\n\n// ExecuteFunc implements component.Command\nfunc (c *Interactive) ExecuteFunc(cliArgs []string) interface{} {\n\treturn c.Execute\n}\n\n// CommandInfoFunc implements component.Command\nfunc (c *Interactive) CommandInfoFunc() interface{} {\n\treturn c.CommandInfo\n}\n\nfunc (c *Interactive) CommandInfo() (*component.CommandInfo, error) {\n\treturn &component.CommandInfo{\n\t\tName:     \"interactive\",\n\t\tHelp:     c.Help(),\n\t\tSynopsis: c.Synopsis(),\n\t\tFlags:    c.Flags(),\n\t}, nil\n}\n\nfunc (c *Interactive) Synopsis() string {\n\treturn \"Test out interactive input\"\n}\n\nfunc (c *Interactive) Help() string {\n\treturn \"Test out interactive input!\"\n}\n\nfunc (c *Interactive) Flags() component.CommandFlags {\n\treturn []*component.CommandFlag{}\n}\n\nfunc (c *Interactive) Execute(trm terminal.UI) int32 {\n\toutput, err := trm.Input(&terminal.Input{Prompt: \"\\nWhat do you have to say: \"})\n\tif err != nil {\n\t\ttrm.Output(\"Error getting input\")\n\t\ttrm.Output(err.Error())\n\t\treturn 1\n\t}\n\ttrm.Output(\"Did you say \" + output)\n\n\toutput, err = trm.Input(&terminal.Input{Prompt: \"\\nTell me a secret: \"})\n\tif err != nil {\n\t\ttrm.Output(\"Error getting input\")\n\t\ttrm.Output(err.Error())\n\t\treturn 1\n\t}\n\ttrm.Output(\"Did you say \" + output)\n\treturn 0\n}\n\nvar (\n\t_ component.Command = (*Interactive)(nil)\n)\n"
  },
  {
    "path": "builtin/myplugin/communicator/communicator.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage communicator\n\nimport (\n\t\"github.com/hashicorp/go-argmapper\"\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\tplugincore \"github.com/hashicorp/vagrant-plugin-sdk/core\"\n\tpb \"github.com/hashicorp/vagrant/builtin/myplugin/proto\"\n)\n\ntype DummyConfig struct {\n}\n\n// DummyCommunicator is a Communicator implementation for myplugin.\ntype DummyCommunicator struct {\n\tconfig DummyConfig\n}\n\nfunc (h *DummyCommunicator) MatchFunc() interface{} {\n\treturn h.Match\n}\n\nfunc (h *DummyCommunicator) Match(machine plugincore.Machine) (isMatch bool, err error) {\n\treturn true, nil\n}\n\nfunc (h *DummyCommunicator) InitFunc() interface{} {\n\treturn h.Init\n}\n\nfunc (h *DummyCommunicator) Init(machine plugincore.Machine) error {\n\treturn nil\n}\n\nfunc (h *DummyCommunicator) ReadyFunc() interface{} {\n\treturn h.Ready\n}\n\nfunc (h *DummyCommunicator) Ready(machine plugincore.Machine) (isReady bool, err error) {\n\treturn false, nil\n}\n\nfunc (h *DummyCommunicator) WaitForReadyFunc() interface{} {\n\treturn h.WaitForReady\n}\n\nfunc (h *DummyCommunicator) WaitForReady(machine plugincore.Machine, wait int) (isReady bool, err error) {\n\treturn false, nil\n}\n\nfunc (h *DummyCommunicator) DownloadFunc() interface{} {\n\treturn h.Download\n}\n\nfunc (h *DummyCommunicator) Download(input struct {\n\targmapper.Struct\n\tMachine     plugincore.Machine `argmapper:\",typeOnly\"`\n\tLogger      hclog.Logger       `argmapper:\",typeOnly\"`\n\tSource      string\n\tDestination string\n},\n) error {\n\tinput.Logger.Debug(\"got Source \", input.Source)\n\tinput.Logger.Debug(\"got Destination \", input.Destination)\n\treturn nil\n}\n\nfunc (h *DummyCommunicator) UploadFunc() interface{} {\n\treturn h.Upload\n}\n\nfunc (h *DummyCommunicator) Upload(input struct {\n\targmapper.Struct\n\tMachine     plugincore.Machine `argmapper:\",typeOnly\"`\n\tLogger      hclog.Logger       `argmapper:\",typeOnly\"`\n\tSource      string\n\tDestination string\n},\n) error {\n\tinput.Logger.Debug(\"got Source \", input.Source)\n\tinput.Logger.Debug(\"got Destination \", input.Destination)\n\treturn nil\n}\n\nfunc (h *DummyCommunicator) ExecuteFunc() interface{} {\n\treturn h.Execute\n}\n\nfunc (h *DummyCommunicator) Execute(\n\tmachine plugincore.Machine,\n\tcommand []string,\n\toptions *pb.CommunicatorOptions,\n) (status int32, err error) {\n\treturn 0, nil\n}\n\nfunc (h *DummyCommunicator) PrivilegedExecuteFunc() interface{} {\n\treturn h.PrivilegedExecute\n}\n\nfunc (h *DummyCommunicator) PrivilegedExecute(\n\tmachine plugincore.Machine,\n\tcommand []string,\n\toptions *pb.CommunicatorOptions,\n) (status int32, err error) {\n\treturn 0, nil\n}\n\nfunc (h *DummyCommunicator) TestFunc() interface{} {\n\treturn h.Test\n}\n\nfunc (h *DummyCommunicator) Test(\n\tmachine plugincore.Machine,\n\tcommand []string,\n\toptions ...pb.CommunicatorOptions,\n) (valid bool, err error) {\n\treturn true, nil\n}\n\nfunc (h *DummyCommunicator) ResetFunc() interface{} {\n\treturn h.Reset\n}\n\nfunc (h *DummyCommunicator) Reset(machine plugincore.Machine) (err error) {\n\treturn nil\n}\n\nvar (\n\t_ component.Communicator = (*DummyCommunicator)(nil)\n)\n"
  },
  {
    "path": "builtin/myplugin/host/alwaystrue.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage host\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/terminal\"\n\t\"github.com/hashicorp/vagrant/builtin/myplugin/host/cap\"\n)\n\ntype HostConfig struct {\n}\n\n// AlwaysTrueHost is a Host implementation for myplugin.\ntype AlwaysTrueHost struct {\n\tconfig HostConfig\n}\n\nfunc (c *AlwaysTrueHost) Seed(args ...interface{}) error {\n\treturn nil\n}\n\nfunc (c *AlwaysTrueHost) Seeds() ([]interface{}, error) {\n\treturn nil, nil\n}\n\n// DetectFunc implements component.Host\nfunc (h *AlwaysTrueHost) HostDetectFunc() interface{} {\n\treturn h.Detect\n}\n\nfunc (h *AlwaysTrueHost) Detect() bool {\n\treturn true\n}\n\n// ParentFunc implements component.Host\nfunc (h *AlwaysTrueHost) ParentFunc() interface{} {\n\treturn h.Parent\n}\n\nfunc (h *AlwaysTrueHost) Parent() string {\n\treturn \"\"\n}\n\n// HasCapabilityFunc implements component.Host\nfunc (h *AlwaysTrueHost) HasCapabilityFunc() interface{} {\n\treturn h.CheckCapability\n}\n\nfunc (h *AlwaysTrueHost) CheckCapability(n *component.NamedCapability) bool {\n\tif n.Capability == \"write_hello\" || n.Capability == \"write_hello_file\" {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// CapabilityFunc implements component.Host\nfunc (h *AlwaysTrueHost) CapabilityFunc(name string) interface{} {\n\tif name == \"write_hello\" {\n\t\treturn h.WriteHelloCap\n\t} else if name == \"write_hello_file\" {\n\t\treturn h.WriteHelloToTempFileCap\n\t}\n\treturn fmt.Errorf(\"requested capability %s not found\", name)\n}\n\nfunc (h *AlwaysTrueHost) WriteHelloCap(ui terminal.UI) error {\n\treturn cap.WriteHello(ui)\n}\n\nfunc (h *AlwaysTrueHost) WriteHelloToTempFileCap() error {\n\treturn cap.WriteHelloToTempfile()\n}\n\nvar (\n\t_ component.Host = (*AlwaysTrueHost)(nil)\n)\n"
  },
  {
    "path": "builtin/myplugin/host/cap/write_hello.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage cap\n\nimport (\n\t\"os\"\n\n\t\"github.com/hashicorp/vagrant-plugin-sdk/terminal\"\n)\n\nfunc WriteHello(ui terminal.UI) error {\n\tmsg := \"Hello from the write hello capability, compliments of the AlwaysTrue Host\"\n\tui.Output(msg)\n\treturn nil\n}\n\nfunc WriteHelloToTempfile() error {\n\tmsg := []byte(\"Hello from the write hello capability, compliments of the AlwaysTrue Host\")\n\terr := os.WriteFile(\"/tmp/write_hello\", msg, 0644)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "builtin/myplugin/locales/assets/en.json",
    "content": "{\n  \"dothing\": \"Tricked ya! I actually do nothing :P\"\n}\n"
  },
  {
    "path": "builtin/myplugin/locales/assets/es.json",
    "content": "{\n  \"dothing\": \"¡Te engañé! en realidad no hago nada :P\"\n}\n"
  },
  {
    "path": "builtin/myplugin/locales/locales.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\n// Code generated for package locales by go-bindata DO NOT EDIT. (@generated)\n// sources:\n// locales/assets/en.json\n// locales/assets/es.json\npackage locales\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc bindataRead(data []byte, name string) ([]byte, error) {\n\tgz, err := gzip.NewReader(bytes.NewBuffer(data))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Read %q: %v\", name, err)\n\t}\n\n\tvar buf bytes.Buffer\n\t_, err = io.Copy(&buf, gz)\n\tclErr := gz.Close()\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Read %q: %v\", name, err)\n\t}\n\tif clErr != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\ntype asset struct {\n\tbytes []byte\n\tinfo  os.FileInfo\n}\n\ntype bindataFileInfo struct {\n\tname    string\n\tsize    int64\n\tmode    os.FileMode\n\tmodTime time.Time\n}\n\n// Name return file name\nfunc (fi bindataFileInfo) Name() string {\n\treturn fi.name\n}\n\n// Size return file size\nfunc (fi bindataFileInfo) Size() int64 {\n\treturn fi.size\n}\n\n// Mode return file mode\nfunc (fi bindataFileInfo) Mode() os.FileMode {\n\treturn fi.mode\n}\n\n// Mode return file modify time\nfunc (fi bindataFileInfo) ModTime() time.Time {\n\treturn fi.modTime\n}\n\n// IsDir return file whether a directory\nfunc (fi bindataFileInfo) IsDir() bool {\n\treturn fi.mode&os.ModeDir != 0\n}\n\n// Sys return file is sys mode\nfunc (fi bindataFileInfo) Sys() interface{} {\n\treturn nil\n}\n\nvar _localesAssetsEnJson = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x00\\xff\\xaa\\xe6\\x52\\x50\\x50\\x4a\\xc9\\x2f\\xc9\\xc8\\xcc\\x4b\\x57\\xb2\\x52\\x50\\x0a\\x29\\xca\\x4c\\xce\\x4e\\x4d\\x51\\xa8\\x4c\\x54\\x54\\xf0\\x54\\x48\\x4c\\x2e\\x29\\x4d\\xcc\\xc9\\xa9\\x54\\x48\\xc9\\x57\\xc8\\x83\\x28\\x52\\xb0\\x0a\\x50\\xe2\\xaa\\xe5\\x02\\x04\\x00\\x00\\xff\\xff\\xae\\xf3\\xa3\\xbd\\x38\\x00\\x00\\x00\")\n\nfunc localesAssetsEnJsonBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t_localesAssetsEnJson,\n\t\t\"locales/assets/en.json\",\n\t)\n}\n\nfunc localesAssetsEnJson() (*asset, error) {\n\tbytes, err := localesAssetsEnJsonBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"locales/assets/en.json\", size: 56, mode: os.FileMode(420), modTime: time.Unix(1651259149, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\nvar _localesAssetsEsJson = []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x00\\xff\\xaa\\xe6\\x52\\x50\\x50\\x4a\\xc9\\x2f\\xc9\\xc8\\xcc\\x4b\\x57\\xb2\\x52\\x50\\x3a\\xb4\\x30\\x24\\x55\\x21\\x35\\x2f\\x3d\\xf1\\xf0\\xc6\\xc3\\x2b\\x15\\x15\\x52\\xf3\\x14\\x8a\\x52\\x13\\x73\\x32\\x53\\x12\\x53\\x14\\xf2\\xf2\\x15\\x32\\x12\\xd3\\xf3\\x15\\xf2\\x12\\x53\\x12\\x15\\xac\\x02\\x94\\xb8\\x6a\\xb9\\x00\\x01\\x00\\x00\\xff\\xff\\xe5\\x85\\x54\\x9c\\x3e\\x00\\x00\\x00\")\n\nfunc localesAssetsEsJsonBytes() ([]byte, error) {\n\treturn bindataRead(\n\t\t_localesAssetsEsJson,\n\t\t\"locales/assets/es.json\",\n\t)\n}\n\nfunc localesAssetsEsJson() (*asset, error) {\n\tbytes, err := localesAssetsEsJsonBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := bindataFileInfo{name: \"locales/assets/es.json\", size: 62, mode: os.FileMode(420), modTime: time.Unix(1651262545, 0)}\n\ta := &asset{bytes: bytes, info: info}\n\treturn a, nil\n}\n\n// Asset loads and returns the asset for the given name.\n// It returns an error if the asset could not be found or\n// could not be loaded.\nfunc Asset(name string) ([]byte, error) {\n\tcannonicalName := strings.Replace(name, \"\\\\\", \"/\", -1)\n\tif f, ok := _bindata[cannonicalName]; ok {\n\t\ta, err := f()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"Asset %s can't read by error: %v\", name, err)\n\t\t}\n\t\treturn a.bytes, nil\n\t}\n\treturn nil, fmt.Errorf(\"Asset %s not found\", name)\n}\n\n// MustAsset is like Asset but panics when Asset would return an error.\n// It simplifies safe initialization of global variables.\nfunc MustAsset(name string) []byte {\n\ta, err := Asset(name)\n\tif err != nil {\n\t\tpanic(\"asset: Asset(\" + name + \"): \" + err.Error())\n\t}\n\n\treturn a\n}\n\n// AssetInfo loads and returns the asset info for the given name.\n// It returns an error if the asset could not be found or\n// could not be loaded.\nfunc AssetInfo(name string) (os.FileInfo, error) {\n\tcannonicalName := strings.Replace(name, \"\\\\\", \"/\", -1)\n\tif f, ok := _bindata[cannonicalName]; ok {\n\t\ta, err := f()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"AssetInfo %s can't read by error: %v\", name, err)\n\t\t}\n\t\treturn a.info, nil\n\t}\n\treturn nil, fmt.Errorf(\"AssetInfo %s not found\", name)\n}\n\n// AssetNames returns the names of the assets.\nfunc AssetNames() []string {\n\tnames := make([]string, 0, len(_bindata))\n\tfor name := range _bindata {\n\t\tnames = append(names, name)\n\t}\n\treturn names\n}\n\n// _bindata is a table, holding each asset generator, mapped to its name.\nvar _bindata = map[string]func() (*asset, error){\n\t\"locales/assets/en.json\": localesAssetsEnJson,\n\t\"locales/assets/es.json\": localesAssetsEsJson,\n}\n\n// AssetDir returns the file names below a certain\n// directory embedded in the file by go-bindata.\n// For example if you run go-bindata on data/... and data contains the\n// following hierarchy:\n//     data/\n//       foo.txt\n//       img/\n//         a.png\n//         b.png\n// then AssetDir(\"data\") would return []string{\"foo.txt\", \"img\"}\n// AssetDir(\"data/img\") would return []string{\"a.png\", \"b.png\"}\n// AssetDir(\"foo.txt\") and AssetDir(\"notexist\") would return an error\n// AssetDir(\"\") will return []string{\"data\"}.\nfunc AssetDir(name string) ([]string, error) {\n\tnode := _bintree\n\tif len(name) != 0 {\n\t\tcannonicalName := strings.Replace(name, \"\\\\\", \"/\", -1)\n\t\tpathList := strings.Split(cannonicalName, \"/\")\n\t\tfor _, p := range pathList {\n\t\t\tnode = node.Children[p]\n\t\t\tif node == nil {\n\t\t\t\treturn nil, fmt.Errorf(\"Asset %s not found\", name)\n\t\t\t}\n\t\t}\n\t}\n\tif node.Func != nil {\n\t\treturn nil, fmt.Errorf(\"Asset %s not found\", name)\n\t}\n\trv := make([]string, 0, len(node.Children))\n\tfor childName := range node.Children {\n\t\trv = append(rv, childName)\n\t}\n\treturn rv, nil\n}\n\ntype bintree struct {\n\tFunc     func() (*asset, error)\n\tChildren map[string]*bintree\n}\n\nvar _bintree = &bintree{nil, map[string]*bintree{\n\t\"locales\": &bintree{nil, map[string]*bintree{\n\t\t\"assets\": &bintree{nil, map[string]*bintree{\n\t\t\t\"en.json\": &bintree{localesAssetsEnJson, map[string]*bintree{}},\n\t\t\t\"es.json\": &bintree{localesAssetsEsJson, map[string]*bintree{}},\n\t\t}},\n\t}},\n}}\n\n// RestoreAsset restores an asset under the given directory\nfunc RestoreAsset(dir, name string) error {\n\tdata, err := Asset(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tinfo, err := AssetInfo(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// RestoreAssets restores an asset under the given directory recursively\nfunc RestoreAssets(dir, name string) error {\n\tchildren, err := AssetDir(name)\n\t// File\n\tif err != nil {\n\t\treturn RestoreAsset(dir, name)\n\t}\n\t// Dir\n\tfor _, child := range children {\n\t\terr = RestoreAssets(dir, filepath.Join(name, child))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc _filePath(dir, name string) string {\n\tcannonicalName := strings.Replace(name, \"\\\\\", \"/\", -1)\n\treturn filepath.Join(append([]string{dir}, strings.Split(cannonicalName, \"/\")...)...)\n}\n"
  },
  {
    "path": "builtin/myplugin/main.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage myplugin\n\nimport (\n\tsdk \"github.com/hashicorp/vagrant-plugin-sdk\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\t\"github.com/hashicorp/vagrant/builtin/myplugin/command\"\n\t\"github.com/hashicorp/vagrant/builtin/myplugin/communicator\"\n\t\"github.com/hashicorp/vagrant/builtin/myplugin/host\"\n\t\"github.com/hashicorp/vagrant/builtin/myplugin/provider\"\n\t\"github.com/hashicorp/vagrant/builtin/myplugin/push\"\n)\n\n//go:generate protoc -I ../../.. --go_opt=plugins=grpc --go_out=../../.. vagrant-ruby/builtin/myplugin/proto/plugin.proto\n\n// Locales data bundling\n//go:generate go-bindata -o ./locales/locales.go -pkg locales locales/assets\n\n// Options are the SDK options to use for instantiation.\nvar CommandOptions = []sdk.Option{\n\tsdk.WithComponents(\n\t\t// &Provider{},\n\t\t&host.AlwaysTrueHost{},\n\t\t&communicator.DummyCommunicator{},\n\t\t&push.Encouragement{},\n\t),\n\tsdk.WithComponent(&command.Command{}, &component.CommandOptions{\n\t\t// Should keep the plugin out of the default help output\n\t\tPrimary: false,\n\t}),\n\tsdk.WithComponent(&provider.Happy{}, &component.ProviderOptions{\n\t\tPriority: 100,\n\t}),\n\tsdk.WithMappers(StructToCommunincatorOptions),\n\tsdk.WithName(\"myplugin\"),\n}\n"
  },
  {
    "path": "builtin/myplugin/mappers.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage myplugin\n\nimport (\n\tpb \"github.com/hashicorp/vagrant/builtin/myplugin/proto\"\n\t\"github.com/mitchellh/mapstructure\"\n\t\"google.golang.org/protobuf/types/known/structpb\"\n)\n\nfunc StructToCommunincatorOptions(in *structpb.Struct) (*pb.CommunicatorOptions, error) {\n\tvar result pb.CommunicatorOptions\n\treturn &result, mapstructure.Decode(in.AsMap(), &result)\n}\n"
  },
  {
    "path": "builtin/myplugin/proto/plugin.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.26.0\n// \tprotoc        v3.19.4\n// source: vagrant-ruby/builtin/myplugin/proto/plugin.proto\n\npackage proto\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype UpResult struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *UpResult) Reset() {\n\t*x = UpResult{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UpResult) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpResult) ProtoMessage() {}\n\nfunc (x *UpResult) ProtoReflect() protoreflect.Message {\n\tmi := &file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpResult.ProtoReflect.Descriptor instead.\nfunc (*UpResult) Descriptor() ([]byte, []int) {\n\treturn file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_rawDescGZIP(), []int{0}\n}\n\ntype CommunicatorOptions struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tKeepAlive string `protobuf:\"bytes,1,opt,name=keep_alive,json=keepAlive,proto3\" json:\"keep_alive,omitempty\"`\n\tTimeout   int64  `protobuf:\"varint,2,opt,name=timeout,proto3\" json:\"timeout,omitempty\"`\n}\n\nfunc (x *CommunicatorOptions) Reset() {\n\t*x = CommunicatorOptions{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CommunicatorOptions) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CommunicatorOptions) ProtoMessage() {}\n\nfunc (x *CommunicatorOptions) ProtoReflect() protoreflect.Message {\n\tmi := &file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CommunicatorOptions.ProtoReflect.Descriptor instead.\nfunc (*CommunicatorOptions) Descriptor() ([]byte, []int) {\n\treturn file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *CommunicatorOptions) GetKeepAlive() string {\n\tif x != nil {\n\t\treturn x.KeepAlive\n\t}\n\treturn \"\"\n}\n\nfunc (x *CommunicatorOptions) GetTimeout() int64 {\n\tif x != nil {\n\t\treturn x.Timeout\n\t}\n\treturn 0\n}\n\nvar File_vagrant_ruby_builtin_myplugin_proto_plugin_proto protoreflect.FileDescriptor\n\nvar file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_rawDesc = []byte{\n\t0x0a, 0x30, 0x76, 0x61, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x2d, 0x72, 0x75, 0x62, 0x79, 0x2f, 0x62,\n\t0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x2f, 0x6d, 0x79, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x12, 0x08, 0x6d, 0x79, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x22, 0x0a, 0x0a, 0x08,\n\t0x55, 0x70, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x4e, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x6d,\n\t0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12,\n\t0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x18,\n\t0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,\n\t0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x42, 0x25, 0x5a, 0x23, 0x76, 0x61, 0x67, 0x72,\n\t0x61, 0x6e, 0x74, 0x2d, 0x72, 0x75, 0x62, 0x79, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e,\n\t0x2f, 0x6d, 0x79, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,\n\t0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_vagrant_ruby_builtin_myplugin_proto_plugin_proto_rawDescOnce sync.Once\n\tfile_vagrant_ruby_builtin_myplugin_proto_plugin_proto_rawDescData = file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_rawDesc\n)\n\nfunc file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_rawDescGZIP() []byte {\n\tfile_vagrant_ruby_builtin_myplugin_proto_plugin_proto_rawDescOnce.Do(func() {\n\t\tfile_vagrant_ruby_builtin_myplugin_proto_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_rawDescData)\n\t})\n\treturn file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_rawDescData\n}\n\nvar file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_goTypes = []interface{}{\n\t(*UpResult)(nil),            // 0: myplugin.UpResult\n\t(*CommunicatorOptions)(nil), // 1: myplugin.CommunicatorOptions\n}\nvar file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_init() }\nfunc file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_init() {\n\tif File_vagrant_ruby_builtin_myplugin_proto_plugin_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_vagrant_ruby_builtin_myplugin_proto_plugin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UpResult); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_vagrant_ruby_builtin_myplugin_proto_plugin_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CommunicatorOptions); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_goTypes,\n\t\tDependencyIndexes: file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_depIdxs,\n\t\tMessageInfos:      file_vagrant_ruby_builtin_myplugin_proto_plugin_proto_msgTypes,\n\t}.Build()\n\tFile_vagrant_ruby_builtin_myplugin_proto_plugin_proto = out.File\n\tfile_vagrant_ruby_builtin_myplugin_proto_plugin_proto_rawDesc = nil\n\tfile_vagrant_ruby_builtin_myplugin_proto_plugin_proto_goTypes = nil\n\tfile_vagrant_ruby_builtin_myplugin_proto_plugin_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "builtin/myplugin/proto/plugin.proto",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\nsyntax = \"proto3\";\n\npackage myplugin;\n\noption go_package = \"vagrant-ruby/builtin/myplugin/proto\";\n\nmessage UpResult {}\n\nmessage CommunicatorOptions {\n  string keep_alive = 1;\n  int64 timeout = 2;\n}\n"
  },
  {
    "path": "builtin/myplugin/provider/happy.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage provider\n\nimport (\n\t\"context\"\n\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/core\"\n)\n\n// Happy is a provider that is just happy to be backing your vagrant VMs.\ntype Happy struct{}\n\nfunc (p *Happy) Action(name string, args ...interface{}) error {\n\treturn nil\n}\n\n// ActionFunc implements component.Provider\nfunc (h *Happy) ActionFunc(actionName string) interface{} {\n\treturn h.Action\n}\n\nfunc (h *Happy) Capability(name string, args ...interface{}) (interface{}, error) {\n\treturn nil, nil\n}\n\n// CapabilityFunc implements component.Provider\nfunc (h *Happy) CapabilityFunc(name string) interface{} {\n\treturn h.Capability\n}\n\nfunc (h *Happy) HasCapability(n *component.NamedCapability) bool {\n\treturn false\n}\n\n// HasCapabilityFunc implements component.Provicer\nfunc (h *Happy) HasCapabilityFunc() interface{} {\n\treturn h.HasCapability\n}\n\nfunc (h *Happy) MachineIdChanged() error {\n\treturn nil\n}\n\n// MachineIdChangedFunc implements component.Provicer\nfunc (h *Happy) MachineIdChangedFunc() interface{} {\n\treturn h.MachineIdChanged\n}\n\nfunc (h *Happy) Installed(context.Context) (bool, error) {\n\treturn true, nil\n}\n\n// InstalledFunc implements component.Provider\nfunc (h *Happy) InstalledFunc() interface{} {\n\treturn h.Installed\n}\n\nfunc (h *Happy) Init() (bool, error) {\n\treturn true, nil\n}\n\n// InitFunc implements component.Provider\nfunc (h *Happy) InitFunc() interface{} {\n\treturn h.Init\n}\n\nfunc (h *Happy) SshInfo() (*core.SshInfo, error) {\n\treturn nil, nil\n}\n\n// SshInfoFunc implements component.Provider\nfunc (h *Happy) SshInfoFunc() interface{} {\n\treturn h.SshInfo\n}\n\nfunc (h *Happy) State() (*core.MachineState, error) {\n\treturn nil, nil\n}\n\n// StateFunc implements component.Provider\nfunc (h *Happy) StateFunc() interface{} {\n\treturn h.State\n}\n\nfunc (h *Happy) Usable() (bool, error) {\n\treturn false, nil\n}\n\n// UsableFunc implements component.Provider\nfunc (h *Happy) UsableFunc() interface{} {\n\treturn h.Usable\n}\n\nvar (\n\t_ component.Provider = (*Happy)(nil)\n)\n"
  },
  {
    "path": "builtin/myplugin/push/encouragement.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage push\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/core\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/terminal\"\n\t\"google.golang.org/protobuf/types/known/structpb\"\n)\n\n// Encouragement is a push strategy that provides encouragement for the code\n// you push. Everybody could use some encouragement sometimes!\ntype Encouragement struct{}\n\nfunc (e *Encouragement) PushFunc() interface{} {\n\treturn e.Push\n}\n\n// Push runs this is the first ever Golang push plugin!\nfunc (e *Encouragement) Push(ui terminal.UI, proj core.Project) error {\n\tui.Output(\"You've invoked a push plugin written in Go! Great work!\")\n\n\tpushConfig, err := findPushConfig(proj, \"myplugin\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif pushConfig != nil {\n\t\tui.Output(\"Look a this nice config you sent along too!\")\n\t\tui.Output(\"We'll print it as JSON for fun:\")\n\n\t\tconfig, err := unpackConfig(pushConfig)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tjsonConfig, err := json.MarshalIndent(config, \"  \", \"\\t\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tui.Output(\"  %s\", jsonConfig)\n\t}\n\n\treturn nil\n}\n\n// findPushConfig finds the relevant PushConfig for the name given.\n//\n// For now, there are no config related helpers, so each push plugin needs to\n// walk its way down to its relevant config in the Vagrantfile.\nfunc findPushConfig(proj core.Project, name string) (*vagrant_plugin_sdk.Vagrantfile_PushConfig, error) {\n\treturn nil, fmt.Errorf(\"unimplemented\")\n\t// v, err := proj.Config()\n\t// if err != nil {\n\t// \treturn nil, err\n\t// }\n\t// for _, p := range v.GetPushConfigs() {\n\t// \tif p.GetName() == name {\n\t// \t\treturn p, nil\n\t// \t}\n\t// }\n\t// return nil, nil\n}\n\n// unpackConfig takes a PushConfig and unpack the underlying map of config\n//\n// For now, there are no config related helpers, so each push plugin needs to\n// unpack from a generic struct into whatever types it might need. For this\n// demo plugin we're just leaving it untyped.\nfunc unpackConfig(pc *vagrant_plugin_sdk.Vagrantfile_PushConfig) (map[string]interface{}, error) {\n\tgc := pc.GetConfig()\n\ts := &structpb.Struct{}\n\terr := gc.GetConfig().UnmarshalTo(s)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn s.AsMap(), nil\n}\n\nvar (\n\t_ component.Push = (*Encouragement)(nil)\n)\n"
  },
  {
    "path": "builtin/otherplugin/command.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage otherplugin\n\nimport (\n\t\"strings\"\n\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\tplugincore \"github.com/hashicorp/vagrant-plugin-sdk/core\"\n\n\t//\t\"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/terminal\"\n\t//\"google.golang.org/protobuf/types/known/anypb\"\n)\n\ntype Command struct{}\n\nfunc (c *Command) ExecuteFunc(cliArgs []string) interface{} {\n\tif len(cliArgs) < 2 {\n\t\treturn c.ExecuteMain\n\t}\n\tswitch cliArgs[1] {\n\tcase \"info\":\n\t\tif len(cliArgs) < 3 {\n\t\t\treturn c.ExecuteInfo\n\t\t}\n\t\tswitch cliArgs[2] {\n\t\tcase \"ofni\":\n\t\t\treturn c.ExecuteOfni\n\t\t}\n\t\treturn c.ExecuteInfo\n\tcase \"dothing\":\n\t\treturn c.ExecuteThing\n\tcase \"use-host\":\n\t\treturn c.ExecuteUseHostPlugin\n\t}\n\treturn c.ExecuteMain\n}\n\nfunc (c *Command) CommandInfoFunc() interface{} {\n\treturn c.CommandInfo\n}\n\nfunc (c *Command) CommandInfo() *component.CommandInfo {\n\treturn &component.CommandInfo{\n\t\tName:     \"otherplugin\",\n\t\tHelp:     \"HELP MEEEEE!\",\n\t\tSynopsis: \"This command does stuff\",\n\t\tFlags: []*component.CommandFlag{\n\t\t\t{\n\t\t\t\tLongName:     \"thing\",\n\t\t\t\tDescription:  \"a thing flag\",\n\t\t\t\tDefaultValue: \"I'm a thing!\",\n\t\t\t\tType:         component.FlagString,\n\t\t\t},\n\t\t},\n\t\tSubcommands: []*component.CommandInfo{\n\t\t\t&component.CommandInfo{\n\t\t\t\tName:     \"info\",\n\t\t\t\tHelp:     \"Shows info\",\n\t\t\t\tSynopsis: \"IT. SHOWS. INFO.\",\n\t\t\t\tFlags:    []*component.CommandFlag{},\n\t\t\t\tSubcommands: []*component.CommandInfo{\n\t\t\t\t\t&component.CommandInfo{\n\t\t\t\t\t\tName:     \"ofni\",\n\t\t\t\t\t\tHelp:     \"Shows ofni\",\n\t\t\t\t\t\tSynopsis: \"BIZZARO info\",\n\t\t\t\t\t\tFlags:    []*component.CommandFlag{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t&component.CommandInfo{\n\t\t\t\tName:     \"dothing\",\n\t\t\t\tHelp:     \"Does thing\",\n\t\t\t\tSynopsis: \"Does this super great thing!\",\n\t\t\t\tFlags: []*component.CommandFlag{\n\t\t\t\t\t{\n\t\t\t\t\t\tLongName:     \"stringflag\",\n\t\t\t\t\t\tDescription:  \"a test flag\",\n\t\t\t\t\t\tDefaultValue: \"I'm a string!\",\n\t\t\t\t\t\tType:         component.FlagString,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t&component.CommandInfo{\n\t\t\t\tName:     \"use-host\",\n\t\t\t\tHelp:     \"Executes a host capability\",\n\t\t\t\tSynopsis: \"Executes a host capability\",\n\t\t\t\tFlags:    []*component.CommandFlag{},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc (c *Command) ExecuteMain(trm terminal.UI, flags map[string]interface{}) int32 {\n\ttrm.Output(\"You gave me the flag: \" + flags[\"thing\"].(string))\n\n\ttrm.Output(\"My subcommands are: `info` and `dothing`\")\n\treturn 0\n}\n\nfunc (c *Command) ExecuteThing(trm terminal.UI, flags map[string]interface{}) int32 {\n\ttrm.Output(\"Tricked ya! I actually do nothing :P\")\n\ttrm.Output(\"You gave me the stringflag: \" + flags[\"stringflag\"].(string))\n\treturn 0\n}\n\nfunc (c *Command) ExecuteInfo(trm terminal.UI, p plugincore.Project) int32 {\n\tmn, _ := p.TargetNames()\n\ttrm.Output(\"\\nMachines in this project\")\n\ttrm.Output(strings.Join(mn[:], \"\\n\"))\n\n\tcwd, _ := p.CWD()\n\tdatadir, _ := p.DataDir()\n\tvagrantfileName, _ := p.VagrantfileName()\n\thome, _ := p.Home()\n\tlocalDataPath, _ := p.LocalData()\n\tdefaultPrivateKeyPath, _ := p.DefaultPrivateKey()\n\n\ttrm.Output(\"\\nEnvironment information\")\n\ttrm.Output(\"Working directory: \" + cwd.String())\n\tif datadir != nil && datadir.DataDir() != nil {\n\t\ttrm.Output(\"Data directory: \" + datadir.DataDir().String())\n\t}\n\ttrm.Output(\"Vagrantfile name: \" + vagrantfileName)\n\ttrm.Output(\"Home directory: \" + home.String())\n\ttrm.Output(\"Local data directory: \" + localDataPath.String())\n\ttrm.Output(\"Default private key path: \" + defaultPrivateKeyPath.String())\n\n\tptrm, err := p.UI()\n\tif err != nil {\n\t\ttrm.Output(\"Failed to get project specific UI! Reason: \" + err.Error())\n\t} else {\n\t\tptrm.Output(\"YAY! This is project specific output!\")\n\t}\n\n\tt, err := p.Target(\"one\", \"\")\n\tif err != nil {\n\t\ttrm.Output(\"Failed to load `one' target -- \" + err.Error())\n\t\treturn 1\n\t}\n\n\tm, err := t.Specialize((*plugincore.Machine)(nil))\n\tif err != nil {\n\t\ttrm.Output(\"Failed to specialize to machine! -- \" + err.Error())\n\t\treturn 1\n\t}\n\n\tmachine := m.(plugincore.Machine)\n\ttrm.Output(\"successfully specialized to machine\")\n\tid, err := machine.ID()\n\tif err != nil {\n\t\ttrm.Output(\"failed to get machine id --> \" + err.Error())\n\t} else {\n\t\ttrm.Output(\"machine id is: \" + id)\n\t}\n\n\treturn 10\n}\n\nfunc (c *Command) ExecuteOfni(trm terminal.UI) int32 {\n\ttrm.Output(\"I am bizzaro info! Call me ofni\")\n\treturn 0\n}\n\nfunc (c *Command) ExecuteUseHostPlugin(trm terminal.UI, basis plugincore.Basis) int32 {\n\ttrm.Output(\"Requesting host plugin...\")\n\thost, err := basis.Host()\n\tif err != nil {\n\t\ttrm.Output(\"Error: Failed to receive host plugin - \" + err.Error())\n\t\treturn 1\n\t}\n\ttrm.Output(\"Host plugin received. Checking for `write_hello` capability...\")\n\tok, err := host.HasCapability(\"write_hello\")\n\tif err != nil {\n\t\ttrm.Output(\"ERROR: \" + err.Error())\n\t\t//\treturn 1\n\t}\n\tif ok {\n\t\ttrm.Output(\"Found `write_hello` capability for host plugin, calling...\")\n\t\t_, err = host.Capability(\"write_hello\", trm)\n\t\tif err != nil {\n\t\t\ttrm.Output(\"Error executing capability - \" + err.Error())\n\t\t\treturn 1\n\t\t}\n\t} else {\n\t\ttrm.Output(\"no `write_hello` capability found\")\n\t}\n\n\treturn 0\n}\n"
  },
  {
    "path": "builtin/otherplugin/guest/alwaystrue.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage guest\n\nimport (\n\t\"errors\"\n\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\tplugincore \"github.com/hashicorp/vagrant-plugin-sdk/core\"\n\t\"github.com/hashicorp/vagrant/builtin/otherplugin/guest/cap\"\n)\n\ntype GuestConfig struct {\n}\n\n// AlwaysTrueGuest is a Guest implementation for myplugin.\ntype AlwaysTrueGuest struct {\n\tconfig GuestConfig\n}\n\nfunc (c *AlwaysTrueGuest) Seed(args ...interface{}) error {\n\treturn nil\n}\n\nfunc (c *AlwaysTrueGuest) Seeds() ([]interface{}, error) {\n\treturn nil, nil\n}\n\n// DetectFunc implements component.Guest\nfunc (h *AlwaysTrueGuest) GuestDetectFunc() interface{} {\n\treturn h.Detect\n}\n\nfunc (h *AlwaysTrueGuest) Detect(t plugincore.Target) bool {\n\tm, err := t.Specialize((*plugincore.Machine)(nil))\n\tif err != nil {\n\t\treturn false\n\t}\n\tmachine := m.(plugincore.Machine)\n\tmachine.ConnectionInfo()\n\t// TODO: need a communicator to connect to guest\n\treturn true\n}\n\n// ParentsFunc implements component.Guest\nfunc (h *AlwaysTrueGuest) ParentFunc() interface{} {\n\treturn h.Parent\n}\n\nfunc (h *AlwaysTrueGuest) Parent() string {\n\treturn \"\"\n}\n\n// HasCapabilityFunc implements component.Guest\nfunc (h *AlwaysTrueGuest) HasCapabilityFunc() interface{} {\n\treturn h.CheckCapability\n}\n\nfunc (h *AlwaysTrueGuest) CheckCapability(n *component.NamedCapability) bool {\n\tif n.Capability == \"hello\" {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// CapabilityFunc implements component.Guest\nfunc (h *AlwaysTrueGuest) CapabilityFunc(name string) interface{} {\n\tif name == \"hello\" {\n\t\treturn h.WriteHelloCap\n\t}\n\treturn errors.New(\"invalid capability requested\")\n}\n\nfunc (h *AlwaysTrueGuest) WriteHelloCap(m plugincore.Machine) error {\n\treturn cap.WriteHello(m)\n}\n\nvar (\n\t_ component.Guest = (*AlwaysTrueGuest)(nil)\n)\n"
  },
  {
    "path": "builtin/otherplugin/guest/cap/write_hello.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage cap\n\nimport (\n\t\"io/ioutil\"\n\n\tplugincore \"github.com/hashicorp/vagrant-plugin-sdk/core\"\n)\n\nfunc WriteHello(machine plugincore.Machine) (err error) {\n\td1 := []byte(\"hello\\ngo\\n\")\n\tioutil.WriteFile(\"/tmp/dat1\", d1, 0644)\n\n\tmachine.ConnectionInfo()\n\t// TODO: write something to guest machine\n\t// need a communicator\n\treturn\n}\n"
  },
  {
    "path": "builtin/otherplugin/main.go",
    "content": "// Copyright IBM Corp. 2010, 2025\n// SPDX-License-Identifier: BUSL-1.1\n\npackage otherplugin\n\nimport (\n\tsdk \"github.com/hashicorp/vagrant-plugin-sdk\"\n\t\"github.com/hashicorp/vagrant-plugin-sdk/component\"\n\t\"github.com/hashicorp/vagrant/builtin/otherplugin/guest\"\n)\n\nvar CommandOptions = []sdk.Option{\n\tsdk.WithComponents(\n\t\t&guest.AlwaysTrueGuest{},\n\t),\n\tsdk.WithComponent(&Command{}, &component.CommandOptions{\n\t\t// Hide command from default help output\n\t\tPrimary: false,\n\t}),\n\tsdk.WithName(\"otherplugin\"),\n}\n"
  },
  {
    "path": "contrib/README.md",
    "content": "# Vagrant Contrib\n\nMiscellaneous contributions which assist people in using Vagrant will\nmake their way into this directory. An up-to-date list of short descriptions\nfor each item will be kept below.\n\n## List of Contrib Items\n\n* `bash` - Contains a bash script for improving autocompletion with bash.\n* `emacs` - Contains a file for enabling Ruby syntax highlighting for `Vagrantfile`s in `emacs`.\n* `st` - Contains a `.sublime-settings` file for enabling Ruby syntax highlighting\n  for `Vagrantfile`s in Sublime Text.\n* `sudoers` - Contains the pieces of `/etc/sudoers` configuration to avoid password entry when\n  starting machines.\n* `vim` - Contains a `.vim` file for enabling Ruby syntax highlighting\n  for `Vagrantfile`s in `vim`.\n* `zsh` - Contains a zsh script for improving autocompletion with zsh.\n"
  },
  {
    "path": "contrib/bash/completion.sh",
    "content": "#!/bin/bash\n\n# (The MIT License)\n#\n# Copyright (c) 2014 Kura\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the 'Software'), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in all\n# copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\n\n__pwdln() {\n   pwdmod=\"${PWD}/\"\n   itr=0\n   until [[ -z \"$pwdmod\" ]];do\n      itr=$(($itr+1))\n      pwdmod=\"${pwdmod#*/}\"\n   done\n   echo -n $(($itr-1))\n}\n\n__vagrantinvestigate() {\n    if [ -f \"${PWD}/.vagrant\" -o -d \"${PWD}/.vagrant\" ];then\n      echo \"${PWD}/.vagrant\"\n      return 0\n   else\n      pwdmod2=\"${PWD}\"\n      for (( i=2; i<=$(__pwdln); i++ ));do\n         pwdmod2=\"${pwdmod2%/*}\"\n         if [ -f \"${pwdmod2}/.vagrant\" -o -d \"${pwdmod2}/.vagrant\" ];then\n            echo \"${pwdmod2}/.vagrant\"\n            return 0\n         fi\n      done\n   fi\n   return 1\n}\n\n_vagrant() {\n    cur=\"${COMP_WORDS[COMP_CWORD]}\"\n    prev=\"${COMP_WORDS[COMP_CWORD-1]}\"\n    commands=\"box cloud connect destroy docker-exec docker-logs docker-run global-status halt help init list-commands login package plugin provision push rdp reload resume rsync rsync-auto share snapshot ssh ssh-config status suspend up version\"\n\n    if [ $COMP_CWORD == 1 ]\n    then\n      COMPREPLY=($(compgen -W \"${commands}\" -- ${cur}))\n      return 0\n    fi\n\n    if [ $COMP_CWORD == 2 ]\n    then\n        case \"$prev\" in\n            \"init\")\n              local box_list=$(find \"${VAGRANT_HOME:-${HOME}/.vagrant.d}/boxes\" -mindepth 1 -maxdepth 1 -type d -exec basename {} \\; | sed -e 's/-VAGRANTSLASH-/\\//')\n              COMPREPLY=($(compgen -W \"${box_list}\" -- ${cur}))\n              return 0\n            ;;\n            \"up\")\n              vagrant_state_file=$(__vagrantinvestigate) || return 1\n              if [[ -d \"${vagrant_state_file}\" ]]\n              then\n                local vm_list=$(find \"${vagrant_state_file}/machines\" -mindepth 1 -maxdepth 1 -type d -exec basename {} \\;)\n              fi\n              local up_commands=\"\\\n                --provision \\\n                --no-provision \\\n                --provision-with \\\n                --destroy-on-error \\\n                --no-destroy-on-error \\\n                --parallel \\\n                --no-parallel\n                --provider \\\n                --install-provider \\\n                --no-install-provider \\\n                -h \\\n                --help\"\n              COMPREPLY=($(compgen -W \"${up_commands} ${vm_list}\" -- ${cur}))\n              return 0\n            ;;\n            \"destroy\"|\"ssh\"|\"provision\"|\"reload\"|\"halt\"|\"suspend\"|\"resume\"|\"ssh-config\")\n              vagrant_state_file=$(__vagrantinvestigate) || return 1\n              if [[ -f \"${vagrant_state_file}\" ]]\n              then\n                running_vm_list=$(grep 'active' \"${vagrant_state_file}\" | sed -e 's/\"active\"://' | tr ',' '\\n' | cut -d '\"' -f 2 | tr '\\n' ' ')\n              else\n                running_vm_list=$(find \"${vagrant_state_file}/machines\" -type f -name \"id\" | awk -F\"/\" '{print $(NF-2)}')\n              fi\n              COMPREPLY=($(compgen -W \"${running_vm_list}\" -- ${cur}))\n              return 0\n            ;;\n            \"box\")\n              box_commands=\"add help list outdated prune remove repackage update\"\n              COMPREPLY=($(compgen -W \"${box_commands}\" -- ${cur}))\n              return 0\n            ;;\n            \"cloud\")\n              cloud_commands=\"auth box search provider publish version\"\n              COMPREPLY=($(compgen -W \"${cloud_commands}\" -- ${cur}))\n              return 0\n            ;;\n            \"plugin\")\n              plugin_commands=\"install license list uninstall update\"\n              COMPREPLY=($(compgen -W \"${plugin_commands}\" -- ${cur}))\n              return 0\n            ;;\n            \"help\")\n              COMPREPLY=($(compgen -W \"${commands}\" -- ${cur}))\n              return 0\n            ;;\n            \"snapshot\")\n              snapshot_commands=\"delete list pop push restore save\"\n              COMPREPLY=($(compgen -W \"${snapshot_commands}\" -- ${cur}))\n              return 0\n            ;;\n            *)\n            ;;\n        esac\n    fi\n\n    if [ $COMP_CWORD == 3 ]\n    then\n      action=\"${COMP_WORDS[COMP_CWORD-2]}\"\n      case \"$action\" in\n        \"up\")\n          if [ \"$prev\" == \"--no-provision\" ]\n          then\n            if [[ -d \"${vagrant_state_file}\" ]]\n            then\n              local vm_list=$(find \"${vagrant_state_file}/machines\" -mindepth 1 -maxdepth 1 -type d -exec basename {} \\;)\n            fi\n            COMPREPLY=($(compgen -W \"${vm_list}\" -- ${cur}))\n            return 0\n          fi\n          ;;\n        \"box\")\n          case \"$prev\" in\n            \"remove\"|\"repackage\")\n              local box_list=$(find \"${VAGRANT_HOME:-${HOME}/.vagrant.d}/boxes\" -mindepth 1 -maxdepth 1 -type d -exec basename {} \\; | sed -e 's/-VAGRANTSLASH-/\\//')\n              COMPREPLY=($(compgen -W \"${box_list}\" -- ${cur}))\n              return 0\n              ;;\n            \"add\")\n              local add_commands=\"\\\n                --name \\\n                --checksum \\\n                --checksum-type \\\n                -c --clean \\\n                -f --force \\\n                \"\n              if [[ $cur == -* ]]; then\n                COMPREPLY=($(compgen -W \"${add_commands}\" -- ${cur}))\n              else\n                COMPREPLY=($(compgen -o default -- \"${cur}\"))\n              fi\n              return 0\n              ;;\n            *)\n              ;;\n          esac\n          ;;\n        \"snapshot\")\n          case \"$prev\" in\n            \"restore\"|\"delete\")\n              local snapshot_list=$(vagrant snapshot list)\n              COMPREPLY=($(compgen -W \"${snapshot_list}\" -- ${cur}))\n              return 0\n            ;;\n          esac\n          ;;\n        *)\n          ;;\n      esac\n    fi\n\n}\ncomplete -F _vagrant vagrant\n"
  },
  {
    "path": "contrib/emacs/vagrant.el",
    "content": ";; Copyright IBM Corp. 2010, 2025\n;; SPDX-License-Identifier: BUSL-1.1\n\n;;--------------------------------------------------------------------\n;; Teach emacs to syntax highlight Vagrantfile as Ruby.\n;;\n;; Installation: Copy the line below into your emacs configuration,\n;; or drop this file anywhere in your \"~/.emacs.d\" directory and be\n;; sure to \"load\" it.\n;;--------------------------------------------------------------------\n(add-to-list 'auto-mode-alist '(\"Vagrantfile$\" . ruby-mode))\n"
  },
  {
    "path": "contrib/st/Ruby.sublime-settings",
    "content": "{\n\t\"extensions\":\n\t[\n\t\t\"Vagrantfile\"\n\t]\n}\n"
  },
  {
    "path": "contrib/sudoers/linux-fedora",
    "content": "Cmnd_Alias VAGRANT_EXPORTS_ADD = /usr/bin/tee -a /etc/exports\nCmnd_Alias VAGRANT_NFSD_CHECK = /usr/bin/systemctl status --no-pager nfs-server.service\nCmnd_Alias VAGRANT_NFSD_START = /usr/bin/systemctl start nfs-server.service\nCmnd_Alias VAGRANT_NFSD_APPLY = /usr/sbin/exportfs -ar\nCmnd_Alias VAGRANT_EXPORTS_REMOVE = /bin/sed -r -e * d -ibak /*/exports\nCmnd_Alias VAGRANT_EXPORTS_REMOVE_2 = /bin/cp /*/exports /etc/exports\n%vagrant ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD_CHECK, VAGRANT_NFSD_START, VAGRANT_NFSD_APPLY, VAGRANT_EXPORTS_REMOVE, VAGRANT_EXPORTS_REMOVE_2\n"
  },
  {
    "path": "contrib/sudoers/linux-suse",
    "content": "Cmnd_Alias VAGRANT_CHOWN = /usr/bin/chown 0\\:0 /tmp/vagrant[a-z0-9-]*\nCmnd_Alias VAGRANT_MV = /usr/bin/mv -f /tmp/vagrant[a-z0-9-]* /etc/exports\nCmnd_Alias VAGRANT_START = /usr/bin/systemctl start --no-pager nfs-server\nCmnd_Alias VAGRANT_STATUS = /usr/bin/systemctl status --no-pager nfs-server\nCmnd_Alias VAGRANT_APPLY = /usr/sbin/exportfs -ar\n%vagrant ALL=(root) NOPASSWD: VAGRANT_CHOWN, VAGRANT_MV, VAGRANT_START, VAGRANT_STATUS, VAGRANT_APPLY\n"
  },
  {
    "path": "contrib/sudoers/linux-ubuntu",
    "content": "# These work with Ubuntu - they might need tweaking for other distributions\n\nCmnd_Alias VAGRANT_EXPORTS_CHOWN = /bin/chown 0\\:0 /tmp/*\nCmnd_Alias VAGRANT_EXPORTS_MV = /bin/mv -f /tmp/* /etc/exports\nCmnd_Alias VAGRANT_NFSD_CHECK = /etc/init.d/nfs-kernel-server status\nCmnd_Alias VAGRANT_NFSD_START = /etc/init.d/nfs-kernel-server start\nCmnd_Alias VAGRANT_NFSD_APPLY = /usr/sbin/exportfs -ar\n%sudo ALL=(root) NOPASSWD: VAGRANT_EXPORTS_CHOWN, VAGRANT_EXPORTS_MV, VAGRANT_NFSD_CHECK, VAGRANT_NFSD_START, VAGRANT_NFSD_APPLY\n"
  },
  {
    "path": "contrib/sudoers/osx",
    "content": "Cmnd_Alias VAGRANT_EXPORTS_ADD = /usr/bin/tee -a /etc/exports\nCmnd_Alias VAGRANT_NFSD = /sbin/nfsd ^(restart|status|update)$\nCmnd_Alias VAGRANT_EXPORTS_REMOVE = /usr/bin/sed -E -e /*/ d -ibak /etc/exports\nCmnd_Alias VAGRANT_SMB_ADD = /usr/sbin/sharing -a * -S * -s * -g * -n *\nCmnd_Alias VAGRANT_SMB_REMOVE = /usr/sbin/sharing -r *\nCmnd_Alias VAGRANT_SMB_LIST = /usr/sbin/sharing -l\nCmnd_Alias VAGRANT_SMB_PLOAD = /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.smb.preferences.plist\nCmnd_Alias VAGRANT_SMB_DLOAD = /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.smbd.plist\nCmnd_Alias VAGRANT_SMB_DSTART = /bin/launchctl start com.apple.smbd\n%admin ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD, VAGRANT_EXPORTS_REMOVE, VAGRANT_SMB_ADD, VAGRANT_SMB_REMOVE, VAGRANT_SMB_LIST, VAGRANT_SMB_PLOAD, VAGRANT_SMB_DLOAD, VAGRANT_SMB_DSTART\n"
  },
  {
    "path": "contrib/vim/vagrantfile.vim",
    "content": "\" Teach vim to syntax highlight Vagrantfile as ruby\n\"\n\" Install: $HOME/.vim/plugin/vagrant.vim\n\" Author: Brandon Philips <brandon@ifup.org>\n\naugroup vagrant\n\tau!\n\tau BufRead,BufNewFile Vagrantfile set filetype=ruby\naugroup END\n"
  },
  {
    "path": "contrib/zsh/_vagrant",
    "content": "#compdef _vagrant vagrant\n\n# ZSH completion for Vagrant\n#\n# To use this completion add this to ~/.zshrc\n# fpath=(/path/to/this/dir $fpath)\n# compinit\n#\n# For development reload the function after making changes\n# unfunction _vagrant && autoload -U _vagrant \n\n\n__box_list ()\n{\n    _wanted application expl 'command' compadd $(command vagrant box list | awk '{print $1}' )\n}\n\n\n__plugin_list ()\n{\n    _wanted application expl 'command' compadd $(command vagrant plugin list | awk '{print $1}')\n}\n\n\nfunction _vagrant () {\n\n  local -a sub_commands && sub_commands=(\n    'autocomplete:manages autocomplete installation on host'\n    'box:manages boxes: installation, removal, etc.'\n    'cloud:manages everything related to Vagrant Cloud'\n    'destroy:stops and deletes all traces of the vagrant machine'\n    'global-status:outputs status Vagrant environments for this user'\n    'halt:stops the vagrant machine'\n    'help:shows the help for a subcommand'\n    'init:initializes a new Vagrant environment by creating a Vagrantfile'\n    'login:'\n    'package:packages a running vagrant environment into a box'\n    'plugin:manages plugins: install, uninstall, update, etc.'\n    'port:displays information about guest port mappings'\n    'powershell:connects to machine via powershell remoting'\n    'provision:provisions the vagrant machine'\n    'push:deploys code in this environment to a configured destination'\n    'rdp:connects to machine via RDP'\n    'reload:restarts vagrant machine, loads new Vagrantfile configuration'\n    'resume:resume a suspended vagrant machine'\n    'snapshot:manages snapshots: saving, restoring, etc.'\n    'ssh:connects to machine via SSH'\n    'ssh-config:outputs OpenSSH valid configuration to connect to the machine'\n    'status:outputs status of the vagrant machine'\n    'suspend:suspends the machine'\n    'up:starts and provisions the vagrant environment'\n    'upload:upload to machine via communicator'\n    'validate:validates the Vagrantfile'\n    'version:prints current and latest Vagrant version'\n    'winrm:executes commands on a machine via WinRM'\n    'winrm-config:outputs WinRM configuration to connect to the machine'\n)\n\n  local -a cloud_arguments && cloud_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a destroy_arguments && destroy_arguments=(\n    '--(no-)parallel=[Enable or disable parallelism if provider supports it (automatically enables force)]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a global_status_arguments && global_status_arguments=(\n    '--prune=[Prune invalid entries.]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a halt_arguments && halt_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a help_arguments && help_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a init_arguments && init_arguments=(\n    '--box-version=[Version of the box to add]'\n    '--output=[Output path for the box. _ for stdout]'\n    '--template=[Path to custom Vagrantfile template]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a login_arguments && login_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a package_arguments && package_arguments=(\n    '--base=[Name of a VM in VirtualBox to package as a base box (VirtualBox Only)]'\n    '--output=[Name of the file to output]'\n    '--include=[Comma separated additional files to package with the box]'\n    '--vagrantfile=[Vagrantfile to package with the box]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a port_arguments && port_arguments=(\n    '--guest=[Output the host port that maps to the given guest port]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a powershell_arguments && powershell_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a provision_arguments && provision_arguments=(\n    '--provision-with=[Enable only certain provisioners, by type or by name.]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a push_arguments && push_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a rdp_arguments && rdp_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a reload_arguments && reload_arguments=(\n    '--(no-)provision=[Enable or disable provisioning]'\n    '--provision-with=[Enable only certain provisioners, by type or by name.]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a resume_arguments && resume_arguments=(\n    '--(no-)provision=[Enable or disable provisioning]'\n    '--provision-with=[Enable only certain provisioners, by type or by name.]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a ssh_arguments && ssh_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a ssh_config_arguments && ssh_config_arguments=(\n    '--host=[Name the host for the config]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a status_arguments && status_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a suspend_arguments && suspend_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a up_arguments && up_arguments=(\n    '--(no-)provision=[Enable or disable provisioning]'\n    '--provision-with=[Enable only certain provisioners, by type or by name.]'\n    '--(no-)destroy-on-error=[Destroy machine if any fatal error happens (default to true)]'\n    '--(no-)parallel=[Enable or disable parallelism if provider supports it]'\n    '--provider=[Back the machine with a specific provider]'\n    '--(no-)install-provider=[If possible, install the provider if it isnt installed]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a upload_arguments && upload_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a validate_arguments && validate_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a version_arguments && version_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a winrm_arguments && winrm_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a winrm_config_arguments && winrm_config_arguments=(\n    '--host=[Name the host for the config]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\n\n\n  _arguments -C ':command:->command' '*::options:->options'\n\ncase $state in \n  (command)\n    _describe -t commands 'command' sub_commands\n    return\n  ;;\n\n  (options)\n    case $line[1] in\n  autocomplete)\n    __vagrant-autocomplete ;;\n  box)\n    __vagrant-box ;;\n  cloud)\n    _arguments -s -S : $cloud_arguments  ;;\n  destroy)\n    _arguments -s -S : $destroy_arguments  ;;\n  global-status)\n    _arguments -s -S : $global_status_arguments  ;;\n  halt)\n    _arguments -s -S : $halt_arguments  ;;\n  help)\n    _arguments -s -S : $help_arguments  ;;\n  init)\n    _arguments -s -S : $init_arguments  ;;\n  login)\n    _arguments -s -S : $login_arguments  ;;\n  package)\n    _arguments -s -S : $package_arguments  ;;\n  plugin)\n    __vagrant-plugin ;;\n  port)\n    _arguments -s -S : $port_arguments  ;;\n  powershell)\n    _arguments -s -S : $powershell_arguments  ;;\n  provision)\n    _arguments -s -S : $provision_arguments  ;;\n  push)\n    _arguments -s -S : $push_arguments  ;;\n  rdp)\n    _arguments -s -S : $rdp_arguments  ;;\n  reload)\n    _arguments -s -S : $reload_arguments  ;;\n  resume)\n    _arguments -s -S : $resume_arguments  ;;\n  snapshot)\n    __vagrant-snapshot ;;\n  ssh)\n    _arguments -s -S : $ssh_arguments  ;;\n  ssh-config)\n    _arguments -s -S : $ssh_config_arguments  ;;\n  status)\n    _arguments -s -S : $status_arguments  ;;\n  suspend)\n    _arguments -s -S : $suspend_arguments  ;;\n  up)\n    _arguments -s -S : $up_arguments  ;;\n  upload)\n    _arguments -s -S : $upload_arguments  ;;\n  validate)\n    _arguments -s -S : $validate_arguments  ;;\n  version)\n    _arguments -s -S : $version_arguments  ;;\n  winrm)\n    _arguments -s -S : $winrm_arguments  ;;\n  winrm-config)\n    _arguments -s -S : $winrm_config_arguments  ;;\nesac\n  ;;\nesac\n\n}\n\nfunction __vagrant-box () {\n\n  local -a sub_commands && sub_commands=(\n    'add:add'\n    'list:list'\n    'outdated:outdated'\n    'prune:prune'\n    'remove:remove'\n    'repackage:repackage'\n    'update:update'\n)\n\n  local -a add_arguments && add_arguments=(\n    '--insecure=[Do not validate SSL certificates]'\n    '--cacert=[CA certificate for SSL download]'\n    '--capath=[CA certificate directory for SSL download]'\n    '--cert=[A client SSL cert, if needed]'\n    '--location-trusted=[Trust Location header from HTTP redirects and use the same credentials for subsequent urls as for the initial one]'\n    '--provider=[Provider the box should satisfy]'\n    '--box-version=[Constrain version of the added box]'\n    '--checksum=[Checksum for the box]'\n    '--checksum-type=[Checksum type (md5, sha1, sha256)]'\n    '--name=[Name of the box]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a list_arguments && list_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a outdated_arguments && outdated_arguments=(\n    '--global=[Check all boxes installed]'\n    '--insecure=[Do not validate SSL certificates]'\n    '--cacert=[CA certificate for SSL download]'\n    '--capath=[CA certificate directory for SSL download]'\n    '--cert=[A client SSL cert, if needed]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a prune_arguments && prune_arguments=(\n    '--name=[The specific box name to check for outdated versions.]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a remove_arguments && remove_arguments=(\n    '--provider=[The specific provider type for the box to remove]'\n    '--box-version=[The specific version of the box to remove]'\n    '--all=[Remove all available versions of the box]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a repackage_arguments && repackage_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a update_arguments && update_arguments=(\n    '--box=[__box flag.]'\n    '--box=[Update a specific box]'\n    '--provider=[Update box with specific provider]'\n    '--insecure=[Do not validate SSL certificates]'\n    '--cacert=[CA certificate for SSL download]'\n    '--capath=[CA certificate directory for SSL download]'\n    '--cert=[A client SSL cert, if needed]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\n\n\n  _arguments -C ':command:->command' '*::options:->options'\n\ncase $state in \n  (command)\n    _describe -t commands 'command' sub_commands\n    return\n  ;;\n\n  (options)\n    case $line[1] in\n  add)\n    _arguments -s -S : $add_arguments  ;;\n  list)\n    _arguments -s -S : $list_arguments  ;;\n  outdated)\n    _arguments -s -S : $outdated_arguments  ;;\n  prune)\n    _arguments -s -S : $prune_arguments  ;;\n  remove)\n    _arguments -s -S : $remove_arguments ':feature:__box_list' ;;\n  repackage)\n    _arguments -s -S : $repackage_arguments ':feature:__box_list' ;;\n  update)\n    _arguments -s -S : $update_arguments ':feature:__box_list' ;;\nesac\n  ;;\nesac\n\n}\n\nfunction __vagrant-snapshot () {\n\n  local -a sub_commands && sub_commands=(\n    'delete:delete'\n    'list:list'\n    'pop:pop'\n    'push:push'\n    'restore:restore'\n    'save:save'\n)\n\n  local -a delete_arguments && delete_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a list_arguments && list_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a pop_arguments && pop_arguments=(\n    '--(no-)provision=[Enable or disable provisioning]'\n    '--provision-with=[Enable only certain provisioners, by type or by name.]'\n    '--no-delete=[Dont delete the snapshot after the restore]'\n    '--no-start=[Dont start the snapshot after the restore]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a push_arguments && push_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a restore_arguments && restore_arguments=(\n    '--(no-)provision=[Enable or disable provisioning]'\n    '--provision-with=[Enable only certain provisioners, by type or by name.]'\n    '--no-start=[Dont start the snapshot after the restore]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a save_arguments && save_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\n\n\n  _arguments -C ':command:->command' '*::options:->options'\n\ncase $state in \n  (command)\n    _describe -t commands 'command' sub_commands\n    return\n  ;;\n\n  (options)\n    case $line[1] in\n  delete)\n    _arguments -s -S : $delete_arguments  ;;\n  list)\n    _arguments -s -S : $list_arguments  ;;\n  pop)\n    _arguments -s -S : $pop_arguments  ;;\n  push)\n    _arguments -s -S : $push_arguments  ;;\n  restore)\n    _arguments -s -S : $restore_arguments  ;;\n  save)\n    _arguments -s -S : $save_arguments  ;;\nesac\n  ;;\nesac\n\n}\n\nfunction __vagrant-plugin () {\n\n  local -a sub_commands && sub_commands=(\n    'expunge:expunge'\n    'install:install'\n    'license:license'\n    'list:list'\n    'repair:repair'\n    'uninstall:uninstall'\n    'update:update'\n)\n\n  local -a expunge_arguments && expunge_arguments=(\n    '--force=[Do not prompt for confirmation]'\n    '--local=[Include plugins from local project for expunge]'\n    '--local-only=[Only expunge local project plugins]'\n    '--global-only=[Only expunge global plugins]'\n    '--reinstall=[Reinstall current plugins after expunge]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a install_arguments && install_arguments=(\n    '--entry-point=[The name of the entry point file for loading the plugin.]'\n    '--plugin-clean-sources=[Remove all plugin sources defined so far (including defaults)]'\n    '--plugin-source=[__plugin_source PLUGIN_SOURCE]'\n    '--plugin-version=[__plugin_version PLUGIN_VERSION]'\n    '--local=[Install plugin for local project only]'\n    '--verbose=[Enable verbose output for plugin installation]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a license_arguments && license_arguments=(\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a list_arguments && list_arguments=(\n    '--local=[Include local project plugins]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a repair_arguments && repair_arguments=(\n    '--local=[Repair plugins in local project]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a uninstall_arguments && uninstall_arguments=(\n    '--local=[Remove plugin from local project]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\nlocal -a update_arguments && update_arguments=(\n    '--local=[Update plugin in local project]'\n    '--(no-)color=[Enable or disable color output]'\n    '--machine-readable=[Enable machine readable output]'\n    '--debug=[Enable debug output]'\n    '--timestamp=[Enable timestamps on log output]'\n    '--debug-timestamp=[Enable debug output with timestamps]'\n    '--no-tty=[Enable non_interactive output]'\n)\n\n\n\n  _arguments -C ':command:->command' '*::options:->options'\n\ncase $state in \n  (command)\n    _describe -t commands 'command' sub_commands\n    return\n  ;;\n\n  (options)\n    case $line[1] in\n  expunge)\n    _arguments -s -S : $expunge_arguments  ;;\n  install)\n    _arguments -s -S : $install_arguments  ;;\n  license)\n    _arguments -s -S : $license_arguments  ;;\n  list)\n    _arguments -s -S : $list_arguments  ;;\n  repair)\n    _arguments -s -S : $repair_arguments ':feature:__plugin_list' ;;\n  uninstall)\n    _arguments -s -S : $uninstall_arguments ':feature:__plugin_list' ;;\n  update)\n    _arguments -s -S : $update_arguments ':feature:__plugin_list' ;;\nesac\n  ;;\nesac\n\n}\n"
  },
  {
    "path": "contrib/zsh/generate_zsh_completion.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'open3'\n\nHEAD = \"\"\"#compdef _vagrant vagrant\n\n# ZSH completion for Vagrant\n#\n# To use this completion add this to ~/.zshrc\n# fpath=(/path/to/this/dir $fpath)\n# compinit\n#\n# For development reload the function after making changes\n# unfunction _vagrant && autoload -U _vagrant \n\"\"\"\n\nBOX_LIST_FUNCTION = \"\"\"\n__box_list ()\n{\n    _wanted application expl 'command' compadd $(command vagrant box list | awk '{print $1}' )\n}\n\"\"\"\n\nPLUGIN_LIST_FUNCTION = \"\"\"\n__plugin_list ()\n{\n    _wanted application expl 'command' compadd $(command vagrant plugin list | awk '{print $1}')\n}\n\"\"\"\n\nADD_FEATURE_FLAGS = [\"remove\", \"repackage\", \"update\", \"repair\", \"uninstall\"]\nVAGRANT_COMMAND = \"vagrant\"  \n\nFLAG_REGEX = /--(\\S)*/\nCMDS_REGEX = /^(\\s){1,}(\\w)(\\S)*/\n\ndef make_string_script_safe(s)\n  s.gsub(\"[\",\"(\").gsub(\"]\",\")\").gsub(\"-\",\"_\").gsub(\"'\", \"\")\nend\n\ndef remove_square_brakets(s)\n  s.gsub(\"[\",\"(\").gsub(\"]\",\")\")\nend\n\ndef format_flags(group_name, flags)\n  group_name = make_string_script_safe(group_name)\n  opts_str = \"local -a #{group_name} && #{group_name}=(\\n\"\n  flags.each do |flag, desc|\n    opts_str = opts_str + \"    '#{remove_square_brakets(flag)}=[#{make_string_script_safe(desc)}]'\\n\"\n  end\n  opts_str + \")\"\nend\n\ndef format_subcommand(group_name, cmds)\n  opts_str = \"local -a #{group_name} && #{group_name}=(\\n\"\n  cmds.each do |cmd, desc|\n    opts_str = opts_str + \"    '#{cmd}:#{desc}'\\n\"\n  end\n  opts_str + \")\"\nend\n\ndef format_case(group_name, cmds, cmd_list, feature_string)\n  case_str = \"\"\"case $state in \n  (command)\n    _describe -t commands 'command' #{group_name}\n    return\n  ;;\n\n  (options)\n    case $line[1] in\n\"\"\"\n\n  cmds.each do |cmd, desc|\n    if cmd_list.include?(cmd)\n      case_append = \"\"\"  #{cmd})\n    _arguments -s -S : $#{make_string_script_safe(cmd)}_arguments #{feature_string if ADD_FEATURE_FLAGS.include?(cmd)} ;;\n\"\"\"\n    else\n      case_append = \"\"\"  #{cmd})\n    __vagrant-#{cmd} ;;\n\"\"\"\n    end\n    case_str = case_str + case_append\n  end\n\n  case_str = case_str + \"\"\"esac\n  ;;\nesac\n\"\"\"\nend\n\ndef extract_flags(top_level_commands)\n  flags = top_level_commands.map { |c| [c.match(FLAG_REGEX)[0], c.split(\"  \")[-1].strip] if c.strip.start_with?(\"--\") }.compact\nend\n\ndef extract_subcommand(top_level_commands)\n  cmds = top_level_commands.map { |c| [c.match(CMDS_REGEX)[0].strip, c.split(\"  \")[-1].strip] if c.match(CMDS_REGEX) }.compact\nend\n\ndef get_top_level_commands(root_command, cmd_list)\n  stdout, stderr, status = Open3.capture3(\"vagrant #{root_command} -h\")\n  top_level_commands = stdout.split(\"\\n\")\n  \n  root_subcommand = extract_subcommand(top_level_commands)\n  commands = format_subcommand(\"sub_commands\", root_subcommand)\n  if root_command == \"box\"\n    feature_string = \"':feature:__box_list'\"\n  elsif root_command == \"plugin\"\n    feature_string = \"':feature:__plugin_list'\"\n  else\n    feature_string = \"\"\n  end\n  case_string = format_case(\"sub_commands\", root_subcommand, cmd_list, feature_string)\n\n  flags_def = \"\"\n  root_subcommand.each do |cmd, desc|\n    next if !cmd_list.include?(cmd)\n    stdout, stderr, status = Open3.capture3(\"vagrant #{root_command} #{cmd} -h\")\n    cmd_help = stdout.split(\"\\n\")\n    flags_def = flags_def + format_flags(\"#{cmd}_arguments\", extract_flags(cmd_help)) + \"\\n\\n\"\n  end\n\n  return commands, flags_def, case_string\nend\n\ndef format_script(root_command, subcommands, function_name)\n  top_level_commands, top_level_args, state_case = get_top_level_commands(root_command, subcommands)\n  \n  script = \"\"\"\nfunction #{function_name} () {\n\n  #{top_level_commands}\n\n  #{top_level_args}\n\n  _arguments -C ':command:->command' '*::options:->options'\n\n#{state_case}\n}\n\"\"\"\nend\n\ndef generate_script\n  subcommand_list = {\n    \"\" => [\"cloud\", \"destroy\", \"global-status\", \"halt\", \"help\", \"login\", \"init\", \"package\", \"port\", \"powershell\", \"provision\", \"push\", \"rdp\", \"reload\", \"resume\", \"ssh\", \"ssh-config\", \"status\", \"suspend\", \"up\", \"upload\", \"validate\", \"version\", \"winrm\", \"winrm-config\"],\n    \"box\" => [\"add\", \"list\", \"outdated\", \"prune\", \"remove\", \"repackage\", \"update\"],\n    \"snapshot\" => [\"delete\", \"list\", \"pop\", \"push\", \"restore\", \"save\"],\n    \"plugin\" => [\"install\", \"expunge\", \"license\", \"list\", \"repair\", \"uninstall\", \"update\"],\n  }\n  \n  script = \"\"\"#{HEAD}\n#{BOX_LIST_FUNCTION}\n#{PLUGIN_LIST_FUNCTION}\n\"\"\"\n\n  subcommand_list.each do |cmd, opts|\n    if cmd != \"\"\n      function_name = \"__vagrant-#{cmd}\"\n    else\n      function_name = \"_vagrant\"\n    end\n    script = script + format_script(cmd, opts, function_name)\n  end\n  script\nend\n\nputs generate_script\n"
  },
  {
    "path": "ext/vagrant/vagrant_ssl/extconf.rb",
    "content": "#!/usr/bin/env ruby\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\nrequire \"mkmf\"\nrequire \"shellwords\"\n\n# If extra flags are included via the environment, append them\nappend_cflags(Shellwords.shellwords(ENV[\"CFLAGS\"])) if ENV[\"CFLAGS\"]\nappend_cppflags(Shellwords.shellwords(ENV[\"CPPFLAGS\"])) if ENV[\"CPPFLAGS\"]\nappend_ldflags(Shellwords.shellwords(ENV[\"LDFLAGS\"])) if ENV[\"LDFLAGS\"]\n\nif have_header(\"openssl/opensslv.h\")\n  append_ldflags([\"-lssl\", \"-lcrypto\"])\n  create_makefile(\"vagrant/vagrant_ssl\")\nelse\n  # If the header file isn't found, just create a dummy\n  # Makefile and stub the library to make it a noop\n  File.open(\"Makefile\", \"wb\") do |f|\n    f.write(dummy_makefile(__dir__).join(\"\\n\"))\n  end\n  FileUtils.touch(\"vagrant_ssl.so\")\nend\n"
  },
  {
    "path": "ext/vagrant/vagrant_ssl/vagrant_ssl.c",
    "content": "/*\n * Copyright IBM Corp. 2010, 2025\n * SPDX-License-Identifier: BUSL-1.1\n */\n\n#include \"vagrant_ssl.h\"\n\n#if defined(_VAGRANT_SSL_PROVIDER_)\n\nstatic VALUE vagrant_ssl_load(VALUE self) {\n  OSSL_PROVIDER *legacy;\n  OSSL_PROVIDER *deflt;\n\n  legacy = OSSL_PROVIDER_load(NULL, \"legacy\");\n  if(legacy == NULL) {\n    rb_raise(rb_eStandardError, \"Failed to load OpenSSL legacy provider\");\n    return self;\n  }\n\n  deflt = OSSL_PROVIDER_load(NULL, \"default\");\n  if(deflt == NULL) {\n    rb_raise(rb_eStandardError, \"Failed to load OpenSSL default provider\");\n    return self;\n  }\n}\n\nvoid Init_vagrant_ssl(void) {\n  VALUE vagrant;\n  vagrant = rb_define_module(\"Vagrant\");\n  rb_define_singleton_method(vagrant, \"vagrant_ssl_load\", vagrant_ssl_load, 0);\n}\n\n#else\n\nvoid Init_vagrant_ssl(void) {}\n\n#endif\n"
  },
  {
    "path": "ext/vagrant/vagrant_ssl/vagrant_ssl.h",
    "content": "/*\n * Copyright IBM Corp. 2010, 2025\n * SPDX-License-Identifier: BUSL-1.1\n */\n\n#if !defined(_VAGRANT_SSL_H_)\n#define _VAGRANT_SSL_H_\n\n#include <openssl/opensslv.h>\n#if OPENSSL_VERSION_NUMBER >= (3 << 28)\n#define _VAGRANT_SSL_PROVIDER_\n\n#include <ruby.h>\n#include <openssl/provider.h>\n#endif\n\nvoid Init_vagrant_ssl(void);\n\n#endif\n"
  },
  {
    "path": "keys/README.md",
    "content": "# Insecure Keypairs\n\nThese keys are the \"insecure\" public/private keypair we offer to\n[base box creators](https://www.vagrantup.com/docs/boxes/base.html) for use in their base boxes so that\nvagrant installations can automatically SSH into the boxes.\n\n# Vagrant Keypairs\n\nThere are currently two \"insecure\" public/private keypairs for \nVagrant. One keypair was generated using the older RSA algorithm\nand the other keypair was generated using the more recent ED25519\nalgorithm. \n\nThe `vagrant.pub` file includes the public key for both keypairs. It \nis important for box creators to include both keypairs as versions of \nVagrant prior to 2.3.8 will only use the RSA private key.\n\n# Custom Keys\n\nIf you're working with a team or company or with a custom box and\nyou want more secure SSH, you should create your own keypair\nand configure the private key in the Vagrantfile with\n`config.ssh.private_key_path`\n"
  },
  {
    "path": "keys/vagrant",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI\nw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP\nkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2\nhMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO\nTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW\nyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd\nELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1\nBw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf\nTK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK\niqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A\nsClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf\n4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP\ncXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk\nEjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN\nCAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX\n3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG\nYEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj\n3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+\ndBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz\n6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC\nP9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF\nllYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ\nkda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH\n+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ\nNE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "keys/vagrant.key.ed25519",
    "content": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\nQyNTUxOQAAACDdWHcQaTZc8Q6nycsP0CqMNRfsLxvYVxqKosrHyTp+WAAAAJj2TBMT9kwT\nEwAAAAtzc2gtZWQyNTUxOQAAACDdWHcQaTZc8Q6nycsP0CqMNRfsLxvYVxqKosrHyTp+WA\nAAAEAveRHRHSCjIxbNKHDRzezD0U3R3UEEmS7R33fzvPQAD91YdxBpNlzxDqfJyw/QKow1\nF+wvG9hXGoqiysfJOn5YAAAAEHNwb3hAdmFncmFudC1kZXYBAgMEBQ==\n-----END OPENSSH PRIVATE KEY-----\n"
  },
  {
    "path": "keys/vagrant.key.rsa",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI\nw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP\nkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2\nhMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO\nTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW\nyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd\nELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1\nBw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf\nTK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK\niqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A\nsClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf\n4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP\ncXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk\nEjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN\nCAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX\n3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG\nYEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj\n3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+\ndBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz\n6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC\nP9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF\nllYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ\nkda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH\n+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ\nNE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "keys/vagrant.pub",
    "content": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN1YdxBpNlzxDqfJyw/QKow1F+wvG9hXGoqiysfJOn5Y vagrant insecure public key\n"
  },
  {
    "path": "keys/vagrant.pub.ed25519",
    "content": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN1YdxBpNlzxDqfJyw/QKow1F+wvG9hXGoqiysfJOn5Y vagrant insecure public key\n"
  },
  {
    "path": "keys/vagrant.pub.rsa",
    "content": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key\n"
  },
  {
    "path": "lib/vagrant/action/builder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    # Action builder which provides a nice DSL for building up\n    # a middleware sequence for Vagrant actions. This code is based\n    # heavily off of `Rack::Builder` and `ActionDispatch::MiddlewareStack`\n    # in Rack and Rails, respectively.\n    #\n    # Usage\n    #\n    # Building an action sequence is very easy:\n    #\n    #     app = Vagrant::Action::Builder.new.tap do |b|\n    #       b.use MiddlewareA\n    #       b.use MiddlewareB\n    #     end\n    #\n    #     Vagrant::Action.run(app)\n    #\n    class Builder\n      # Container for Action arguments\n      MiddlewareArguments = Struct.new(:parameters, :block, :keywords, keyword_init: true)\n      # Item within the stack\n      StackItem = Struct.new(:middleware, :arguments, keyword_init: true)\n\n      # This is the stack of middlewares added. This should NOT be used\n      # directly.\n      #\n      # @return [Array]\n      attr_reader :stack\n\n      # Action Hooks allow plugin authors to inject their code wherever they\n      # want in the action stack. The methods they get are:\n      #\n      #   - prepend/append, which puts their middleware at the beginning or end\n      #     of the whole stack\n      #   - before/after, which attaches their middleware to an existing item\n      #     in the stack\n      #\n      # Applying Action Hooks properly gets tricky because the action stack\n      # becomes deeply nested with things like Action::Builtin::Call and\n      # Builder#use(other_builder). The way it breaks down is:\n      #\n      #  - prepend/append hooks should be applied only at the top level stack,\n      #    so they run once at the beginning or end\n      #  - before/after hooks should be applied in every sub-builder, because\n      #    they will only actually attach if they find their target sibling\n      #\n      # We achieve this behavior by tracking if we are a \"primary\" Builder, and\n      # only running prepend/append operations when we are.\n      #\n      # Note this difference only applies to action_hooks registered with\n      # machine action names and not action_hooks which reference middleware\n      # directly, which only support prepend/append and are handled in\n      # #apply_dynamic_updates.\n      #\n      # @return [Boolean] true if this is a primary / top-level Builder\n      # @see Vagrant::Action::PrimaryRunner\n      # @see Vagrant::Action::Hook\n      attr_accessor :primary\n\n      # This is a shortcut for a middleware sequence with only one item\n      # in it. For a description of the arguments and the documentation, please\n      # see {#use} instead.\n      #\n      # @return [Builder]\n      def self.build(middleware, *args, **keywords, &block)\n        new.use(middleware, *args, **keywords, &block)\n      end\n\n      def initialize\n        @stack = []\n        @logger = Log4r::Logger.new(\"vagrant::action::builder\")\n      end\n\n      # Implement a custom copy that copies the stack variable over so that\n      # we don't clobber that.\n      def initialize_copy(original)\n        super\n\n        @stack = original.stack.dup\n      end\n\n      # Returns a mergeable version of the builder. If `use` is called with\n      # the return value of this method, then the stack will merge, instead\n      # of being treated as a separate single middleware.\n      def flatten\n        lambda do |env|\n          self.call(env)\n        end\n      end\n\n      # Adds a middleware class to the middleware stack. Any additional\n      # args and a block, if given, are saved and passed to the initializer\n      # of the middleware.\n      #\n      # @param [Class] middleware The middleware class\n      def use(middleware, *args, **keywords, &block)\n        item = StackItem.new(\n          middleware: middleware,\n          arguments: MiddlewareArguments.new(\n            parameters: args,\n            keywords: keywords,\n            block: block\n          )\n        )\n\n        if middleware.kind_of?(Builder)\n          # Merge in the other builder's stack into our own\n          self.stack.concat(middleware.stack)\n        else\n          self.stack << item\n        end\n\n        self\n      end\n\n      # Inserts a middleware at the given index or directly before the\n      # given middleware object.\n      def insert(idx_or_item, middleware, *args, **keywords, &block)\n        item = StackItem.new(\n          middleware: middleware,\n          arguments: MiddlewareArguments.new(\n            parameters: args,\n            keywords: keywords,\n            block: block\n          )\n        )\n\n        if idx_or_item.is_a?(Integer)\n          index = idx_or_item\n        else\n          index = self.index(idx_or_item)\n        end\n\n        raise \"no such middleware to insert before: #{index.inspect}\" unless index\n\n        if middleware.kind_of?(Builder)\n          middleware.stack.reverse.each do |stack_item|\n            stack.insert(index, stack_item)\n          end\n        else\n          stack.insert(index, item)\n        end\n      end\n\n      alias_method :insert_before, :insert\n\n      # Inserts a middleware after the given index or middleware object.\n      def insert_after(idx_or_item, middleware, *args, **keywords, &block)\n        if idx_or_item.is_a?(Integer)\n          index = idx_or_item\n        else\n          index = self.index(idx_or_item)\n        end\n\n        raise \"no such middleware to insert after: #{index.inspect}\" unless index\n        insert(index + 1, middleware, *args, &block)\n      end\n\n      # Replaces the given middleware object or index with the new\n      # middleware.\n      def replace(index, middleware, *args, **keywords, &block)\n        if index.is_a?(Integer)\n          delete(index)\n          insert(index, middleware, *args, **keywords, &block)\n        else\n          insert_before(index, middleware, *args, **keywords, &block)\n          delete(index)\n        end\n      end\n\n      # Deletes the given middleware object or index\n      def delete(index)\n        index = self.index(index) unless index.is_a?(Integer)\n        stack.delete_at(index)\n      end\n\n      # Runs the builder stack with the given environment.\n      def call(env)\n        to_app(env).call(env)\n      end\n\n      # Returns the numeric index for the given middleware object.\n      #\n      # @param [Object] object The item to find the index for\n      # @return [Integer]\n      def index(object)\n        stack.each_with_index do |item, i|\n          return i if item == object\n          return i if item.middleware == object\n          return i if item.middleware.respond_to?(:name) &&\n            item.middleware.name == object\n        end\n\n        nil\n      end\n\n      # Converts the builder stack to a runnable action sequence.\n      #\n      # @param [Hash] env The action environment hash\n      # @return [Warden] A callable object\n      def to_app(env)\n        # Start with a duplicate of ourself which can\n        # be modified\n        builder = self.dup\n\n        # Apply all dynamic modifications of the stack. This\n        # will generate dynamic hooks for all actions within\n        # the stack, load any triggers for action classes, and\n        # apply them to the builder's stack\n        builder.apply_dynamic_updates(env)\n\n        # Now that the stack is fully expanded, apply any\n        # action hooks that may be defined so they are on\n        # the outermost locations of the stack\n        builder.apply_action_name(env)\n\n        # Wrap the middleware stack with the Warden to provide a consistent\n        # and predictable behavior upon exceptions.\n        Warden.new(builder.stack.dup, env)\n      end\n\n      # Find any action hooks or triggers which have been defined\n      # for items within the stack. Update the stack with any\n      # hooks or triggers found.\n      #\n      # @param [Hash] env Call environment\n      # @return [Builder] self\n      def apply_dynamic_updates(env)\n        triggers = env[:triggers]\n\n        # Use a Hook as a convenient interface for injecting\n        # any applicable trigger actions within the stack\n        machine_name = env[:machine].name if env[:machine]\n\n        # Iterate over all items in the stack and apply new items\n        # into the hook as they are found. Must be sure to dup the\n        # stack here since we are modifying the stack in the loop.\n        stack.dup.each do |item|\n          hook = Hook.new\n\n          action = item.first\n          next if action.is_a?(Proc)\n\n          # Start with adding any action triggers that may be defined\n          if triggers && !triggers.find(action, :before, machine_name, :action).empty?\n            hook.prepend(Vagrant::Action::Builtin::Trigger,\n              action.name, triggers, :before, :action)\n          end\n\n          if triggers && !triggers.find(action, :after, machine_name, :action).empty?\n            hook.append(Vagrant::Action::Builtin::Trigger,\n              action.name, triggers, :after, :action)\n          end\n\n          # Next look for any hook triggers that may be defined against\n          # the dynamically generated action class hooks\n          if triggers && !triggers.find(action, :before, machine_name, :hook).empty?\n            hook.prepend(Vagrant::Action::Builtin::Trigger,\n              action.name, triggers, :before, :hook)\n          end\n\n          if triggers && !triggers.find(action, :after, machine_name, :hook).empty?\n            hook.append(Vagrant::Action::Builtin::Trigger,\n              action.name, triggers, :after, :hook)\n          end\n\n          # Finally load any registered hooks for dynamically generated\n          # action class based hooks\n          Vagrant.plugin(\"2\").manager.find_action_hooks(action).each do |hook_proc|\n            hook_proc.call(hook)\n          end\n\n          hook.apply(self, root: item)\n        end\n\n        # Apply the hook to ourself to update the stack\n        self\n      end\n\n      # If action hooks have not already been set, this method\n      # will perform three tasks:\n      #   1. Load any hook triggers defined for the action_name\n      #   2. Load any action_hooks defined from plugins\n      #   3. Load any action triggers based on machine action called (not action classes)\n      #\n      # @param [Hash] env Call environment\n      # @return [Builder]\n      def apply_action_name(env)\n        env[:builder_raw_applied] ||= []\n        return self if !env[:action_name]\n\n        hook = Hook.new\n        machine_name = env[:machine].name if env[:machine]\n\n        # Start with loading any hook triggers if applicable\n        if env[:triggers]\n          if !env[:triggers].find(env[:action_name], :before, machine_name, :hook).empty?\n            hook.prepend(Vagrant::Action::Builtin::Trigger,\n              env[:action_name], env[:triggers], :before, :hook)\n          end\n          if !env[:triggers].find(env[:action_name], :after, machine_name, :hook).empty?\n            hook.append(Vagrant::Action::Builtin::Trigger,\n              env[:action_name], env[:triggers], :after, :hook)\n          end\n        end\n\n        # Next we load up all the action hooks that plugins may\n        # have defined\n        action_hooks = Vagrant.plugin(\"2\").manager.action_hooks(env[:action_name])\n        action_hooks.each do |hook_proc|\n          hook_proc.call(hook)\n        end\n\n        # Finally load any action triggers defined. The action triggers\n        # are the originally implemented trigger style. They run before\n        # and after specific provider actions (like :up, :halt, etc) and\n        # are different from true action triggers\n        if env[:triggers] && !env[:builder_raw_applied].include?(env[:raw_action_name])\n          env[:builder_raw_applied] << env[:raw_action_name]\n\n          if !env[:triggers].find(env[:raw_action_name], :before, machine_name, :action, all: true).empty?\n            hook.prepend(Vagrant::Action::Builtin::Trigger,\n              env[:raw_action_name], env[:triggers], :before, :action, all: true)\n          end\n          if !env[:triggers].find(env[:raw_action_name], :after, machine_name, :action, all: true).empty?\n            # NOTE: These after triggers need to be delayed before running to\n            #       allow the rest of the call stack to complete before being\n            #       run. The delayed action is prepended to the stack (not appended)\n            #       to ensure it is called first, which results in it properly\n            #       waiting for everything to finish before itself completing.\n            builder = self.class.build(Vagrant::Action::Builtin::Trigger,\n              env[:raw_action_name], env[:triggers], :after, :action, all: true)\n            hook.prepend(Vagrant::Action::Builtin::Delayed, builder)\n          end\n        end\n\n        # If the hooks are empty, then there was nothing to apply and\n        # we can just send ourself back\n        return self if hook.empty?\n\n        # Apply all the hooks to the new builder instance\n        hook.apply(self, {\n          # Only primary builders run prepend/append, otherwise nested builders\n          # would duplicate hooks. See explanation at self#primary.\n          no_prepend_or_append: !primary,\n        })\n\n        self\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/box_add.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"digest/sha1\"\nrequire \"log4r\"\nrequire \"pathname\"\nrequire \"uri\"\n\nrequire \"vagrant/box_metadata\"\nrequire \"vagrant/util/downloader\"\nrequire \"vagrant/util/file_checksum\"\nrequire \"vagrant/util/file_mutex\"\nrequire \"vagrant/util/platform\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This middleware will download a remote box and add it to the\n      # given box collection.\n      class BoxAdd\n        # This is the size in bytes that if a file exceeds, is considered\n        # to NOT be metadata.\n        METADATA_SIZE_LIMIT = 20971520\n\n        # This is the amount of time to \"resume\" downloads if a partial box\n        # file already exists.\n        RESUME_DELAY = 24 * 60 * 60\n\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::box_add\")\n          @parser = URI::RFC2396_Parser.new\n        end\n\n        def call(env)\n          @download_interrupted = false\n\n          unless env[:box_name].nil?\n            begin\n              if URI.parse(env[:box_name]).kind_of?(URI::HTTP)\n                env[:ui].warn(I18n.t(\"vagrant.box_add_url_warn\"))\n              end\n            rescue URI::InvalidURIError\n              # do nothing\n            end\n          end\n\n          url = Array(env[:box_url]).map do |u|\n            u = u.gsub(\"\\\\\", \"/\")\n            if Util::Platform.windows? && u =~ /^[a-z]:/i\n              # On Windows, we need to be careful about drive letters\n              u = \"file:///#{@parser.escape(u)}\"\n            end\n\n            if u =~ /^[a-z0-9]+:.*$/i && !u.start_with?(\"file://\")\n              # This is not a file URL... carry on\n              next u\n            end\n\n            # Expand the path and try to use that, if possible\n            p = File.expand_path(@parser.unescape(u.gsub(/^file:\\/\\//, \"\")))\n            p = Util::Platform.cygwin_windows_path(p)\n            next \"file://#{@parser.escape(p.gsub(\"\\\\\", \"/\"))}\" if File.file?(p)\n\n            u\n          end\n\n          # If we received a shorthand URL (\"mitchellh/precise64\"),\n          # then expand it properly.\n          expanded = false\n          # Mark if only a single url entry was provided\n          single_entry = url.size == 1\n\n          url = url.map do |url_entry|\n            if url_entry =~ /^[^\\/]+\\/[^\\/]+$/ && !File.file?(url_entry)\n              server = Vagrant.server_url env[:box_server_url]\n              raise Errors::BoxServerNotSet if !server\n\n              expanded = true\n              # If only a single entry, expand to both the API endpoint and\n              # the direct shorthand endpoint.\n              if single_entry\n                url_entry = [\n                  \"#{server}/api/v2/vagrant/#{url_entry}\",\n                  \"#{server}/#{url_entry}\"\n                ]\n              else\n                url_entry = \"#{server}/#{url_entry}\"\n              end\n            end\n\n            url_entry\n          end.flatten\n\n          # Call the hook to transform URLs into authenticated URLs.\n          # In the case we don't have a plugin that does this, then it\n          # will just return the same URLs.\n          hook_env    = env[:hook].call(\n            :authenticate_box_url, box_urls: url.dup)\n          authed_urls = hook_env[:box_urls]\n          if !authed_urls || authed_urls.length != url.length\n            raise \"Bad box authentication hook, did not generate proper results.\"\n          end\n\n          # Test if any of our URLs point to metadata\n          is_metadata_results = authed_urls.map do |u|\n            begin\n              metadata_url?(u, env)\n            rescue Errors::DownloaderError => e\n              e\n            end\n          end\n\n          # If only a single entry was provided, and it was expanded,\n          # inspect the metadata check results and extract the one that\n          # was successful, with preference to the API endpoint\n          if single_entry && expanded\n            idx = is_metadata_results.index { |v| v === true }\n            # If none of the urls were successful, set the index\n            # as the last entry\n            idx = is_metadata_results.size - 1 if idx.nil?\n\n            # Now reset collections with single value\n            is_metadata_results = [is_metadata_results[idx]]\n            authed_urls = [authed_urls[idx]]\n            url = [url[idx]]\n          end\n\n          if expanded && url.length == 1\n            is_error = is_metadata_results.find do |b|\n              b.is_a?(Errors::DownloaderError)\n            end\n\n            if is_error\n              raise Errors::BoxAddShortNotFound,\n                error: is_error.extra_data[:message],\n                name: env[:box_url],\n                url: url\n            end\n          end\n\n          is_error = is_metadata_results.find do |b|\n            b.is_a?(Errors::DownloaderError)\n          end\n          if is_error\n            raise Errors::BoxMetadataDownloadError,  \n              message: is_error.extra_data[:message]\n          end\n\n          is_metadata = is_metadata_results.any? { |b| b === true }\n          if is_metadata && url.length > 1\n            raise Errors::BoxAddMetadataMultiURL,\n              urls: url.join(\", \")\n          end\n\n          if is_metadata\n            url = [url.first, authed_urls.first]\n            add_from_metadata(url, env, expanded)\n          else\n            add_direct(authed_urls, env)\n          end\n\n          @app.call(env)\n        end\n\n        # Adds a box file directly (no metadata component, versioning,\n        # etc.)\n        #\n        # @param [Array<String>] urls\n        # @param [Hash] env\n        def add_direct(urls, env)\n          env[:ui].output(I18n.t(\"vagrant.box_adding_direct\"))\n\n          name = env[:box_name]\n          if !name || name == \"\"\n            raise Errors::BoxAddNameRequired\n          end\n\n          if env[:box_version]\n            raise Errors::BoxAddDirectVersion\n          end\n\n          provider = env[:box_provider]\n          provider = Array(provider) if provider\n\n          box_add(\n            urls,\n            name,\n            \"0\",\n            provider,\n            nil,\n            env,\n            checksum: env[:box_checksum],\n            checksum_type: env[:box_checksum_type],\n            architecture: env[:box_architecture]\n          )\n        end\n\n        # Adds a box given that the URL is a metadata document.\n        #\n        # @param [String | Array<String>] url The URL of the metadata for\n        #   the box to add. If this is an array, then it must be a two-element\n        #   array where the first element is the original URL and the second\n        #   element is an authenticated URL.\n        # @param [Hash] env\n        # @param [Bool] expanded True if the metadata URL was expanded with\n        #   a Atlas server URL.\n        def add_from_metadata(url, env, expanded)\n          original_url = env[:box_url]\n          architecture = env[:box_architecture]\n          display_architecture = architecture == :auto ?\n                                   Util::Platform.architecture : architecture\n          provider = env[:box_provider]\n          provider = Array(provider) if provider\n          version = env[:box_version]\n\n          authenticated_url = url\n          if url.is_a?(Array)\n            # We have both a normal URL and \"authenticated\" URL. Split\n            # them up.\n            authenticated_url = url[1]\n            url               = url[0]\n          end\n\n          display_original_url = Util::CredentialScrubber.scrub(Array(original_url).first)\n          display_url = Util::CredentialScrubber.scrub(url)\n\n          env[:ui].output(I18n.t(\n            \"vagrant.box_loading_metadata\",\n            name: display_original_url))\n          if original_url != url\n            env[:ui].detail(I18n.t(\n              \"vagrant.box_expanding_url\", url: display_url))\n          end\n\n          metadata = nil\n          begin\n            metadata_path = download(\n              authenticated_url, env, json: true, ui: false)\n            return if @download_interrupted\n\n            File.open(metadata_path) do |f|\n              metadata = BoxMetadata.new(f, url: authenticated_url)\n            end\n          rescue Errors::DownloaderError => e\n            raise if !expanded\n            raise Errors::BoxAddShortNotFound,\n              error: e.extra_data[:message],\n              name: display_original_url,\n              url: display_url\n          ensure\n            metadata_path.delete if metadata_path && metadata_path.file?\n          end\n\n          if env[:box_name] && metadata.name != env[:box_name]\n            raise Errors::BoxAddNameMismatch,\n              actual_name: metadata.name,\n              requested_name: env[:box_name]\n          end\n\n          metadata_version  = metadata.version(\n            version || \">= 0\",\n            provider: provider,\n            architecture: architecture,\n          )\n\n          if !metadata_version\n            if provider\n              # If no version found that supports the provider, then the\n              # box has no support for the provider\n              if !metadata.version(\">= 0\", provider: provider)\n                raise Errors::BoxAddNoMatchingProvider,\n                  name: metadata.name,\n                  requested: Array(provider).join(\", \"),\n                  url: display_url\n              end\n\n              # Get all versions that support the provider and architecture\n              available_versions = metadata.versions(\n                provider: provider,\n                architecture: architecture\n              )\n\n              # If no versions are found, then the box does not provide\n              # support for the requested architecture using the requested\n              # architecture\n              if available_versions.empty?\n                supported_providers = metadata.versions(architecture: architecture).map do |v|\n                  metadata.version(v).providers(architecture)\n                end.compact.uniq.sort\n\n                # If no providers are found, then the box does not\n                # have any support for the requested architecture\n                if supported_providers.empty?\n                  raise Errors::BoxAddNoArchitectureSupport,\n                    architecture: display_architecture,\n                    name: metadata.name,\n                    url: display_url\n                end\n\n                raise Errors::BoxAddNoMatchingArchitecture,\n                  provider: Array(provider).join(\", \"),\n                  architecture: display_architecture,\n                  name: metadata.name,\n                  url: display_url,\n                  supported_providers: supported_providers\n              end\n\n              raise Errors::BoxAddNoMatchingProviderVersion,\n                constraints: version || \">= 0\",\n                provider: Array(provider).join(\", \"),\n                architecture: display_architecture,\n                name: metadata.name,\n                url: display_url,\n                versions: available_versions.reverse.join(\", \")\n            else\n              # Report that no version can match the constraints requested\n              # but show what versions are supported\n              raise Errors::BoxAddNoMatchingVersion,\n                constraints: version || \">= 0\",\n                name: metadata.name,\n                url: display_url,\n                versions: metadata.versions(architecture: architecture).reverse.join(\", \")\n            end\n          end\n\n          metadata_provider = nil\n          if provider\n            # If a provider was specified, make sure we get that specific\n            # version.\n            provider.each do |p|\n              metadata_provider = metadata_version.provider(p, architecture)\n              break if metadata_provider\n            end\n          elsif metadata_version.providers(architecture).length == 1\n            # If we have only one provider in the metadata, just use that\n            # provider.\n            metadata_provider = metadata_version.provider(\n              metadata_version.providers(architecture).first, architecture)\n          else\n            providers = metadata_version.providers(architecture).sort\n\n            choice = 0\n            options = providers.map do |p|\n              choice += 1\n              \"#{choice}) #{p}\"\n            end.join(\"\\n\")\n\n            # We have more than one provider, ask the user what they want\n            choice = env[:ui].ask(I18n.t(\n              \"vagrant.box_add_choose_provider\",\n              options: options) + \" \", prefix: false)\n            choice = choice.to_i if choice\n            while !choice || choice <= 0 || choice > providers.length\n              choice = env[:ui].ask(I18n.t(\n                \"vagrant.box_add_choose_provider_again\") + \" \",\n                prefix: false)\n              choice = choice.to_i if choice\n            end\n\n            metadata_provider = metadata_version.provider(\n              providers[choice-1], architecture)\n          end\n\n          provider_url = metadata_provider.url\n          if provider_url != authenticated_url\n            # Authenticate the provider URL since we're using auth\n            hook_env    = env[:hook].call(:authenticate_box_url, box_urls: [provider_url])\n            authed_urls = hook_env[:box_urls]\n            if !authed_urls || authed_urls.length != 1\n              raise \"Bad box authentication hook, did not generate proper results.\"\n            end\n            provider_url = authed_urls[0]\n          end\n\n          # The architecture name used when adding the box should be\n          # the value extracted from the metadata provider\n          arch_name = metadata_provider.architecture\n\n          # In the special case where the architecture name is \"unknown\" and\n          # it is listed as the default architecture, unset the architecture\n          # name so it is installed without architecture information\n          if arch_name == \"unknown\" && metadata_provider.default_architecture\n            arch_name = nil\n          end\n\n          box_add(\n            [[provider_url, metadata_provider.url]],\n            metadata.name,\n            metadata_version.version,\n            metadata_provider.name,\n            url,\n            env,\n            checksum: metadata_provider.checksum,\n            checksum_type: metadata_provider.checksum_type,\n            architecture: arch_name,\n          )\n        end\n\n        protected\n\n        # Shared helper to add a box once you know various details\n        # about it. Shared between adding via metadata or by direct.\n        #\n        # @param [Array<String>] urls\n        # @param [String] name\n        # @param [String] version\n        # @param [String] provider\n        # @param [Hash] env\n        # @return [Box]\n        def box_add(urls, name, version, provider, md_url, env, **opts)\n          display_architecture = opts[:architecture] == :auto ?\n                                   Util::Platform.architecture : opts[:architecture]\n          env[:ui].output(I18n.t(\n            \"vagrant.box_add_with_version\",\n            name: name,\n            version: version,\n            providers: [\n              provider,\n              display_architecture ? \"(#{display_architecture})\" : nil\n            ].compact.join(\" \")))\n\n          # Verify the box we're adding doesn't already exist\n          if provider && !env[:box_force]\n            box = env[:box_collection].find(\n              name, provider, version, opts[:architecture])\n            if box\n              raise Errors::BoxAlreadyExists,\n                name: name,\n                provider: provider,\n                version: version\n            end\n          end\n\n          # Now we have a URL, we have to download this URL.\n          box = nil\n          begin\n            box_url = nil\n\n            urls.each do |url|\n              show_url = nil\n              if url.is_a?(Array)\n                show_url = url[1]\n                url      = url[0]\n              end\n\n              begin\n                box_url = download(url, env, show_url: show_url)\n                break\n              rescue Errors::DownloaderError => e\n                # If we don't have multiple URLs, just raise the error\n                raise if urls.length == 1\n\n                env[:ui].error(I18n.t(\n                  \"vagrant.box_download_error\",  message: e.message))\n                box_url = nil\n              end\n            end\n\n            if opts[:checksum] && opts[:checksum_type]\n              if opts[:checksum].to_s.strip.empty?\n                @logger.warn(\"Given checksum is empty, cannot validate checksum for box\")\n              elsif opts[:checksum_type].to_s.strip.empty?\n                @logger.warn(\"Given checksum type is empty, cannot validate checksum for box\")\n              else\n                env[:ui].detail(I18n.t(\"vagrant.actions.box.add.checksumming\"))\n                validate_checksum(\n                  opts[:checksum_type], opts[:checksum], box_url)\n              end\n            end\n\n            # Add the box!\n            box = env[:box_collection].add(\n              box_url, name, version,\n              force: env[:box_force],\n              metadata_url: md_url,\n              providers: provider,\n              architecture: opts[:architecture]\n            )\n          ensure\n            # Make sure we delete the temporary file after we add it,\n            # unless we were interrupted, in which case we keep it around\n            # so we can resume the download later.\n            if !@download_interrupted\n              @logger.debug(\"Deleting temporary box: #{box_url}\")\n              begin\n                box_url.delete if box_url\n              rescue Errno::ENOENT\n                # Not a big deal, the temp file may not actually exist\n              end\n            end\n          end\n\n          env[:ui].success(I18n.t(\n            \"vagrant.box_added\",\n            name: box.name,\n            version: box.version,\n            provider: [\n              provider,\n              display_architecture ? \"(#{display_architecture})\" : nil\n            ].compact.join(\" \")))\n\n          # Store the added box in the env for future middleware\n          env[:box_added] = box\n\n          box\n        end\n\n        # Returns the download options for the download.\n        #\n        # @return [Hash]\n        def downloader(url, env, **opts)\n          opts[:ui] = true if !opts.key?(:ui)\n\n          temp_path = env[:tmp_path].join(\"box\" + Digest::SHA1.hexdigest(url))\n          @logger.info(\"Downloading box: #{url} => #{temp_path}\")\n\n          if File.file?(url) || url !~ /^[a-z0-9]+:.*$/i\n            @logger.info(\"URL is a file or protocol not found and assuming file.\")\n            file_path = File.expand_path(url)\n            file_path = Util::Platform.cygwin_windows_path(file_path)\n            file_path = file_path.gsub(\"\\\\\", \"/\")\n            file_path = \"/#{file_path}\" if !file_path.start_with?(\"/\")\n            url = \"file://#{file_path}\"\n          end\n\n          # If the temporary path exists, verify it is not too old. If its\n          # too old, delete it first because the data may have changed.\n          if temp_path.file?\n            delete = false\n            if env[:box_clean]\n              @logger.info(\"Cleaning existing temp box file.\")\n              delete = true\n            elsif temp_path.mtime.to_i < (Time.now.to_i - RESUME_DELAY)\n              @logger.info(\"Existing temp file is too old. Removing.\")\n              delete = true\n            end\n\n            temp_path.unlink if delete\n          end\n\n          downloader_options = {}\n          downloader_options[:ca_cert] = env[:box_download_ca_cert]\n          downloader_options[:ca_path] = env[:box_download_ca_path]\n          downloader_options[:continue] = true\n          downloader_options[:insecure] = env[:box_download_insecure]\n          downloader_options[:client_cert] = env[:box_download_client_cert]\n          downloader_options[:headers] = [\"Accept: application/json\"] if opts[:json]\n          downloader_options[:ui] = env[:ui] if opts[:ui]\n          downloader_options[:location_trusted] = env[:box_download_location_trusted]\n          downloader_options[:disable_ssl_revoke_best_effort] = env[:box_download_disable_ssl_revoke_best_effort]\n          downloader_options[:box_extra_download_options] = env[:box_extra_download_options]\n\n          d = Util::Downloader.new(url, temp_path, downloader_options)\n          env[:hook].call(:authenticate_box_downloader, downloader: d)\n          d\n        end\n\n        def download(url, env, **opts)\n          opts[:ui] = true if !opts.key?(:ui)\n\n          d = downloader(url, env, **opts)\n          env[:hook].call(:authenticate_box_downloader, downloader: d)\n\n          # Download the box to a temporary path. We store the temporary\n          # path as an instance variable so that the `#recover` method can\n          # access it.\n          if opts[:ui]\n            show_url = opts[:show_url]\n            show_url ||= url\n            display_url = Util::CredentialScrubber.scrub(show_url)\n\n            translation = \"vagrant.box_downloading\"\n\n            # Adjust status message when 'downloading' a local box.\n            if show_url.start_with?(\"file://\")\n              translation = \"vagrant.box_unpacking\"\n            end\n\n            env[:ui].detail(I18n.t(\n              translation,\n              url: display_url))\n            if File.file?(d.destination)\n              env[:ui].info(I18n.t(\"vagrant.actions.box.download.resuming\"))\n            end\n          end\n\n          begin\n            mutex_path = d.destination + \".lock\"\n            Util::FileMutex.new(mutex_path).with_lock do\n              begin\n                d.download!\n              rescue Errors::DownloaderInterrupted\n                # The downloader was interrupted, so just return, because that\n                # means we were interrupted as well.\n                @download_interrupted = true\n                env[:ui].info(I18n.t(\"vagrant.actions.box.download.interrupted\"))\n              end\n            end\n          rescue Errors::VagrantLocked\n            raise Errors::DownloadAlreadyInProgress,\n              dest_path: d.destination,\n              lock_file_path: mutex_path\n          end\n\n          Pathname.new(d.destination)\n        end\n\n        # Tests whether the given URL points to a metadata file or a\n        # box file without completely downloading the file.\n        #\n        # @param [String] url\n        # @return [Boolean] true if metadata\n        def metadata_url?(url, env)\n          d = downloader(url, env, json: true, ui: false)\n          env[:hook].call(:authenticate_box_downloader, downloader: d)\n\n          # If we're downloading a file, cURL just returns no\n          # content-type (makes sense), so we just test if it is JSON\n          # by trying to parse JSON!\n          uri = URI.parse(d.source)\n          if uri.scheme == \"file\"\n            url = uri.path\n            url ||= uri.opaque\n            #7570 Strip leading slash left in front of drive letter by uri.path\n            Util::Platform.windows? && url.gsub!(/^\\/([a-zA-Z]:)/, '\\1')\n            url = @parser.unescape(url)\n\n            begin\n              File.open(url, \"r\") do |f|\n                if f.size > METADATA_SIZE_LIMIT\n                  # Quit early, don't try to parse the JSON of gigabytes\n                  # of box files...\n                  return false\n                end\n\n                BoxMetadata.new(f, url: url)\n              end\n              return true\n            rescue Errors::BoxMetadataMalformed\n              return false\n            rescue Errno::EINVAL\n              # Actually not sure what causes this, but its always\n              # in a case that isn't true.\n              return false\n            rescue Errno::EISDIR\n              return false\n            rescue Errno::ENOENT\n              return false\n            end\n          end\n\n          # If this isn't HTTP, then don't do the HEAD request\n          if !uri.scheme.downcase.start_with?(\"http\")\n            @logger.info(\"not checking metadata since box URI isn't HTTP\")\n            return false\n          end\n\n          output = d.head\n          match  = output.scan(/^Content-Type: (.+?)$/i).last\n          return false if !match\n          !!(match.last.chomp =~ /application\\/json/)\n        end\n\n        def validate_checksum(checksum_type, _checksum, path)\n          checksum = _checksum.strip()\n          @logger.info(\"Validating checksum with #{checksum_type}\")\n          @logger.info(\"Expected checksum: #{checksum}\")\n\n          _actual = FileChecksum.new(path, checksum_type).checksum\n          actual = _actual.strip()\n          @logger.info(\"Actual checksum: #{actual}\")\n          if actual.casecmp(checksum) != 0\n            raise Errors::BoxChecksumMismatch,\n              actual: actual,\n              expected: checksum\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/box_check_outdated.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This middleware checks if there are outdated boxes. By default,\n      # it only checks locally, but if `box_outdated_refresh` is set, it\n      # will refresh the metadata associated with a box.\n      class BoxCheckOutdated\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\n            \"vagrant::action::builtin::box_check_outdated\")\n        end\n\n        def call(env)\n          machine = env[:machine]\n\n          if !env[:box_outdated_force]\n            if !machine.config.vm.box_check_update\n              @logger.debug(\n                \"Not checking for update: no force and no update config\")\n              return @app.call(env)\n            end\n          end\n\n          if !machine.box\n            # We don't have a box. Just ignore, we can't check for\n            # outdated...\n            @logger.warn(\"Not checking for update, no box\")\n            return @app.call(env)\n          end\n\n          box = machine.box\n          if box.version == \"0\" && !box.metadata_url\n            return @app.call(env)\n          end\n\n          constraints = machine.config.vm.box_version\n          # Have download options specified in the environment override\n          # options specified for the machine.\n          download_options = {\n            automatic_check: !env[:box_outdated_force],\n            ca_cert: env[:ca_cert] || machine.config.vm.box_download_ca_cert,\n            ca_path: env[:ca_path] || machine.config.vm.box_download_ca_path,\n            client_cert: env[:client_cert] ||\n                           machine.config.vm.box_download_client_cert,\n            insecure: !env[:insecure].nil? ?\n                        env[:insecure] : machine.config.vm.box_download_insecure,\n            disable_ssl_revoke_best_effort: env.fetch(:box_download_disable_ssl_revoke_best_effort,\n                                                      machine.config.vm.box_download_disable_ssl_revoke_best_effort),\n            box_extra_download_options: env[:box_extra_download_options] || machine.config.vm.box_extra_download_options,\n          }\n\n          env[:ui].output(I18n.t(\n            \"vagrant.box_outdated_checking_with_refresh\",\n            name: box.name,\n            version: box.version))\n          update = nil\n          begin\n            update = box.has_update?(constraints, download_options: download_options)\n          rescue Errors::BoxMetadataDownloadError => e\n            env[:ui].warn(I18n.t(\n              \"vagrant.box_outdated_metadata_download_error\",\n              message: e.extra_data[:message]))\n          rescue Errors::BoxMetadataMalformed  => e\n            @logger.warn(e.to_s)\n            env[:ui].warn(I18n.t(\"vagrant.box_malformed_continue_on_update\"))\n          rescue Errors::VagrantError => e\n            raise if !env[:box_outdated_ignore_errors]\n            env[:ui].detail(I18n.t(\n              \"vagrant.box_outdated_metadata_error_single\",\n              message: e.message))\n          end\n          env[:box_outdated] = update != nil\n          local_update = check_outdated_local(env)\n          if update && (local_update.nil? || (local_update.version < update[1].version))\n            env[:ui].warn(I18n.t(\n              \"vagrant.box_outdated_single\",\n              name: update[0].name,\n              provider: box.provider,\n              current: box.version,\n              latest: update[1].version))\n          elsif local_update\n            env[:ui].warn(I18n.t(\n              \"vagrant.box_outdated_local\",\n              name: local_update.name,\n              old: box.version,\n              new: local_update.version))\n            env[:box_outdated] = true\n          else\n            env[:box_outdated] = false\n          end\n\n          @app.call(env)\n        end\n\n        def check_outdated_local(env)\n          machine = env[:machine]\n\n          # Make sure we respect the constraints set within the Vagrantfile\n          version = machine.config.vm.box_version\n          version += \", \" if version\n          version ||= \"\"\n          version += \"> #{machine.box.version}\"\n\n          env[:box_collection].find(\n            machine.box.name, machine.box.provider, version)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/box_remove.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This middleware will remove a box for a given provider.\n      class BoxRemove\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::box_remove\")\n        end\n\n        def call(env)\n          box_name     = env[:box_name]\n          box_architecture = env[:box_architecture] if env[:box_architecture]\n          box_provider = env[:box_provider].to_sym if env[:box_provider]\n          box_version  = env[:box_version]\n          box_remove_all_versions = env[:box_remove_all_versions]\n          box_remove_all_providers = env[:box_remove_all_providers]\n          box_remove_all_architectures = env[:box_remove_all_architectures]\n\n          box_info = Util::HashWithIndifferentAccess.new\n          env[:box_collection].all.each do |name, version, provider, architecture|\n            next if name != box_name\n            box_info[version] ||= Util::HashWithIndifferentAccess.new\n            box_info[version][provider] ||= []\n            box_info[version][provider] << architecture\n          end\n\n          # If there's no box info, then the box doesn't exist here\n          if box_info.empty?\n            raise Errors::BoxRemoveNotFound, name: box_name\n          end\n\n          # Filtering only matters if not removing all versions\n          if !box_remove_all_versions\n            # If no version was provided, and not removing all versions,\n            # only allow one version to proceed\n            if !box_version && box_info.size > 1\n              raise Errors::BoxRemoveMultiVersion,\n                name: box_name,\n                versions: box_info.keys.sort.map { |k| \" * #{k}\" }.join(\"\\n\")\n            end\n\n            # If a version was provided, make sure it exists\n            if box_version\n              if !box_info.keys.include?(box_version)\n                raise Errors::BoxRemoveVersionNotFound,\n                  name: box_name,\n                  version: box_version,\n                  versions: box_info.keys.sort.map { |k| \" * #{k}\" }.join(\"\\n\")\n              else\n                box_info.delete_if { |k, _| k != box_version }\n              end\n            end\n\n            # Only a single version remains\n            box_version = box_info.keys.first\n\n            # Further filtering only matters if not removing all providers\n            if !box_remove_all_providers\n              # If no provider was given, check if there are more\n              # than a single provider for the version\n              if !box_provider && box_info.values.first.size > 1\n                raise Errors::BoxRemoveMultiProvider,\n                  name: box_name,\n                  version: box_version,\n                  providers: box_info.values.first.keys.map(&:to_s).sort.join(\", \")\n              end\n\n              # If a provider was given, check the version has it\n              if box_provider\n                if !box_info.values.first.key?(box_provider)\n                  raise Errors::BoxRemoveProviderNotFound,\n                    name: box_name,\n                    version: box_version,\n                    provider: box_provider.to_s,\n                    providers: box_info.values.first.keys.map(&:to_s).sort.join(\", \")\n                else\n                  box_info.values.first.delete_if { |k, _| k.to_s != box_provider.to_s }\n                end\n              end\n\n              # Only a single provider remains\n              box_provider = box_info.values.first.keys.first\n\n              # Further filtering only matters if not removing all architectures\n              if !box_remove_all_architectures\n                # If no architecture was given, check if there are more\n                # than a single architecture for the provider in version\n                if !box_architecture && box_info.values.first.values.first.size > 1\n                  raise Errors::BoxRemoveMultiArchitecture,\n                    name: box_name,\n                    version: box_version,\n                    provider: box_provider.to_s,\n                    architectures: box_info.values.first.values.first.sort.join(\", \")\n                end\n\n                # If architecture was given, check the provider for the version has it\n                if box_architecture\n                  if !box_info.values.first.values.first.include?(box_architecture)\n                    raise Errors::BoxRemoveArchitectureNotFound,\n                      name: box_name,\n                      version: box_version,\n                      provider: box_provider.to_s,\n                      architecture: box_architecture,\n                      architectures: box_info.values.first.values.first.sort.join(\", \")\n                  else\n                    box_info.values.first.values.first.delete_if { |v| v != box_architecture }\n                  end\n                end\n              end\n            end\n          end\n\n          box_info.each do |version, provider_info|\n            provider_info.each do |provider, architecture_info|\n              provider = provider.to_sym\n              architecture_info.each do |architecture|\n                box = env[:box_collection].find(\n                  box_name, provider, version, architecture\n                )\n\n                # Verify that this box is not in use by an active machine,\n                # otherwise warn the user.\n                users = box.in_use?(env[:machine_index]) || []\n                users = users.find_all { |u| u.valid?(env[:home_path]) }\n                if !users.empty?\n                  # Build up the output to show the user.\n                  users = users.map do |entry|\n                    \"#{entry.name} (ID: #{entry.id})\"\n                  end.join(\"\\n\")\n\n                  force_key = :force_confirm_box_remove\n                  message   = I18n.t(\n                    \"vagrant.commands.box.remove_in_use_query\",\n                    name: box.name,\n                    architecture: box.architecture,\n                    provider: box.provider,\n                    version: box.version,\n                    users: users) + \" \"\n\n                  # Ask the user if we should do this\n                  stack = Builder.new.tap do |b|\n                    b.use Confirm, message, force_key\n                  end\n\n                  # Keep used boxes, even if \"force\" is applied\n                  keep_used_boxes = env[:keep_used_boxes]\n\n                  result = env[:action_runner].run(stack, env)\n                  if !result[:result] || keep_used_boxes\n                    # They said \"no\", so continue with the next box\n                    next\n                  end\n                end\n\n                env[:ui].info(I18n.t(\"vagrant.commands.box.removing\",\n                  name: box.name,\n                  architecture: box.architecture,\n                  provider: box.provider,\n                  version: box.version))\n\n                box.destroy!\n                env[:box_collection].clean(box.name)\n\n                # Passes on the removed box to the rest of the middleware chain\n                env[:box_removed] = box\n              end\n            end\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/box_update.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This middleware updates a specific box if there are updates available.\n      class BoxUpdate\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\n            \"vagrant::action::builtin::box_update\")\n        end\n\n        def call(env)\n          machine = env[:machine]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/call.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This middleware class allows a sort of \"conditional\" run within\n      # a single middleware sequence. It takes another middleware runnable,\n      # runs it with the same environment, then yields the resulting env to a block,\n      # allowing that block to determine the next course of action in the\n      # middleware sequence.\n      #\n      # The first argument to this middleware sequence is anywhere middleware\n      # runnable, whether it be a class, lambda, or something else that\n      # responds to `call`. This middleware runnable is run with the same\n      # environment as this class.\n      #\n      # After running, {Call} takes the environment and yields it to a block\n      # given to initialize the class, along with an instance of {Builder}.\n      # The result is used to build up a new sequence on the given builder.\n      # This builder is then run.\n      class Call\n        # For documentation, read the description of the {Call} class.\n        #\n        # @param [Object] callable A valid middleware runnable object. This\n        #   can be a class, a lambda, or an object that responds to `call`.\n        # @yield [result, builder] This block is expected to build on `builder`\n        #   which is the next middleware sequence that will be run.\n        def initialize(app, env, callable, *callable_args, &block)\n          raise ArgumentError, \"A block must be given to Call\" if !block\n\n          @app      = app\n          @callable = callable\n          @callable_args = callable_args\n          @block    = block\n          @child_app = nil\n        end\n\n        def call(env)\n          runner  = Runner.new\n\n          # Build the callable that we'll run\n          callable = Builder.build(@callable, *@callable_args)\n\n          # Run our callable with our environment\n          new_env = runner.run(callable, env)\n\n          # Build our new builder based on the result\n          builder = Builder.new\n          @block.call(new_env, builder)\n\n          # Append our own app onto the builder so we slide the new\n          # stack into our own chain...\n          builder.use @app\n          @child_app = builder.to_app(new_env)\n          final_env  = runner.run(@child_app, new_env)\n\n          # Merge the environment into our original environment\n          env.merge!(final_env)\n        end\n\n        def recover(env)\n          # Call back into our compiled application and recover it.\n          @child_app.recover(env) if @child_app\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/cleanup_disks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      class CleanupDisks\n        # Removes any attached disks no longer defined in a Vagrantfile config\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::disk\")\n        end\n\n        def call(env)\n          machine = env[:machine]\n          defined_disks = get_disks(machine, env)\n\n          # Call into providers machine implementation for disk management\n          disk_meta_file = read_disk_metadata(machine)\n\n          if !disk_meta_file.empty?\n            if machine.provider.capability?(:cleanup_disks)\n              machine.provider.capability(:cleanup_disks, defined_disks, disk_meta_file)\n            else\n              env[:ui].warn(I18n.t(\"vagrant.actions.disk.provider_unsupported\",\n                                   provider: machine.provider_name))\n            end\n          end\n\n          # Continue On\n          @app.call(env)\n        end\n\n        def read_disk_metadata(machine)\n          meta_file = machine.data_dir.join(\"disk_meta\")\n          if File.file?(meta_file)\n            disk_meta = JSON.parse(meta_file.read)\n          else\n            @logger.info(\"No previous disk_meta file defined for guest #{machine.name}\")\n            disk_meta = {}\n          end\n\n          return disk_meta\n        end\n\n        def get_disks(machine, env)\n          return @_disks if @_disks\n\n          @_disks = []\n          @_disks = machine.config.vm.disks\n\n          @_disks\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/cloud_init_setup.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/mime'\nrequire 'tmpdir'\n\nmodule Vagrant\n  module Action\n    module Builtin\n      class CloudInitSetup\n        TEMP_PREFIX = \"vagrant-cloud-init-iso-temp-\".freeze\n\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::cloudinit::setup\")\n        end\n\n        def call(env)\n          catch(:complete) do\n            machine = env[:machine]\n\n            # The sentinel file in this check is written by the cloud init\n            # wait action and is only written after cloud init has completed.\n            @logger.info(\"Checking cloud-init sentinel file...\")\n            sentinel_path = machine.data_dir.join(\"action_cloud_init\")\n            if sentinel_path.file?\n              contents = sentinel_path.read.chomp\n              if machine.id.to_s == contents\n                if machine.config.vm.cloud_init_first_boot_only\n                  @logger.info(\"Sentinel found for cloud-init, skipping\")\n                  throw :complete\n                else\n                  @logger.info(\"Sentinel found for cloud-init but is configuration enabled\")\n                end\n              else\n                @logger.debug(\"Found stale sentinel file, removing... (#{machine.id} != #{contents})\")\n              end\n              sentinel_path.unlink\n            end\n\n            user_data_configs = machine.config.vm.cloud_init_configs.select { |c|\n              c.type == :user_data\n            }\n\n            if !user_data_configs.empty?\n              user_data = setup_user_data(machine, env, user_data_configs)\n              meta_data = { \"instance-id\" => \"i-#{machine.id.split('-').join}\" }\n\n              write_cfg_iso(machine, env, user_data, meta_data)\n            end\n          end\n\n          # Continue On\n          @app.call(env)\n        end\n\n        # @param [Vagrant::Machine] machine\n        # @param [Vagrant::Environment] env\n        # @param [Array<#VagrantPlugins::Kernel_V2::VagrantConfigCloudInit>] user_data_cfgs\n        # @return [Vagrant::Util::Mime::MultiPart] user_data\n        def setup_user_data(machine, env, user_data_cfgs)\n          machine.ui.info(I18n.t(\"vagrant.actions.vm.cloud_init_user_data_setup\"))\n\n          text_cfgs = user_data_cfgs.map { |cfg| read_text_cfg(machine, cfg) }\n\n          user_data = generate_cfg_msg(machine, text_cfgs)\n          user_data\n        end\n\n        # Reads an individual cloud_init config and stores its contents and the\n        # content_type as a MIME text\n        #\n        # @param [Vagrant::Machine] machine\n        # @param [VagrantPlugins::Kernel_V2::VagrantConfigCloudInit] cfg\n        # @return [Vagrant::Util::Mime::Entity] text_msg\n        def read_text_cfg(machine, cfg)\n          if cfg.path\n            text = File.read(Pathname.new(cfg.path).expand_path(machine.env.root_path))\n          else\n            text = cfg.inline\n          end\n\n          text_msg = Vagrant::Util::Mime::Entity.new(text, cfg.content_type)\n          text_msg.disposition = \"attachment; filename=\\\"#{File.basename(cfg.content_disposition_filename).gsub('\"', '\\\"')}\\\"\" if cfg.content_disposition_filename\n          text_msg\n        end\n\n        # Combines all known cloud_init configs into a multipart mixed MIME text\n        # message\n        #\n        # @param [Vagrant::Machine] machine\n        # @param [Array<Vagrant::Util::Mime::Entity>] text_msg - One or more text configs\n        # @return [Vagrant::Util::Mime::Multipart] msg\n        def generate_cfg_msg(machine, text_cfgs)\n          msg = Vagrant::Util::Mime::Multipart.new\n          msg.headers[\"MIME-Version\"] = \"1.0\"\n\n          text_cfgs.each do |c|\n            msg.add(c)\n          end\n\n          msg\n        end\n\n        # Writes the contents of the guests cloud_init config to a tmp\n        # dir and passes that source directory along to the host cap to be\n        # written to an iso\n        #\n        # @param [Vagrant::Machine] machine\n        # @param [Vagrant::Util::Mime::Multipart] user_data\n        # @param [Hash] meta_data\n        def write_cfg_iso(machine, env, user_data, meta_data)\n          raise Errors::CreateIsoHostCapNotFound if !env[:env].host.capability?(:create_iso)\n\n          iso_path = catch(:iso_path) do\n            # This iso sentinel file is used to store the path of the\n            # generated iso file and its checksum. If the file does\n            # not exist, or the actual checksum of the file does not\n            # match that stored in the sentinel file, it is ignored\n            # and the iso is generated. This is used to prevent multiple\n            # iso file from being created over time.\n            iso_sentinel = env[:machine].data_dir.join(\"action_cloud_init_iso\")\n            if iso_sentinel.file?\n              checksum, path = iso_sentinel.read.chomp.split(\":\", 2)\n              if File.exist?(path) && Vagrant::Util::FileChecksum.new(path, :sha256).checksum == checksum\n                throw :iso_path, Pathname.new(path)\n              end\n              iso_sentinel.unlink\n            end\n\n            begin\n              source_dir = Pathname.new(Dir.mktmpdir(TEMP_PREFIX))\n              File.open(\"#{source_dir}/user-data\", 'w') { |file| file.write(user_data.to_s) }\n              File.open(\"#{source_dir}/meta-data\", 'w') { |file| file.write(meta_data.to_yaml) }\n\n              env[:env].host.capability(\n                :create_iso,\n                source_dir,\n                volume_id: \"cidata\"\n              ).tap { |path|\n                checksum = Vagrant::Util::FileChecksum.new(path.to_path, :sha256).checksum\n                iso_sentinel.write(\"#{checksum}:#{path.to_path}\")\n              }\n            ensure\n              FileUtils.remove_entry(source_dir)\n            end\n          end\n\n          attach_disk_config(machine, env, iso_path.to_path)\n        end\n\n        # Adds a new :dvd disk config with the given iso_path to be attached\n        # to the guest later\n        #\n        # @param [Vagrant::Machine] machine\n        # @param [Vagrant::Environment] env\n        # @param [String] iso_path\n        def attach_disk_config(machine, env, iso_path)\n          @logger.info(\"Adding cloud_init iso '#{iso_path}' to disk config\")\n          machine.config.vm.disk :dvd, file: iso_path, name: \"vagrant-cloud_init-disk\"\n          machine.config.vm.disks.each { |d| d.finalize! if d.type == :dvd && d.file == iso_path }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/cloud_init_wait.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      class CloudInitWait\n\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::cloudinitwait\")\n        end\n\n        def call(env)\n          catch(:complete) do\n            machine = env[:machine]\n            sentinel_path = machine.data_dir.join(\"action_cloud_init\")\n\n            @logger.info(\"Checking cloud-init sentinel file...\")\n            if sentinel_path.file?\n              contents = sentinel_path.read.chomp\n              if machine.id.to_s == contents\n                @logger.info(\"Sentinel found for cloud-init, skipping\")\n                throw :complete\n              end\n              @logger.debug(\"Found stale sentinel file, removing... (#{machine.id} != #{contents})\")\n              sentinel_path.unlink\n            end\n\n            cloud_init_wait_cmd = \"cloud-init status --wait\"\n            if !machine.config.vm.cloud_init_configs.empty?\n              if machine.communicate.test(\"command -v cloud-init\")\n                env[:ui].output(I18n.t(\"vagrant.cloud_init_waiting\"))\n                result = machine.communicate.sudo(cloud_init_wait_cmd, error_check: false)\n                if result != 0\n                  raise Vagrant::Errors::CloudInitCommandFailed, cmd: cloud_init_wait_cmd, guest_name: machine.name\n                end\n              else\n                raise Vagrant::Errors::CloudInitNotFound, guest_name: machine.name\n              end\n            end\n            # Write sentinel path\n            sentinel_path.write(machine.id.to_s)\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/config_validate.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/template_renderer\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This class validates the configuration and raises an exception\n      # if there are any validation errors.\n      class ConfigValidate\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          if !env.key?(:config_validate) || env[:config_validate]\n            errors = env[:machine].config.validate(env[:machine], env[:ignore_provider])\n\n            if errors && !errors.empty?\n              raise Errors::ConfigInvalid,\n                errors: Util::TemplateRenderer.render(\n                  \"config/validation_failed\",\n                  errors: errors)\n            end\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/confirm.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This class asks the user to confirm some sort of question with\n      # a \"Y/N\" question. The only parameter is the text to ask the user.\n      # The result is placed in `env[:result]` so that it can be used\n      # with the {Call} class.\n      class Confirm\n        # For documentation, read the description of the {Confirm} class.\n        #\n        # @param [String] message The message to ask the user.\n        # @param [Symbol] force_key The key that if present and true in\n        #   the environment hash will skip the confirmation question.\n        def initialize(app, env, message, force_key=nil, **opts)\n          @app      = app\n          @message  = message\n          @force_key = force_key\n          @allowed  = opts[:allowed]\n        end\n\n        def call(env)\n          choice = nil\n\n          # If we have a force key set and we're forcing, then set\n          # the result to \"Y\"\n          choice = \"Y\" if @force_key && env[@force_key]\n\n          if !choice\n            while true\n              # If we haven't chosen yes, then ask the user via TTY\n              choice = env[:ui].ask(@message)\n\n              # If we don't have an allowed set just exit\n              break if !@allowed\n              break if @allowed.include?(choice)\n            end\n          end\n\n          # The result is only true if the user said \"Y\"\n          env[:result] = choice && choice.upcase == \"Y\"\n          env[\"#{@force_key}_result\".to_sym] = env[:result]\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/delayed.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This class is used to delay execution until the end of\n      # a configured stack\n      class Delayed\n        # @param [Object] callable The object to call (must respond to #call)\n        def initialize(app, env, callable)\n          if !callable.respond_to?(:call)\n            raise TypeError, \"Callable argument is expected to respond to `#call`\"\n          end\n          @app         = app\n          @env         = env\n          @callable    = callable\n        end\n\n        def call(env)\n          # Allow the rest of the call stack to execute\n          @app.call(env)\n          # Now call our delayed stack\n          @callable.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/destroy_confirm.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"confirm\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This class asks the user to confirm the destruction of a machine\n      # that Vagrant manages. This is provided as a built-in on top of\n      # {Confirm} because it sets up the proper keys and such so that\n      # `vagrant destroy -f` works properly.\n      class DestroyConfirm < Confirm\n        def initialize(app, env)\n          force_key = :force_confirm_destroy\n          message   = I18n.t(\"vagrant.commands.destroy.confirmation\",\n                             name: env[:machine].name)\n\n          super(app, env, message, force_key, allowed: [\"y\", \"n\", \"Y\", \"N\"])\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/disk.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      class Disk\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::disk\")\n        end\n\n        def call(env)\n          machine = env[:machine]\n          defined_disks = get_disks(machine, env)\n\n          # Call into providers machine implementation for disk management\n          configured_disks = {}\n          if !defined_disks.empty?\n            if machine.provider.capability?(:configure_disks)\n             configured_disks = machine.provider.capability(:configure_disks, defined_disks)\n            else\n              env[:ui].warn(I18n.t(\"vagrant.actions.disk.provider_unsupported\",\n                                 provider: machine.provider_name))\n            end\n          end\n\n          # Always write the disk metadata even if the configured\n          # disks is empty. This ensure that old entries are not\n          # orphaned in the metadata file.\n          write_disk_metadata(machine, configured_disks)\n\n          # Continue On\n          @app.call(env)\n        end\n\n        def write_disk_metadata(machine, current_disks)\n          meta_file = machine.data_dir.join(\"disk_meta\")\n          @logger.debug(\"Writing disk metadata file to #{meta_file}\")\n          File.open(meta_file.to_s, \"w+\") do |file|\n            file.write(JSON.dump(current_disks))\n          end\n        end\n\n        def get_disks(machine, env)\n          return @_disks if @_disks\n\n          @_disks = []\n          @_disks = machine.config.vm.disks\n\n          @_disks\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/env_set.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This middleware class allows you to modify the environment hash\n      # in the middle of a middleware sequence. The new environmental data\n      # will take affect at this stage in the middleware and will persist\n      # through.\n      class EnvSet\n        def initialize(app, env, new_env=nil)\n          @app     = app\n          @new_env = new_env || {}\n        end\n\n        def call(env)\n          # Merge in the new data\n          env.merge!(@new_env)\n\n          # Carry on\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/graceful_halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"timeout\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This middleware class will attempt to perform a graceful shutdown\n      # of the machine using the guest implementation. This middleware is\n      # compatible with the {Call} middleware so you can branch based on\n      # the result, which is true if the halt succeeded and false otherwise.\n      class GracefulHalt\n        # Note: Any of the arguments can be arrays as well.\n        #\n        # @param [Symbol] target_state The target state ID that means that\n        #   the machine was properly shut down.\n        # @param [Symbol] source_state The source state ID that the machine\n        #   must be in to be shut down.\n        def initialize(app, env, target_state, source_state=nil)\n          @app          = app\n          @logger       = Log4r::Logger.new(\"vagrant::action::builtin::graceful_halt\")\n          @source_state = source_state\n          @target_state = target_state\n        end\n\n        def call(env)\n          graceful = true\n          graceful = !env[:force_halt] if env.key?(:force_halt)\n\n          # By default, we didn't succeed.\n          env[:result] = false\n\n          if graceful && @source_state\n            @logger.info(\"Verifying source state of machine: #{@source_state.inspect}\")\n\n            # If we're not in the proper source state, then we don't\n            # attempt to halt the machine\n            current_state = env[:machine].state.id\n            if current_state != @source_state\n              @logger.info(\"Invalid source state, not halting: #{current_state}\")\n              graceful = false\n            end\n          end\n\n          # Only attempt to perform graceful shutdown under certain cases\n          # checked above.\n          if graceful\n            env[:ui].output(I18n.t(\"vagrant.actions.vm.halt.graceful\"))\n\n            begin\n              env[:machine].guest.capability(:halt)\n\n              @logger.debug(\"Waiting for target graceful halt state: #{@target_state}\")\n              begin\n                Timeout.timeout(env[:machine].config.vm.graceful_halt_timeout) do\n                  while env[:machine].state.id != @target_state\n                    sleep 1\n                  end\n                end\n              rescue Timeout::Error\n                # Don't worry about it, we catch the case later.\n              end\n            rescue Errors::GuestCapabilityNotFound\n              # This happens if insert_public_key is called on a guest that\n              # doesn't support it. This will block a destroy so we let it go.\n            rescue Errors::MachineGuestNotReady\n              env[:ui].detail(I18n.t(\"vagrant.actions.vm.halt.guest_not_ready\"))\n            end\n\n            # The result of this matters on whether we reached our\n            # proper target state or not.\n            env[:result] = env[:machine].state.id == @target_state\n\n            if env[:result]\n              @logger.info(\"Gracefully halted.\")\n            else\n              @logger.info(\"Graceful halt failed.\")\n            end\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/handle_box.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"thread\"\nrequire \"log4r\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This built-in middleware handles the `box` setting by verifying\n      # the box is already installed, downloading the box if it isn't,\n      # updating the box if it is requested, etc.\n      class HandleBox\n        @@big_lock = Mutex.new\n        @@small_locks = Hash.new { |h,k| h[k] = Mutex.new }\n\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::handle_box\")\n        end\n\n        def call(env)\n          machine = env[:machine]\n\n          if !machine.config.vm.box || machine.config.vm.box.to_s.empty?\n            @logger.info(\"Skipping HandleBox because no box is set\")\n            return @app.call(env)\n          end\n\n          # Acquire a lock for this box to handle multi-threaded\n          # environments.\n          lock = nil\n          @@big_lock.synchronize do\n            lock = @@small_locks[machine.config.vm.box]\n          end\n\n          box_updated = false\n          lock.synchronize do\n            if machine.box\n              @logger.info(\"Machine already has box. HandleBox will not run.\")\n              next\n            end\n\n            handle_box(env)\n            box_updated = true\n          end\n\n          if box_updated\n            # Reload the environment and set the VM to be the new loaded VM.\n            new_machine = machine.vagrantfile.machine(\n              machine.name, machine.provider_name,\n              machine.env.boxes, machine.data_dir, machine.env)\n            env[:machine].box = new_machine.box\n            env[:machine].config = new_machine.config\n            env[:machine].provider_config = new_machine.provider_config\n          end\n\n          @app.call(env)\n        end\n\n        def handle_box(env)\n          machine = env[:machine]\n\n          # Determine the set of formats that this box can be in\n          box_download_ca_cert = machine.config.vm.box_download_ca_cert\n          box_download_ca_path = machine.config.vm.box_download_ca_path\n          box_download_client_cert = machine.config.vm.box_download_client_cert\n          box_download_insecure = machine.config.vm.box_download_insecure\n          box_download_checksum_type = machine.config.vm.box_download_checksum_type\n          box_download_checksum = machine.config.vm.box_download_checksum\n          box_download_location_trusted = machine.config.vm.box_download_location_trusted\n          box_download_disable_ssl_revoke_best_effort = machine.config.vm.box_download_disable_ssl_revoke_best_effort\n          box_extra_download_options = machine.config.vm.box_extra_download_options\n          box_formats = machine.provider_options[:box_format] ||\n            machine.provider_name\n\n          version_ui = machine.config.vm.box_version\n          version_ui ||= \">= 0\"\n\n          env[:ui].output(I18n.t(\n            \"vagrant.box_auto_adding\", name: machine.config.vm.box))\n          env[:ui].detail(\"Box Provider: #{Array(box_formats).join(\", \")}\")\n          env[:ui].detail(\"Box Version: #{version_ui}\")\n\n          begin\n            env[:action_runner].run(Vagrant::Action.action_box_add, env.merge({\n              box_name: machine.config.vm.box,\n              box_url: machine.config.vm.box_url || machine.config.vm.box,\n              box_architecture: machine.config.vm.box_architecture,\n              box_server_url: machine.config.vm.box_server_url,\n              box_provider: box_formats,\n              box_version: machine.config.vm.box_version,\n              box_download_client_cert: box_download_client_cert,\n              box_download_ca_cert: box_download_ca_cert,\n              box_download_ca_path: box_download_ca_path,\n              box_download_insecure: box_download_insecure,\n              box_checksum_type: box_download_checksum_type,\n              box_checksum: box_download_checksum,\n              box_download_location_trusted: box_download_location_trusted,\n              box_download_disable_ssl_revoke_best_effort: box_download_disable_ssl_revoke_best_effort,\n              box_extra_download_options: box_extra_download_options,\n            }))\n          rescue Errors::BoxAlreadyExists\n            # Just ignore this, since it means the next part will succeed!\n            # This can happen in a multi-threaded environment.\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/handle_box_url.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    module Builtin\n      class HandleBoxUrl < HandleBox\n        def call(env)\n          env[:ui].warn(\"HandleBoxUrl middleware is deprecated. Use HandleBox instead.\")\n          env[:ui].warn(\"This is a bug with the provider. Please contact the creator\")\n          env[:ui].warn(\"of the provider you use to fix this.\")\n          super\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"set\"\nrequire \"log4r\"\nrequire \"socket\"\n\nrequire \"vagrant/util/is_port_open\"\nrequire \"vagrant/util/ipv4_interfaces\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This middleware class will detect and handle collisions with\n      # forwarded ports, whether that means raising an error or repairing\n      # them automatically.\n      #\n      # Parameters it takes from the environment hash:\n      #\n      #   * `:port_collision_repair` - If true, it will attempt to repair\n      #     port collisions. If false, it will raise an exception when\n      #     there is a collision.\n      #\n      #   * `:port_collision_extra_in_use` - An array of ports that are\n      #     considered in use.\n      #\n      #   * `:port_collision_remap` - A hash remapping certain host ports\n      #     to other host ports.\n      #\n      class HandleForwardedPortCollisions\n        include Util::IsPortOpen\n        include Util::IPv4Interfaces\n\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::handle_port_collisions\")\n        end\n\n        def call(env)\n          @leased  = []\n          @machine = env[:machine]\n\n          # Acquire a process-level lock so that we don't choose a port\n          # that someone else also chose.\n          begin\n            env[:machine].env.lock(\"fpcollision\") do\n              handle(env)\n            end\n          rescue Errors::EnvironmentLockedError\n            sleep 1\n            retry\n          end\n\n          @app.call(env)\n\n          # Always run the recover method so that we release leases\n          recover(env)\n        end\n\n        def recover(env)\n          lease_release\n        end\n\n        protected\n\n        def handle(env)\n          @logger.info(\"Detecting any forwarded port collisions...\")\n\n          # Get the extra ports we consider in use\n          extra_in_use = env[:port_collision_extra_in_use] || {}\n\n          # If extras are provided as an Array (previous behavior) convert\n          # to Hash as expected for IP aliasing support\n          if extra_in_use.is_a?(Array)\n            extra_in_use = Hash[extra_in_use.map{|port| [port, Set.new([\"*\"])]}]\n          end\n\n          # Get the remap\n          remap = env[:port_collision_remap] || {}\n\n          # Determine the handler we'll use if we have any port collisions\n          repair = !!env[:port_collision_repair]\n\n          # The method we'll use to check if a port is open.\n          port_checker = env[:port_collision_port_check]\n          port_checker ||= method(:port_check)\n\n          # Log out some of our parameters\n          @logger.debug(\"Extra in use: #{extra_in_use.inspect}\")\n          @logger.debug(\"Remap: #{remap.inspect}\")\n          @logger.debug(\"Repair: #{repair.inspect}\")\n\n          # Determine a list of usable ports for repair\n          usable_ports = Set.new(env[:machine].config.vm.usable_port_range)\n          usable_ports.subtract(extra_in_use.keys)\n\n          # Pass one, remove all defined host ports from usable ports\n          with_forwarded_ports(env) do |options|\n            usable_ports.delete(options[:host])\n          end\n\n          # Pass two, detect/handle any collisions\n          with_forwarded_ports(env) do |options|\n            guest_port = options[:guest]\n            host_port  = options[:host]\n            host_ip    = options[:host_ip]\n\n            if options[:disabled]\n              @logger.debug(\"Skipping disabled port #{host_port}.\")\n              next\n            end\n\n            if options[:protocol] && options[:protocol] != \"tcp\"\n              @logger.debug(\"Skipping #{host_port} because UDP protocol.\")\n              next\n            end\n\n            if remap[host_port]\n              remap_port = remap[host_port]\n              @logger.debug(\"Remap port override: #{host_port} => #{remap_port}\")\n              host_port = remap_port\n            end\n\n            # If the port is open (listening for TCP connections)\n            in_use = is_forwarded_already(extra_in_use, host_port, host_ip) ||\n              call_port_checker(port_checker, host_ip, host_port) ||\n              lease_check(host_ip, host_port)\n\n            if in_use\n              if !repair || !options[:auto_correct]\n                raise Errors::ForwardPortCollision,\n                  guest_port: guest_port.to_s,\n                  host_port:  host_port.to_s\n              end\n\n              @logger.info(\"Attempting to repair FP collision: #{host_port}\")\n\n              repaired_port = nil\n              while !usable_ports.empty?\n                # Attempt to repair the forwarded port\n                repaired_port = usable_ports.to_a.sort[0]\n                usable_ports.delete(repaired_port)\n\n                # If the port is in use, then we can't use this either...\n                in_use = is_forwarded_already(extra_in_use, repaired_port, host_ip) ||\n                  call_port_checker(port_checker, host_ip, repaired_port) ||\n                  lease_check(host_ip, repaired_port)\n                if in_use\n                  @logger.info(\"Repaired port also in use: #{repaired_port}. Trying another...\")\n                  next\n                end\n\n                # We have a port so break out\n                break\n              end\n\n              # If we have no usable ports then we can't repair\n              if !repaired_port && usable_ports.empty?\n                raise Errors::ForwardPortAutolistEmpty,\n                  vm_name:    env[:machine].name,\n                  guest_port: guest_port.to_s,\n                  host_port:  host_port.to_s\n              end\n\n              # Modify the args in place\n              options[:host] = repaired_port\n\n              @logger.info(\"Repaired FP collision: #{host_port} to #{repaired_port}\")\n\n              # Notify the user\n              env[:ui].info(I18n.t(\"vagrant.actions.vm.forward_ports.fixed_collision\",\n                                   host_port:  host_port.to_s,\n                                   guest_port: guest_port.to_s,\n                                   new_port:   repaired_port.to_s))\n            end\n          end\n        end\n\n        def lease_check(host_ip=nil, host_port)\n          # Check if this port is \"leased\". We use a leasing system of\n          # about 60 seconds to avoid any forwarded port collisions in\n          # a highly parallelized environment.\n          leasedir = @machine.env.data_dir.join(\"fp-leases\")\n          leasedir.mkpath\n\n          if host_ip.nil?\n            lease_file_name = host_port.to_s\n          else\n            lease_file_name = \"#{host_ip.gsub('.','_')}_#{host_port.to_s}\"\n          end\n\n          invalid = false\n          oldest  = Time.now.to_i - 60\n          leasedir.children.each do |child|\n            # Delete old, invalid leases while we're looking\n            if child.file? && child.mtime.to_i < oldest\n              child.delete\n            end\n\n            if child.basename.to_s == lease_file_name\n              invalid = true\n            end\n          end\n\n          # If its invalid, then the port is \"open\" and in use\n          return true if invalid\n\n          # Otherwise, create the lease\n          leasedir.join(lease_file_name).open(\"w+\") do |f|\n            f.binmode\n            f.write(Time.now.to_i.to_s + \"\\n\")\n          end\n\n          # Add to the leased array so we unlease it right away\n          @leased << lease_file_name\n\n          # Things look good to us!\n          false\n        end\n\n        def lease_release\n          leasedir = @machine.env.data_dir.join(\"fp-leases\")\n\n          @leased.each do |port|\n            path = leasedir.join(port)\n            path.delete if path.file?\n          end\n        end\n\n        # This functions checks to see if the current instance's hostport and\n        # hostip for forwarding is in use by the virtual machines created\n        # previously.\n        def is_forwarded_already(extra_in_use, hostport, hostip)\n          hostip = '*' if hostip.nil? || hostip.empty?\n          # ret. false if none of the VMs we spun up had this port forwarded.\n          return false if not extra_in_use.has_key?(hostport)\n\n          # ret. true if the user has requested to bind on all interfaces but\n          # we already have a rule in one the VMs we spun up.\n          if hostip == '*'\n            if extra_in_use.fetch(hostport).size != 0\n              return true\n            else\n              return false\n            end\n          end\n\n          return extra_in_use.fetch(hostport).include?(hostip)\n        end\n\n        def port_check(host_ip, host_port)\n          self.class.port_check(@machine, host_ip, host_port)\n        end\n\n        def self.port_check(machine, host_ip, host_port)\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::handle_port_collisions\")\n          # If no host_ip is specified, intention taken to be listen on all interfaces.\n          test_host_ip = host_ip || \"0.0.0.0\"\n          if Util::Platform.windows? && test_host_ip == \"0.0.0.0\"\n            @logger.debug(\"Testing port #{host_port} on all IPv4 interfaces...\")\n            available_interfaces = Vagrant::Util::IPv4Interfaces.ipv4_interfaces.select do |interface|\n              @logger.debug(\"Testing #{interface[0]} with IP address #{interface[1]}\")\n              !Vagrant::Util::IsPortOpen.is_port_open?(interface[1], host_port)\n            end\n            if available_interfaces.empty?\n              @logger.debug(\"Cannot forward port #{host_port} on any interfaces.\")\n              true\n            else\n              @logger.debug(\"Port #{host_port} will forward to the guest on the following interfaces: #{available_interfaces}\")\n              false\n            end\n          else\n            # Do a regular check\n            if test_host_ip != \"0.0.0.0\" && !Addrinfo.ip(test_host_ip).ipv4_loopback? &&\n                Vagrant::Util::IPv4Interfaces.ipv4_interfaces.none? { |iface| iface[1] == test_host_ip }\n              @logger.warn(\"host IP address is not local to this device host_ip=#{test_host_ip}\")\n            end\n            Vagrant::Util::IsPortOpen.is_port_open?(test_host_ip, host_port)\n          end\n        end\n\n        def with_forwarded_ports(env)\n          env[:machine].config.vm.networks.each do |type, options|\n            # Ignore anything but forwarded ports\n            next if type != :forwarded_port\n\n            yield options\n          end\n        end\n\n        def call_port_checker(port_checker, host_ip, host_port)\n          call_args = [host_ip, host_port]\n          # Trim args if checker method does not support inclusion of host_ip\n          call_args = call_args.slice(call_args.size - port_checker.arity.abs, port_checker.arity.abs)\n          port_checker[*call_args]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/has_provisioner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This middleware is used with Call to test if this machine\n      # has available provisioners\n      class HasProvisioner\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::has_provisioner\")\n        end\n\n        def call(env)\n          machine = env[:machine]\n\n          if machine.provider.capability?(:has_communicator)\n            has_communicator = machine.provider.capability(:has_communicator)\n          else\n            has_communicator = true\n          end\n\n          env[:skip] = []\n          if !has_communicator\n            machine.config.vm.provisioners.each do |p|\n              if p.communicator_required\n                env[:skip].push(p)\n                @logger.info(\"Skipping running provisioner #{p.name || 'no name'}, type: #{p.type}\")\n                p.run = :never\n              end\n            end\n          end\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/is_env_set.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This middleware is meant to be used with Call and can check if\n      # a variable in env is set.\n      class IsEnvSet\n        def initialize(app, env, key, **opts)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::is_env_set\")\n          @key    = key\n        end\n\n        def call(env)\n          @logger.debug(\"Checking if env is set: '#{@key}'\")\n          env[:result] = !!env[@key]\n          @logger.debug(\" - Result: #{env[:result].inspect}\")\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/is_state.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\r\n  module Action\r\n    module Builtin\r\n      # This middleware is meant to be used with Call and can check if\r\n      # a machine is in the given state ID.\r\n      class IsState\r\n        # Note: Any of the arguments can be arrays as well.\r\n        #\r\n        # @param [Symbol] target_state The target state ID that means that\r\n        #   the machine was properly shut down.\r\n        # @param [Symbol] source_state The source state ID that the machine\r\n        #   must be in to be shut down.\r\n        def initialize(app, env, check, **opts)\r\n          @app    = app\r\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::is_state\")\r\n          @check  = check\r\n          @invert = !!opts[:invert]\r\n        end\r\n\r\n        def call(env)\r\n          @logger.debug(\"Checking if machine state is '#{@check}'\")\r\n          state = env[:machine].state.id\r\n          @logger.debug(\"-- Machine state: #{state}\")\r\n\r\n          env[:result] = @check == state\r\n          env[:result] = !env[:result] if @invert\r\n          @app.call(env)\r\n        end\r\n      end\r\n    end\r\n  end\r\nend\r\n"
  },
  {
    "path": "lib/vagrant/action/builtin/lock.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This class creates a multi-process lock using `flock`. The lock\n      # is active for the remainder of the middleware stack.\n      class Lock\n        def initialize(app, env, options=nil)\n          @app     = app\n          @logger  = Log4r::Logger.new(\"vagrant::action::builtin::lock\")\n          @options ||= options || {}\n          raise ArgumentError, \"Please specify a lock path\" if !@options[:path]\n          raise ArgumentError, \"Please specify an exception.\" if !@options[:exception]\n        end\n\n        def call(env)\n          lock_path = @options[:path]\n          lock_path = lock_path.call(env) if lock_path.is_a?(Proc)\n\n          env_key   = \"has_lock_#{lock_path}\"\n\n          if !env[env_key]\n            # If we already have the key in our environment we assume the\n            # lock is held by our middleware stack already and we allow\n            # nesting.\n            File.open(lock_path, \"w+\") do |f|\n              # The file locking fails only if it returns \"false.\" If it\n              # succeeds it returns a 0, so we must explicitly check for\n              # the proper error case.\n              @logger.info(\"Locking: #{lock_path}\")\n              if f.flock(File::LOCK_EX | File::LOCK_NB) === false\n                exception = @options[:exception]\n                exception = exception.call(env) if exception.is_a?(Proc)\n                raise exception\n              end\n\n              # Set that we gained the lock and call deeper into the\n              # middleware, but make sure we UNSET the lock when we leave.\n              begin\n                env[env_key] = true\n                @app.call(env)\n              ensure\n                @logger.info(\"Unlocking: #{lock_path}\")\n                env[env_key] = false\n                f.flock(File::LOCK_UN)\n              end\n            end\n          else\n            # Just call up the middleware because we already hold the lock\n            @app.call(env)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/message.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\r\n  module Action\r\n    module Builtin\r\n      # This middleware simply outputs a message to the UI.\r\n      class Message\r\n        def initialize(app, env, message, **opts)\r\n          @app     = app\r\n          @message = message\r\n          @opts    = opts\r\n        end\r\n\r\n        def call(env)\r\n          if !@opts[:post]\r\n            env[:ui].output(@message)\r\n          end\r\n\r\n          @app.call(env)\r\n\r\n          if @opts[:post]\r\n            env[:ui].output(@message)\r\n          end\r\n        end\r\n      end\r\n    end\r\n  end\r\nend\r\n"
  },
  {
    "path": "lib/vagrant/action/builtin/mixin_provisioners.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    module Builtin\n      module MixinProvisioners\n        # This returns all the instances of the configured provisioners.\n        # This is safe to call multiple times since it will cache the results.\n        #\n        # @return [Array<Provisioner, Hash>]\n        def provisioner_instances(env)\n          return @_provisioner_instances if @_provisioner_instances\n\n          # Make the mapping that'll keep track of provisioner => type\n          @_provisioner_types = {}\n\n          # Get all the configured provisioners\n          @_provisioner_instances = env[:machine].config.vm.provisioners.map do |provisioner|\n            # Instantiate the provisioner\n            klass  = Vagrant.plugin(\"2\").manager.provisioners[provisioner.type]\n\n            # This can happen in the case the configuration isn't validated.\n            next nil if !klass\n\n            result = klass.new(env[:machine], provisioner.config)\n\n            # Store in the type map so that --provision-with works properly\n            @_provisioner_types[result] = provisioner.type\n\n            # Set top level provisioner name to provisioner configs name if top level name not set.\n            # This is mostly for handling the shell provisioner, if a user has set its name like:\n            #\n            #   config.vm.provision \"shell\", name: \"my_provisioner\"\n            #\n            # Where `name` is a shell config option, not a top level provisioner class option\n            #\n            # Note: `name` is set to a symbol, since it is converted to one via #Config::VM.provision\n            provisioner_name = provisioner.name\n            if !provisioner_name\n              if provisioner.config.respond_to?(:name) &&\n                  provisioner.config.name\n                provisioner_name = provisioner.config.name.to_sym\n              end\n            else\n              provisioner_name = provisioner_name.to_sym\n            end\n\n            # Build up the options\n            options = {\n              name: provisioner_name,\n              run:  provisioner.run,\n              before:  provisioner.before,\n              after:  provisioner.after,\n              communicator_required: provisioner.communicator_required,\n            }\n\n            # Return the result\n            [result, options]\n          end\n\n          @_provisioner_instances = sort_provisioner_instances(@_provisioner_instances)\n          return @_provisioner_instances.compact\n        end\n\n        private\n\n        # Sorts provisioners based on order specified with before/after options\n        #\n        # @return [Array<Provisioner, Hash>]\n        def sort_provisioner_instances(pvs)\n          final_provs = []\n          root_provs = []\n          # extract root provisioners\n          root_provs = pvs.find_all { |_, o| o[:before].nil? && o[:after].nil? }\n\n          if root_provs.size == pvs.size\n            # no dependencies found\n            return pvs\n          end\n\n          # ensure placeholder variables are Arrays\n          dep_provs = []\n          each_provs = []\n          all_provs = []\n\n          # extract dependency provisioners\n          dep_provs = pvs.find_all { |_, o| o[:before].is_a?(String) || o[:after].is_a?(String) }\n          # extract each provisioners\n          each_provs = pvs.find_all { |_,o| o[:before] == :each || o[:after] == :each }\n          # extract all provisioners\n          all_provs = pvs.find_all { |_,o| o[:before] == :all || o[:after] == :all }\n\n          # insert provisioners in order\n          final_provs = root_provs\n          dep_provs.each do |p,options|\n            idx = 0\n            if options[:before]\n              idx = final_provs.index { |_, o| o[:name].to_s == options[:before] }\n              final_provs.insert(idx, [p, options])\n            elsif options[:after]\n              idx = final_provs.index { |_, o| o[:name].to_s == options[:after] }\n              idx += 1\n              final_provs.insert(idx, [p, options])\n            end\n          end\n\n          # Add :each and :all provisioners in reverse to preserve order in Vagrantfile\n          tmp_final_provs = []\n          final_provs.each_with_index do |(prv,o), i|\n            tmp_before = []\n            tmp_after = []\n\n            each_provs.reverse_each do |p, options|\n              if options[:before]\n                tmp_before << [p,options]\n              elsif options[:after]\n                tmp_after << [p,options]\n              end\n            end\n\n            tmp_final_provs += tmp_before unless tmp_before.empty?\n            tmp_final_provs += [[prv,o]]\n            tmp_final_provs += tmp_after unless tmp_after.empty?\n          end\n          final_provs = tmp_final_provs\n\n          # Add all to final array\n          all_provs.reverse_each do |p,options|\n            if options[:before]\n              final_provs.insert(0, [p,options])\n            elsif options[:after]\n              final_provs.push([p,options])\n            end\n          end\n\n          return final_provs\n        end\n\n        # This will return a mapping of a provisioner instance to its\n        # type.\n        def provisioner_type_map(env)\n          # Call this in order to initial the map if it hasn't been already\n          provisioner_instances(env)\n\n          # Return the type map\n          @_provisioner_types\n        end\n\n        # @private\n        # Reset the cached values for platform. This is not considered a public\n        # API and should only be used for testing.\n        def self.reset!\n          instance_variables.each(&method(:remove_instance_variable))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/mixin_synced_folders.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\nrequire \"set\"\nrequire 'vagrant/util/scoped_hash_override'\n\nmodule Vagrant\n  module Action\n    module Builtin\n      module MixinSyncedFolders\n        include Vagrant::Util::ScopedHashOverride\n\n        # This goes over all the registered synced folder types and returns\n        # the highest priority implementation that is usable for this machine.\n        def default_synced_folder_type(machine, plugins)\n          ordered = []\n\n          # First turn the plugins into an array\n          plugins.each do |key, data|\n            impl     = data[0]\n            priority = data[1]\n\n            ordered << [priority, key, impl]\n          end\n\n          # Order the plugins by priority. Higher is tried before lower.\n          ordered = ordered.sort { |a, b| b[0] <=> a[0] }\n\n          allowed_types = machine.config.vm.allowed_synced_folder_types\n          if allowed_types\n            ordered = allowed_types.map do |type|\n              ordered.find do |_, key, impl|\n                key == type\n              end\n            end.compact\n          end\n\n          # Find the proper implementation\n          ordered.each do |_, key, impl|\n            return key if impl.new.usable?(machine)\n          end\n\n          return nil\n        end\n\n        # This finds the options in the env that are set for a given\n        # synced folder type.\n        def impl_opts(name, env)\n          {}.tap do |result|\n            env.each do |k, v|\n              if k.to_s.start_with?(\"#{name}_\")\n                # While I generally don't like the 'rescue' syntax,\n                # we do this to just fall back to the default value\n                # if it isn't dup-able.\n                k = k.dup rescue k\n                v = v.dup rescue v\n\n                result[k] = v\n              end\n            end\n          end\n        end\n\n        # This returns the available synced folder implementations. This\n        # is a separate method so that it can be easily stubbed by tests.\n        def plugins\n          @plugins ||= Vagrant.plugin(\"2\").manager.synced_folders\n        end\n\n        # This saves the synced folders data to the machine data directory.\n        # They can then be retrieved again with `synced_folders` by passing\n        # the `cached` option to it.\n        #\n        # @param [Machine] machine The machine that the folders belong to\n        # @param [Hash] folders The result from a {#synced_folders} call.\n        def save_synced_folders(machine, folders, opts={})\n          if opts[:merge]\n            existing = cached_synced_folders(machine)\n            if existing\n              if opts[:vagrantfile]\n                # Go through and find any cached that were from the\n                # Vagrantfile itself. We remove those if it was requested.\n                existing.each do |impl, fs|\n                  fs.each do |id, data|\n                    fs.delete(id) if data[:__vagrantfile]\n                  end\n                end\n              end\n\n              folders.each do |impl, fs|\n                existing[impl] ||= {}\n                fs.each do |id, data|\n                  existing[impl][id] = data\n                end\n              end\n\n              folders = existing\n            end\n          end\n\n          # Remove implementation instances\n          folder_data = JSON.dump(folders.to_h)\n\n          machine.data_dir.join(\"synced_folders\").open(\"w\") do |f|\n            f.write(folder_data)\n          end\n        end\n\n        # This returns the set of shared folders that should be done for\n        # this machine. It returns the folders in a hash keyed by the\n        # implementation class for the synced folders.\n        #\n        # @return [Hash<Symbol, Hash<String, Hash>>]\n        def synced_folders(machine, **opts)\n          return cached_synced_folders(machine) if opts[:cached]\n\n          config = opts[:config]\n          root   = false\n          if !config\n            config = machine.config.vm\n            root   = true\n          end\n\n          config_folders = config.synced_folders\n          folders = Vagrant::Plugin::V2::SyncedFolder::Collection.new\n\n          # Determine all the synced folders as well as the implementation\n          # they're going to use.\n          config_folders.each do |id, data|\n            # Ignore disabled synced folders\n            next if data[:disabled]\n\n            impl = \"\"\n            impl = data[:type].to_sym if data[:type] && !data[:type].empty?\n\n            if impl != \"\"\n              impl_class = plugins[impl]\n              if !impl_class\n                # This should never happen because configuration validation\n                # should catch this case. But we put this here as an assert\n                raise \"Internal error. Report this as a bug. Invalid: #{data[:type]}\"\n              end\n\n              if !opts[:disable_usable_check]\n                if !impl_class[0].new.usable?(machine, true)\n                  # Verify that explicitly defined shared folder types are\n                  # actually usable.\n                  raise Errors::SyncedFolderUnusable, type: data[:type].to_s\n                end\n              end\n            end\n\n            # Get the data to store\n            data = data.dup\n            if root\n              # If these are the root synced folders (attached directly)\n              # to the Vagrantfile, then we mark it as such.\n              data[:__vagrantfile] = true\n            end\n\n            # Keep track of this shared folder by the implementation.\n            folders[impl] ||= {}\n            folders[impl][id] = data\n          end\n\n          # If we have folders with the \"default\" key, then determine the\n          # most appropriate implementation for this.\n          if folders.key?(\"\") && !folders[\"\"].empty?\n            default_impl = default_synced_folder_type(machine, plugins)\n            if !default_impl\n              types = plugins.to_hash.keys.map { |t| t.to_s }.sort.join(\", \")\n              raise Errors::NoDefaultSyncedFolderImpl, types: types\n            end\n\n            folders[default_impl] ||= {}\n            folders[default_impl].merge!(folders[\"\"])\n            folders.delete(\"\")\n          end\n\n          # Apply the scoped hash overrides to get the options\n          folders.dup.each do |impl_name, fs|\n            impl = plugins[impl_name].first.new._initialize(machine, impl_name)\n            new_fs = {}\n            fs.each do |id, data|\n              data[:plugin] = impl\n              id         = data[:id] if data[:id]\n              new_fs[id] = scoped_hash_override(data, impl_name)\n            end\n\n            folders[impl_name] = new_fs\n          end\n\n          folders\n        end\n\n        # This finds the difference between two lists of synced folder\n        # definitions.\n        #\n        # This will return a hash with three keys: \"added\", \"removed\",\n        # and \"modified\". These will contain a set of IDs of folders\n        # that were added, removed, or modified, respectively.\n        #\n        # The parameters should be results from the {#synced_folders} call.\n        #\n        # @return [hash]\n        def synced_folders_diff(one, two)\n          existing_ids = {}\n          one.each do |impl, fs|\n            fs.each do |id, data|\n              existing_ids[id] = data\n            end\n          end\n\n          result = Hash.new { |h, k| h[k] = Set.new }\n          two.each do |impl, fs|\n            fs.each do |id, data|\n              existing = existing_ids.delete(id)\n              if !existing\n                result[:added] << id\n                next\n              end\n\n              # Exists, so we have to compare the host and guestpath, which\n              # is most important...\n              if existing[:hostpath] != data[:hostpath] ||\n                existing[:guestpath] != data[:guestpath]\n                result[:modified] << id\n              end\n            end\n          end\n\n          existing_ids.each do |k, _|\n            result[:removed] << k\n          end\n\n          result\n        end\n\n        protected\n\n        def cached_synced_folders(machine)\n          import = JSON.parse(machine.data_dir.join(\"synced_folders\").read)\n          import.each do |type, folders|\n            impl = plugins[type.to_sym].first.new._initialize(machine, type.to_sym)\n            folders.each { |_, v| v[:plugin] = impl }\n          end\n          # Symbolize the keys we want as symbols\n          import.keys.dup.each do |k|\n            import[k].values.each do |item|\n              item.keys.dup.each do |ik|\n                item[ik.to_sym] = item.delete(ik)\n              end\n            end\n            import[k.to_sym] = import.delete(k)\n          end\n          Vagrant::Plugin::V2::SyncedFolder::Collection[import]\n        rescue Errno::ENOENT\n          # If the file doesn't exist, we probably just have a machine created\n          # by a version of Vagrant that didn't cache shared folders. Report no\n          # shared folders to be safe.\n          Vagrant::Plugin::V2::SyncedFolder::Collection.new\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/prepare_clone.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      class PrepareClone\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::action::vm::prepare_clone\")\n        end\n\n        def call(env)\n          # If we aren't cloning, then do nothing\n          if !env[:machine].config.vm.clone\n            return @app.call(env)\n          end\n\n          # We need to get the machine ID from this Vagrant environment\n          clone_env = env[:machine].env.environment(\n            env[:machine].config.vm.clone)\n          raise Errors::CloneNotFound if !clone_env.root_path\n\n          # Get the machine itself\n          clone_machine = clone_env.machine(\n            clone_env.primary_machine_name, env[:machine].provider_name)\n          raise Errors::CloneMachineNotFound if !clone_machine.id\n\n          # Set the ID of the master so we know what to clone from\n          env[:clone_id] = clone_machine.id\n          env[:clone_machine] = clone_machine\n\n          # Continue\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/provision.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nrequire_relative \"mixin_provisioners\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This class will run the configured provisioners against the\n      # machine.\n      #\n      # This action should be placed BEFORE the machine is booted so it\n      # can do some setup, and then run again (on the return path) against\n      # a running machine.\n      class Provision\n        include MixinProvisioners\n\n        def initialize(app, env)\n          @app             = app\n          @logger          = Log4r::Logger.new(\"vagrant::action::builtin::provision\")\n        end\n\n        def call(env)\n          @env = env\n\n          # Tracks whether we were configured to provision\n          config_enabled = true\n          config_enabled = env[:provision_enabled] if env.key?(:provision_enabled)\n\n          # Check if we already provisioned, and if so, disable the rest\n          provision_enabled = true\n\n          ignore_sentinel = true\n          if env.key?(:provision_ignore_sentinel)\n            ignore_sentinel = env[:provision_ignore_sentinel]\n          end\n          if ignore_sentinel\n            @logger.info(\"Ignoring sentinel check, forcing provision\")\n          end\n\n          @logger.info(\"Checking provisioner sentinel file...\")\n          sentinel_path = env[:machine].data_dir.join(\"action_provision\")\n          update_sentinel = false\n          if sentinel_path.file?\n            # The sentinel file is in the format of \"version:data\" so that\n            # we can remain backwards compatible with previous sentinels.\n            # Versions so far:\n            #\n            #   Vagrant < 1.5.0: A timestamp. The weakness here was that\n            #     if it wasn't cleaned up, it would incorrectly not provision\n            #     new machines.\n            #\n            #   Vagrant >= 1.5.0: \"1.5:ID\", where ID is the machine ID.\n            #     We compare both so we know whether it is a new machine.\n            #\n            contents = sentinel_path.read.chomp\n            parts    = contents.split(\":\", 2)\n\n            if parts.length == 1\n              @logger.info(\"Old-style sentinel found! Not provisioning.\")\n              provision_enabled = false if !ignore_sentinel\n              update_sentinel = true\n            elsif parts[0] == \"1.5\" && parts[1] == env[:machine].id.to_s\n              @logger.info(\"Sentinel found! Not provisioning.\")\n              provision_enabled = false if !ignore_sentinel\n            else\n              @logger.info(\"Sentinel found with another machine ID. Removing.\")\n              sentinel_path.unlink\n            end\n          end\n\n          # Store the value so that other actions can use it\n          env[:provision_enabled] = provision_enabled if !env.key?(:provision_enabled)\n\n          # Ask the provisioners to modify the configuration if needed\n          provisioner_instances(env).each do |p, _|\n            p.configure(env[:machine].config)\n          end\n\n          # Continue, we need the VM to be booted.\n          @app.call(env)\n\n          # If we're configured to not provision, notify the user and stop\n          if !config_enabled\n            env[:ui].info(I18n.t(\"vagrant.actions.vm.provision.disabled_by_config\"))\n            return\n          end\n\n          # If we're not provisioning because of the sentinel, tell the user\n          # but continue trying for the \"always\" provisioners\n          if !provision_enabled\n            env[:ui].info(I18n.t(\"vagrant.actions.vm.provision.disabled_by_sentinel\"))\n          end\n\n          # Write the sentinel if we have to\n          if update_sentinel || !sentinel_path.file?\n            @logger.info(\"Writing provisioning sentinel so we don't provision again\")\n            sentinel_path.open(\"w\") do |f|\n              f.write(\"1.5:#{env[:machine].id}\")\n            end\n          end\n\n          type_map = provisioner_type_map(env)\n          provisioner_instances(env).each do |p, options|\n            type_name = type_map[p]\n\n            if options[:run] == :never\n              next if env[:provision_types].nil? || !env[:provision_types].include?(options[:name])\n            else\n              next if env[:provision_types] && \\\n                !env[:provision_types].include?(type_name) && \\\n                !env[:provision_types].include?(options[:name])\n\n              # Don't run if sentinel is around and we're not always running\n              next if !provision_enabled && options[:run] != :always\n            end\n\n            name = type_name\n            if options[:name]\n              name = \"#{options[:name]} (#{type_name})\"\n            end\n\n            env[:ui].info(I18n.t(\n              \"vagrant.actions.vm.provision.beginning\",\n              provisioner: name))\n\n            env[:hook].call(:provisioner_run, env.merge(\n              callable: method(:run_provisioner),\n              provisioner: p,\n              provisioner_name: type_name,\n            ))\n          end\n        end\n\n        # This is pulled out into a separate method so that users can\n        # subclass and implement custom behavior if they'd like to work around\n        # this step.\n        def run_provisioner(env)\n          env[:provisioner].provision\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/provisioner_cleanup.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nrequire_relative \"mixin_provisioners\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This action will run the cleanup methods on provisioners and should\n      # be used as part of any Destroy action.\n      class ProvisionerCleanup\n        include MixinProvisioners\n\n        def initialize(app, env, place=nil)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::provision_cleanup\")\n\n          place ||= :after\n          @place = place.to_sym\n        end\n\n        def call(env)\n          do_cleanup(env) if @place == :before\n\n          # Continue, we need the VM to be booted.\n          @app.call(env)\n\n          do_cleanup(env) if @place == :after\n        end\n\n        def do_cleanup(env)\n          type_map = provisioner_type_map(env)\n\n          # Ask the provisioners to modify the configuration if needed\n          provisioner_instances(env).each do |p, _|\n            name = type_map[p].to_s\n\n            # Check if the subclass defined a cleanup method. The parent\n            # provisioning class defines a `cleanup` method, so we cannot use\n            # `respond_to?` here. Instead, we have to check if _this_ instance\n            # defines a cleanup task.\n            if p.public_methods(false).include?(:cleanup)\n              env[:ui].info(I18n.t(\"vagrant.provisioner_cleanup\",\n                name: name,\n              ))\n              p.cleanup\n            else\n              @logger.debug(\"Skipping cleanup tasks for `#{name}' - not defined\")\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/set_hostname.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This middleware sets the hostname of the guest according to the\n      # \"vm.hostname\" configuration parameter if it is set. This middleware\n      # should be placed such that the after the @app.call, a booted machine\n      # is available (this generally means BEFORE the boot middleware).\n      class SetHostname\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::set_hostname\")\n        end\n\n        def call(env)\n          @app.call(env)\n\n          hostname = env[:machine].config.vm.hostname\n          allow_hosts_modification = env[:machine].config.vm.allow_hosts_modification\n          if !hostname.nil? && allow_hosts_modification\n            env[:ui].info I18n.t(\"vagrant.actions.vm.hostname.setting\")\n            env[:machine].guest.capability(:change_host_name, hostname)\n          else\n            @logger.info(\"`allow_hosts_modification` set to false. Hosts modification has been disabled, skiping changing hostname.\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/ssh_exec.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nrequire \"vagrant/util/ssh\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This class will exec into a full fledged SSH console into the\n      # remote machine. This middleware assumes that the VM is running and\n      # ready for SSH, and uses the {Machine#ssh_info} method to retrieve\n      # SSH information necessary to connect.\n      #\n      # Note: If there are any middleware after `SSHExec`, they will **not**\n      # run, since exec replaces the currently running process.\n      class SSHExec\n        # For quick access to the `SSH` class.\n        include Vagrant::Util\n\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          # Grab the SSH info from the machine or the environment\n          info = env[:ssh_info]\n          info ||= env[:machine].ssh_info\n\n          # If the result is nil, then the machine is telling us that it is\n          # not yet ready for SSH, so we raise this exception.\n          raise Errors::SSHNotReady if info.nil?\n\n          info[:private_key_path] ||= []\n\n          if info[:private_key_path].empty? && info[:password]\n            env[:ui].warn(I18n.t(\"vagrant.ssh_exec_password\"))\n          end\n\n          # Exec!\n          SSH.exec(info, env[:ssh_opts])\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/ssh_run.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/ssh\"\nrequire \"vagrant/util/shell_quote\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This class will run a single command on the remote machine and will\n      # mirror the output to the UI. The resulting exit status of the command\n      # will exist in the `:ssh_run_exit_status` key in the environment.\n      class SSHRun\n        # For quick access to the `SSH` class.\n        include Vagrant::Util\n\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::ssh_run\")\n        end\n\n        def call(env)\n          # Grab the SSH info from the machine or the environment\n          info = env[:ssh_info]\n          info ||= env[:machine].ssh_info\n\n          # If the result is nil, then the machine is telling us that it is\n          # not yet ready for SSH, so we raise this exception.\n          raise Errors::SSHNotReady if info.nil?\n\n          info[:private_key_path] ||= []\n\n          if info[:keys_only] && info[:private_key_path].empty?\n            raise Errors::SSHRunRequiresKeys\n          end\n\n          # Get the command and wrap it in a login shell\n          command = ShellQuote.escape(env[:ssh_run_command], \"'\")\n\n          if env[:machine].config.vm.communicator == :winssh\n            shell = env[:machine].config.winssh.shell\n          else\n            shell = env[:machine].config.ssh.shell\n          end\n\n          if shell == \"cmd\"\n            # Add an extra space to the command so cmd.exe quoting works\n            # properly\n            command = \"#{shell} /C #{command} \"\n          elsif shell == \"powershell\"\n            command = \"$ProgressPreference = \\\"SilentlyContinue\\\"; #{command}\"\n            command = Base64.strict_encode64(command.encode(\"UTF-16LE\", \"UTF-8\"))\n            command = \"#{shell} -encodedCommand #{command}\"\n          else\n            command = \"#{shell} -c '#{command}'\"\n          end\n\n          # Execute!\n          opts = env[:ssh_opts] || {}\n          opts[:extra_args] ||= []\n\n          # Allow the user to specify a tty or non-tty manually, but if they\n          # don't then we default to a TTY unless they are using WinSSH\n          if !opts[:extra_args].include?(\"-t\") &&\n              !opts[:extra_args].include?(\"-T\") &&\n              env[:tty] &&\n              env[:machine].config.vm.communicator != :winssh\n            opts[:extra_args] << \"-t\"\n          end\n\n          opts[:extra_args] << command\n          opts[:subprocess] = true\n          env[:ssh_run_exit_status] = _raw_ssh_exec(env, info, opts)\n\n          # Call the next middleware\n          @app.call(env)\n        end\n\n        def _raw_ssh_exec(env, info, opts)\n          Util::SSH.exec(info, opts)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/synced_folder_cleanup.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nrequire_relative \"mixin_synced_folders\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This middleware will run cleanup tasks for synced folders using\n      # the appropriate synced folder plugin.\n      class SyncedFolderCleanup\n        include MixinSyncedFolders\n\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::synced_folder_cleanup\")\n        end\n\n        def call(env)\n          folders = synced_folders(env[:machine])\n\n          # Go through each folder and do cleanup\n          folders.each_key do |impl_name|\n            @logger.info(\"Invoking synced folder cleanup for: #{impl_name}\")\n            plugins[impl_name.to_sym][0].new.cleanup(\n              env[:machine], impl_opts(impl_name, env))\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/synced_folders.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nrequire 'vagrant/util/platform'\n\nrequire_relative \"mixin_synced_folders\"\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This middleware will setup the synced folders for the machine using\n      # the appropriate synced folder plugin.\n      class SyncedFolders\n        include MixinSyncedFolders\n\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::action::builtin::synced_folders\")\n        end\n\n        def call(env)\n          opts = {\n            cached: !!env[:synced_folders_cached],\n            config: env[:synced_folders_config],\n          }\n\n          @logger.info(\"SyncedFolders loading from cache: #{opts[:cached]}\")\n          folders = synced_folders(env[:machine], **opts)\n          original_folders = folders\n\n          folders.each do |impl_name, fs|\n            @logger.info(\"Synced Folder Implementation: #{impl_name}\")\n\n            fs.each do |id, data|\n              # Log every implementation and their paths\n              @logger.info(\"  - #{id}: #{data[:hostpath]} => #{data[:guestpath]}\")\n            end\n          end\n\n          # Go through each folder and make sure to create it if\n          # it does not exist on host\n          folders.each do |_, fs|\n            fs.each do |id, data|\n              next if data[:hostpath_exact]\n\n              data[:hostpath] = File.expand_path(\n                data[:hostpath], env[:root_path])\n\n              # Expand the symlink if this is a path that exists\n              if File.file?(data[:hostpath])\n                data[:hostpath] = File.realpath(data[:hostpath])\n              end\n\n              # Create the hostpath if it doesn't exist and we've been told to\n              if !File.directory?(data[:hostpath]) && data[:create]\n                @logger.info(\"Creating shared folder host directory: #{data[:hostpath]}\")\n                begin\n                  Pathname.new(data[:hostpath]).mkpath\n                rescue Errno::EACCES\n                  raise Vagrant::Errors::SharedFolderCreateFailed,\n                    path: data[:hostpath]\n                end\n              end\n\n              if File.directory?(data[:hostpath])\n                data[:hostpath] = File.realpath(data[:hostpath])\n                data[:hostpath] = Util::Platform.fs_real_path(data[:hostpath]).to_s\n              end\n            end\n          end\n\n          # Build up the instances of the synced folders. We do this once\n          # so that they can store state.\n          folders = folders.map do |impl_name, fs|\n            instance = plugins[impl_name.to_sym][0].new\n            [instance, impl_name, fs]\n          end\n\n          # Go through each folder and prepare the folders\n          folders.each do |impl, impl_name, fs|\n            if !env[:synced_folders_disable]\n              @logger.info(\"Invoking synced folder prepare for: #{impl_name}\")\n              impl.prepare(env[:machine], fs, impl_opts(impl_name, env))\n            end\n          end\n\n          # Continue, we need the VM to be booted.\n          @app.call(env)\n\n          # Once booted, setup the folder contents\n          folders.each do |impl, impl_name, fs|\n            if !env[:synced_folders_disable]\n              @logger.info(\"Invoking synced folder enable: #{impl_name}\")\n              impl.enable(env[:machine], fs, impl_opts(impl_name, env))\n              next\n            end\n\n            # We're disabling synced folders\n            to_disable = {}\n            fs.each do |id, data|\n              next if !env[:synced_folders_disable].include?(id)\n              to_disable[id] = data\n            end\n\n            @logger.info(\"Invoking synced folder disable: #{impl_name}\")\n            to_disable.each do |id, _|\n              @logger.info(\"  - Disabling: #{id}\")\n            end\n            impl.disable(env[:machine], to_disable, impl_opts(impl_name, env))\n          end\n\n          # If we disabled folders, we have to delete some from the\n          # save, so we load the entire cached thing, and delete them.\n          if env[:synced_folders_disable]\n            all = synced_folders(env[:machine], cached: true)\n            all.each do |impl, fs|\n              fs.keys.each do |id|\n                if env[:synced_folders_disable].include?(id)\n                  fs.delete(id)\n                end\n              end\n            end\n\n            save_synced_folders(env[:machine], all)\n          else\n            save_opts = { merge: true }\n            save_opts[:vagrantfile] = true if !opts[:config]\n\n            # Save the synced folders\n            save_synced_folders(env[:machine], original_folders, **save_opts)\n          end\n\n          # Persist the mounts by adding them to fstab (only if the guest is available)\n          begin\n            persist_mount = env[:machine].guest.capability?(:persist_mount_shared_folder)\n          rescue Errors::MachineGuestNotReady\n            persist_mount = false\n          end\n          if persist_mount\n            # Persist the mounts by adding them to fstab\n            if env[:machine].config.vm.allow_fstab_modification\n              fstab_folders = original_folders\n            else\n              fstab_folders = nil\n            end\n            env[:machine].guest.capability(:persist_mount_shared_folder, fstab_folders)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/trigger.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This class is used within the Builder class for injecting triggers into\n      # different parts of the call stack.\n      class Trigger\n        # @param [Class, String, Symbol] name Name of trigger to fire\n        # @param [Vagrant::Plugin::V2::Triger] triggers Trigger object\n        # @param [Symbol] timing When trigger should fire (:before/:after)\n        # @param [Symbol] type Type of trigger\n        def initialize(app, env, name, triggers, timing, type=:action, all: false)\n          @app         = app\n          @env         = env\n          @triggers    = triggers\n          @name        = name\n          @timing      = timing\n          @type        = type\n          @all         = all\n\n          if ![:before, :after].include?(timing)\n            raise ArgumentError,\n              \"Invalid value provided for `timing` (allowed: :before or :after)\"\n          end\n        end\n\n        def call(env)\n          machine = env[:machine]\n          machine_name = machine.name if machine\n\n          @triggers.fire(@name, @timing, machine_name, @type, all: @all)\n          # Carry on\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/builtin/wait_for_communicator.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    module Builtin\n      # This waits for the communicator to be ready for a set amount of\n      # time.\n      class WaitForCommunicator\n        def initialize(app, env, states=nil)\n          @app    = app\n          @states = states\n        end\n\n        def call(env)\n          # Wait for ready in a thread so that we can continually check\n          # for interrupts.\n          ready_thr = Thread.new do\n            Thread.current[:result] = env[:machine].communicate.wait_for_ready(\n              env[:machine].config.vm.boot_timeout)\n          end\n\n          # Start a thread that verifies the VM stays in a good state.\n          states_thr = Thread.new do\n            Thread.current[:result] = true\n\n            # Otherwise, periodically verify the VM isn't in a bad state.\n            while true\n              state = env[:machine].state.id\n\n              # Used to report invalid states\n              Thread.current[:last_known_state] = state\n\n              # Check if we have the proper state so we can break out\n              if @states && !@states.include?(state)\n                Thread.current[:result] = false\n                break\n              end\n\n              # Sleep a bit so we don't hit 100% CPU constantly.\n              sleep 1\n            end\n          end\n\n          # Wait for a result or an interrupt\n          env[:ui].output(I18n.t(\"vagrant.boot_waiting\"))\n          while ready_thr.alive? && states_thr.alive?\n            sleep 1\n            return if env[:interrupted]\n          end\n\n          # Join so that they can raise exceptions if there were any\n          ready_thr.join if !ready_thr.alive?\n          states_thr.join if !states_thr.alive?\n\n          # If it went into a bad state, then raise an error\n          if !states_thr[:result]\n            raise Errors::VMBootBadState,\n              valid: @states.join(\", \"),\n              invalid: states_thr[:last_known_state]\n          end\n\n          # If it didn't boot, raise an error\n          if !ready_thr[:result]\n            raise Errors::VMBootTimeout\n          end\n\n          env[:ui].output(I18n.t(\"vagrant.boot_completed\"))\n\n          # Make sure our threads are all killed\n          ready_thr.kill\n          states_thr.kill\n\n          @app.call(env)\n        ensure\n          ready_thr.kill\n          states_thr.kill\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/general/package.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'fileutils'\nrequire \"pathname\"\nrequire 'vagrant/util/safe_chdir'\nrequire 'vagrant/util/subprocess'\nrequire 'vagrant/util/presence'\n\nmodule Vagrant\n  module Action\n    module General\n      # A general packaging (tar) middleware. Given the following options,\n      # it will do the right thing:\n      #\n      #   * package.output - The filename of the outputted package.\n      #   * package.include - An array of files to include in the package.\n      #   * package.info - Path of desired info.json file to include\n      #   * package.directory - The directory which contains the contents to\n      #       compress into the package.\n      #\n      # This middleware always produces the final file in the current working\n      # directory (FileUtils.pwd)\n      class Package\n        include Util\n\n        # Perform sanity validations that the provided output filepath is sane.\n        # In particular, this function validates:\n        #\n        #   - The output path is a regular file (not a directory or symlink)\n        #   - No file currently exists at the given path\n        #   - A directory of package files was actually provided (internal)\n        #\n        # @param [String] output path to the output file\n        # @param [String] directory path to a directory containing the files\n        def self.validate!(output, directory)\n          filename = File.basename(output.to_s)\n          output   = fullpath(output)\n\n          if File.directory?(output)\n            raise Vagrant::Errors::PackageOutputDirectory\n          end\n\n          if File.exist?(output)\n            raise Vagrant::Errors::PackageOutputExists, filename: filename\n          end\n\n          if !Vagrant::Util::Presence.present?(directory) || !File.directory?(directory)\n            raise Vagrant::Errors::PackageRequiresDirectory\n          end\n        end\n\n        # Calculate the full path of the given path, relative to the current\n        # working directory (where the command was run).\n        #\n        # @param [String] output the relative path\n        def self.fullpath(output)\n          File.expand_path(output, Dir.pwd)\n        end\n\n        # The path to the final output file.\n        # @return [String]\n        attr_reader :fullpath\n\n        def initialize(app, env)\n          @app = app\n\n          env[\"package.files\"]  ||= {}\n          env[\"package.info\"]   ||= \"\"\n          env[\"package.output\"] ||= \"package.box\"\n\n          @fullpath = self.class.fullpath(env[\"package.output\"])\n        end\n\n        def call(env)\n          @env = env\n\n          self.class.validate!(env[\"package.output\"], env[\"package.directory\"])\n\n          package_with_folder_path if env[\"package.output\"].include?(File::SEPARATOR)\n\n          raise Errors::PackageOutputDirectory if File.directory?(fullpath)\n\n          raise Errors::PackageInvalidInfo if invalid_info?\n\n          @app.call(env)\n\n          @env[:ui].info I18n.t(\"vagrant.actions.general.package.compressing\", fullpath: fullpath)\n\n          copy_include_files\n          copy_info\n          setup_private_key\n          write_metadata_json\n          compress\n        end\n\n        def package_with_folder_path\n          folder_path = File.expand_path(\"..\", @fullpath)\n          create_box_folder(folder_path) unless File.directory?(folder_path)\n        end\n\n        def create_box_folder(folder_path)\n          @env[:ui].info(I18n.t(\"vagrant.actions.general.package.box_folder\", folder_path: folder_path))\n          FileUtils.mkdir_p(folder_path)\n        end\n\n        def recover(env)\n          @env = env\n\n          # There are certain exceptions that we don't delete the file for.\n          ignore_exc = [Errors::PackageOutputDirectory, Errors::PackageOutputExists]\n          ignore_exc.each do |exc|\n            return if env[\"vagrant.error\"].is_a?(exc)\n          end\n\n          # Cleanup any packaged files if the packaging failed at some point.\n          File.delete(fullpath) if File.exist?(fullpath)\n        end\n\n        # This method copies the include files (passed in via command line)\n        # to the temporary directory so they are included in a sub-folder within\n        # the actual box\n        def copy_include_files\n          include_directory = Pathname.new(@env[\"package.directory\"]).join(\"include\")\n\n          @env[\"package.files\"].each do |from, dest|\n            # We place the file in the include directory\n            to = include_directory.join(dest)\n\n            @env[:ui].info I18n.t(\"vagrant.actions.general.package.packaging\", file: from)\n            FileUtils.mkdir_p(to.parent)\n\n            # Copy directory contents recursively.\n            if File.directory?(from)\n              FileUtils.cp_r(Dir.glob(from), to.parent, preserve: true)\n            else\n              FileUtils.cp(from, to, preserve: true)\n            end\n          end\n        rescue Errno::EEXIST => e\n          raise if !e.to_s.include?(\"symlink\")\n\n          # The directory contains symlinks. Show a nicer error.\n          raise Errors::PackageIncludeSymlink\n        end\n\n        # This method copies the specified info.json file to the temporary directory\n        # so that it is accessible via the 'box list -i' command\n        def copy_info\n          info_path = Pathname.new(@env[\"package.info\"])\n\n          if info_path.file?\n            FileUtils.cp(info_path, @env[\"package.directory\"], preserve: true)\n          end\n        end\n\n        # Compress the exported file into a package\n        def compress\n          # Get the output path. We have to do this up here so that the\n          # pwd returns the proper thing.\n          output_path = fullpath.to_s\n\n          # Switch into that directory and package everything up\n          Util::SafeChdir.safe_chdir(@env[\"package.directory\"]) do\n            # Find all the files in our current directory and tar it up!\n            files = Dir.glob(File.join(\".\", \"*\"))\n\n            # Package!\n            Util::Subprocess.execute(\"bsdtar\", \"-czf\", output_path, *files)\n          end\n        end\n\n        # Write the metadata file into the box so that the provider\n        # can be automatically detected when adding the box\n        def write_metadata_json\n          meta_path = File.join(@env[\"package.directory\"], \"metadata.json\")\n          return if File.exist?(meta_path)\n\n          if @env[:machine] && @env[:machine].provider_name\n            provider_name = @env[:machine].provider_name\n          elsif @env[:env] && @env[:env].default_provider\n            provider_name = @env[:env].default_provider\n          else\n            return\n          end\n          File.write(meta_path, {provider: provider_name}.to_json)\n        end\n\n        # This will copy the generated private key into the box and use\n        # it for SSH by default. We have to do this because we now generate\n        # random keypairs on boot, so packaged boxes would stop working\n        # without this.\n        def setup_private_key\n          # If we don't have machine, we do nothing (weird)\n          return if !@env[:machine]\n\n          # If we don't have a data dir, we also do nothing (base package)\n          return if !@env[:machine].data_dir\n\n          # If we don't have a generated private key, we do nothing\n          path = @env[:machine].data_dir.join(\"private_key\")\n          if !path.file?\n            # If we have a private key that was copied into this box,\n            # then we copy that. This is a bit of a heuristic and can be a\n            # security risk if the key is named the correct thing, but\n            # we'll take that risk for dev environments.\n            (@env[:machine].config.ssh.private_key_path || []).each do |p|\n              # If we have the correctly named key, copy it\n              if File.basename(p) == \"vagrant_private_key\"\n                path = Pathname.new(p)\n                break\n              end\n            end\n          end\n\n          # If we still have no matching key, do nothing\n          return if !path.file?\n\n          # Copy it into our box directory\n          dir = Pathname.new(@env[\"package.directory\"])\n          new_path = dir.join(\"vagrant_private_key\")\n          FileUtils.cp(path, new_path)\n\n          # Append it to the Vagrantfile (or create a Vagrantfile)\n          vf_path = dir.join(\"Vagrantfile\")\n          mode = \"w+\"\n          mode = \"a\" if vf_path.file?\n          vf_path.open(mode) do |f|\n            f.binmode\n            f.puts\n            f.puts %Q[Vagrant.configure(\"2\") do |config|]\n            f.puts %Q[  config.ssh.private_key_path = File.expand_path(\"../vagrant_private_key\", __FILE__)]\n            f.puts %Q[end]\n          end\n        end\n\n        # Check to see if package.info is a valid file and titled info.json\n        def invalid_info?\n          if @env[\"package.info\"] != \"\"\n            info_path = Pathname.new(@env[\"package.info\"])\n\n            return !info_path.file? || File.basename(info_path) != \"info.json\"\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/general/package_setup_files.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    module General\n      class PackageSetupFiles\n        def initialize(app, env)\n          @app = app\n\n          env[\"package.include\"] ||= []\n          env[\"package.vagrantfile\"] ||= nil\n        end\n\n        def call(env)\n          files = {}\n          env[\"package.include\"].each do |file|\n            source = Pathname.new(file)\n            dest   = nil\n\n            # If the source is relative then we add the file as-is to the include\n            # directory. Otherwise, we copy only the file into the root of the\n            # include directory. Kind of strange, but seems to match what people\n            # expect based on history.\n            if source.relative?\n              dest = source\n            else\n              dest = source.basename\n            end\n\n            # Assign the mapping\n            files[file] = dest\n          end\n\n          if env[\"package.vagrantfile\"]\n            # Vagrantfiles are treated special and mapped to a specific file\n            files[env[\"package.vagrantfile\"]] = \"_Vagrantfile\"\n          end\n\n          # Verify the mapping\n          files.each do |from, _|\n            raise Vagrant::Errors::PackageIncludeMissing,\n                  file: from if !File.exist?(from)\n          end\n\n          # Save the mapping\n          env[\"package.files\"] = files\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/general/package_setup_folders.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\nrequire_relative \"package\"\n\nmodule Vagrant\n  module Action\n    module General\n      class PackageSetupFolders\n        include Vagrant::Util::Presence\n\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[\"package.output\"] ||= \"package.box\"\n          env[\"package.directory\"] ||= Dir.mktmpdir(\"vagrant-package-\", env[:tmp_path])\n\n          # Match up a couple environmental variables so that the other parts of\n          # Vagrant will do the right thing.\n          env[\"export.temp_dir\"] = env[\"package.directory\"]\n\n          Vagrant::Action::General::Package.validate!(\n              env[\"package.output\"], env[\"package.directory\"])\n\n          @app.call(env)\n        end\n\n        def recover(env)\n          dir = env[\"package.directory\"]\n          if File.exist?(dir)\n            FileUtils.rm_rf(dir)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/hook.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    # This class manages hooks into existing {Builder} stacks, and lets you\n    # add and remove middleware classes. This is the primary method by which\n    # plugins can hook into built-in middleware stacks.\n    class Hook\n      # This is a hash of the middleware to prepend to a certain\n      # other middleware.\n      #\n      # @return [Hash<Class, Array<Builder::StackItem>>]\n      attr_reader :before_hooks\n\n      # This is a hash of the middleware to append to a certain other\n      # middleware.\n      #\n      # @return [Hash<Class, Array<Builder::StackItem>>]\n      attr_reader :after_hooks\n\n      # This is a list of the hooks to just prepend to the beginning\n      #\n      # @return [Array<Builder::StackItem>]\n      attr_reader :prepend_hooks\n\n      # This is a list of the hooks to just append to the end\n      #\n      # @return [Array<Builder::StackItem>]\n      attr_reader :append_hooks\n\n      def initialize\n        @before_hooks  = Hash.new { |h, k| h[k] = [] }\n        @after_hooks   = Hash.new { |h, k| h[k] = [] }\n        @prepend_hooks = []\n        @append_hooks  = []\n      end\n\n      # Add a middleware before an existing middleware.\n      #\n      # @param [Class] existing The existing middleware.\n      # @param [Class] new The new middleware.\n      def before(existing, new, *args, **keywords, &block)\n        item = Builder::StackItem.new(\n          middleware: new,\n          arguments: Builder::MiddlewareArguments.new(\n            parameters: args,\n            keywords: keywords,\n            block: block\n          )\n        )\n        @before_hooks[existing] << item\n      end\n\n      # Add a middleware after an existing middleware.\n      #\n      # @param [Class] existing The existing middleware.\n      # @param [Class] new The new middleware.\n      def after(existing, new, *args, **keywords, &block)\n        item = Builder::StackItem.new(\n          middleware: new,\n          arguments: Builder::MiddlewareArguments.new(\n            parameters: args,\n            keywords: keywords,\n            block: block\n          )\n        )\n        @after_hooks[existing] << item\n      end\n\n      # Append a middleware to the end of the stack. Note that if the\n      # middleware sequence ends early, then the new middleware won't\n      # be run.\n      #\n      # @param [Class] new The middleware to append.\n      def append(new, *args, **keywords, &block)\n        item = Builder::StackItem.new(\n          middleware: new,\n          arguments: Builder::MiddlewareArguments.new(\n            parameters: args,\n            keywords: keywords,\n            block: block\n          )\n        )\n        @append_hooks << item\n      end\n\n      # Prepend a middleware to the beginning of the stack.\n      #\n      # @param [Class] new The new middleware to prepend.\n      def prepend(new, *args, **keywords, &block)\n        item = Builder::StackItem.new(\n          middleware: new,\n          arguments: Builder::MiddlewareArguments.new(\n            parameters: args,\n            keywords: keywords,\n            block: block\n          )\n        )\n        @prepend_hooks << item\n      end\n\n      # @return [Boolean]\n      def empty?\n        before_hooks.empty? &&\n          after_hooks.empty? &&\n          prepend_hooks.empty? &&\n          append_hooks.empty?\n      end\n\n      # This applies the given hook to a builder. This should not be\n      # called directly.\n      #\n      # @param [Builder] builder\n      def apply(builder, options={})\n        if !options[:no_prepend_or_append]\n          # Prepends first\n          @prepend_hooks.each do |item|\n            if options[:root]\n              idx = builder.index(options[:root])\n            else\n              idx = 0\n            end\n            builder.insert(idx, item.middleware, *item.arguments.parameters,\n              **item.arguments.keywords, &item.arguments.block)\n          end\n\n          # Appends\n          @append_hooks.each do |item|\n            if options[:root]\n              idx = builder.index(options[:root])\n              builder.insert(idx + 1, item.middleware, *item.arguments.parameters,\n                **item.arguments.keywords, &item.arguments.block)\n            else\n              builder.use(item.middleware, *item.arguments.parameters,\n                **item.arguments.keywords, &item.arguments.block)\n            end\n          end\n        end\n\n        # Before hooks\n        @before_hooks.each do |key, list|\n          next if !builder.index(key)\n\n          list.each do |item|\n            builder.insert_before(key, item.middleware, *item.arguments.parameters,\n              **item.arguments.keywords, &item.arguments.block)\n          end\n        end\n\n        # After hooks\n        @after_hooks.each do |key, list|\n          next if !builder.index(key)\n\n          list.each do |item|\n            builder.insert_after(key, item.middleware, *item.arguments.parameters,\n              **item.arguments.keywords, &item.arguments.block)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/primary_runner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Action\n    # A PrimaryRunner is a special kind of \"top-level\" Action::Runner - it\n    # informs any Action::Builders it interacts with that they are also\n    # primary. This allows Builders to distinguish whether or not they are\n    # nested, which they need to know for proper action_hook handling.\n    #\n    # @see Vagrant::Action::Builder#primary\n    class PrimaryRunner < Runner\n      def primary?\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/runner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'log4r'\n\nrequire 'vagrant/action/hook'\nrequire 'vagrant/util/busy'\nrequire 'vagrant/util/experimental'\n\nmodule Vagrant\n  module Action\n    class Runner\n      @@reported_interrupt = false\n\n      # @param globals [Hash] variables for the env to be passed to the action\n      # @yieldreturn [Hash] lazy-loaded vars merged into the env before action run\n      def initialize(globals=nil, &block)\n        @globals      = globals || {}\n        @lazy_globals = block\n        @logger       = Log4r::Logger.new(\"vagrant::action::runner\")\n      end\n\n      # @see PrimaryRunner\n      # @see Vagrant::Action::Builder#primary\n      def primary?\n        false\n      end\n\n      def run(callable_id, options=nil)\n        callable = callable_id\n        if !callable.kind_of?(Builder)\n          if callable_id.kind_of?(Class) || callable_id.respond_to?(:call)\n            callable = Builder.build(callable_id)\n          end\n        end\n\n        if !callable || !callable.respond_to?(:call)\n          raise ArgumentError,\n            \"Argument to run must be a callable object or registered action.\"\n        end\n\n        if callable.is_a?(Builder)\n          callable.primary = self.primary?\n        end\n\n        # Create the initial environment with the options given\n        environment = {}\n        environment.merge!(@globals)\n        environment.merge!(@lazy_globals.call) if @lazy_globals\n        environment.merge!(options || {})\n\n        # NOTE: Triggers are initialized later in the Action::Runer because of\n        # how `@lazy_globals` are evaluated. Rather than trying to guess where\n        # the `env` is coming from, we can wait until they're merged into a single\n        # hash above.\n        env = environment[:env]\n        machine = environment[:machine]\n\n        environment[:triggers] = machine.triggers if machine\n\n        if env\n          ui = Vagrant::UI::Prefixed.new(env.ui, \"vagrant\")\n          environment[:triggers] ||= Vagrant::Plugin::V2::Trigger.\n            new(env, env.vagrantfile.config.trigger, machine, ui)\n        end\n\n        # Run the action chain in a busy block, marking the environment as\n        # interrupted if a SIGINT occurs, and exiting cleanly once the\n        # chain has been run.\n        ui = environment[:ui] if environment.key?(:ui)\n        int_callback = lambda do\n          if environment[:interrupted]\n            if ui\n              begin\n                ui.error I18n.t(\"vagrant.actions.runner.exit_immediately\")\n              rescue ThreadError\n                # We're being called in a trap-context. Wrap in a thread.\n                Thread.new {\n                  ui.error I18n.t(\"vagrant.actions.runner.exit_immediately\")\n                }.join(THREAD_MAX_JOIN_TIMEOUT)\n              end\n            end\n            abort\n          end\n\n          if ui && !@@reported_interrupt\n            begin\n              ui.warn I18n.t(\"vagrant.actions.runner.waiting_cleanup\")\n            rescue ThreadError\n              # We're being called in a trap-context. Wrap in a thread.\n              Thread.new {\n                ui.warn I18n.t(\"vagrant.actions.runner.waiting_cleanup\")\n              }.join(THREAD_MAX_JOIN_TIMEOUT)\n            end\n          end\n          environment[:interrupted] = true\n          @@reported_interrupt = true\n        end\n\n        action_name = environment[:action_name]\n\n        # We place a process lock around every action that is called\n        @logger.info(\"Running action: #{action_name} #{callable_id}\")\n        Util::Busy.busy(int_callback) { callable.call(environment) }\n\n        # Return the environment in case there are things in there that\n        # the caller wants to use.\n        environment\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action/warden.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire 'vagrant/util/experimental'\n\nmodule Vagrant\n  module Action\n    # The action warden is a middleware which injects itself between\n    # every other middleware, watching for exceptions which are raised\n    # and performing proper cleanup on every action by calling the `recover`\n    # method. The warden therefore allows middlewares to not worry about\n    # exceptional events, and by providing a simple callback, can clean up\n    # in any erroneous case.\n    #\n    # Warden will \"just work\" behind the scenes, and is not of particular\n    # interest except to those who are curious about the internal workings\n    # of Vagrant.\n    class Warden\n      attr_accessor :actions, :stack\n\n      def initialize(actions, env)\n        @stack      = []\n        @actions    = actions.map { |m| finalize_action(m, env) }.flatten\n        @logger     = Log4r::Logger.new(\"vagrant::action::warden\")\n        @last_error = nil\n      end\n\n      def call(env)\n        return if @actions.empty?\n\n        begin\n          # Call the next middleware in the sequence, appending to the stack\n          # of \"recoverable\" middlewares in case something goes wrong!\n          raise Errors::VagrantInterrupt if env[:interrupted]\n          action = @actions.shift\n          @logger.info(\"Calling IN action: #{action}\")\n          @stack.unshift(action).first.call(env)\n          raise Errors::VagrantInterrupt if env[:interrupted]\n          @logger.info(\"Calling OUT action: #{action}\")\n        rescue SystemExit, NoMemoryError\n          # This means that an \"exit\" or \"abort\" was called, or we have run out\n          # of memory. In these cases, we just exit immediately.\n          raise\n        rescue Exception => e\n          # We guard this so that the Warden only outputs this once for\n          # an exception that bubbles up.\n          if e != @last_error\n            @logger.error(\"Error occurred: #{e}\")\n            @last_error = e\n          end\n\n          env[\"vagrant.error\"] = e\n\n          # Something went horribly wrong. Start the rescue chain then\n          # reraise the exception to properly kick us out of limbo here.\n          recover(env)\n          raise\n        end\n      end\n\n      # We implement the recover method ourselves in case a Warden is\n      # embedded within another Warden. To recover, we just do our own\n      # recovery process on our stack.\n      def recover(env)\n        @logger.info(\"Beginning recovery process...\")\n\n        @stack.each do |act|\n          if act.respond_to?(:recover)\n            @logger.info(\"Calling recover: #{act}\")\n            act.recover(env)\n          end\n        end\n\n        @logger.info(\"Recovery complete.\")\n\n        # Clear stack so that warden down the middleware chain doesn't\n        # rescue again.\n        @stack.clear\n      end\n\n      # A somewhat confusing function which simply initializes each\n      # middleware properly to call the next middleware in the sequence.\n      def finalize_action(action, env)\n        if action.is_a?(Builder::StackItem)\n          klass = action.middleware\n          args = action.arguments.parameters\n          keywords = action.arguments.keywords\n          block = action.arguments.block\n        else\n          klass = action\n          args = []\n          keywords = {}\n        end\n\n        args = nil if args.empty?\n        keywords = nil if keywords.empty?\n\n        if klass.is_a?(Class)\n          # NOTE: We need to detect if we are passing args and/or\n          #       keywords and do it explicitly. Earlier versions\n          #       are not as lax about splatting keywords when the\n          #       target method is not expecting them.\n          if args && keywords\n            klass.new(self, env, *args, **keywords, &block)\n          elsif args\n            klass.new(self, env, *args, &block)\n          elsif keywords\n            klass.new(self, env, **keywords, &block)\n          else\n            klass.new(self, env, &block)\n          end\n        elsif klass.respond_to?(:call)\n          # Make it a lambda which calls the item then forwards\n          # up the chain\n          lambda do |e|\n            klass.call(e)\n            self.call(e)\n          end\n        else\n          raise \"Invalid action: #{action.inspect}\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/action.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/action/builder'\n\nmodule Vagrant\n  module Action\n    autoload :Builder,       'vagrant/action/builder'\n    autoload :Hook,          'vagrant/action/hook'\n    autoload :Runner,        'vagrant/action/runner'\n    autoload :PrimaryRunner, 'vagrant/action/primary_runner'\n    autoload :Warden,        'vagrant/action/warden'\n\n    # Builtin contains middleware classes that are shipped with Vagrant-core\n    # and are thus available to all plugins as a \"standard library\" of sorts.\n    module Builtin\n      autoload :BoxAdd,    \"vagrant/action/builtin/box_add\"\n      autoload :BoxCheckOutdated, \"vagrant/action/builtin/box_check_outdated\"\n      autoload :BoxRemove, \"vagrant/action/builtin/box_remove\"\n      autoload :BoxUpdate, \"vagrant/action/builtin/box_update\"\n      autoload :Call,    \"vagrant/action/builtin/call\"\n      autoload :CleanupDisks, \"vagrant/action/builtin/cleanup_disks\"\n      autoload :CloudInitSetup, \"vagrant/action/builtin/cloud_init_setup\"\n      autoload :CloudInitWait, \"vagrant/action/builtin/cloud_init_wait\"\n      autoload :ConfigValidate, \"vagrant/action/builtin/config_validate\"\n      autoload :Confirm, \"vagrant/action/builtin/confirm\"\n      autoload :Delayed, \"vagrant/action/builtin/delayed\"\n      autoload :DestroyConfirm, \"vagrant/action/builtin/destroy_confirm\"\n      autoload :Disk, \"vagrant/action/builtin/disk\"\n      autoload :EnvSet,  \"vagrant/action/builtin/env_set\"\n      autoload :GracefulHalt, \"vagrant/action/builtin/graceful_halt\"\n      autoload :HandleBox, \"vagrant/action/builtin/handle_box\"\n      autoload :HandleBoxUrl, \"vagrant/action/builtin/handle_box_url\"\n      autoload :HandleForwardedPortCollisions, \"vagrant/action/builtin/handle_forwarded_port_collisions\"\n      autoload :HasProvisioner, \"vagrant/action/builtin/has_provisioner\"\n      autoload :IsEnvSet, \"vagrant/action/builtin/is_env_set\"\n      autoload :IsState, \"vagrant/action/builtin/is_state\"\n      autoload :Lock, \"vagrant/action/builtin/lock\"\n      autoload :Message, \"vagrant/action/builtin/message\"\n      autoload :MixinProvisioners, \"vagrant/action/builtin/mixin_provisioners\"\n      autoload :MixinSyncedFolders, \"vagrant/action/builtin/mixin_synced_folders\"\n      autoload :PrepareClone, \"vagrant/action/builtin/prepare_clone\"\n      autoload :Provision, \"vagrant/action/builtin/provision\"\n      autoload :ProvisionerCleanup, \"vagrant/action/builtin/provisioner_cleanup\"\n      autoload :SetHostname, \"vagrant/action/builtin/set_hostname\"\n      autoload :SSHExec, \"vagrant/action/builtin/ssh_exec\"\n      autoload :SSHRun,  \"vagrant/action/builtin/ssh_run\"\n      autoload :SyncedFolderCleanup, \"vagrant/action/builtin/synced_folder_cleanup\"\n      autoload :SyncedFolders, \"vagrant/action/builtin/synced_folders\"\n      autoload :Trigger, \"vagrant/action/builtin/trigger\"\n      autoload :WaitForCommunicator, \"vagrant/action/builtin/wait_for_communicator\"\n    end\n\n    module General\n      autoload :Package, 'vagrant/action/general/package'\n      autoload :PackageSetupFiles, 'vagrant/action/general/package_setup_files'\n      autoload :PackageSetupFolders, 'vagrant/action/general/package_setup_folders'\n    end\n\n    # This is the action that will add a box from a URL. This middleware\n    # sequence is built-in to Vagrant. Plugins can hook into this like any\n    # other middleware sequence. This is particularly useful for provider\n    # plugins, which can hook in to do things like verification of boxes\n    # that are downloaded.\n    def self.action_box_add\n      Builder.new.tap do |b|\n        b.use Builtin::BoxAdd\n      end\n    end\n\n    # This actions checks if a box is outdated in a given Vagrant\n    # environment for a single machine.\n    def self.action_box_outdated\n      Builder.new.tap do |b|\n        b.use Builtin::BoxCheckOutdated\n      end\n    end\n\n    # This is the action that will remove a box given a name (and optionally\n    # a provider). This middleware sequence is built-in to Vagrant. Plugins\n    # can hook into this like any other middleware sequence.\n    def self.action_box_remove\n      Builder.new.tap do |b|\n        b.use Builtin::BoxRemove\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/alias.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/registry\"\n\nmodule Vagrant\n  # This class imports and processes CLI aliases stored in ~/.vagrant.d/aliases\n  class Alias\n    def initialize(env)\n      @aliases = Registry.new\n      @env = env\n\n      if env.aliases_path.file?\n        env.aliases_path.readlines.each do |line|\n          # separate keyword-command pairs\n          keyword, command = interpret(line)\n\n          if keyword && command\n            register(keyword, command)\n          end\n        end\n      end\n    end\n\n    # This returns all the registered alias commands.\n    def commands\n      @aliases\n    end\n\n    # This interprets a raw line from the aliases file.\n    def interpret(line)\n      # is it a comment?\n      return nil if line.strip.start_with?(\"#\")\n\n      keyword, command = line.split(\"=\", 2).collect(&:strip)\n\n      # validate the keyword\n      if keyword.match(/\\s/i)\n        raise Errors::AliasInvalidError, alias: line, message: \"Alias keywords must not contain any whitespace.\"\n      end\n\n      [keyword, command]\n    end\n\n    # This registers an alias.\n    def register(keyword, command)\n      @aliases.register(keyword.to_sym) do\n        lambda do |args|\n          # directly execute shell commands\n          if command.start_with?(\"!\")\n            return Util::SafeExec.exec \"#{command[1..-1]} #{args.join(\" \")}\".strip\n          end\n\n          return CLI.new(command.split.concat(args), @env).execute\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/batch_action.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'thread'\nrequire \"log4r\"\n\nmodule Vagrant\n  # This class executes multiple actions as a single batch, parallelizing\n  # the action calls if possible.\n  class BatchAction\n    def initialize(allow_parallel=true)\n      @actions          = []\n      @allow_parallel   = allow_parallel\n      @logger           = Log4r::Logger.new(\"vagrant::batch_action\")\n    end\n\n    # Add an action to the batch of actions that will be run.\n    #\n    # This will **not** run the action now. The action will be run\n    # when {#run} is called.\n    #\n    # @param [Machine] machine The machine to run the action on\n    # @param [Symbol] action The action to run\n    # @param [Hash] options Any additional options to send in.\n    def action(machine, action, options=nil)\n      @actions << [machine, action, options]\n    end\n\n    # Custom runs a custom proc against a machine.\n    #\n    # @param [Machine] machine The machine to run against.\n    def custom(machine, &block)\n      @actions << [machine, block, nil]\n    end\n\n    # Run all the queued up actions, parallelizing if possible.\n    #\n    # This will parallelize if and only if the provider of every machine\n    # supports parallelization and parallelization is possible from\n    # initialization of the class.\n    def run\n      par = false\n\n      if @allow_parallel\n        par = true\n        @logger.info(\"Enabling parallelization by default.\")\n      end\n\n      if par\n        @actions.each do |machine, _, _|\n          if !machine.provider_options[:parallel]\n            @logger.info(\"Disabling parallelization because provider doesn't support it: #{machine.provider_name}\")\n            par = false\n            break\n          end\n        end\n      end\n\n      if par && @actions.length <= 1\n        @logger.info(\"Disabling parallelization because only executing one action\")\n        par = false\n      end\n\n      @logger.info(\"Batch action will parallelize: #{par.inspect}\")\n\n      threads = []\n      @actions.each do |machine, action, options|\n        @logger.info(\"Starting action: #{machine} #{action} #{options}\")\n\n        # Create the new thread to run our action. This is basically just\n        # calling the action but also contains some error handling in it\n        # as well.\n        thread = Thread.new do\n          Thread.current[:error] = nil\n\n          # Note that this thread is being used for running\n          # a batch action\n          Thread.current[:batch_parallel_action] = par\n\n          # Record our pid when we started in order to figure out if\n          # we've forked...\n          start_pid = Process.pid\n\n          begin\n            if action.is_a?(Proc)\n              action.call(machine)\n            else\n              machine.send(:action, action, options)\n            end\n          rescue Exception => e\n            # If we're not parallelizing, then raise the error. We also\n            # don't raise the error if we've forked, because it'll hang\n            # the process.\n            raise if !par && Process.pid == start_pid\n\n            # Store the exception that will be processed later\n            Thread.current[:error] = e\n\n            # We can only do the things below if we do not fork, otherwise\n            # it'll hang the process.\n            if Process.pid == start_pid\n              # Let the user know that this process had an error early\n              # so that they see it while other things are happening.\n              machine.ui.error(I18n.t(\"vagrant.general.batch_notify_error\"))\n            end\n          end\n\n          # If we forked during the process run, we need to do a hard\n          # exit here. Ruby's fork only copies the running process (which\n          # would be us), so if we return from this thread, it results\n          # in a zombie Ruby process.\n          if Process.pid != start_pid\n            # We forked.\n\n            exit_status = true\n            if Thread.current[:error]\n              # We had an error, print the stack trace and exit immediately.\n              exit_status = false\n              error = Thread.current[:error]\n              @logger.error(error.inspect)\n              @logger.error(error.message)\n              @logger.error(error.backtrace.join(\"\\n\"))\n            end\n\n            Process.exit!(exit_status)\n          end\n        end\n\n        # Set some attributes on the thread for later\n        thread[:machine] = machine\n\n        if !par\n          thread.join(THREAD_MAX_JOIN_TIMEOUT) while thread.alive?\n        end\n        threads << thread\n      end\n\n      errors = []\n\n      threads.each do |thread|\n        # Wait for the thread to complete\n        thread.join(THREAD_MAX_JOIN_TIMEOUT) while thread.alive?\n\n        # If the thread had an error, then store the error to show later\n        if thread[:error]\n          e = thread[:error]\n          # If the error isn't a Vagrant error, then store the backtrace\n          # as well.\n          if !thread[:error].is_a?(Errors::VagrantError)\n            e       = thread[:error]\n            message = e.message\n            message += \"\\n\"\n            message += \"\\n#{e.backtrace.join(\"\\n\")}\"\n\n            errors << I18n.t(\"vagrant.general.batch_unexpected_error\",\n                             machine: thread[:machine].name,\n                             message: message)\n          else\n            errors << I18n.t(\"vagrant.general.batch_vagrant_error\",\n                             machine: thread[:machine].name,\n                             message: thread[:error].message)\n          end\n        end\n      end\n\n      if !errors.empty?\n        raise Errors::BatchMultiError, message: errors.join(\"\\n\\n\")\n      end\n\n      # Check if any threads set an exit code and exit if found. If\n      # multiple threads have exit code values set, the first encountered\n      # will be the value used.\n      threads.each do |thread|\n        if thread[:exit_code]\n          @logger.debug(\"Found exit code set within batch action thread. Exiting\")\n          Process.exit!(thread[:exit_code])\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/box.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'fileutils'\nrequire \"tempfile\"\n\nrequire \"json\"\nrequire \"log4r\"\n\nrequire \"vagrant/box_metadata\"\nrequire \"vagrant/util/downloader\"\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/safe_chdir\"\nrequire \"vagrant/util/subprocess\"\n\nmodule Vagrant\n  # Represents a \"box,\" which is a package Vagrant environment that is used\n  # as a base image when creating a new guest machine.\n  class Box\n    include Comparable\n\n    # The required fields in a boxes `metadata.json` file\n    REQUIRED_METADATA_FIELDS = [\"provider\"]\n\n    # Number of seconds to wait between checks for box updates\n    BOX_UPDATE_CHECK_INTERVAL = 3600\n\n    # The box name. This is the logical name used when adding the box.\n    #\n    # @return [String]\n    attr_reader :name\n\n    # This is the provider that this box is built for.\n    #\n    # @return [Symbol]\n    attr_reader :provider\n\n    # This is the architecture that this box is build for.\n    #\n    # @return [String]\n    attr_reader :architecture\n\n    # The version of this box.\n    #\n    # @return [String]\n    attr_reader :version\n\n    # This is the directory on disk where this box exists.\n    #\n    # @return [Pathname]\n    attr_reader :directory\n\n    # This is the metadata for the box. This is read from the \"metadata.json\"\n    # file that all boxes require.\n    #\n    # @return [Hash]\n    attr_reader :metadata\n\n    # This is the URL to the version info and other metadata for this\n    # box.\n    #\n    # @return [String]\n    attr_reader :metadata_url\n\n    # This is used to initialize a box.\n    #\n    # @param [String] name Logical name of the box.\n    # @param [Symbol] provider The provider that this box implements.\n    # @param [Pathname] directory The directory where this box exists on\n    #   disk.\n    # @param [String] architecture Architecture the box was built for\n    # @param [String] metadata_url Metadata URL for box\n    # @param [Hook] hook A hook to apply to the box downloader, for example, for authentication\n    def initialize(name, provider, version, directory, architecture: nil, metadata_url: nil, hook: nil)\n      @name      = name\n      @version   = version\n      @provider  = provider\n      @directory = directory\n      @architecture = architecture\n      @metadata_url = metadata_url\n      @hook = hook\n\n      metadata_file = directory.join(\"metadata.json\")\n      raise Errors::BoxMetadataFileNotFound, name: @name if !metadata_file.file?\n\n      begin\n        @metadata = JSON.parse(directory.join(\"metadata.json\").read)\n        validate_metadata_json(@metadata)\n      rescue JSON::ParserError\n        raise Errors::BoxMetadataCorrupted, name: @name\n      end\n\n      @logger = Log4r::Logger.new(\"vagrant::box\")\n    end\n\n    def validate_metadata_json(metadata)\n      metatdata_fields = metadata.keys\n      REQUIRED_METADATA_FIELDS.each do |field|\n        if !metatdata_fields.include?(field)\n          raise Errors::BoxMetadataMissingRequiredFields,\n            name: @name,\n            required_field: field,\n            all_fields: REQUIRED_METADATA_FIELDS.join(\", \")\n        end\n      end\n    end\n\n    # This deletes the box. This is NOT undoable.\n    def destroy!\n      # Delete the directory to delete the box.\n      FileUtils.rm_r(@directory)\n\n      # Just return true always\n      true\n    rescue Errno::ENOENT\n      # This means the directory didn't exist. Not a problem.\n      return true\n    end\n\n    # Checks if this box is in use according to the given machine\n    # index and returns the entries that appear to be using the box.\n    #\n    # The entries returned, if any, are not tested for validity\n    # with {MachineIndex::Entry#valid?}, so the caller should do that\n    # if the caller cares.\n    #\n    # @param [MachineIndex] index\n    # @return [Array<MachineIndex::Entry>]\n    def in_use?(index)\n      results = []\n      index.each do |entry|\n        box_data = entry.extra_data[\"box\"]\n        next if !box_data\n\n        # If all the data matches, record it\n        if box_data[\"name\"] == self.name &&\n          box_data[\"provider\"] == self.provider.to_s &&\n          box_data[\"architecture\"] == self.architecture &&\n          box_data[\"version\"] == self.version.to_s\n          results << entry\n        end\n      end\n\n      return nil if results.empty?\n      results\n    end\n\n    # Loads the metadata URL and returns the latest metadata associated\n    # with this box.\n    #\n    # @param [Hash] download_options Options to pass to the downloader.\n    # @return [BoxMetadata]\n    def load_metadata(download_options={})\n      tf = Tempfile.new(\"vagrant-load-metadata\")\n      tf.close\n\n      url = @metadata_url\n      if File.file?(url) || url !~ /^[a-z0-9]+:.*$/i\n        url = File.expand_path(url)\n        url = Util::Platform.cygwin_windows_path(url)\n        url = \"file:#{url}\"\n      end\n\n      opts = { headers: [\"Accept: application/json\"] }.merge(download_options)\n      d = Util::Downloader.new(url, tf.path, opts)\n      if @hook\n        @hook.call(:authenticate_box_downloader, downloader: d)\n      end\n      d.download!\n      BoxMetadata.new(File.open(tf.path, \"r\"), url: url)\n    rescue Errors::DownloaderError => e\n      raise Errors::BoxMetadataDownloadError,\n        message: e.extra_data[:message]\n    ensure\n      tf.unlink if tf\n    end\n\n    # Checks if the box has an update and returns the metadata, version,\n    # and provider. If the box doesn't have an update that satisfies the\n    # constraints, it will return nil.\n    #\n    # This will potentially make a network call if it has to load the\n    # metadata from the network.\n    #\n    # @param [String] version Version constraints the update must\n    #   satisfy. If nil, the version constrain defaults to being a\n    #   larger version than this box.\n    # @return [Array]\n    def has_update?(version=nil, download_options: {})\n      if !@metadata_url\n        raise Errors::BoxUpdateNoMetadata, name: @name\n      end\n\n      if download_options.delete(:automatic_check) && !automatic_update_check_allowed?\n        @logger.info(\"Skipping box update check\")\n        return\n      end\n\n      version += \", \" if version\n      version ||= \"\"\n      version += \"> #{@version}\"\n      md      = self.load_metadata(download_options)\n      newer   = md.version(version, provider: @provider, architecture: @architecture)\n      \n      return nil if newer == nil || !md.compatible_version_update?(@version, newer.version, provider: @provider, architecture: @architecture)\n\n      [md, newer, newer.provider(@provider, @architecture)]\n    end\n\n    # Check if a box update check is allowed. Uses a file\n    # in the box data directory to track when the last auto\n    # update check was performed and returns true if the\n    # BOX_UPDATE_CHECK_INTERVAL has passed.\n    #\n    # @return [Boolean]\n    def automatic_update_check_allowed?\n      check_path = directory.join(\"box_update_check\")\n      if check_path.exist?\n        last_check_span = Time.now.to_i - check_path.mtime.to_i\n        if last_check_span < BOX_UPDATE_CHECK_INTERVAL\n          @logger.info(\"box update check is under the interval threshold\")\n          return false\n        end\n      end\n      FileUtils.touch(check_path)\n      true\n    end\n\n    # This repackages this box and outputs it to the given path.\n    #\n    # @param [Pathname] path The full path (filename included) of where\n    #   to output this box.\n    # @return [Boolean] true if this succeeds.\n    def repackage(path)\n      @logger.debug(\"Repackaging box '#{@name}' to: #{path}\")\n\n      Util::SafeChdir.safe_chdir(@directory) do\n        # Find all the files in our current directory and tar it up!\n        files = Dir.glob(File.join(\".\", \"**\", \"*\")).select { |f| File.file?(f) }\n\n        # Package!\n        Util::Subprocess.execute(\"bsdtar\", \"-czf\", path.to_s, *files)\n      end\n\n      @logger.info(\"Repackaged box '#{@name}' successfully: #{path}\")\n\n      true\n    end\n\n    # Implemented for comparison with other boxes. Comparison is\n    # implemented by comparing names, providers, and architectures.\n    def <=>(other)\n      return super if !other.is_a?(self.class)\n\n      # Comparison is done by composing the name and provider\n      \"#{@name}-#{@version}-#{@provider}-#{@architecture}\" <=>\n      \"#{other.name}-#{other.version}-#{other.provider}-#{other.architecture}\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/box_collection.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"digest/sha1\"\nrequire \"fileutils\"\nrequire \"monitor\"\nrequire \"tmpdir\"\nrequire \"log4r\"\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/subprocess\"\n\nmodule Vagrant\n  # Represents a collection a boxes found on disk. This provides methods\n  # for accessing/finding individual boxes, adding new boxes, or deleting\n  # boxes.\n  class BoxCollection\n    TEMP_PREFIX = \"vagrant-box-add-temp-\".freeze\n    VAGRANT_SLASH = \"-VAGRANTSLASH-\".freeze\n    VAGRANT_COLON = \"-VAGRANTCOLON-\".freeze\n\n    # The directory where the boxes in this collection are stored.\n    #\n    # A box collection matches a very specific folder structure that Vagrant\n    # expects in order to easily manage and modify boxes. The folder structure\n    # is the following:\n    #\n    #     COLLECTION_ROOT/BOX_NAME/PROVIDER/[ARCHITECTURE]/metadata.json\n    #\n    # Where:\n    #\n    #   * COLLECTION_ROOT - This is the root of the box collection, and is\n    #     the directory given to the initializer.\n    #   * BOX_NAME - The name of the box. This is a logical name given by\n    #     the user of Vagrant.\n    #   * PROVIDER - The provider that the box was built for (VirtualBox,\n    #     VMware, etc.).\n    #   * ARCHITECTURE - Optional. The architecture that the box was built\n    #     for (amd64, arm64, 386, etc.).\n    #   * metadata.json - A simple JSON file that at the bare minimum\n    #     contains a \"provider\" key that matches the provider for the\n    #     box. This metadata JSON, however, can contain anything.\n    #\n    # @return [Pathname]\n    attr_reader :directory\n\n    # Initializes the collection.\n    #\n    # @param [Pathname] directory The directory that contains the collection\n    #   of boxes.\n    def initialize(directory, options=nil)\n      options ||= {}\n\n      @directory = directory\n      @hook      = options[:hook]\n      @lock      = Monitor.new\n      @temp_root = options[:temp_dir_root]\n      @logger    = Log4r::Logger.new(\"vagrant::box_collection\")\n    end\n\n    # This adds a new box to the system.\n    #\n    # There are some exceptional cases:\n    # * BoxAlreadyExists - The box you're attempting to add already exists.\n    # * BoxProviderDoesntMatch - If the given box provider doesn't match the\n    #   actual box provider in the untarred box.\n    # * BoxUnpackageFailure - An invalid tar file.\n    #\n    # Preconditions:\n    # * File given in `path` must exist.\n    #\n    # @param [Pathname] path Path to the box file on disk.\n    # @param [String] name Logical name for the box.\n    # @param [String] version The version of this box.\n    # @param [Array<String>] providers The providers that this box can\n    #   be a part of. This will be verified with the `metadata.json` and is\n    #   meant as a basic check. If this isn't given, then whatever provider\n    #   the box represents will be added.\n    # @param [Boolean] force If true, any existing box with the same name\n    #   and provider will be replaced.\n    def add(path, name, version, **opts)\n      architecture = opts[:architecture]\n      providers = opts[:providers]\n      providers = Array(providers) if providers\n      provider = nil\n\n      # A helper to check if a box exists. We store this in a variable\n      # since we call it multiple times.\n      check_box_exists = lambda do |box_formats, box_architecture|\n        box = find(name, box_formats, version, box_architecture)\n        next if !box\n\n        if !opts[:force]\n          @logger.error(\n            \"Box already exists, can't add: #{name} v#{version} #{box_formats.join(\", \")}\")\n          raise Errors::BoxAlreadyExists,\n            name: name,\n            provider: box_formats.join(\", \"),\n            version: version\n        end\n\n        # We're forcing, so just delete the old box\n        @logger.info(\n          \"Box already exists, but forcing so removing: \" +\n          \"#{name} v#{version} #{box_formats.join(\", \")}\")\n        box.destroy!\n      end\n\n      with_collection_lock do\n        log_provider = providers ? providers.join(\", \") : \"any provider\"\n        @logger.debug(\"Adding box: #{name} (#{log_provider} - #{architecture.inspect}) from #{path}\")\n\n        # Verify the box doesn't exist early if we're given a provider. This\n        # can potentially speed things up considerably since we don't need\n        # to unpack any files.\n        check_box_exists.call(providers, architecture) if providers\n\n        # Create a temporary directory since we're not sure at this point if\n        # the box we're unpackaging already exists (if no provider was given)\n        with_temp_dir do |temp_dir|\n          # Extract the box into a temporary directory.\n          @logger.debug(\"Unpacking box into temporary directory: #{temp_dir}\")\n          result = Util::Subprocess.execute(\n            \"bsdtar\", \"--no-same-owner\", \"--no-same-permissions\", \"-v\", \"-x\", \"-m\", \"-S\", \"-s\", \"|\\\\\\\\\\|/|\", \"-C\", temp_dir.to_s, \"-f\", path.to_s)\n          if result.exit_code != 0\n            raise Errors::BoxUnpackageFailure,\n              output: result.stderr.to_s\n          end\n\n          # If we get a V1 box, we want to update it in place\n          if v1_box?(temp_dir)\n            @logger.debug(\"Added box is a V1 box. Upgrading in place.\")\n            temp_dir = v1_upgrade(temp_dir)\n          end\n\n          # We re-wrap ourselves in the safety net in case we upgraded.\n          # If we didn't upgrade, then this is still safe because the\n          # helper will only delete the directory if it exists\n          with_temp_dir(temp_dir) do |final_temp_dir|\n            # Get an instance of the box we just added before it is finalized\n            # in the system so we can inspect and use its metadata.\n            box = Box.new(name, nil, version, final_temp_dir)\n\n            # Get the provider, since we'll need that to at the least add it\n            # to the system or check that it matches what is given to us.\n            box_provider = box.metadata[\"provider\"]\n\n            if providers\n              found = providers.find { |p| p.to_sym == box_provider.to_sym }\n              if !found\n                @logger.error(\"Added box provider doesnt match expected: #{log_provider}\")\n                raise Errors::BoxProviderDoesntMatch,\n                  expected: log_provider, actual: box_provider\n              end\n            else\n              # Verify the box doesn't already exist\n              check_box_exists.call([box_provider], architecture)\n            end\n\n            # We weren't given a provider, so store this one.\n            provider = box_provider.to_sym\n\n            # Create the directory for this box, not including the provider\n            root_box_dir = @directory.join(dir_name(name))\n            box_dir = root_box_dir.join(version)\n            box_dir.mkpath\n            @logger.debug(\"Box directory: #{box_dir}\")\n\n            # This is the final directory we'll move it to\n            if architecture\n              arch = architecture\n              arch = Util::Platform.architecture if architecture == :auto\n              box_dir = box_dir.join(arch)\n            end\n            provider_dir = box_dir.join(provider.to_s)\n            @logger.debug(\"Provider directory: #{provider_dir}\")\n\n            if provider_dir.exist?\n              @logger.debug(\"Removing existing provider directory...\")\n              provider_dir.rmtree\n            end\n\n            # Move to final destination\n            provider_dir.mkpath\n\n            # Recursively move individual files from the temporary directory\n            # to the final location. We do this instead of moving the entire\n            # directory to avoid issues on Windows. [GH-1424]\n            copy_pairs = [[final_temp_dir, provider_dir]]\n            while !copy_pairs.empty?\n              from, to = copy_pairs.shift\n              from.children(true).each do |f|\n                dest = to.join(f.basename)\n\n                # We don't copy entire directories, so create the\n                # directory and then add to our list to copy.\n                if f.directory?\n                  dest.mkpath\n                  copy_pairs << [f, dest]\n                  next\n                end\n\n                # Copy the single file\n                @logger.debug(\"Moving: #{f} => #{dest}\")\n                FileUtils.mv(f, dest)\n              end\n            end\n\n            if opts[:metadata_url]\n              root_box_dir.join(\"metadata_url\").open(\"w\") do |f|\n                f.write(opts[:metadata_url])\n              end\n            end\n          end\n        end\n      end\n\n      # Return the box\n      find(name, provider, version, architecture)\n    end\n\n    # This returns an array of all the boxes on the system, given by\n    # their name and their provider.\n    #\n    # @return [Array] Array of `[name, version, provider, architecture]` of the boxes\n    #   installed on this system.\n    def all\n      results = []\n\n      with_collection_lock do\n        @logger.debug(\"Finding all boxes in: #{@directory}\")\n        @directory.children(true).each do |child|\n          # Ignore non-directories, since files are not interesting to\n          # us in our folder structure.\n          next if !child.directory?\n\n          box_name = undir_name(child.basename.to_s)\n\n          # Otherwise, traverse the subdirectories and see what versions\n          # we have.\n          child.children(true).each do |versiondir|\n            next if !versiondir.directory?\n            next if versiondir.basename.to_s.start_with?(\".\")\n\n            version = versiondir.basename.to_s\n            # Ensure version of box is correct before continuing\n            if !Gem::Version.correct?(version)\n              ui = Vagrant::UI::Prefixed.new(Vagrant::UI::Colored.new, \"vagrant\")\n              ui.warn(I18n.t(\"vagrant.box_version_malformed\",\n                             version: version, box_name: box_name))\n              @logger.debug(\"Invalid version #{version} for box #{box_name}\")\n              next\n            end\n\n            versiondir.children(true).each do |architecture_or_provider|\n              # If the entry is not a directory, it is invalid and should be ignored\n              if !architecture_or_provider.directory?\n                @logger.debug(\"Invalid box #{box_name} (v#{version}) - invalid item: #{architecture_or_provider}\")\n                next\n              end\n\n              # Now the directory can be assumed to be the architecture\n              architecture_name = architecture_or_provider.basename.to_s.to_sym\n\n              # Cycle through directories to find providers\n              architecture_or_provider.children(true).each do |provider|\n                if !provider.directory?\n                  @logger.debug(\"Invalid box #{box_name} (v#{version}, #{architecture_name}) - invalid item: #{provider}\")\n                  next\n                end\n\n                # If the entry contains a metadata file, add it\n                if provider.join(\"metadata.json\").file?\n                  provider_name = provider.basename.to_s.to_sym\n                  @logger.debug(\"Box: #{box_name} (#{provider_name} (#{architecture_name}), #{version})\")\n                  results << [box_name, version, provider_name, architecture_name]\n                end\n              end\n\n              # If the base entry contains a metadata file, then it was\n              # added prior to architecture support and is a provider directory.\n              # If it contains a metadata file, include it with the results only\n              # if an entry hasn't already been included for the local system's\n              # architecture\n              if architecture_or_provider.join(\"metadata.json\").file?\n                provider_name = architecture_or_provider.basename.to_s.to_sym\n                if results.include?([box_name, version, provider_name, Util::Platform.architecture.to_sym])\n                  next\n                end\n                @logger.debug(\"Box: #{box_name} (#{provider_name}, #{version})\")\n                results << [box_name, version, provider_name, nil]\n              end\n            end\n          end\n        end\n      end\n      # Sort the list to group like providers and properly ordered versions\n      results.sort_by! do |box_result|\n        [\n          box_result[0],\n          box_result[2],\n          Gem::Version.new(box_result[1]),\n          box_result[3] || :\"\"\n        ]\n      end\n      results\n    end\n\n    # Find a box in the collection with the given name and provider.\n    #\n    # @param [String] name Name of the box (logical name).\n    # @param [Array] providers Providers that the box implements.\n    # @param [String] version Version constraints to adhere to. Example:\n    #   \"~> 1.0\" or \"= 1.0, ~> 1.1\"\n    # @return [Box] The box found, or `nil` if not found.\n    def find(name, providers, version, box_architecture=:auto)\n      providers = Array(providers)\n      architecture = box_architecture\n      architecture = Util::Platform.architecture if architecture == :auto\n\n      # Build up the requirements we have\n      requirements = version.to_s.split(\",\").map do |v|\n        begin\n          Gem::Requirement.new(v.strip)\n        rescue Gem::Requirement::BadRequirementError\n          raise Errors::BoxVersionInvalid,\n                version: v.strip\n        end\n      end\n\n      with_collection_lock do\n        box_directory = @directory.join(dir_name(name))\n        if !box_directory.directory?\n          @logger.info(\"Box not found: #{name} (#{providers.join(\", \")})\")\n          return nil\n        end\n\n        # Keep a mapping of Gem::Version mangled versions => directories.\n        # ie. 0.1.0.pre.alpha.2 => 0.1.0-alpha.2\n        # This is so we can sort version numbers properly here, but still\n        # refer to the real directory names in path checks below and pass an\n        # unmangled version string to Box.new\n        version_dir_map = {}\n\n        versions = box_directory.children(true).map do |versiondir|\n          next if !versiondir.directory?\n          next if versiondir.basename.to_s.start_with?(\".\")\n\n          version = Gem::Version.new(versiondir.basename.to_s)\n          version_dir_map[version.to_s] = versiondir.basename.to_s\n          version\n        end.compact\n\n        # Traverse through versions with the latest version first\n        versions.sort.reverse.each do |v|\n          if !requirements.all? { |r| r.satisfied_by?(v) }\n            # Unsatisfied version requirements\n            next\n          end\n\n          versiondir = box_directory.join(version_dir_map[v.to_s])\n\n          providers.each do |provider|\n            providerdir = versiondir.join(architecture.to_s).join(provider.to_s)\n\n            # If the architecture was automatically set to the host\n            # architecture, then a match on the architecture subdirectory\n            # or the provider directory (which is a box install prior to\n            # architecture support) is valid\n            if box_architecture == :auto\n              if !providerdir.directory?\n                providerdir = versiondir.join(provider.to_s)\n              end\n\n              if providerdir.join(\"metadata.json\").file?\n                @logger.info(\"Box found: #{name} (#{provider})\")\n\n                metadata_url = nil\n                metadata_url_file = box_directory.join(\"metadata_url\")\n                metadata_url = metadata_url_file.read if metadata_url_file.file?\n\n                if metadata_url && @hook\n                  hook_env     = @hook.call(\n                    :authenticate_box_url, box_urls: [metadata_url])\n                  metadata_url = hook_env[:box_urls].first\n                end\n\n                return Box.new(\n                  name, provider, version_dir_map[v.to_s], providerdir,\n                  architecture: box_architecture, metadata_url: metadata_url, hook: @hook\n                )\n              end\n            end\n\n            # If there is no metadata file found, skip\n            next if !providerdir.join(\"metadata.json\").file?\n\n            @logger.info(\"Box found: #{name} (#{provider})\")\n\n            metadata_url = nil\n            metadata_url_file = box_directory.join(\"metadata_url\")\n            metadata_url = metadata_url_file.read if metadata_url_file.file?\n\n            if metadata_url && @hook\n              hook_env     = @hook.call(\n                :authenticate_box_url, box_urls: [metadata_url])\n              metadata_url = hook_env[:box_urls].first\n            end\n\n            return Box.new(\n              name, provider, version_dir_map[v.to_s], providerdir,\n              architecture: box_architecture, metadata_url: metadata_url, hook: @hook\n            )\n          end\n        end\n      end\n\n      nil\n    end\n\n    # This upgrades a v1.1 - v1.4 box directory structure up to a v1.5\n    # directory structure. This will raise exceptions if it fails in any\n    # way.\n    def upgrade_v1_1_v1_5\n      with_collection_lock do\n        temp_dir = Pathname.new(Dir.mktmpdir(TEMP_PREFIX, @temp_root))\n\n        @directory.children(true).each do |boxdir|\n          # Ignore all non-directories because they can't be boxes\n          next if !boxdir.directory?\n\n          box_name = boxdir.basename.to_s\n\n          # If it is a v1 box, then we need to upgrade it first\n          if v1_box?(boxdir)\n            upgrade_dir = v1_upgrade(boxdir)\n            FileUtils.mv(upgrade_dir, boxdir.join(\"virtualbox\"))\n          end\n\n          # Create the directory for this box\n          new_box_dir = temp_dir.join(dir_name(box_name), \"0\")\n          new_box_dir.mkpath\n\n          # Go through each provider and move it\n          boxdir.children(true).each do |providerdir|\n            FileUtils.cp_r(providerdir, new_box_dir.join(providerdir.basename))\n          end\n        end\n\n        # Move the folder into place\n        @directory.rmtree\n        FileUtils.mv(temp_dir.to_s, @directory.to_s)\n      end\n    end\n\n    # Cleans the directory for a box by removing the folders that are\n    # empty.\n    def clean(name)\n      return false if exists?(name)\n      path = File.join(directory, dir_name(name))\n      FileUtils.rm_rf(path)\n    end\n\n    protected\n\n    # Returns the directory name for the box of the given name.\n    #\n    # @param [String] name\n    # @return [String]\n    def dir_name(name)\n      name = name.dup\n      name.gsub!(\":\", VAGRANT_COLON) if Util::Platform.windows?\n      name.gsub!(\"/\", VAGRANT_SLASH)\n      name\n    end\n\n    # Returns the directory name for the box cleaned up\n    def undir_name(name)\n      name = name.dup\n      name.gsub!(VAGRANT_COLON, \":\")\n      name.gsub!(VAGRANT_SLASH, \"/\")\n      name\n    end\n\n    # This checks if the given directory represents a V1 box on the\n    # system.\n    #\n    # @param [Pathname] dir Directory where the box is unpacked.\n    # @return [Boolean]\n    def v1_box?(dir)\n      # We detect a V1 box given by whether there is a \"box.ovf\" which\n      # is a heuristic but is pretty accurate.\n      dir.join(\"box.ovf\").file?\n    end\n\n    # This upgrades the V1 box contained unpacked in the given directory\n    # and returns the directory of the upgraded version. This is\n    # _destructive_ to the contents of the old directory. That is, the\n    # contents of the old V1 box will be destroyed or moved.\n    #\n    # Preconditions:\n    # * `dir` is a valid V1 box. Verify with {#v1_box?}\n    #\n    # @param [Pathname] dir Directory where the V1 box is unpacked.\n    # @return [Pathname] Path to the unpackaged V2 box.\n    def v1_upgrade(dir)\n      @logger.debug(\"Upgrading box in directory: #{dir}\")\n\n      temp_dir = Pathname.new(Dir.mktmpdir(TEMP_PREFIX, @temp_root))\n      @logger.debug(\"Temporary directory for upgrading: #{temp_dir}\")\n\n      # Move all the things into the temporary directory\n      dir.children(true).each do |child|\n        # Don't move the temp_dir\n        next if child == temp_dir\n\n        # Move every other directory into the temporary directory\n        @logger.debug(\"Copying to upgrade directory: #{child}\")\n        FileUtils.mv(child, temp_dir.join(child.basename))\n      end\n\n      # If there is no metadata.json file, make one, since this is how\n      # we determine if the box is a V2 box.\n      metadata_file = temp_dir.join(\"metadata.json\")\n      if !metadata_file.file?\n        metadata_file.open(\"w\") do |f|\n          f.write(JSON.generate({\n            provider: \"virtualbox\"\n          }))\n        end\n      end\n\n      # Return the temporary directory\n      temp_dir\n    end\n\n    # This locks the region given by the block with a lock on this\n    # collection.\n    def with_collection_lock\n      @lock.synchronize do\n        return yield\n      end\n    end\n\n    # This is a helper that makes sure that our temporary directories\n    # are cleaned up no matter what.\n    #\n    # @param [String] dir Path to a temporary directory\n    # @return [Object] The result of whatever the yield is\n    def with_temp_dir(dir=nil)\n      dir ||= Dir.mktmpdir(TEMP_PREFIX, @temp_root)\n      dir = Pathname.new(dir)\n\n      yield dir\n    ensure\n      FileUtils.rm_rf(dir.to_s)\n    end\n\n    # Checks if a box with a given name exists.\n    def exists?(box_name)\n      all.any? { |box| box.first.eql?(box_name) }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/box_metadata.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\n\nmodule Vagrant\n  # BoxMetadata represents metadata about a box, including the name\n  # it should have, a description of it, the versions it has, and\n  # more.\n  class BoxMetadata\n    # The name that the box should be if it is added.\n    #\n    # @return [String]\n    attr_accessor :name\n\n    # The long-form human-readable description of a box.\n    #\n    # @return [String]\n    attr_accessor :description\n\n    # Loads the metadata associated with the box from the given\n    # IO.\n    #\n    # @param [IO] io An IO object to read the metadata from.\n    def initialize(io, **_)\n      begin\n        @raw = JSON.load(io)\n      rescue JSON::ParserError => e\n        raise Errors::BoxMetadataMalformed,\n          error: e.to_s\n      end\n\n      @raw ||= {}\n      @name = @raw[\"name\"]\n      @description = @raw[\"description\"]\n      @version_map = (@raw[\"versions\"] || []).map do |v|\n        begin\n          [Gem::Version.new(v[\"version\"]), Version.new(v)]\n        rescue ArgumentError\n          raise Errors::BoxMetadataMalformedVersion,\n            version: v[\"version\"].to_s\n        end\n      end\n      @version_map = Hash[@version_map]\n    end\n\n    # Returns data about a single version that is included in this\n    # metadata.\n    #\n    # @param [String] version The version to return, this can also\n    #   be a constraint.\n    # @option [Symbol, Array<Symbol>] :provider Provider filter\n    # @option [Symbol] :architecture Architecture filter\n    #\n    # @return [Version] The matching version or nil if a matching\n    #   version was not found.\n    def version(version, **opts)\n      requirements = version.split(\",\").map do |v|\n        Gem::Requirement.new(v.strip)\n      end\n\n      providers = nil\n      providers = Array(opts[:provider]).map(&:to_sym) if opts[:provider]\n      # NOTE: The :auto value is not expanded here since no architecture\n      #       value comparisons are being done within this method\n      architecture = opts.fetch(:architecture, :auto)\n\n      @version_map.keys.sort.reverse.each do |v|\n        next if !requirements.all? { |r| r.satisfied_by?(v) }\n        version = @version_map[v]\n        valid_providers = version.providers\n\n        # If filtering by provider(s), apply filter\n        valid_providers &= providers if providers\n\n        # Skip if no valid providers are found\n        next if valid_providers.empty?\n\n        # Skip if no valid provider includes support\n        # the desired architecture\n        next if architecture && valid_providers.none? { |p|\n          version.provider(p, architecture)\n        }\n\n        return version\n      end\n\n      nil\n    end\n\n    # Returns all the versions supported by this metadata. These\n    # versions are sorted so the last element of the list is the\n    # latest version. Optionally filter versions by a matching\n    # provider.\n    #\n    # @option [Symbol, Array<Symbol>] :provider Provider filter\n    # @option [Symbol] :architecture Architecture filter\n    #\n    # @return[Array<String>]\n    def versions(**opts)\n      architecture = opts[:architecture]\n      provider = Array(opts[:provider]).map(&:to_sym) if opts[:provider]\n\n      # Return full version list if no filters provided\n      if provider.nil? && architecture.nil?\n        return @version_map.keys.sort.map(&:to_s)\n      end\n\n      # If a specific provider is not provided, filter\n      # only on architecture\n      if provider.nil?\n        return @version_map.select { |_, version|\n          !version.providers(architecture).empty?\n        }.keys.sort.map(&:to_s)\n      end\n\n      @version_map.select { |_, version|\n        provider.any? { |pv| version.provider(pv, architecture) }\n      }.keys.sort.map(&:to_s)\n    end\n\n    def compatible_version_update?(current_version, new_version, **opts) \n      return false if Gem::Version.new(new_version) <= Gem::Version.new(current_version)\n\n      architecture = opts[:architecture]\n      provider = opts[:provider]\n      # If the specific provider isn't available, there is nothing further to check for compatibility\n      return true if provider.nil?\n\n      # If the current_provider isn't found in the metadata, it cannot be compared against. Default to allowing updates\n      current_provider  = version(current_version.to_s, provider: provider, architecture: architecture)&.provider(provider, architecture)\n      return true if current_provider.nil? \n\n      # If the new provider isn't found, the new version isn't compatible\n      new_provider = version(new_version.to_s, provider: provider, architecture: architecture)&.provider(provider, architecture)\n      return false if new_provider.nil?\n\n      # Disallow updates from a known architecture to an unknown architecture, because it can not be verified, unless architecture is explicitly set to `nil`\n      return false if !architecture.nil? &&  new_provider.architecture == \"unknown\" && current_provider.architecture != \"unknown\"\n\n      # New version is compatible\n      return true\n    end \n\n    # Represents a single version within the metadata.\n    class Version\n      # The version that this Version object represents.\n      #\n      # @return [String]\n      attr_accessor :version\n\n      def initialize(raw=nil, **_)\n        return if !raw\n\n        @version = raw[\"version\"]\n        @providers = raw.fetch(\"providers\", []).map do |data|\n          Provider.new(data)\n        end\n        @provider_map = @providers.group_by(&:name)\n        @provider_map = Util::HashWithIndifferentAccess.new(@provider_map)\n      end\n\n      # Returns a [Provider] for the given name, or nil if it isn't\n      # supported by this version.\n      def provider(name, architecture=nil)\n        name = name.to_sym\n        arch_name = architecture\n        arch_name = Util::Platform.architecture if arch_name == :auto\n        arch_name = arch_name.to_s if arch_name\n\n        # If the provider doesn't exist in the map, return immediately\n        return if !@provider_map.key?(name)\n\n        # If the arch_name value is set, filter based\n        # on architecture and return match if found. If\n        # no match is found and architecture wasn't automatically\n        # detected, return nil as an explicit match is\n        # being requested\n        if arch_name\n          match = @provider_map[name].detect do |p|\n            p.architecture == arch_name\n          end\n\n          return match if match || architecture != :auto\n        end\n\n        # If the passed architecture value was :auto and no explicit\n        # match for the architecture was found, check for a provider\n        # that is flagged as the default architecture, and has an\n        # architecture value of \"unknown\"\n        #\n        # NOTE: This preserves expected behavior with legacy boxes\n        if architecture == :auto\n          match = @provider_map[name].detect do |p|\n            p.architecture == \"unknown\" &&\n              p.default_architecture\n          end\n\n          return match if match\n        end\n\n        # If the architecture value is set to nil, then just return\n        # whatever is defined as the default architecture\n        if architecture.nil?\n          match = @provider_map[name].detect(&:default_architecture)\n\n          return match if match\n        end\n\n        # The metadata consumed may not include architecture information,\n        # in which case the match would just be the single provider\n        # defined within the provider map for the name\n        if @provider_map[name].size == 1 && !@provider_map[name].first.architecture_support?\n          return @provider_map[name].first\n        end\n\n        # Otherwise, there is no match\n        nil\n      end\n\n      # Returns the providers that are available for this version\n      # of the box.\n      #\n      # @return [Array<Symbol>]\n      def providers(architecture=nil)\n        return @provider_map.keys.map(&:to_sym) if architecture.nil?\n\n        @provider_map.keys.find_all { |k|\n          provider(k, architecture)\n        }.map(&:to_sym)\n      end\n    end\n\n    # Provider represents a single provider-specific box available\n    # for a version for a box.\n    class Provider\n      # The name of the provider.\n      #\n      # @return [String]\n      attr_accessor :name\n\n      # The URL of the box.\n      #\n      # @return [String]\n      attr_accessor :url\n\n      # The checksum value for this box, if any.\n      #\n      # @return [String]\n      attr_accessor :checksum\n\n      # The type of checksum (if any) associated with this provider.\n      #\n      # @return [String]\n      attr_accessor :checksum_type\n\n      # The architecture of the box\n      #\n      # @return [String]\n      attr_accessor :architecture\n\n      # Marked as the default architecture\n      #\n      # @return [Boolean, NilClass]\n      attr_accessor :default_architecture\n\n      def initialize(raw, **_)\n        @name = raw[\"name\"]\n        @url  = raw[\"url\"]\n        @checksum = raw[\"checksum\"]\n        @checksum_type = raw[\"checksum_type\"]\n        @architecture = raw[\"architecture\"]\n        @default_architecture = raw[\"default_architecture\"]\n      end\n\n      def architecture_support?\n        !@default_architecture.nil?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/bundler.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"monitor\"\nrequire \"pathname\"\nrequire \"set\"\nrequire \"tempfile\"\nrequire \"fileutils\"\nrequire \"uri\"\n\nrequire \"rubygems/package\"\nrequire \"rubygems/uninstaller\"\nrequire \"rubygems/name_tuple\"\n\nrequire_relative \"shared_helpers\"\nrequire_relative \"version\"\nrequire_relative \"util/safe_env\"\n\nmodule Vagrant\n  # This class manages Vagrant's interaction with Bundler. Vagrant uses\n  # Bundler as a way to properly resolve all dependencies of Vagrant and\n  # all Vagrant-installed plugins.\n  class Bundler\n    class SolutionFile\n      # @return [Pathname] path to plugin file\n      attr_reader :plugin_file\n      # @return [Pathname] path to solution file\n      attr_reader :solution_file\n      # @return [Array<Gem::Resolver::DependencyRequest>] list of required dependencies\n      attr_reader :dependency_list\n\n      # @param [Pathname] plugin_file Path to plugin file\n      # @param [Pathname] solution_file Custom path to solution file\n      def initialize(plugin_file:, solution_file: nil)\n        @logger = Log4r::Logger.new(\"vagrant::bundler::solution_file\")\n        @plugin_file = Pathname.new(plugin_file.to_s)\n        if solution_file\n          @solution_file = Pathname.new(solution_file.to_s)\n        else\n          @solution_file = Pathname.new(@plugin_file.to_s + \".sol\")\n        end\n        @valid = false\n        @dependency_list = [].freeze\n        @logger.debug(\"new solution file instance plugin_file=#{plugin_file} \" \\\n          \"solution_file=#{solution_file}\")\n        load\n      end\n\n      # Set the list of dependencies for this solution\n      #\n      # @param [Array<Gem::Dependency>] dependency_list List of dependencies for the solution\n      # @return [Array<Gem::Resolver::DependencyRequest>]\n      def dependency_list=(dependency_list)\n        Array(dependency_list).each do |d|\n          if !d.is_a?(Gem::Dependency)\n            raise TypeError, \"Expected `Gem::Dependency` but received `#{d.class}`\"\n          end\n        end\n        @dependency_list = dependency_list.map do |d|\n          Gem::Resolver::DependencyRequest.new(d, nil).freeze\n        end.freeze\n      end\n\n      # @return [Boolean] contained solution is valid\n      def valid?\n        @valid\n      end\n\n      # @return [FalseClass] invalidate this solution file\n      def invalidate!\n        @valid = false\n        @logger.debug(\"manually invalidating solution file #{self}\")\n        @valid\n      end\n\n      # Delete the solution file\n      #\n      # @return [Boolean] true if file was deleted\n      def delete!\n        if !solution_file.exist?\n          @logger.debug(\"solution file does not exist. nothing to delete.\")\n          return false\n        end\n        @logger.debug(\"deleting solution file - #{solution_file}\")\n        solution_file.delete\n        true\n      end\n\n      # Store the solution file\n      def store!\n        if !plugin_file.exist?\n          @logger.debug(\"plugin file does not exist, not storing solution\")\n          return\n        end\n        if !solution_file.dirname.exist?\n          @logger.debug(\"creating directory for solution file: #{solution_file.dirname}\")\n          solution_file.dirname.mkpath\n        end\n        @logger.debug(\"writing solution file contents to disk\")\n        solution_file.write({\n          dependencies: dependency_list.map { |d|\n            [d.dependency.name, d.dependency.requirements_list]\n          },\n          checksum: plugin_file_checksum,\n          vagrant_version: Vagrant::VERSION\n        }.to_json)\n        @valid = true\n      end\n\n      def to_s # :nodoc:\n        \"<Vagrant::Bundler::SolutionFile:#{plugin_file}:\" \\\n          \"#{solution_file}:#{valid? ? \"valid\" : \"invalid\"}>\"\n      end\n\n      protected\n\n      # Load the solution file for the plugin path provided\n      # if it exists. Validate solution is still applicable\n      # before injecting dependencies.\n      def load\n        if !plugin_file.exist? || !solution_file.exist?\n          @logger.debug(\"missing file so skipping loading\")\n          return\n        end\n        solution = read_solution || return\n        return if !valid_solution?(\n          checksum: solution[:checksum],\n          version: solution[:vagrant_version]\n        )\n        @logger.debug(\"loading solution dependency list\")\n        @dependency_list = Array(solution[:dependencies]).map do |name, requirements|\n          gd = Gem::Dependency.new(name, requirements)\n          Gem::Resolver::DependencyRequest.new(gd, nil).freeze\n        end.freeze\n        @logger.debug(\"solution dependency list: #{dependency_list}\")\n        @valid = true\n      end\n\n      # Validate the given checksum matches the plugin file\n      # checksum\n      #\n      # @param [String] checksum Checksum value to validate\n      # @return [Boolean]\n      def valid_solution?(checksum:, version:)\n        file_checksum = plugin_file_checksum\n        @logger.debug(\"solution validation check CHECKSUM #{file_checksum} <-> #{checksum}\" \\\n          \" VERSION #{Vagrant::VERSION} <-> #{version}\")\n        plugin_file_checksum == checksum &&\n          Vagrant::VERSION == version\n      end\n\n      # @return [String] checksum of plugin file\n      def plugin_file_checksum\n        digest = Digest::SHA256.new\n        digest.file(plugin_file.to_s)\n        digest.hexdigest\n      end\n\n      # Read contents of solution file and parse\n      #\n      # @return [Hash]\n      def read_solution\n        @logger.debug(\"reading solution file - #{solution_file}\")\n        begin\n          hash = JSON.load(solution_file.read)\n          Vagrant::Util::HashWithIndifferentAccess.new(hash)\n        rescue => err\n          @logger.warn(\"failed to load solution file, ignoring (error: #{err})\")\n          nil\n        end\n      end\n    end\n\n    # Location of HashiCorp gem repository\n    HASHICORP_GEMSTORE = \"https://gems.hashicorp.com/\".freeze\n\n    # Default gem repositories\n    DEFAULT_GEM_SOURCES = [\n      HASHICORP_GEMSTORE,\n      \"https://rubygems.org/\".freeze\n    ].freeze\n\n    def self.instance\n      @bundler ||= self.new\n    end\n\n    # @return [Pathname] Global plugin path\n    attr_reader :plugin_gem_path\n    # @return [Pathname] Global plugin solution set path\n    attr_reader :plugin_solution_path\n    # @return [Pathname] Vagrant environment specific plugin path\n    attr_reader :env_plugin_gem_path\n    # @return [Pathname] Vagrant environment data path\n    attr_reader :environment_data_path\n    # @return [Array<Gem::Specification>, nil] List of builtin specs\n    attr_accessor :builtin_specs\n\n    def initialize\n      @builtin_specs = []\n      @plugin_gem_path = Vagrant.user_data_path.join(\"gems\", RUBY_VERSION).freeze\n      @logger = Log4r::Logger.new(\"vagrant::bundler\")\n    end\n\n    # Enable Vagrant environment specific plugins at given data path\n    #\n    # @param [Pathname] Path to Vagrant::Environment data directory\n    # @return [Pathname] Path to environment specific gem directory\n    def environment_path=(env_data_path)\n      if !env_data_path.is_a?(Pathname)\n        raise TypeError, \"Expected `Pathname` but received `#{env_data_path.class}`\"\n      end\n      @env_plugin_gem_path = env_data_path.join(\"plugins\", \"gems\", RUBY_VERSION).freeze\n      @environment_data_path = env_data_path\n    end\n\n    # Use the given options to create a solution file instance\n    # for use during initialization. When a Vagrant environment\n    # is in use, solution files will be stored within the environment's\n    # data directory. This is because the solution for loading global\n    # plugins is dependent on any solution generated for local plugins.\n    # When no Vagrant environment is in use (running Vagrant without a\n    # Vagrantfile), the Vagrant user data path will be used for solution\n    # storage since only the global plugins will be used.\n    #\n    # @param [Hash] opts Options passed to #init!\n    # @return [SolutionFile]\n    def load_solution_file(opts={})\n      return if !opts[:local] && !opts[:global]\n      return if opts[:local] && opts[:global]\n      return if opts[:local] && environment_data_path.nil?\n      solution_path = (environment_data_path || Vagrant.user_data_path) + \"bundler\"\n      solution_path += opts[:local] ? \"local.sol\" : \"global.sol\"\n      SolutionFile.new(\n        plugin_file: opts[:local] || opts[:global],\n        solution_file: solution_path\n      )\n    end\n\n    # Initializes Bundler and the various gem paths so that we can begin\n    # loading gems.\n    def init!(plugins, repair=false, **opts)\n      if !@initial_specifications\n        @initial_specifications = Gem::Specification.find_all{true}\n      else\n        Gem::Specification.all = @initial_specifications\n        Gem::Specification.reset\n      end\n\n      solution_file = load_solution_file(opts)\n      @logger.debug(\"solution file in use for init: #{solution_file}\")\n\n      solution = nil\n      composed_set = generate_vagrant_set\n\n      # Force the composed set to allow prereleases\n      if Vagrant.allow_prerelease_dependencies?\n        @logger.debug(\"enabling prerelease dependency matching due to user request\")\n        composed_set.prerelease = true\n      end\n\n      if solution_file&.valid?\n        @logger.debug(\"loading cached solution set\")\n        solution = solution_file.dependency_list.map do |dep|\n          spec = composed_set.find_all(dep).select do |dep_spec|\n            next(true) unless Gem.loaded_specs.has_key?(dep_spec.name)\n\n            Gem.loaded_specs[dep_spec.name].version.eql?(dep_spec.version)\n          end.first\n\n          if !spec\n            @logger.warn(\"failed to locate specification for dependency - #{dep}\")\n            @logger.warn(\"invalidating solution file - #{solution_file}\")\n            solution_file.invalidate!\n            break\n          end\n          dep_r = Gem::Resolver::DependencyRequest.new(dep, nil)\n          Gem::Resolver::ActivationRequest.new(spec, dep_r)\n        end\n      end\n\n      if !solution_file&.valid?\n        @logger.debug(\"generating solution set for configured plugins\")\n        # Add HashiCorp RubyGems source\n        if !Gem.sources.include?(HASHICORP_GEMSTORE)\n          sources = [HASHICORP_GEMSTORE] + Gem.sources.sources\n          Gem.sources.replace(sources)\n        end\n\n        # Generate dependencies for all registered plugins\n        plugin_deps = plugins.map do |name, info|\n          Gem::Dependency.new(name, info['installed_gem_version'].to_s.empty? ? '> 0' : info['installed_gem_version'])\n        end\n\n        @logger.debug(\"Current generated plugin dependency list: #{plugin_deps}\")\n\n        # Load dependencies into a request set for resolution\n        request_set = Gem::RequestSet.new(*plugin_deps)\n        # Never allow dependencies to be remotely satisfied during init\n        request_set.remote = false\n\n        begin\n          @logger.debug(\"resolving solution from available specification set\")\n          # Resolve the request set to ensure proper activation order\n          solution = request_set.resolve(composed_set)\n          @logger.debug(\"solution set for configured plugins has been resolved\")\n        rescue Gem::UnsatisfiableDependencyError => failure\n          if repair\n            raise failure if @init_retried\n            @logger.debug(\"Resolution failed but attempting to repair. Failure: #{failure}\")\n            install(plugins)\n            @init_retried = true\n            retry\n          else\n            raise\n          end\n        end\n      end\n\n      # Activate the gems\n      @logger.debug(\"activating solution set\")\n      activate_solution(solution)\n\n      if solution_file && !solution_file.valid?\n        solution_file.dependency_list = solution.map do |activation|\n          activation.request.dependency\n        end\n        solution_file.store!\n        @logger.debug(\"solution set stored to - #{solution_file}\")\n      end\n\n      full_vagrant_spec_list = @initial_specifications +\n        solution.map(&:full_spec)\n\n      if(defined?(::Bundler))\n        @logger.debug(\"Updating Bundler with full specification list\")\n        ::Bundler.rubygems.replace_entrypoints(full_vagrant_spec_list)\n      end\n\n      Gem.post_reset do\n        Gem::Specification.all = full_vagrant_spec_list\n      end\n\n      Gem::Specification.reset\n      nil\n    end\n\n    # Removes any temporary files created by init\n    def deinit\n      # no-op\n    end\n\n    # Installs the list of plugins.\n    #\n    # @param [Hash] plugins\n    # @param [Boolean] env_local Environment local plugin install\n    # @return [Array<Gem::Specification>]\n    def install(plugins, env_local=false)\n      internal_install(plugins, nil, env_local: env_local)\n    end\n\n    # Installs a local '*.gem' file so that Bundler can find it.\n    #\n    # @param [String] path Path to a local gem file.\n    # @return [Gem::Specification]\n    def install_local(path, opts={})\n      plugin_source = Gem::Source::SpecificFile.new(path)\n      plugin_info = {\n        plugin_source.spec.name => {\n          \"gem_version\" => plugin_source.spec.version.to_s,\n          \"local_source\" => plugin_source,\n          \"sources\" => opts.fetch(:sources, [])\n        }\n      }\n      @logger.debug(\"Installing local plugin - #{plugin_info}\")\n      internal_install(plugin_info, nil, env_local: opts[:env_local])\n      plugin_source.spec\n    end\n\n    # Update updates the given plugins, or every plugin if none is given.\n    #\n    # @param [Hash] plugins\n    # @param [Array<String>] specific Specific plugin names to update. If\n    #   empty or nil, all plugins will be updated.\n    def update(plugins, specific, **opts)\n      specific ||= []\n      update = opts.merge({gems: specific.empty? ? true : specific})\n      internal_install(plugins, update)\n    end\n\n    # Clean removes any unused gems.\n    def clean(plugins, **opts)\n      @logger.debug(\"Cleaning Vagrant plugins of stale gems.\")\n      # Generate dependencies for all registered plugins\n      plugin_deps = plugins.map do |name, info|\n        gem_version = info['installed_gem_version']\n        gem_version = info['gem_version'] if gem_version.to_s.empty?\n        gem_version = \"> 0\" if gem_version.to_s.empty?\n        Gem::Dependency.new(name, gem_version)\n      end\n\n      @logger.debug(\"Current plugin dependency list: #{plugin_deps}\")\n\n      # Load dependencies into a request set for resolution\n      request_set = Gem::RequestSet.new(*plugin_deps)\n      # Never allow dependencies to be remotely satisfied during cleaning\n      request_set.remote = false\n\n      # Sets that we can resolve our dependencies from. Note that we only\n      # resolve from the current set as all required deps are activated during\n      # init.\n      current_set = generate_vagrant_set\n\n      # Collect all plugin specifications\n      plugin_specs = Dir.glob(plugin_gem_path.join('specifications/*.gemspec').to_s).map do |spec_path|\n        Gem::Specification.load(spec_path)\n      end\n\n      # Include environment specific specification if enabled\n      if env_plugin_gem_path\n        plugin_specs += Dir.glob(env_plugin_gem_path.join('specifications/*.gemspec').to_s).map do |spec_path|\n          Gem::Specification.load(spec_path)\n        end\n      end\n\n      @logger.debug(\"Generating current plugin state solution set.\")\n\n      # Resolve the request set to ensure proper activation order\n      solution = request_set.resolve(current_set)\n      solution_specs = solution.map(&:full_spec)\n      solution_full_names = solution_specs.map(&:full_name)\n\n      # Find all specs installed to plugins directory that are not\n      # found within the solution set.\n      plugin_specs.delete_if do |spec|\n        solution_full_names.include?(spec.full_name)\n      end\n\n      if env_plugin_gem_path\n        # If we are cleaning locally, remove any global specs. If\n        # not, remove any local specs\n        if opts[:env_local]\n          @logger.debug(\"Removing specifications that are not environment local\")\n          plugin_specs.delete_if do |spec|\n            spec.full_gem_path.to_s.include?(plugin_gem_path.realpath.to_s)\n          end\n        else\n          @logger.debug(\"Removing specifications that are environment local\")\n          plugin_specs.delete_if do |spec|\n            spec.full_gem_path.to_s.include?(env_plugin_gem_path.realpath.to_s)\n          end\n        end\n      end\n\n      @logger.debug(\"Specifications to be removed - #{plugin_specs.map(&:full_name)}\")\n\n      # Now delete all unused specs\n      plugin_specs.each do |spec|\n        @logger.debug(\"Uninstalling gem - #{spec.full_name}\")\n        Gem::Uninstaller.new(spec.name,\n          version: spec.version,\n          install_dir: plugin_gem_path,\n          all: true,\n          executables: true,\n          force: true,\n          ignore: true,\n        ).uninstall_gem(spec)\n      end\n\n      solution.find_all do |spec|\n        plugins.keys.include?(spec.name)\n      end\n    end\n\n    # During the duration of the yielded block, Bundler loud output\n    # is enabled.\n    def verbose\n      if block_given?\n        initial_state = @verbose\n        @verbose = true\n        yield\n        @verbose = initial_state\n      else\n        @verbose = true\n      end\n    end\n\n    protected\n\n    def internal_install(plugins, update, **extra)\n      update = {} if !update.is_a?(Hash)\n      skips = []\n      source_list = {}\n      system_plugins = plugins.map do |plugin_name, plugin_info|\n        plugin_name if plugin_info[\"system\"]\n      end.compact\n      installer_set = VagrantSet.new(:both)\n      installer_set.system_plugins = system_plugins\n\n      # Generate all required plugin deps\n      plugin_deps = plugins.map do |name, info|\n        gem_version = info['gem_version'].to_s.empty? ? '> 0' : info['gem_version']\n        if update[:gems] == true || (update[:gems].respond_to?(:include?) && update[:gems].include?(name))\n          if Gem::Requirement.new(gem_version).exact?\n            gem_version = \"> 0\"\n            @logger.debug(\"Detected exact version match for `#{name}` plugin update. Reset to loosen constraint #{gem_version.inspect}.\")\n          end\n          skips << name\n        end\n        source_list[name] ||= []\n        if plugin_source = info.delete(\"local_source\")\n          installer_set.add_local(plugin_source.spec.name, plugin_source.spec, plugin_source)\n          source_list[name] << plugin_source.path\n        end\n        Array(info[\"sources\"]).each do |source|\n          if !source.end_with?(\"/\")\n            source = source + \"/\"\n          end\n          source_list[name] << source\n        end\n        Gem::Dependency.new(name, *gem_version.split(\",\"))\n      end\n\n      if Vagrant.strict_dependency_enforcement\n        @logger.debug(\"Enabling strict dependency enforcement\")\n        plugin_deps += vagrant_internal_specs.map do |spec|\n          # NOTE: When working within bundler, skip any system plugins and\n          # default gems. However, when not within bundler (in the installer)\n          # include them as strict dependencies to prevent the resolver from\n          # attempting to create a solution with a newer version. The request\n          # set does allow for resolving conservatively but it can't be set\n          # from the public API (requires an instance variable set on the resolver\n          # instance) so strict dependencies are used instead.\n          if Vagrant.in_bundler?\n            next if system_plugins.include?(spec.name)\n            # # If this spec is for a default plugin included in\n            # # the ruby stdlib, ignore it\n            next if spec.default_gem?\n          end\n\n          # If we are not running within the installer and\n          # we are not within a bundler environment then we\n          # only want activated specs\n          if !Vagrant.in_installer? && !Vagrant.in_bundler?\n            next if !spec.activated?\n          end\n          Gem::Dependency.new(spec.name, spec.version)\n        end.compact\n      else\n        @logger.debug(\"Disabling strict dependency enforcement\")\n      end\n\n      dep_list = plugin_deps.sort_by(&:name).map { |d|\n        \"#{d.name} #{d.requirement}\"\n      }.join(\"\\n - \")\n      @logger.debug(\"Dependency list for installation:\\n - #{dep_list}\")\n\n      all_sources = source_list.values.flatten.uniq\n      default_sources = DEFAULT_GEM_SOURCES & all_sources\n      all_sources -= DEFAULT_GEM_SOURCES\n\n      # Only allow defined Gem sources\n      Gem.sources.clear\n\n      @logger.debug(\"Enabling user defined remote RubyGems sources\")\n      all_sources.each do |src|\n        begin\n          next if File.file?(src) || URI.parse(src).scheme.nil?\n        rescue URI::InvalidURIError\n          next\n        end\n        @logger.debug(\"Adding RubyGems source #{src}\")\n        Gem.sources << src\n      end\n\n      @logger.debug(\"Enabling default remote RubyGems sources\")\n      default_sources.each do |src|\n        @logger.debug(\"Adding source - #{src}\")\n        Gem.sources << src\n      end\n\n      validate_configured_sources!\n\n      source_list.values.each{|srcs| srcs.delete_if{|src| default_sources.include?(src)}}\n      installer_set.prefer_sources = source_list\n\n      @logger.debug(\"Current source list for install: #{Gem.sources.to_a}\")\n\n      # Create the request set for the new plugins\n      request_set = Gem::RequestSet.new(*plugin_deps)\n\n      installer_set = Gem::Resolver.compose_sets(\n        installer_set,\n        generate_builtin_set(system_plugins),\n        generate_plugin_set(skips)\n      )\n\n      if Vagrant.allow_prerelease_dependencies?\n        @logger.debug(\"enabling prerelease dependency matching based on user request\")\n        request_set.prerelease = true\n        installer_set.prerelease = true\n      end\n\n      @logger.debug(\"Generating solution set for installation.\")\n\n      # Generate the required solution set for new plugins\n      solution = request_set.resolve(installer_set)\n\n      activate_solution(solution)\n\n      # Remove gems which are already installed\n      request_set.sorted_requests.delete_if do |act_req|\n        rs = act_req.spec\n        if vagrant_internal_specs.detect{ |i| i.name == rs.name && i.version == rs.version }\n          @logger.debug(\"Removing activation request from install. Already installed. (#{rs.spec.full_name})\")\n          true\n        end\n      end\n\n      @logger.debug(\"Installing required gems.\")\n\n      # Install all remote gems into plugin path. Set the installer to ignore dependencies\n      # as we know the dependencies are satisfied and it will attempt to validate a gem's\n      # dependencies are satisfied by gems in the install directory (which will likely not\n      # be true)\n      install_path = extra[:env_local] ? env_plugin_gem_path : plugin_gem_path\n      result = request_set.install_into(install_path.to_s, true,\n        ignore_dependencies: true,\n        prerelease: Vagrant.prerelease? || Vagrant.allow_prerelease_dependencies?,\n        wrappers: true,\n        document: []\n      )\n\n      result = result.map(&:full_spec)\n      result.each do |spec|\n        existing_paths = $LOAD_PATH.find_all{|s| s.include?(spec.full_name) }\n        if !existing_paths.empty?\n          @logger.debug(\"Removing existing LOAD_PATHs for #{spec.full_name} - \" +\n            existing_paths.join(\", \"))\n          existing_paths.each{|s| $LOAD_PATH.delete(s) }\n        end\n        spec.full_require_paths.each do |r_path|\n          if !$LOAD_PATH.include?(r_path)\n            @logger.debug(\"Adding path to LOAD_PATH - #{r_path}\")\n            $LOAD_PATH.unshift(r_path)\n          end\n        end\n      end\n      result\n    end\n\n    # Generate the composite resolver set totally all of vagrant (builtin + plugin set)\n    def generate_vagrant_set\n      sets = [generate_builtin_set, generate_plugin_set]\n      if env_plugin_gem_path && env_plugin_gem_path.exist?\n        sets << generate_plugin_set(env_plugin_gem_path)\n      end\n      Gem::Resolver.compose_sets(*sets)\n    end\n\n    # @return [Array<[Gem::Specification]>] spec list\n    def vagrant_internal_specs\n      # activate any dependencies up front so we can always\n      # pin them when resolving\n      self_spec = Gem::Specification.find { |s| s.name == \"vagrant\" && s.activated? }\n      if !self_spec\n        @logger.warn(\"Failed to locate activated vagrant specification. Activating...\")\n        self_spec = Gem::Specification.find { |s| s.name == \"vagrant\" }\n        if !self_spec\n          @logger.error(\"Failed to locate Vagrant RubyGem specification\")\n          raise Vagrant::Errors::SourceSpecNotFound\n        end\n        self_spec.activate\n        @logger.info(\"Activated vagrant specification version - #{self_spec.version}\")\n      end\n      # discover all the gems we have available\n      list = {}\n      if Gem.respond_to?(:default_specifications_dir)\n        spec_dir = Gem.default_specifications_dir\n      else\n        spec_dir = Gem::Specification.default_specifications_dir\n      end\n      directories = [spec_dir]\n      if Vagrant.in_bundler?\n        Gem::Specification.find_all{true}.each do |spec|\n          list[spec.name] = spec\n        end\n      else\n        builtin_specs.each do |spec|\n          list[spec.name] = spec\n        end\n      end\n      if Vagrant.in_installer?\n        directories += Gem::Specification.dirs.find_all do |path|\n          !path.start_with?(Gem.user_dir)\n        end\n      end\n      Gem::Specification.each_spec(directories) do |spec|\n        if !list[spec.name] || list[spec.name].version < spec.version\n          list[spec.name] = spec\n        end\n      end\n      list.values\n    end\n\n    # Iterates each configured RubyGem source to validate that it is properly\n    # available. If source is unavailable an exception is raised.\n    def validate_configured_sources!\n      Gem.sources.each_source do |src|\n        begin\n          src.load_specs(:released)\n        rescue Gem::Exception => source_error\n          if ENV[\"VAGRANT_ALLOW_PLUGIN_SOURCE_ERRORS\"]\n            @logger.warn(\"Failed to load configured plugin source: #{src}!\")\n            @logger.warn(\"Error received attempting to load source (#{src}): #{source_error}\")\n            @logger.warn(\"Ignoring plugin source load failure due user request via env variable\")\n          else\n            @logger.error(\"Failed to load configured plugin source `#{src}`: #{source_error}\")\n            raise Vagrant::Errors::PluginSourceError,\n              source: src.uri.to_s,\n              error_msg: source_error.message\n          end\n        end\n      end\n    end\n\n    # Generate the builtin resolver set\n    def generate_builtin_set(system_plugins=[])\n      builtin_set = BuiltinSet.new\n      @logger.debug(\"Generating new builtin set instance.\")\n      vagrant_internal_specs.each do |spec|\n        if !system_plugins.include?(spec.name)\n          builtin_set.add_builtin_spec(spec)\n        end\n      end\n      builtin_set\n    end\n\n    # Generate the plugin resolver set. Optionally provide specification names (short or\n    # full) that should be ignored\n    #\n    # @param [Pathname] path to plugins\n    # @param [Array<String>] gems to skip\n    # @return [PluginSet]\n    def generate_plugin_set(*args)\n      plugin_path = args.detect{|i| i.is_a?(Pathname) } || plugin_gem_path\n      skip = args.detect{|i| i.is_a?(Array) } || []\n      plugin_set = PluginSet.new\n      @logger.debug(\"Generating new plugin set instance. Skip gems - #{skip}\")\n      Dir.glob(plugin_path.join('specifications/*.gemspec').to_s).each do |spec_path|\n        spec = Gem::Specification.load(spec_path)\n        desired_spec_path = File.join(spec.gem_dir, \"#{spec.name}.gemspec\")\n        # Vendor set requires the spec to be within the gem directory. Some gems will package their\n        # spec file, and that's not what we want to load.\n        if !File.exist?(desired_spec_path) || !FileUtils.cmp(spec.spec_file, desired_spec_path)\n          File.write(desired_spec_path, spec.to_ruby)\n        end\n        next if skip.include?(spec.name) || skip.include?(spec.full_name)\n        plugin_set.add_vendor_gem(spec.name, spec.gem_dir)\n      end\n      plugin_set\n    end\n\n    # Activate a given solution\n    def activate_solution(solution)\n      retried = false\n      begin\n        @logger.debug(\"Activating solution set: #{solution.map(&:full_name)}\")\n        solution.each do |activation_request|\n          unless activation_request.full_spec.activated?\n            @logger.debug(\"Activating gem #{activation_request.full_spec.full_name}\")\n            activation_request.full_spec.activate\n            if(defined?(::Bundler))\n              @logger.debug(\"Marking gem #{activation_request.full_spec.full_name} loaded within Bundler.\")\n              ::Bundler.rubygems.mark_loaded activation_request.full_spec\n            end\n          end\n        end\n      rescue Gem::LoadError => e\n        # Depending on the version of Ruby, the ordering of the solution set\n        # will be either 0..n (molinillo) or n..0 (pre-molinillo). Instead of\n        # attempting to determine what's in use, or if it has some how changed\n        # again, just reverse order on failure and attempt again.\n        if retried\n          @logger.error(\"Failed to load solution set - #{e.class}: #{e}\")\n          matcher = e.message.match(/Could not find '(?<gem_name>[^']+)'/)\n          if matcher && !matcher[\"gem_name\"].empty?\n            desired_activation_request = solution.detect do |request|\n              request.name == matcher[\"gem_name\"]\n            end\n            if desired_activation_request && !desired_activation_request.full_spec.activated?\n              @logger.warn(\"Found misordered activation request for #{desired_activation_request.full_name}. Moving to solution HEAD.\")\n              solution.delete(desired_activation_request)\n              solution.unshift(desired_activation_request)\n              retry\n            end\n          end\n\n          raise\n        else\n          @logger.debug(\"Failed to load solution set. Retrying with reverse order.\")\n          retried = true\n          solution.reverse!\n          retry\n        end\n      end\n    end\n\n    # This is a custom Gem::Resolver::InstallerSet. It will prefer sources which are\n    # explicitly provided over default sources when matches are found. This is generally\n    # the entire set used for performing full resolutions on install.\n    class VagrantSet < Gem::Resolver::InstallerSet\n      attr_accessor :prefer_sources\n      attr_accessor :system_plugins\n\n      def initialize(domain, defined_sources={})\n        @prefer_sources = defined_sources\n        @system_plugins = []\n        super(domain)\n      end\n\n      # Allow InstallerSet to find matching specs, then filter\n      # for preferred sources\n      def find_all(req)\n        result = super\n        if system_plugins.include?(req.name)\n          result.delete_if do |spec|\n            spec.is_a?(Gem::Resolver::InstalledSpecification)\n          end\n        end\n        subset = result.find_all do |idx_spec|\n          preferred = false\n          if prefer_sources[req.name]\n            if idx_spec.source.respond_to?(:path)\n              preferred = prefer_sources[req.name].include?(idx_spec.source.path.to_s)\n            end\n            if !preferred\n              preferred = prefer_sources[req.name].include?(idx_spec.source.uri.to_s)\n            end\n          end\n          preferred\n        end\n        subset.empty? ? result : subset\n      end\n    end\n\n    # This is a custom Gem::Resolver::Set for use with vagrant \"system\" gems. It\n    # allows the installed set of gems to be used for providing a solution while\n    # enforcing strict constraints. This ensures that plugins cannot \"upgrade\"\n    # gems that are builtin to vagrant itself.\n    class BuiltinSet < Gem::Resolver::Set\n      def initialize\n        super\n        @remote = false\n        @specs = []\n      end\n\n      def add_builtin_spec(spec)\n        @specs.push(spec).uniq!\n      end\n\n      def find_all(req)\n        r = @specs.select do |spec|\n          # When matching requests against builtin specs, we _always_ enable\n          # prerelease matching since any prerelease that's found in this\n          # set has been added explicitly and should be available for all\n          # plugins to resolve against. This includes Vagrant itself since\n          # it is considered a prerelease when in development mode\n          req.match?(spec, true)\n        end.map do |spec|\n          Gem::Resolver::InstalledSpecification.new(self, spec)\n        end\n        # If any of the results are a prerelease, we need to mark the request\n        # to allow prereleases so the solution can be properly fulfilled\n        if r.any? { |x| x.version.prerelease? }\n          req.dependency.prerelease = true\n        end\n        r\n      end\n    end\n\n    # This is a custom Gem::Resolver::Set for use with Vagrant plugins. It is\n    # a modified Gem::Resolver::VendorSet that supports multiple versions of\n    # a specific gem\n    class PluginSet < Gem::Resolver::VendorSet\n      ##\n      # Adds a specification to the set with the given +name+ which has been\n      # unpacked into the given +directory+.\n      def add_vendor_gem(name, directory)\n        gemspec = File.join(directory, \"#{name}.gemspec\")\n        spec = Gem::Specification.load(gemspec)\n        if !spec\n          raise Gem::GemNotFoundException,\n            \"unable to find #{gemspec} for gem #{name}\"\n        end\n\n        spec.full_gem_path = File.expand_path(directory)\n        spec.base_dir = File.dirname(spec.base_dir)\n\n        @specs[spec.name] ||= []\n        @specs[spec.name] << spec\n        @directories[spec] = directory\n\n        spec\n      end\n\n      ##\n      # Returns an Array of VendorSpecification objects matching the\n      # DependencyRequest +req+.\n      def find_all(req)\n        @specs.values.flatten.select do |spec|\n          req.match?(spec, prerelease)\n        end.map do |spec|\n          source = Gem::Source::Vendor.new(@directories[spec])\n          Gem::Resolver::VendorSpecification.new(self, spec, source)\n        end\n      end\n\n      ##\n      # Loads a spec with the given +name+. +version+, +platform+ and +source+ are\n      # ignored.\n      def load_spec(name, version, platform, source)\n        version = Gem::Version.new(version) if !version.is_a?(Gem::Version)\n        @specs.fetch(name, []).detect{|s| s.name == name && s.version == version}\n      end\n    end\n  end\nend\n\n# Patch for Ruby 2.2 and Bundler to behave properly when uninstalling plugins\nif Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')\n  if defined?(::Bundler) && !::Bundler::SpecSet.instance_methods.include?(:delete)\n    class Gem::Specification\n      def self.remove_spec(spec)\n        Gem::Specification.reset\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/capability_host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  # This module enables a class to host capabilities. Prior to being able\n  # to use any capabilities, the `initialize_capabilities!` method must be\n  # called.\n  #\n  # Capabilities allow small pieces of functionality to be plugged in using\n  # the Vagrant plugin model. Capabilities even allow for a certain amount\n  # of inheritance, where only a subset of capabilities may be implemented but\n  # a parent implements the rest.\n  #\n  # Capabilities are used heavily in Vagrant for host/guest interactions. For\n  # example, \"mount_nfs_folder\" is a guest-OS specific operation, so capabilities\n  # defer these operations to the guest.\n  module CapabilityHost\n    # Initializes the capability system by detecting the proper capability\n    # host to execute on and building the chain of capabilities to execute.\n    #\n    # @param [Symbol] host The host to use for the capabilities, or nil if\n    #   we should auto-detect it.\n    # @param [Hash<Symbol, Array<Class, Symbol>>] hosts Potential capability\n    #   hosts. The key is the name of the host, value[0] is a class that\n    #   implements `#detect?` and value[1] is a parent host (if any).\n    # @param [Hash<Symbol, Hash<Symbol, Class>>] capabilities The capabilities\n    #   that are supported. The key is the host of the capability. Within that\n    #   is a hash where the key is the name of the capability and the value\n    #   is the class/module implementing it.\n    def initialize_capabilities!(host, hosts, capabilities, *args)\n      @cap_logger = Log4r::Logger.new(\n        \"vagrant::capability_host::#{self.class.to_s.downcase}\")\n\n      if host && !hosts[host]\n        raise Errors::CapabilityHostExplicitNotDetected, value: host.to_s\n      end\n\n      if !host\n        host = autodetect_capability_host(hosts, *args) if !host\n        raise Errors::CapabilityHostNotDetected if !host\n      end\n\n      if !hosts[host]\n        # This should never happen because the autodetect above uses the\n        # hosts hash to look up hosts. And if an explicit host is specified,\n        # we do another check higher up.\n        raise \"Internal error. Host not found: #{host}\"\n      end\n\n      name      = host\n      host_info = hosts[name]\n      host      = host_info[0].new\n      chain     = []\n      chain << [name, host]\n\n      # Build the proper chain of parents if there are any.\n      # This allows us to do \"inheritance\" of capabilities later\n      if host_info[1]\n        parent_name = host_info[1]\n        parent_info = hosts[parent_name]\n        while parent_info\n          chain << [parent_name, parent_info[0].new]\n          parent_name = parent_info[1]\n          parent_info = hosts[parent_name]\n        end\n      end\n\n      @cap_host_chain = chain\n      @cap_args       = args\n      @cap_caps       = capabilities\n      true\n    end\n\n    # Returns the chain of hosts that will be checked for capabilities.\n    #\n    # @return [Array<Array<Symbol, Class>>]\n    def capability_host_chain\n      @cap_host_chain\n    end\n\n    # Tests whether the given capability is possible.\n    #\n    # @param [Symbol] cap_name Capability name\n    # @return [Boolean]\n    def capability?(cap_name)\n      !capability_module(cap_name.to_sym).nil?\n    end\n\n    # Executes the capability with the given name, optionally passing more\n    # arguments onwards to the capability. If the capability returns a value,\n    # it will be returned.\n    #\n    # @param [Symbol] cap_name Name of the capability\n    def capability(cap_name, *args)\n      cap_mod = capability_module(cap_name.to_sym)\n      if !cap_mod\n        raise Errors::CapabilityNotFound,\n          cap:  cap_name.to_s,\n          host: @cap_host_chain[0][0].to_s\n      end\n\n      cap_method = nil\n      begin\n        cap_method = cap_mod.method(cap_name)\n      rescue NameError\n        raise Errors::CapabilityInvalid,\n          cap: cap_name.to_s,\n          host: @cap_host_chain[0][0].to_s\n      end\n\n      args = @cap_args + args\n      @cap_logger.info(\n        \"Execute capability: #{cap_name} #{args.inspect} (#{@cap_host_chain[0][0]})\")\n      cap_method.call(*args)\n    end\n\n    protected\n\n    def autodetect_capability_host(hosts, *args)\n      @cap_logger.info(\"Autodetecting host type for #{args.inspect}\")\n\n      # Get the mapping of hosts with the most parents. We start searching\n      # with the hosts with the most parents first.\n      parent_count = {}\n      hosts.each do |name, parts|\n        parent_count[name] = 0\n\n        parent = parts[1]\n        while parent\n          parent_count[name] += 1\n          parent = hosts[parent]\n          parent = parent[1] if parent\n        end\n      end\n\n      # Now swap around the mapping so that it is a mapping of\n      # count to the actual list of host names\n      parent_count_to_hosts = {}\n      parent_count.each do |name, count|\n        parent_count_to_hosts[count] ||= []\n        parent_count_to_hosts[count] << name\n      end\n\n      sorted_counts = parent_count_to_hosts.keys.sort.reverse\n      sorted_counts.each do |count|\n        parent_count_to_hosts[count].each do |name|\n          @cap_logger.debug(\"Trying: #{name}\")\n          host_info = hosts[name]\n          host      = host_info[0].new\n\n          if host.detect?(*args)\n            @cap_logger.info(\"Detected: #{name}!\")\n            return name\n          end\n        end\n      end\n\n      return nil\n    end\n\n    # Returns the registered module for a capability with the given name.\n    #\n    # @param [Symbol] cap_name\n    # @return [Module]\n    def capability_module(cap_name)\n      @cap_logger.debug(\"Searching for cap: #{cap_name}\")\n      @cap_host_chain.each do |host_name, host|\n        @cap_logger.debug(\"Checking in: #{host_name}\")\n        caps = @cap_caps[host_name]\n\n        if caps && caps.key?(cap_name)\n          @cap_logger.debug(\"Found cap: #{cap_name} in #{host_name}\")\n          return caps[cap_name]\n        end\n      end\n\n      nil\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/cli.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'log4r'\nrequire 'optparse'\n\nrequire 'vagrant/util/experimental'\n\nmodule Vagrant\n  # Manages the command line interface to Vagrant.\n  class CLI < Vagrant.plugin(\"2\", :command)\n\n    def initialize(argv, env)\n      super\n\n      @logger = Log4r::Logger.new(\"vagrant::cli\")\n      @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)\n\n      ui = Vagrant::UI::Prefixed.new(env.ui, \"vagrant\")\n      @triggers = Vagrant::Plugin::V2::Trigger.new(env, env.vagrantfile.config.trigger, nil, ui)\n\n      Util::CheckpointClient.instance.setup(env).check\n      @logger.info(\"CLI: #{@main_args.inspect} #{@sub_command.inspect} #{@sub_args.inspect}\")\n    end\n\n    def execute\n      if @main_args.include?(\"-h\") || @main_args.include?(\"--help\")\n        # Help is next in short-circuiting everything. Print\n        # the help and exit.\n        help\n        return 0\n      end\n\n      if @sub_command == \"login\"\n        $stderr.puts \"WARNING: This command has been deprecated and aliased to `vagrant cloud auth login`\"\n      end\n\n      # If we reached this far then we must have a subcommand. If not,\n      # then we also just print the help and exit.\n      command_plugin = nil\n      if @sub_command\n        command_plugin = Vagrant.plugin(\"2\").manager.commands[@sub_command.to_sym]\n\n        if !command_plugin\n          alias_command = Alias.new(@env).commands[@sub_command.to_sym]\n\n          if alias_command\n            return alias_command.call(@sub_args)\n          end\n        end\n      end\n\n      if !command_plugin || !@sub_command\n        help\n        return 1\n      end\n\n      command_class = command_plugin[0].call\n      @logger.debug(\"Invoking command class: #{command_class} #{@sub_args.inspect}\")\n\n      Util::CheckpointClient.instance.display\n\n      # Initialize and execute the command class, returning the exit status.\n      result = 0\n      begin\n        @triggers.fire(@sub_command, :before, nil, :command)\n        result = command_class.new(@sub_args, @env).execute\n        @triggers.fire(@sub_command, :after, nil, :command)\n      rescue Interrupt\n        @env.ui.info(I18n.t(\"vagrant.cli_interrupt\"))\n        result = 1\n      end\n\n      result = 0 if !result.is_a?(Integer)\n      return result\n    end\n\n    # This prints out the help for the CLI.\n    def help\n      # We use the optionparser for this. Its just easier. We don't use\n      # an optionparser above because I don't think the performance hits\n      # of creating a whole object are worth checking only a couple flags.\n      opts = OptionParser.new do |o|\n        o.banner = \"Usage: vagrant [options] <command> [<args>]\"\n        o.separator \"\"\n        o.on(\"-v\", \"--version\", \"Print the version and exit.\")\n        o.on(\"-h\", \"--help\", \"Print this help.\")\n        o.separator \"\"\n        o.separator \"Common commands:\"\n\n        # Add the available subcommands as separators in order to print them\n        # out as well.\n        commands = {}\n        longest = 0\n        Vagrant.plugin(\"2\").manager.commands.each do |key, data|\n          # Skip non-primary commands. These only show up in extended\n          # help output.\n          next if !data[1][:primary]\n\n          key           = key.to_s\n          klass         = data[0].call\n          commands[key] = klass.synopsis\n          longest       = key.length if key.length > longest\n        end\n\n        commands.keys.sort.each do |key|\n          o.separator \"     #{key.ljust(longest+2)} #{commands[key]}\"\n          @env.ui.machine(\"cli-command\", key.dup)\n        end\n\n        o.separator \"\"\n        o.separator \"For help on any individual command run `vagrant COMMAND -h`\"\n        o.separator \"\"\n        o.separator \"Additional subcommands are available, but are either more advanced\"\n        o.separator \"or not commonly used. To see all subcommands, run the command\"\n        o.separator \"`vagrant list-commands`.\"\n      end\n\n      @env.ui.info(opts.help, prefix: false)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/config/loader.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nrequire \"log4r\"\n\nmodule Vagrant\n  module Config\n    # This class is responsible for loading Vagrant configuration,\n    # usually in the form of Vagrantfiles.\n    #\n    # Loading works by specifying the sources for the configuration\n    # as well as the order the sources should be loaded. Configuration\n    # set later always overrides those set earlier; this is how\n    # configuration \"scoping\" is implemented.\n    class Loader\n      # Initializes a configuration loader.\n      #\n      # @param [Registry] versions A registry of the available versions and\n      #   their associated loaders.\n      # @param [Array] version_order An array of the order of the versions\n      #   in the registry. This is used to determine if upgrades are\n      #   necessary. Additionally, the last version in this order is always\n      #   considered the \"current\" version.\n      def initialize(versions, version_order)\n        @logger        = Log4r::Logger.new(\"vagrant::config::loader\")\n        @config_cache  = {}\n        @proc_cache    = {}\n        @sources       = {}\n        @versions      = versions\n        @version_order = version_order\n      end\n\n      # Set the configuration data for the given name.\n      #\n      # The `name` should be a symbol and must uniquely identify the data\n      # being given.\n      #\n      # `data` can either be a path to a Ruby Vagrantfile or a `Proc` directly.\n      # `data` can also be an array of such values.\n      #\n      # At this point, no configuration is actually loaded. Note that calling\n      # `set` multiple times with the same name will override any previously\n      # set values. In this way, the last set data for a given name wins.\n      def set(name, sources)\n        # Sources should be an array\n        sources = [sources] if !sources.kind_of?(Array)\n\n        reliably_inspected_sources = sources.reduce({}) { |accum, source|\n          begin\n            accum[source] = source.inspect\n          rescue Encoding::CompatibilityError\n            accum[source] = \"<!Vagrant failed to call #inspect source with object id #{source.object_id} and class #{source.class} due to a string encoding error>\"\n          end\n\n          accum\n        }\n\n        @logger.info(\"Set #{name.inspect} = #{reliably_inspected_sources.values}\")\n\n        # Gather the procs for every source, since that is what we care about.\n        procs = []\n        sources.each do |source|\n          if !@proc_cache.key?(source)\n            # Load the procs for this source and cache them. This caching\n            # avoids the issue where a file may have side effects when loading\n            # and loading it multiple times causes unexpected behavior.\n            @logger.debug(\"Populating proc cache for #{reliably_inspected_sources[source]}\")\n            @proc_cache[source] = procs_for_source(source, reliably_inspected_sources)\n          end\n\n          # Add on to the array of procs we're going to use\n          procs.concat(@proc_cache[source])\n        end\n\n        # Set this source by name.\n        @sources[name] = procs\n      end\n\n      # This loads the configuration sources in the given order and returns\n      # an actual configuration object that is ready to be used.\n      #\n      # @param [Array<Symbol>] order The order of configuration to load.\n      # @return [Object] The configuration object. This is different for\n      #   each configuration version.\n      def load(order)\n        @logger.info(\"Loading configuration in order: #{order.inspect}\")\n\n        unknown_sources = @sources.keys - order\n        if !unknown_sources.empty?\n          @logger.warn(\"Unknown config sources: #{unknown_sources.inspect}\")\n        end\n\n        # Get the current version config class to use\n        current_version      = @version_order.last\n        current_config_klass = @versions.get(current_version)\n\n        # This will hold our result\n        result = current_config_klass.init\n\n        # Keep track of the warnings and errors that may come from\n        # upgrading the Vagrantfiles\n        warnings = []\n        errors   = []\n\n        if !@sources[:root].nil? && @sources[:root].eql?(@sources[:home])\n          # Vagrants home dir is set to the same dir as its project directory\n          # so we don't want to load and merge the same Vagrantfile config\n          # and execute its settings/procs twice\n          #\n          # Note: This protection won't work if there are two separate but\n          # identical Vagrantfiles in the home and project dir\n          @logger.info(\"Duplicate Vagrantfile config objects detected in :root and :home.\")\n          @sources.delete(:home)\n          @logger.info(\"Removed :home config from being loaded\")\n        end\n\n        order.each do |key|\n          next if !@sources.key?(key)\n\n          @sources[key].each do |version, proc|\n            if !@config_cache.key?(proc)\n              @logger.debug(\"Loading from: #{key} (evaluating)\")\n\n              # Get the proper version loader for this version and load\n              version_loader = @versions.get(version)\n              begin\n                version_config = version_loader.load(proc)\n              rescue NameError => e\n                line = \"(unknown)\"\n                path = \"(unknown)\"\n                if e.backtrace && e.backtrace[0]\n                  backtrace_tokens = e.backtrace[0].split(\":\")\n                  path = e.backtrace.first.slice(0, e.backtrace.first.rindex(':')).rpartition(':').first\n                  backtrace_tokens.each do |part|\n                    if part =~ /\\d+/\n                      line = part.to_i\n                      break\n                    end\n                  end\n                end\n\n                raise Errors::VagrantfileNameError,\n                  path: path,\n                  line: line,\n                  message: e.message.sub(/' for .*$/, \"'\")\n              end\n\n              # Store the errors/warnings associated with loading this\n              # configuration. We'll store these for later.\n              version_warnings = []\n              version_errors   = []\n\n              # If this version is not the current version, then we need\n              # to upgrade to the latest version.\n              if version != current_version\n                @logger.debug(\"Upgrading config from version #{version} to #{current_version}\")\n                version_index = @version_order.index(version)\n                current_index = @version_order.index(current_version)\n\n                (version_index + 1).upto(current_index) do |index|\n                  next_version = @version_order[index]\n                  @logger.debug(\"Upgrading config to version #{next_version}\")\n\n                  # Get the loader of this version and ask it to upgrade\n                  loader = @versions.get(next_version)\n                  upgrade_result = loader.upgrade(version_config)\n\n                  this_warnings = upgrade_result[1]\n                  this_errors   = upgrade_result[2]\n                  @logger.debug(\"Upgraded to version #{next_version} with \" +\n                                \"#{this_warnings.length} warnings and \" +\n                                \"#{this_errors.length} errors\")\n\n                  # Append loading this to the version warnings and errors\n                  version_warnings += this_warnings\n                  version_errors   += this_errors\n\n                  # Store the new upgraded version\n                  version_config = upgrade_result[0]\n                end\n              end\n\n              # Cache the loaded configuration along with any warnings\n              # or errors so that they can be retrieved later.\n              @config_cache[proc] = [version_config, version_warnings, version_errors]\n            else\n              @logger.debug(\"Loading from: #{key} (cache)\")\n            end\n\n            # Merge the configurations\n            cache_data = @config_cache[proc]\n            result = current_config_klass.merge(result, cache_data[0])\n\n            # Append the total warnings/errors\n            warnings += cache_data[1]\n            errors   += cache_data[2]\n          end\n        end\n\n        @logger.debug(\"Configuration loaded successfully, finalizing and returning\")\n        [current_config_klass.finalize(result), warnings, errors]\n      end\n\n      # This method is used for doing partial loads of the\n      # Vagrantfile. It will load the contents of a single\n      # location and return the config. No merging is performed\n      # and no finalization is applied.\n      #\n      # @param key [Symbol] name of location\n      # @return [Object] configuration\n      # @note: This will load either version, but we assume a v2 result\n      # @todo(spox): check version and raise error on v1\n      def partial_load(key)\n        raise KeyError,\n              \"Unknown path key provided (#{key})\" if !@sources.key?(key)\n\n        version, proc = @sources[key].first\n        @logger.debug(\"Loading from: #{key} (evaluating)\")\n\n        # Get the proper version loader for this version and load\n        version_loader = @versions.get(version)\n        raise KeyError,\n              \"Failed to create loader for requested version: #{version}\" if version_loader.nil?\n\n        begin\n          version_config = version_loader.load(proc)\n        rescue NameError => e\n          line = \"(unknown)\"\n          path = \"(unknown)\"\n          if e.backtrace && e.backtrace[0]\n            backtrace_tokens = e.backtrace[0].split(\":\")\n            path = e.backtrace.first.slice(0, e.backtrace.first.rindex(':')).rpartition(':').first\n            backtrace_tokens.each do |part|\n              if part =~ /\\d+/\n                line = part.to_i\n                break\n              end\n            end\n          end\n\n          raise Errors::VagrantfileNameError,\n                path: path,\n                line: line,\n                message: e.message.sub(/' for .*$/, \"'\") + \"\\n#{e.backtrace.join(\"\\n\")}\" + \"\\nVersion #{version.inspect} loader: #{version_loader.inspect} versions: #{@versions.inspect}\"\n        end\n        version_config\n      end\n\n      protected\n\n      # This returns an array of `Proc` objects for the given source.\n      # The `Proc` objects returned will expect a single argument for\n      # the configuration object and are expected to mutate this\n      # configuration object.\n      def procs_for_source(source, reliably_inspected_sources)\n        # Convert all pathnames to strings so we just have their path\n        source = source.to_s if source.is_a?(Pathname)\n\n        if source.is_a?(Array)\n          # An array must be formatted as [version, proc], so verify\n          # that and then return it\n          raise ArgumentError, \"String source must have format [version, proc]\" if source.length != 2\n\n          # Return it as an array since we're expected to return an array\n          # of [version, proc] pairs, but an array source only has one.\n          return [source]\n        elsif source.is_a?(String)\n          # Strings are considered paths, so load them\n          return procs_for_path(source)\n        else\n          raise ArgumentError, \"Unknown configuration source: #{reliably_inspected_sources[source]}\"\n        end\n      end\n\n      # This returns an array of `Proc` objects for the given path source.\n      #\n      # @param [String] path Path to the file which contains the proper\n      #   `Vagrant.configure` calls.\n      # @return [Array<Proc>]\n      def procs_for_path(path)\n        @logger.debug(\"Load procs for pathname: #{path}\")\n\n        return Config.capture_configures do\n          begin\n            Kernel.load path\n          rescue SyntaxError => e\n            # Report syntax errors in a nice way.\n            raise Errors::VagrantfileSyntaxError, file: e.message\n          rescue SystemExit\n            # Continue raising that exception...\n            raise\n          rescue Vagrant::Errors::VagrantError\n            # Continue raising known Vagrant errors since they already\n            # contain well worded error messages and context.\n            raise\n          rescue Exception => e\n            @logger.error(\"Vagrantfile load error: #{e.message}\")\n            @logger.error(e.backtrace.join(\"\\n\"))\n\n            line = \"(unknown)\"\n            if e.backtrace && e.backtrace[0]\n              e.backtrace[0].split(\":\").each do |part|\n                if part =~ /\\d+/\n                  line = part.to_i\n                  break\n                end\n              end\n            end\n\n            # Report the generic exception\n            raise Errors::VagrantfileLoadError,\n              path: path,\n              line: line,\n              exception_class: e.class,\n              message: e.message\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/config/v1/dummy_config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Config\n    module V1\n      # This is a configuration object that can have anything done\n      # to it. Anything, and it just appears to keep working.\n      class DummyConfig\n        def method_missing(name, *args, &block)\n          DummyConfig.new\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/config/v1/loader.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/config/v1/root\"\n\nmodule Vagrant\n  module Config\n    module V1\n      # This is the loader that handles configuration loading for V1\n      # configurations.\n      class Loader < VersionBase\n        # Returns a bare empty configuration object.\n        #\n        # @return [V1::Root]\n        def self.init\n          new_root_object\n        end\n\n        # Finalizes the configuration by making sure there is at least\n        # one VM defined in it.\n        def self.finalize(config)\n          # Call the `#finalize` method on each of the configuration keys.\n          # They're expected to modify themselves in our case.\n          config.finalize!\n\n          # Return the object\n          config\n        end\n\n        # Loads the configuration for the given proc and returns a configuration\n        # object.\n        #\n        # @param [Proc] config_proc\n        # @return [Object]\n        def self.load(config_proc)\n          # Create a root configuration object\n          root = new_root_object\n\n          # Call the proc with the root\n          config_proc.call(root)\n\n          # Return the root object, which doubles as the configuration object\n          # we actually use for accessing as well.\n          root\n        end\n\n        # Merges two configuration objects.\n        #\n        # @param [V1::Root] old The older root config.\n        # @param [V1::Root] new The newer root config.\n        # @return [V1::Root]\n        def self.merge(old, new)\n          # Grab the internal states, we use these heavily throughout the process\n          old_state = old.__internal_state\n          new_state = new.__internal_state\n\n          # The config map for the new object is the old one merged with the\n          # new one.\n          config_map = old_state[\"config_map\"].merge(new_state[\"config_map\"])\n\n          # Merge the keys.\n          old_keys = old_state[\"keys\"]\n          new_keys = new_state[\"keys\"]\n          keys     = {}\n          old_keys.each do |key, old_value|\n            if new_keys.key?(key)\n              # We need to do a merge, which we expect to be available\n              # on the config class itself.\n              keys[key] = old_value.merge(new_keys[key])\n            else\n              # We just take the old value, but dup it so that we can modify.\n              keys[key] = old_value.dup\n            end\n          end\n\n          new_keys.each do |key, new_value|\n            # Add in the keys that the new class has that we haven't merged.\n            if !keys.key?(key)\n              keys[key] = new_value.dup\n            end\n          end\n\n          # Return the final root object\n          V1::Root.new(config_map, keys)\n        end\n\n        protected\n\n        def self.new_root_object\n          # Get all the registered configuration objects and use them. If\n          # we're currently on version 1, then we load all the config objects,\n          # otherwise we load only the upgrade safe ones, since we're\n          # obviously being loaded for an upgrade.\n          config_map = nil\n          plugin_manager = Vagrant.plugin(\"1\").manager\n          if Config::CURRENT_VERSION == \"1\"\n            config_map = plugin_manager.config\n          else\n            config_map = plugin_manager.config_upgrade_safe\n          end\n\n          # Create the configuration root object\n          V1::Root.new(config_map)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/config/v1/root.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"set\"\n\nmodule Vagrant\n  module Config\n    module V1\n      # This is the root configuration class. An instance of this is what\n      # is passed into version 1 Vagrant configuration blocks.\n      class Root\n        # Initializes a root object that maps the given keys to specific\n        # configuration classes.\n        #\n        # @param [Hash] config_map Map of key to config class.\n        def initialize(config_map, keys=nil)\n          @keys              = keys || {}\n          @config_map        = config_map\n          @missing_key_calls = Set.new\n        end\n\n        # We use method_missing as a way to get the configuration that is\n        # used for Vagrant and load the proper configuration classes for\n        # each.\n        def method_missing(name, *args)\n          return @keys[name] if @keys.key?(name)\n\n          config_klass = @config_map[name.to_sym]\n          if config_klass\n            # Instantiate the class and return the instance\n            @keys[name] = config_klass.new\n            return @keys[name]\n          else\n            # Record access to a missing key as an error\n            @missing_key_calls.add(name.to_s)\n            return DummyConfig.new\n          end\n        end\n\n        # Called to finalize this object just prior to it being used by\n        # the Vagrant system. The \"!\" signifies that this is expected to\n        # mutate itself.\n        def finalize!\n          @keys.each do |_key, instance|\n            instance.finalize!\n          end\n        end\n\n        # Returns the internal state of the root object. This is used\n        # by outside classes when merging, and shouldn't be called directly.\n        # Note the strange method name is to attempt to avoid any name\n        # clashes with potential configuration keys.\n        def __internal_state\n          {\n            \"config_map\"        => @config_map,\n            \"keys\"              => @keys,\n            \"missing_key_calls\" => @missing_key_calls\n          }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/config/v1.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Config\n    module V1\n      autoload :DummyConfig, \"vagrant/config/v1/dummy_config\"\n      autoload :Loader, \"vagrant/config/v1/loader\"\n      autoload :Root,   \"vagrant/config/v1/root\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/config/v2/dummy_config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Config\n    module V2\n      # This is a configuration object that can have anything done\n      # to it. Anything, and it just appears to keep working.\n      class DummyConfig < Vagrant::Plugin::V2::Config\n        LOG  = Log4r::Logger.new(\"vagrant::config::v2::dummy_config\")\n\n        def method_missing(name, *args, &block)\n          # There are a few scenarios where ruby will attempt to implicity\n          # coerce a given object into a certain type. DummyConfigs can end up\n          # in some of these scenarios when they're being shipped around in\n          # callbacks with splats. If method_missing allows these methods to be\n          # called but continues to return DummyConfig back, Ruby will raise a\n          # TypeError. Doing the normal thing of raising NoMethodError allows\n          # DummyConfig to behave normally as its being passed through splats.\n          #\n          # For a bit more detail and some keywords for further searching, see:\n          # https://ruby-doc.org/core-2.7.2/doc/implicit_conversion_rdoc.html\n          if [:to_hash, :to_ary].include?(name)\n            return super\n          end\n\n          # Trying to define a variable\n          if name.to_s.match(/^[\\w]*=/)\n            LOG.debug(\"found name #{name}\")\n            LOG.debug(\"setting instance variable name #{name.to_s.split(\"=\")[0]}\")\n            var_name = \"@#{name.to_s.split(\"=\")[0]}\"\n            self.instance_variable_set(var_name, args[0])\n          else\n            DummyConfig.new\n          end\n        end\n\n        def merge(c)\n          c\n        end\n\n        def set_options(options)\n          options.each do |key, value|\n            if key.to_s.match(/^[\\w]*=/)\n              var_name = \"@#{key.to_s}\"\n              self.instance_variable_set(var_name, value)\n            end\n          end\n        end\n\n        def instance_variables_hash\n          instance_variables.inject({}) do |acc, iv|\n            acc[iv.to_s[1..-1]] = instance_variable_get(iv)\n            acc\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/config/v2/loader.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/config/v2/root\"\n\nmodule Vagrant\n  module Config\n    module V2\n      # This is the loader that handles configuration loading for V2\n      # configurations.\n      class Loader < VersionBase\n        # Returns a bare empty configuration object.\n        #\n        # @return [V2::Root]\n        def self.init\n          new_root_object\n        end\n\n        # Finalizes the configuration by making sure there is at least\n        # one VM defined in it.\n        def self.finalize(config)\n          # Call the `#finalize` method on each of the configuration keys.\n          # They're expected to modify themselves in our case.\n          config.finalize!\n\n          # Return the object\n          config\n        end\n\n        # Loads the configuration for the given proc and returns a configuration\n        # object.\n        #\n        # @param [Proc] config_proc\n        # @return [Object]\n        def self.load(config_proc)\n          # Create a root configuration object\n          root = new_root_object\n\n          # Call the proc with the root\n          config_proc.call(root)\n\n          # Return the root object, which doubles as the configuration object\n          # we actually use for accessing as well.\n          root\n        end\n\n        # Merges two configuration objects.\n        #\n        # @param [V2::Root] old The older root config.\n        # @param [V2::Root] new The newer root config.\n        # @return [V2::Root]\n        def self.merge(old, new)\n          # Grab the internal states, we use these heavily throughout the process\n          old_state = old.__internal_state\n          new_state = new.__internal_state\n\n          # Make sure we instantiate every key in the config so that we\n          # merge every key. This avoids issues with the same reference\n          # being part of the config.\n          old_state[\"config_map\"].each do |k, _|\n            old.public_send(k)\n          end\n          new_state[\"config_map\"].each do |k, _|\n            new.public_send(k)\n          end\n\n          # The config map for the new object is the old one merged with the\n          # new one.\n          config_map = old_state[\"config_map\"].merge(new_state[\"config_map\"])\n\n          # Merge the keys.\n          old_keys = old_state[\"keys\"]\n          new_keys = new_state[\"keys\"]\n          keys     = {}\n          old_keys.each do |key, old_value|\n            if new_keys.key?(key)\n              # We need to do a merge, which we expect to be available\n              # on the config class itself.\n              keys[key] = old_value.merge(new_keys[key])\n            else\n              # We just take the old value, but dup it so that we can modify.\n              keys[key] = old_value.dup\n            end\n          end\n\n          new_keys.each do |key, new_value|\n            # Add in the keys that the new class has that we haven't merged.\n            if !keys.key?(key)\n              keys[key] = new_value.dup\n            end\n          end\n\n          # Merge the missing keys\n          new_missing_key_calls =\n            old_state[\"missing_key_calls\"] + new_state[\"missing_key_calls\"]\n\n          # Return the final root object\n          V2::Root.new(config_map).tap do |result|\n            result.__set_internal_state({\n              \"config_map\"        => config_map,\n              \"keys\"              => keys,\n              \"missing_key_calls\" => new_missing_key_calls\n            })\n          end\n        end\n\n        # Upgrade a V1 configuration to a V2 configuration. We do this by\n        # creating a V2 configuration, and calling \"upgrade\" on each of the\n        # V1 configurations, expecting them to set the right settings on the\n        # new root.\n        #\n        # @param [V1::Root] old\n        # @return [Array] A 3-tuple result.\n        def self.upgrade(old)\n          # Get a new root\n          root = new_root_object\n\n          # Store the warnings/errors\n          warnings = []\n          errors   = []\n\n          # Go through the old keys and upgrade them if they can be\n          old.__internal_state[\"keys\"].each do |_, old_value|\n            if old_value.respond_to?(:upgrade)\n              result = old_value.upgrade(root)\n\n              # Sanity check to guard against random return values\n              if result.is_a?(Array)\n                warnings += result[0]\n                errors   += result[1]\n              end\n            end\n          end\n\n          old.__internal_state[\"missing_key_calls\"].to_a.sort.each do |key|\n            warnings << I18n.t(\"vagrant.config.loader.bad_v1_key\", key: key)\n          end\n\n          [root, warnings, errors]\n        end\n\n        protected\n\n        def self.new_root_object\n          # Get all the registered plugins for V2\n          config_map = Vagrant.plugin(\"2\").manager.config\n\n          # Create the configuration root object\n          V2::Root.new(config_map)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/config/v2/root.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"set\"\n\nrequire \"vagrant/config/v2/util\"\n\nmodule Vagrant\n  module Config\n    module V2\n      # This is the root configuration class. An instance of this is what\n      # is passed into version 2 Vagrant configuration blocks.\n      class Root\n        # Initializes a root object that maps the given keys to specific\n        # configuration classes.\n        #\n        # @param [Hash] config_map Map of key to config class.\n        def initialize(config_map, keys=nil)\n          @keys              = keys || {}\n          @config_map        = config_map\n          @missing_key_calls = Set.new\n          @logger            = Log4r::Logger.new(\"vagrant::config\")\n        end\n\n        # We use method_missing as a way to get the configuration that is\n        # used for Vagrant and load the proper configuration classes for\n        # each.\n        def method_missing(name, *args)\n          return @keys[name] if @keys.key?(name)\n\n          config_klass = @config_map[name.to_sym]\n          if config_klass\n            # Instantiate the class and return the instance\n            @keys[name] = config_klass.new\n          else\n            @logger.debug(\"missing key request name=#{name} loc=#{caller.first}\")\n            # Record access to a missing key as an error\n            @missing_key_calls.add(name.to_s)\n            @keys[name] = DummyConfig.new\n          end\n\n          @keys[name]\n        end\n\n        # Called to finalize this object just prior to it being used by\n        # the Vagrant system. The \"!\" signifies that this is expected to\n        # mutate itself.\n        def finalize!\n          @config_map.each do |key, klass|\n            if !@keys.key?(key)\n              @keys[key] = klass.new\n            end\n          end\n\n          @keys.each do |_key, instance|\n            instance.finalize!\n            instance._finalize!\n          end\n        end\n\n        # This validates the configuration and returns a hash of error\n        # messages by section. If there are no errors, an empty hash\n        # is returned.\n        #\n        # @param [Environment] env\n        # @return [Hash]\n        def validate(machine, ignore_provider=nil)\n          # Go through each of the configuration keys and validate\n          errors = {}\n          @keys.each do |_key, instance|\n            if instance.respond_to?(:validate)\n              # Validate this single item, and if we have errors then\n              # we merge them into our total errors list.\n              if _key == :vm\n                result = instance.validate(machine, ignore_provider)\n              else\n                result = instance.validate(machine)\n              end\n              if result && !result.empty?\n                errors = Util.merge_errors(errors, result)\n              end\n            end\n          end\n\n          # Go through and delete empty keys\n          errors.keys.each do |key|\n            errors.delete(key) if errors[key].empty?\n          end\n\n          # If we have missing keys, record those as errors\n          if !@missing_key_calls.empty?\n            errors[\"Vagrant\"] = @missing_key_calls.to_a.sort.map do |key|\n              I18n.t(\"vagrant.config.root.bad_key\", key: key)\n            end\n          end\n\n          errors\n        end\n\n        # Returns the internal state of the root object. This is used\n        # by outside classes when merging, and shouldn't be called directly.\n        # Note the strange method name is to attempt to avoid any name\n        # clashes with potential configuration keys.\n        def __internal_state\n          {\n            \"config_map\"        => @config_map,\n            \"keys\"              => @keys,\n            \"missing_key_calls\" => @missing_key_calls\n          }\n        end\n\n        # This sets the internal state. This is used by the core to do some\n        # merging logic and shouldn't be used by the general public.\n        def __set_internal_state(state)\n          @config_map        = state[\"config_map\"] if state.key?(\"config_map\")\n          @keys              = state[\"keys\"] if state.key?(\"keys\")\n          @missing_key_calls = state[\"missing_key_calls\"] if state.key?(\"missing_key_calls\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/config/v2/util.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Config\n    module V2\n      class Util\n        # This merges two error hashes from validate methods.\n        #\n        # @param [Hash] first\n        # @param [Hash] second\n        # @return [Hash] Merged result\n        def self.merge_errors(first, second)\n          first.dup.tap do |result|\n            second.each do |key, value|\n              result[key] ||= []\n              result[key] += value\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/config/v2.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Config\n    module V2\n      autoload :DummyConfig, \"vagrant/config/v2/dummy_config\"\n      autoload :Loader, \"vagrant/config/v2/loader\"\n      autoload :Root, \"vagrant/config/v2/root\"\n      autoload :Util, \"vagrant/config/v2/util\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/config/version_base.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Config\n    # This is the base class for any configuration versions, and includes\n    # the stub methods that configuration versions must implement. Vagrant\n    # supports configuration versioning so that backwards compatibility can be\n    # maintained for past Vagrantfiles while newer configurations are added.\n    # Vagrant only introduces new configuration versions for major versions\n    # of Vagrant.\n    class VersionBase\n      # Returns an empty configuration object. This can be any kind of object,\n      # since it is treated as an opaque value on the other side, used only\n      # for things like calling into {merge}.\n      #\n      # @return [Object]\n      def self.init\n        raise NotImplementedError\n      end\n\n      # This is called just before configuration loading is complete of\n      # a potentially completely-merged value to perform final touch-ups\n      # to the configuration, if required.\n      #\n      # This is an optional method to implement. The default implementation\n      # will simply return the same object.\n      #\n      # This will ONLY be called if this is the version that is being\n      # used. In the case that an `upgrade` is called, this will never\n      # be called.\n      #\n      # @param [Object] obj Final configuration object.\n      # @param [Object] Finalized configuration object.\n      def self.finalize(obj)\n        obj\n      end\n\n      # Loads the configuration for the given proc and returns a configuration\n      # object. The return value is treated as an opaque object, so it can be\n      # anything you'd like. The return value is the object that is passed\n      # into methods like {merge}, so it should be something you expect.\n      #\n      # @param [Proc] proc The proc that is to be configured.\n      # @return [Object]\n      def self.load(proc)\n        raise NotImplementedError\n      end\n\n      # Merges two configuration objects, returning the merged object.\n      # The values of `old` and `new` are the opaque objects returned by\n      # {load} or {init}.\n      #\n      # Once again, the return object is treated as an opaque value by\n      # the Vagrant configuration loader, so it can be anything you'd like.\n      #\n      # @param [Object] old Old configuration object.\n      # @param [Object] new New configuration object.\n      # @return [Object] The merged configuration object.\n      def self.merge(old, new)\n        raise NotImplementedError\n      end\n\n      # This is called if a previous version of configuration needs to be\n      # upgraded to this version. Each version of configuration should know\n      # how to upgrade the version immediately prior to it. This should be\n      # a best effort upgrade that makes many assumptions. The goal is for\n      # this to work in almost every case, but perhaps with some warnings.\n      # The return value for this is a 3-tuple: `[object, warnings, errors]`,\n      # where `object` is the upgraded configuration object, `warnings` is\n      # an array of warning messages, and `errors` is an array of error\n      # messages.\n      #\n      # @param [Object] old The version of the configuration object just\n      #   prior to this one.\n      # @return [Array] The 3-tuple result. Please see the above documentation\n      #   for more information on the exact structure of this object.\n      def self.upgrade(old)\n        raise NotImplementedError\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/registry\"\n\nmodule Vagrant\n  module Config\n    autoload :Loader,        'vagrant/config/loader'\n    autoload :VersionBase,   'vagrant/config/version_base'\n\n    autoload :V1,            'vagrant/config/v1'\n    autoload :V2,            'vagrant/config/v2'\n\n    # This is a mutex used to guarantee that only one thread can load\n    # procs at any given time.\n    CONFIGURE_MUTEX = Mutex.new\n\n    # This is the registry which keeps track of what configuration\n    # versions are available, mapped by the version string used in\n    # `Vagrant.configure` calls.\n    VERSIONS = Registry.new\n    VERSIONS.register(\"1\") { V1::Loader }\n    VERSIONS.register(\"2\") { V2::Loader }\n\n    # This is the order of versions. This is used by the loader to figure out\n    # how to \"upgrade\" versions up to the desired (current) version. The\n    # current version is always considered to be the last version in this\n    # list.\n    VERSIONS_ORDER = [\"1\", \"2\"]\n    CURRENT_VERSION = VERSIONS_ORDER.last\n\n    # This is the method which is called by all Vagrantfiles to configure Vagrant.\n    # This method expects a block which accepts a single argument representing\n    # an instance of the {Config::Top} class.\n    #\n    # Note that the block is not run immediately. Instead, it's proc is stored\n    # away for execution later.\n    def self.run(version=\"1\", &block)\n      # Store it for later\n      @last_procs ||= []\n      @last_procs << [version.to_s, block]\n    end\n\n    # This is a method which will yield to a block and will capture all\n    # ``Vagrant.configure`` calls, returning an array of `Proc`s.\n    #\n    # Wrapping this around anytime you call code which loads configurations\n    # will force a mutex so that procs never get mixed up. This keeps\n    # the configuration loading part of Vagrant thread-safe.\n    def self.capture_configures\n      CONFIGURE_MUTEX.synchronize do\n        # Reset the last procs so that we start fresh\n        @last_procs = []\n\n        # Yield to allow the caller to do whatever loading needed\n        yield\n\n        # Return the last procs we've seen while still in the mutex,\n        # knowing we're safe.\n        return @last_procs\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/environment.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'fileutils'\nrequire 'json'\nrequire 'pathname'\nrequire 'set'\nrequire 'thread'\n\nrequire 'log4r'\n\nrequire 'vagrant/util/file_mode'\nrequire 'vagrant/util/platform'\nrequire 'vagrant/util/hash_with_indifferent_access'\nrequire \"vagrant/util/silence_warnings\"\nrequire \"vagrant/vagrantfile\"\nrequire \"vagrant/version\"\n\nmodule Vagrant\n  # A \"Vagrant environment\" represents a configuration of how Vagrant\n  # should behave: data directories, working directory, UI output,\n  # etc. In day-to-day usage, every `vagrant` invocation typically\n  # leads to a single Vagrant environment.\n  class Environment\n    # This is the current version that this version of Vagrant is\n    # compatible with in the home directory.\n    #\n    # @return [String]\n    CURRENT_SETUP_VERSION = \"1.5\"\n\n    DEFAULT_LOCAL_DATA = \".vagrant\"\n\n    # The `cwd` that this environment represents\n    attr_reader :cwd\n\n    # The persistent data directory where global data can be stored. It\n    # is up to the creator of the data in this directory to properly\n    # remove it when it is no longer needed.\n    #\n    # @return [Pathname]\n    attr_reader :data_dir\n\n    # The valid name for a Vagrantfile for this environment.\n    attr_reader :vagrantfile_name\n\n    # The {UI} object to communicate with the outside world.\n    attr_reader :ui\n\n    # This is the UI class to use when creating new UIs.\n    attr_reader :ui_class\n\n    # The directory to the \"home\" folder that Vagrant will use to store\n    # global state.\n    attr_reader :home_path\n\n    # The directory to the directory where local, environment-specific\n    # data is stored.\n    attr_reader :local_data_path\n\n    # The directory where temporary files for Vagrant go.\n    attr_reader :tmp_path\n\n    # File where command line aliases go.\n    attr_reader :aliases_path\n\n    # The directory where boxes are stored.\n    attr_reader :boxes_path\n\n    # The path where the plugins are stored (gems)\n    attr_reader :gems_path\n\n    # The path to the default private keys directory\n    attr_reader :default_private_keys_directory\n\n    # The paths for each of the default private keys\n    attr_reader :default_private_key_paths\n\n    # Initializes a new environment with the given options. The options\n    # is a hash where the main available key is `cwd`, which defines where\n    # the environment represents. There are other options available but\n    # they shouldn't be used in general. If `cwd` is nil, then it defaults\n    # to the `Dir.pwd` (which is the cwd of the executing process).\n    def initialize(opts=nil)\n      opts = {\n        cwd:              nil,\n        home_path:        nil,\n        local_data_path:  nil,\n        ui_class:         nil,\n        ui_opts:          nil,\n        vagrantfile_name: nil,\n      }.merge(opts || {})\n\n      # Set the default working directory to look for the vagrantfile\n      opts[:cwd] ||= ENV[\"VAGRANT_CWD\"] if ENV.key?(\"VAGRANT_CWD\")\n      opts[:cwd] ||= Dir.pwd\n      opts[:cwd] = Pathname.new(opts[:cwd])\n      if !opts[:cwd].directory?\n        raise Errors::EnvironmentNonExistentCWD, cwd: opts[:cwd].to_s\n      end\n      opts[:cwd] = opts[:cwd].expand_path\n\n      # Set the default ui class\n      opts[:ui_class] ||= UI::Silent\n\n      # Set the Vagrantfile name up. We append \"Vagrantfile\" and \"vagrantfile\" so that\n      # those continue to work as well, but anything custom will take precedence.\n      opts[:vagrantfile_name] ||= ENV[\"VAGRANT_VAGRANTFILE\"] if \\\n        ENV.key?(\"VAGRANT_VAGRANTFILE\")\n      opts[:vagrantfile_name] = [opts[:vagrantfile_name]] if \\\n        opts[:vagrantfile_name] && !opts[:vagrantfile_name].is_a?(Array)\n\n      # Set instance variables for all the configuration parameters.\n      @cwd              = opts[:cwd]\n      @home_path        = opts[:home_path]\n      @vagrantfile_name = opts[:vagrantfile_name]\n      @ui               = opts.fetch(:ui, opts[:ui_class].new)\n      @ui_class         = opts[:ui_class]\n\n      if @ui.nil?\n        if opts[:ui_opts].nil?\n          @ui = opts[:ui_class].new\n        else\n          @ui = opts[:ui_class].new(*opts[:ui_opts])\n        end\n      end\n\n      # This is the batch lock, that enforces that only one {BatchAction}\n      # runs at a time from {#batch}.\n      @batch_lock = Mutex.new\n\n      @locks = {}\n\n      @logger = Log4r::Logger.new(\"vagrant::environment\")\n      @logger.info(\"Environment initialized (#{self})\")\n      @logger.info(\"  - cwd: #{cwd}\")\n\n      # Setup the home directory\n      @home_path  ||= Vagrant.user_data_path\n      @home_path  = Util::Platform.fs_real_path(@home_path)\n      @boxes_path = @home_path.join(\"boxes\")\n      @data_dir   = @home_path.join(\"data\")\n      @gems_path  = Vagrant::Bundler.instance.plugin_gem_path\n      @tmp_path   = @home_path.join(\"tmp\")\n      @machine_index_dir = @data_dir.join(\"machine-index\")\n\n      @aliases_path = Pathname.new(ENV[\"VAGRANT_ALIAS_FILE\"]).expand_path if ENV.key?(\"VAGRANT_ALIAS_FILE\")\n      @aliases_path ||= @home_path.join(\"aliases\")\n\n      # Prepare the directories\n      setup_home_path\n\n      # Setup the local data directory. If a configuration path is given,\n      # it is expanded relative to the root path. Otherwise, we use the\n      # default (which is also expanded relative to the root path).\n      if !root_path.nil?\n        if !ENV[\"VAGRANT_DOTFILE_PATH\"].to_s.empty? && !opts[:child]\n          opts[:local_data_path] ||= Pathname.new(File.expand_path(ENV[\"VAGRANT_DOTFILE_PATH\"], root_path))\n        else\n          opts[:local_data_path] ||= root_path.join(DEFAULT_LOCAL_DATA)\n        end\n      end\n      if opts[:local_data_path]\n        @local_data_path = Pathname.new(File.expand_path(opts[:local_data_path], @cwd))\n      end\n\n      @logger.debug(\"Effective local data path: #{@local_data_path}\")\n\n      # If we have a root path, load the \".vagrantplugins\" file.\n      if root_path\n        plugins_file = root_path.join(\".vagrantplugins\")\n        if plugins_file.file?\n          @logger.info(\"Loading plugins file: #{plugins_file}\")\n          load plugins_file\n        end\n      end\n\n      setup_local_data_path\n\n      # Setup the default private key\n      @default_private_key_path = @home_path.join(\"insecure_private_key\")\n      @default_private_keys_directory = @home_path.join(\"insecure_private_keys\")\n      if !@default_private_keys_directory.directory?\n        @default_private_keys_directory.mkdir\n      end\n      @default_private_key_paths = []\n      copy_insecure_private_keys\n\n      # Initialize localized plugins\n      plugins = Vagrant::Plugin::Manager.instance.localize!(self)\n      # Load any environment local plugins\n      Vagrant::Plugin::Manager.instance.load_plugins(plugins)\n\n      # Initialize globalize plugins\n      plugins = Vagrant::Plugin::Manager.instance.globalize!\n      # Load any global plugins\n      Vagrant::Plugin::Manager.instance.load_plugins(plugins)\n\n      plugins = process_configured_plugins\n\n      # Call the hooks that does not require configurations to be loaded\n      # by using a \"clean\" action runner\n      hook(:environment_plugins_loaded, runner: Action::PrimaryRunner.new(env: self))\n\n      # Call the environment load hooks\n      hook(:environment_load, runner: Action::PrimaryRunner.new(env: self))\n    end\n\n    # The path to the default private key\n    # NOTE: deprecated, used default_private_keys_directory instead\n    def default_private_key_path\n      # TODO(spox): Add deprecation warning\n      @default_private_key_path\n    end\n\n    # Return a human-friendly string for pretty printed or inspected\n    # instances.\n    #\n    # @return [String]\n    def inspect\n      \"#<#{self.class}: #{@cwd}>\".encode('external')\n    end\n\n    # Action runner for executing actions in the context of this environment.\n    #\n    # @return [Action::Runner]\n    def action_runner\n      @action_runner ||= Action::PrimaryRunner.new do\n        {\n          action_runner:  action_runner,\n          box_collection: boxes,\n          hook:           method(:hook),\n          host:           host,\n          machine_index:  machine_index,\n          gems_path:      gems_path,\n          home_path:      home_path,\n          root_path:      root_path,\n          tmp_path:       tmp_path,\n          ui:             @ui,\n          env:            self\n        }\n      end\n    end\n\n    # Returns a list of machines that this environment is currently\n    # managing that physically have been created.\n    #\n    # An \"active\" machine is a machine that Vagrant manages that has\n    # been created. The machine itself may be in any state such as running,\n    # suspended, etc. but if a machine is \"active\" then it exists.\n    #\n    # Note that the machines in this array may no longer be present in\n    # the Vagrantfile of this environment. In this case the machine can\n    # be considered an \"orphan.\" Determining which machines are orphan\n    # and which aren't is not currently a supported feature, but will\n    # be in a future version.\n    #\n    # @return [Array<String, Symbol>]\n    def active_machines\n      # We have no active machines if we have no data path\n      return [] if !@local_data_path\n\n      machine_folder = @local_data_path.join(\"machines\")\n\n      # If the machine folder is not a directory then we just return\n      # an empty array since no active machines exist.\n      return [] if !machine_folder.directory?\n\n      # Traverse the machines folder accumulate a result\n      result = []\n\n      machine_folder.children(true).each do |name_folder|\n        # If this isn't a directory then it isn't a machine\n        next if !name_folder.directory?\n\n        name = name_folder.basename.to_s.to_sym\n        name_folder.children(true).each do |provider_folder|\n          # If this isn't a directory then it isn't a provider\n          next if !provider_folder.directory?\n\n          # If this machine doesn't have an ID, then ignore\n          next if !provider_folder.join(\"id\").file?\n\n          provider = provider_folder.basename.to_s.to_sym\n          result << [name, provider]\n        end\n      end\n\n      # Return the results\n      result\n    end\n\n    # This creates a new batch action, yielding it, and then running it\n    # once the block is called.\n    #\n    # This handles the case where batch actions are disabled by the\n    # VAGRANT_NO_PARALLEL environmental variable.\n    def batch(parallel=true)\n      parallel = false if ENV[\"VAGRANT_NO_PARALLEL\"]\n\n      @batch_lock.synchronize do\n        BatchAction.new(parallel).tap do |b|\n          # Yield it so that the caller can setup actions\n          yield b\n\n          # And run it!\n          b.run\n        end\n      end\n    end\n\n    # Makes a call to the CLI with the given arguments as if they\n    # came from the real command line (sometimes they do!). An example:\n    #\n    #     env.cli(\"package\", \"--vagrantfile\", \"Vagrantfile\")\n    #\n    def cli(*args)\n      CLI.new(args.flatten, self).execute\n    end\n\n    # This returns the provider name for the default provider for this\n    # environment.\n    #\n    # @param check_usable [Boolean] (true) whether to filter for `.usable?` providers\n    # @param exclude [Array<Symbol>] ([]) list of provider names to exclude from\n    #   consideration\n    # @param force_default [Boolean] (true) whether to prefer the value of\n    #   VAGRANT_DEFAULT_PROVIDER over other strategies if it is set\n    # @param machine [Symbol] (nil) a machine name to scope this lookup\n    # @return [Symbol] Name of the default provider.\n    def default_provider(**opts)\n      opts[:exclude]       = Set.new(opts[:exclude]) if opts[:exclude]\n      opts[:force_default] = true if !opts.key?(:force_default)\n      opts[:check_usable] = true if !opts.key?(:check_usable)\n\n      # Implement the algorithm from\n      # https://www.vagrantup.com/docs/providers/basic_usage.html#default-provider\n      # with additional steps 2.5 and 3.5 from\n      # https://bugzilla.redhat.com/show_bug.cgi?id=1444492\n      # to allow system-configured provider priorities.\n      #\n      # 1. The --provider flag on a vagrant up is chosen above all else, if it is\n      #    present.\n      #\n      # (Step 1 is done by the caller; this method is only called if --provider\n      # wasn't given.)\n      #\n      # 2. If the VAGRANT_DEFAULT_PROVIDER environmental variable is set, it\n      #    takes next priority and will be the provider chosen.\n\n      default = ENV[\"VAGRANT_DEFAULT_PROVIDER\"].to_s\n      if default.empty?\n        default = nil\n      else\n        default = default.to_sym\n        @logger.debug(\"Default provider: `#{default}`\")\n      end\n\n      # If we're forcing the default, just short-circuit and return\n      # that (the default behavior)\n      if default && opts[:force_default]\n        @logger.debug(\"Using forced default provider: `#{default}`\")\n        return default\n      end\n\n      # Determine the config to use to look for provider definitions. By\n      # default it is the global but if we're targeting a specific machine,\n      # then look there.\n      root_config = vagrantfile.config\n      if opts[:machine]\n        machine_info = vagrantfile.machine_config(opts[:machine], nil, nil, nil)\n        root_config = machine_info[:config]\n      end\n\n      # Get the list of providers within our configuration, in order.\n      config = root_config.vm.__providers\n\n      # Get the list of usable providers with their internally-declared\n      # priorities.\n      usable = []\n      Vagrant.plugin(\"2\").manager.providers.each do |key, data|\n        impl  = data[0]\n        popts = data[1]\n\n        # Skip excluded providers\n        next if opts[:exclude] && opts[:exclude].include?(key)\n\n        # Skip providers that can't be defaulted, unless they're in our\n        # config, in which case someone made our decision for us.\n        if !config.include?(key)\n          next if popts.key?(:defaultable) && !popts[:defaultable]\n        end\n\n        # Skip providers that aren't usable.\n        next if opts[:check_usable] && !impl.usable?(false)\n\n        # Each provider sets its own priority, defaulting to 5 so we can trust\n        # it's always set.\n        usable << [popts[:priority], key]\n      end\n      @logger.debug(\"Initial usable provider list: #{usable}\")\n\n      # Sort the usable providers by priority. Higher numbers are higher\n      # priority, otherwise alpha sort.\n      usable = usable.sort {|a, b| a[0] == b[0] ? a[1] <=> b[1] : b[0] <=> a[0]}\n                      .map {|prio, key| key}\n      @logger.debug(\"Priority sorted usable provider list: #{usable}\")\n\n      # If we're not forcing the default, but it's usable and hasn't been\n      # otherwise excluded, return it now.\n      if usable.include?(default)\n        @logger.debug(\"Using default provider `#{default}` as it was found in usable list.\")\n        return default\n      end\n\n      # 2.5. Vagrant will go through all of the config.vm.provider calls in the\n      #      Vagrantfile and try each in order. It will choose the first\n      #      provider that is usable and listed in VAGRANT_PREFERRED_PROVIDERS.\n\n      preferred = ENV.fetch('VAGRANT_PREFERRED_PROVIDERS', '')\n                     .split(',')\n                     .map {|s| s.strip}\n                     .select {|s| !s.empty?}\n                     .map {|s| s.to_sym}\n      @logger.debug(\"Preferred provider list: #{preferred}\")\n\n      config.each do |key|\n        if usable.include?(key) && preferred.include?(key)\n          @logger.debug(\"Using preferred provider `#{key}` detected in configuration and usable.\")\n          return key\n        end\n      end\n\n      # 3. Vagrant will go through all of the config.vm.provider calls in the\n      #    Vagrantfile and try each in order. It will choose the first provider\n      #    that is usable. For example, if you configure Hyper-V, it will never\n      #    be chosen on Mac this way. It must be both configured and usable.\n\n      config.each do |key|\n        if usable.include?(key)\n          @logger.debug(\"Using provider `#{key}` detected in configuration and usable.\")\n          return key\n        end\n      end\n\n      # 3.5. Vagrant will go through VAGRANT_PREFERRED_PROVIDERS and find the\n      #      first plugin that reports it is usable.\n\n      preferred.each do |key|\n        if usable.include?(key)\n          @logger.debug(\"Using preferred provider `#{key}` found in usable list.\")\n          return key\n        end\n      end\n\n      # 4. Vagrant will go through all installed provider plugins (including the\n      #    ones that come with Vagrant), and find the first plugin that reports\n      #    it is usable. There is a priority system here: systems that are known\n      #    better have a higher priority than systems that are worse. For\n      #    example, if you have the VMware provider installed, it will always\n      #    take priority over VirtualBox.\n\n      if !usable.empty?\n        @logger.debug(\"Using provider `#{usable[0]}` as it is the highest priority in the usable list.\")\n        return usable[0]\n      end\n\n      # 5. If Vagrant still has not found any usable providers, it will error.\n\n      # No providers available is a critical error for Vagrant.\n      raise Errors::NoDefaultProvider\n    end\n\n    # Returns whether or not we know how to install the provider with\n    # the given name.\n    #\n    # @return [Boolean]\n    def can_install_provider?(name)\n      host.capability?(provider_install_key(name))\n    end\n\n    # Installs the provider with the given name.\n    #\n    # This will raise an exception if we don't know how to install the\n    # provider with the given name. You should guard this call with\n    # `can_install_provider?` for added safety.\n    #\n    # An exception will be raised if there are any failures installing\n    # the provider.\n    def install_provider(name)\n      host.capability(provider_install_key(name))\n    end\n\n    # Returns the collection of boxes for the environment.\n    #\n    # @return [BoxCollection]\n    def boxes\n      @_boxes ||= BoxCollection.new(\n        boxes_path,\n        hook: method(:hook),\n        temp_dir_root: tmp_path)\n    end\n\n    # Returns the {Config::Loader} that can be used to load Vagrantfiles\n    # given the settings of this environment.\n    #\n    # @return [Config::Loader]\n    def config_loader\n      return @config_loader if @config_loader\n\n      home_vagrantfile = nil\n      root_vagrantfile = nil\n      home_vagrantfile = find_vagrantfile(home_path) if home_path\n      if root_path\n        root_vagrantfile = find_vagrantfile(root_path, @vagrantfile_name)\n      end\n\n      @config_loader = Config::Loader.new(\n        Config::VERSIONS, Config::VERSIONS_ORDER)\n      @config_loader.set(:home, home_vagrantfile) if home_vagrantfile\n      @config_loader.set(:root, root_vagrantfile) if root_vagrantfile\n      @config_loader\n    end\n\n    # Loads another environment for the given Vagrantfile, sharing as much\n    # useful state from this Environment as possible (such as UI and paths).\n    # Any initialization options can be overridden using the opts hash.\n    #\n    # @param [String] vagrantfile Path to a Vagrantfile\n    # @return [Environment]\n    def environment(vagrantfile, **opts)\n      path = File.expand_path(vagrantfile, root_path)\n      file = File.basename(path)\n      path = File.dirname(path)\n\n      Util::SilenceWarnings.silence! do\n        Environment.new({\n          child:     true,\n          cwd:       path,\n          home_path: home_path,\n          ui_class:  ui_class,\n          vagrantfile_name: file,\n        }.merge(opts))\n      end\n    end\n\n    # This defines a hook point where plugin action hooks that are registered\n    # against the given name will be run in the context of this environment.\n    #\n    # @param [Symbol] name Name of the hook.\n    # @param [Action::Runner] action_runner A custom action runner for running hooks.\n    def hook(name, opts=nil)\n      @logger.info(\"Running hook: #{name}\")\n\n      opts ||= {}\n      opts[:callable] ||= Action::Builder.new\n      opts[:runner] ||= action_runner\n      opts[:action_name] = name\n      opts[:env] = self\n      opts.delete(:runner).run(opts.delete(:callable), opts)\n    end\n\n    # Returns the host object associated with this environment.\n    #\n    # @return [Class]\n    def host\n      return @host if defined?(@host)\n\n      # Determine the host class to use. \":detect\" is an old Vagrant config\n      # that shouldn't be valid anymore, but we respect it here by assuming\n      # its old behavior. No need to deprecate this because I think it is\n      # fairly harmless.\n      host_klass = vagrantfile.config.vagrant.host\n      host_klass = nil if host_klass == :detect\n\n      begin\n        @host = Host.new(\n          host_klass,\n          Vagrant.plugin(\"2\").manager.hosts,\n          Vagrant.plugin(\"2\").manager.host_capabilities,\n          self)\n      rescue Errors::CapabilityHostNotDetected\n        # If the auto-detect failed, then we create a brand new host\n        # with no capabilities and use that. This should almost never happen\n        # since Vagrant works on most host OS's now, so this is a \"slow path\"\n        klass = Class.new(Vagrant.plugin(\"2\", :host)) do\n          def detect?(env); true; end\n        end\n\n        hosts     = { generic: [klass, nil] }\n        host_caps = {}\n\n        @host = Host.new(:generic, hosts, host_caps, self)\n      rescue Errors::CapabilityHostExplicitNotDetected => e\n        raise Errors::HostExplicitNotDetected, e.extra_data\n      end\n    end\n\n    # This acquires a process-level lock with the given name.\n    #\n    # The lock file is held within the data directory of this environment,\n    # so make sure that all environments that are locking are sharing\n    # the same data directory.\n    #\n    # This will raise Errors::EnvironmentLockedError if the lock can't\n    # be obtained.\n    #\n    # @param [String] name Name of the lock, since multiple locks can\n    #   be held at one time.\n    def lock(name=\"global\", **opts)\n      f = nil\n\n      # If we don't have a block, then locking is useless, so ignore it\n      return if !block_given?\n\n      # This allows multiple locks in the same process to be nested\n      return yield if @locks[name] || opts[:noop]\n\n      # The path to this lock\n      lock_path = data_dir.join(\"lock.#{name}.lock\")\n\n      @logger.debug(\"Attempting to acquire process-lock: #{name}\")\n      lock(\"dotlock\", noop: name == \"dotlock\", retry: true) do\n        f = File.open(lock_path, \"w+\")\n      end\n\n      # The file locking fails only if it returns \"false.\" If it\n      # succeeds it returns a 0, so we must explicitly check for\n      # the proper error case.\n      while f.flock(File::LOCK_EX | File::LOCK_NB) === false\n        @logger.warn(\"Process-lock in use: #{name}\")\n\n        if !opts[:retry]\n          raise Errors::EnvironmentLockedError,\n            name: name\n        end\n\n        sleep 0.2\n      end\n\n      @logger.info(\"Acquired process lock: #{name}\")\n\n      result = nil\n      begin\n        # Mark that we have a lock\n        @locks[name] = true\n\n        result = yield\n      ensure\n        # We need to make sure that no matter what this is always\n        # reset to false so we don't think we have a lock when we\n        # actually don't.\n        @locks.delete(name)\n        @logger.info(\"Released process lock: #{name}\")\n      end\n\n      # Clean up the lock file, this requires another lock\n      if name != \"dotlock\"\n        lock(\"dotlock\", retry: true) do\n          f.close\n          begin\n            File.delete(lock_path)\n          rescue\n            @logger.error(\n              \"Failed to delete lock file #{lock_path} - some other thread \" +\n              \"might be trying to acquire it. ignoring this error\")\n          end\n        end\n      end\n\n      # Return the result\n      return result\n    ensure\n      begin\n        f.close if f\n      rescue IOError\n      end\n    end\n\n    # This executes the push with the given name, raising any exceptions that\n    # occur.\n    #\n    # @param name [String] Push plugin name\n    # @param manager [Vagrant::Plugin::Manager] Plugin Manager to use,\n    # defaults to the primary one registered but parameterized so it can be\n    # overridden in server mode\n    #\n    # @see VagrantPlugins::CommandServe::Service::PushService Server mode behavior\n    #\n    # Precondition: the push is not nil and exists.\n    def push(name, manager: Vagrant.plugin(\"2\").manager)\n      @logger.info(\"Getting push: #{name}\")\n\n      name = name.to_sym\n\n      pushes = self.vagrantfile.config.push.__compiled_pushes\n      if !pushes.key?(name)\n        raise Vagrant::Errors::PushStrategyNotDefined,\n          name: name,\n          pushes: pushes.keys\n      end\n\n      strategy, config = pushes[name]\n      push_registry = manager.pushes\n      klass, _ = push_registry.get(strategy)\n      if klass.nil?\n        raise Vagrant::Errors::PushStrategyNotLoaded,\n          name: strategy,\n          pushes: push_registry.keys\n      end\n\n      klass.new(self, config).push\n    end\n\n    # The list of pushes defined in this Vagrantfile.\n    #\n    # @return [Array<Symbol>]\n    def pushes\n      self.vagrantfile.config.push.__compiled_pushes.keys\n    end\n\n    # This returns a machine with the proper provider for this environment.\n    # The machine named by `name` must be in this environment.\n    #\n    # @param [Symbol] name Name of the machine (as configured in the\n    #   Vagrantfile).\n    # @param [Symbol] provider The provider that this machine should be\n    #   backed by.\n    # @param [Boolean] refresh If true, then if there is a cached version\n    #   it is reloaded.\n    # @return [Machine]\n    def machine(name, provider, refresh=false)\n      @logger.info(\"Getting machine: #{name} (#{provider})\")\n\n      # Compose the cache key of the name and provider, and return from\n      # the cache if we have that.\n      cache_key = [name, provider]\n      @machines ||= {}\n      if refresh\n        @logger.info(\"Refreshing machine (busting cache): #{name} (#{provider})\")\n        @machines.delete(cache_key)\n      end\n\n      if @machines.key?(cache_key)\n        @logger.info(\"Returning cached machine: #{name} (#{provider})\")\n        return @machines[cache_key]\n      end\n\n      @logger.info(\"Uncached load of machine.\")\n\n      # Determine the machine data directory and pass it to the machine.\n      machine_data_path = @local_data_path.join(\n        \"machines/#{name}/#{provider}\")\n\n      # Create the machine and cache it for future calls. This will also\n      # return the machine from this method.\n      @machines[cache_key] = vagrantfile.machine(\n        name, provider, boxes, machine_data_path, self)\n    end\n\n    # The {MachineIndex} to store information about the machines.\n    #\n    # @return [MachineIndex]\n    def machine_index\n      @machine_index ||= MachineIndex.new(@machine_index_dir)\n    end\n\n    # This returns a list of the configured machines for this environment.\n    # Each of the names returned by this method is valid to be used with\n    # the {#machine} method.\n    #\n    # @return [Array<Symbol>] Configured machine names.\n    def machine_names\n      vagrantfile.machine_names\n    end\n\n    # This returns the name of the machine that is the \"primary.\" In the\n    # case of  a single-machine environment, this is just the single machine\n    # name. In the case of a multi-machine environment, then this can\n    # potentially be nil if no primary machine is specified.\n    #\n    # @return [Symbol]\n    def primary_machine_name\n      vagrantfile.primary_machine_name\n    end\n\n    # The root path is the path where the top-most (loaded last)\n    # Vagrantfile resides. It can be considered the project root for\n    # this environment.\n    #\n    # @return [String]\n    def root_path\n      return @root_path if defined?(@root_path)\n\n      root_finder = lambda do |path|\n        # Note: To remain compatible with Ruby 1.8, we have to use\n        # a `find` here instead of an `each`.\n        vf = find_vagrantfile(path, @vagrantfile_name)\n        return path if vf\n        return nil if path.root? || !File.exist?(path)\n        root_finder.call(path.parent)\n      end\n\n      @root_path = root_finder.call(cwd)\n    end\n\n    # Unload the environment, running completion hooks. The environment\n    # should not be used after this (but CAN be, technically). It is\n    # recommended to always immediately set the variable to `nil` after\n    # running this so you can't accidentally run any more methods. Example:\n    #\n    #     env.unload\n    #     env = nil\n    #\n    def unload\n      hook(:environment_unload)\n    end\n\n    # Represents the default Vagrantfile, or the Vagrantfile that is\n    # in the working directory or a parent of the working directory\n    # of this environment.\n    #\n    # The existence of this function is primarily a convenience. There\n    # is nothing stopping you from instantiating your own {Vagrantfile}\n    # and loading machines in any way you see fit. Typical behavior of\n    # Vagrant, however, loads this Vagrantfile.\n    #\n    # This Vagrantfile is comprised of two major sources: the Vagrantfile\n    # in the user's home directory as well as the \"root\" Vagrantfile or\n    # the Vagrantfile in the working directory (or parent).\n    #\n    # @return [Vagrantfile]\n    def vagrantfile\n      @vagrantfile ||= Vagrantfile.new(config_loader, [:home, :root])\n    end\n\n    #---------------------------------------------------------------\n    # Load Methods\n    #---------------------------------------------------------------\n\n    # This sets the `@home_path` variable properly.\n    #\n    # @return [Pathname]\n    def setup_home_path\n      @logger.info(\"Home path: #{@home_path}\")\n\n      # Setup the list of child directories that need to be created if they\n      # don't already exist.\n      dirs    = [\n        @home_path,\n        @home_path.join(\"rgloader\"),\n        @boxes_path,\n        @data_dir,\n        @gems_path,\n        @tmp_path,\n        @machine_index_dir,\n      ]\n\n      # Go through each required directory, creating it if it doesn't exist\n      dirs.each do |dir|\n        next if File.directory?(dir)\n\n        begin\n          @logger.info(\"Creating: #{dir}\")\n          FileUtils.mkdir_p(dir)\n        rescue Errno::EACCES, Errno::EROFS\n          raise Errors::HomeDirectoryNotAccessible, home_path: @home_path.to_s\n        end\n      end\n\n      # Attempt to write into the home directory to verify we can\n      begin\n        # Append a random suffix to avoid race conditions if Vagrant\n        # is running in parallel with other Vagrant processes.\n        suffix = (0...32).map { (65 + rand(26)).chr }.join\n        path   = @home_path.join(\"perm_test_#{suffix}\")\n        path.open(\"w\") do |f|\n          f.write(\"hello\")\n        end\n        path.unlink\n      rescue Errno::EACCES\n        raise Errors::HomeDirectoryNotAccessible, home_path: @home_path.to_s\n      end\n\n      # Create the version file that we use to track the structure of\n      # the home directory. If we have an old version, we need to explicitly\n      # upgrade it. Otherwise, we just mark that it's the current version.\n      version_file = @home_path.join(\"setup_version\")\n      if version_file.file?\n        version = version_file.read.chomp\n        if version > CURRENT_SETUP_VERSION\n          raise Errors::HomeDirectoryLaterVersion\n        end\n\n        case version\n        when CURRENT_SETUP_VERSION\n          # We're already good, at the latest version.\n        when \"1.1\"\n          # We need to update our directory structure\n          upgrade_home_path_v1_1\n\n          # Delete the version file so we put our latest version in\n          version_file.delete\n        else\n          raise Errors::HomeDirectoryUnknownVersion,\n            path: @home_path.to_s,\n            version: version\n        end\n      end\n\n      if !version_file.file?\n        @logger.debug(\n          \"Creating home directory version file: #{CURRENT_SETUP_VERSION}\")\n        version_file.open(\"w\") do |f|\n          f.write(CURRENT_SETUP_VERSION)\n        end\n      end\n\n      # Create the rgloader/loader file so we can use encoded files.\n      loader_file = @home_path.join(\"rgloader\", \"loader.rb\")\n      if !loader_file.file?\n        source_loader = Vagrant.source_root.join(\"templates/rgloader.rb\")\n        FileUtils.cp(source_loader.to_s, loader_file.to_s)\n      end\n    end\n\n    # This creates the local data directory and show an error if it\n    # couldn't properly be created.\n    def setup_local_data_path(force=false)\n      if @local_data_path.nil?\n        @logger.warn(\"No local data path is set. Local data cannot be stored.\")\n        return\n      end\n\n      @logger.info(\"Local data path: #{@local_data_path}\")\n\n      # If the local data path is a file, then we are probably seeing an\n      # old (V1) \"dotfile.\" In this case, we upgrade it. The upgrade process\n      # will remove the old data file if it is successful.\n      if @local_data_path.file?\n        upgrade_v1_dotfile(@local_data_path)\n      end\n\n      # If we don't have a root path, we don't setup anything\n      return if !force && root_path.nil?\n\n      begin\n        @logger.debug(\"Creating: #{@local_data_path}\")\n        FileUtils.mkdir_p(@local_data_path)\n        # Create the rgloader/loader file so we can use encoded files.\n        loader_file = @local_data_path.join(\"rgloader\", \"loader.rb\")\n        if !loader_file.file?\n          source_loader = Vagrant.source_root.join(\"templates/rgloader.rb\")\n          FileUtils.mkdir_p(@local_data_path.join(\"rgloader\").to_s)\n          FileUtils.cp(source_loader.to_s, loader_file.to_s)\n        end\n      rescue Errno::EACCES\n        raise Errors::LocalDataDirectoryNotAccessible,\n          local_data_path: @local_data_path.to_s\n      end\n    end\n\n    protected\n\n    # Attempt to guess the configured provider in use. Will fallback\n    # to the default provider if an explicit provider name is not\n    # provided. This can be pretty error prone, but is used during\n    # initial environment setup to allow loading plugins so it doesn't\n    # need to be perfect\n    #\n    # @return [String]\n    def guess_provider\n      gp = nil\n      ARGV.each_with_index do |val, idx|\n        if val.start_with?(\"--provider=\")\n          gp = val.split(\"=\", 2).last\n          break\n        elsif val == \"--provider\"\n          gp = ARGV[idx+1]\n          break\n        end\n      end\n      return gp.to_sym if gp\n      begin\n        default_provider\n      rescue Errors::NoDefaultProvider\n        # if a provider cannot be determined just return nil\n        nil\n      end\n    end\n\n    # Load any configuration provided by guests defined within\n    # the Vagrantfile to pull plugin information they may have\n    # defined.\n    def find_configured_plugins\n      plugins = []\n      provider = guess_provider\n      vagrantfile.machine_names.each do |mname|\n        ldp = @local_data_path.join(\"machines/#{mname}/#{provider}\") if @local_data_path\n        plugins << vagrantfile.machine_config(mname, provider, boxes, ldp, false)[:config]\n      end\n      result = plugins.reverse.inject(Vagrant::Util::HashWithIndifferentAccess.new) do |memo, val|\n        Vagrant::Util::DeepMerge.deep_merge(memo, val.vagrant.plugins)\n      end\n      Vagrant::Util::DeepMerge.deep_merge(result, vagrantfile.config.vagrant.plugins)\n    end\n\n    # Check for any local plugins defined within the Vagrantfile. If\n    # found, validate they are available. If they are not available,\n    # request to install them, or raise an exception\n    #\n    # @return [Hash] plugin list for loading\n    def process_configured_plugins\n      return if !Vagrant.plugins_enabled?\n      errors = vagrantfile.config.vagrant.validate(nil)\n      if !Array(errors[\"vagrant\"]).empty?\n        raise Errors::ConfigInvalid,\n          errors: Util::TemplateRenderer.render(\n            \"config/validation_failed\",\n            errors: {vagrant: errors[\"vagrant\"]}\n          )\n      end\n      # Check if defined plugins are installed\n      installed = Plugin::Manager.instance.installed_plugins\n      needs_install = []\n      config_plugins = find_configured_plugins\n      config_plugins.each do |name, info|\n        if !installed[name]\n          needs_install << name\n        end\n      end\n      if !needs_install.empty?\n        ui.warn(I18n.t(\"vagrant.plugins.local.uninstalled_plugins\",\n          plugins: needs_install.sort.join(\", \")))\n        if !Vagrant.auto_install_local_plugins?\n          answer = nil\n          until [\"y\", \"n\"].include?(answer)\n            answer = ui.ask(I18n.t(\"vagrant.plugins.local.request_plugin_install\") + \" [N]: \")\n            answer = answer.strip.downcase\n            answer = \"n\" if answer.to_s.empty?\n          end\n          if answer == \"n\"\n            raise Errors::PluginMissingLocalError,\n              plugins: needs_install.sort.join(\", \")\n          end\n        end\n        needs_install.each do |name|\n          pconfig = Util::HashWithIndifferentAccess.new(config_plugins[name])\n          ui.info(I18n.t(\"vagrant.commands.plugin.installing\", name: name))\n\n          options = {sources: Vagrant::Bundler::DEFAULT_GEM_SOURCES.dup, env_local: true}\n          options[:sources] = pconfig[:sources] if pconfig[:sources]\n          options[:require] = pconfig[:entry_point] if pconfig[:entry_point]\n          options[:version] = pconfig[:version] if pconfig[:version]\n\n          spec = Plugin::Manager.instance.install_plugin(name, **options)\n\n          ui.info(I18n.t(\"vagrant.commands.plugin.installed\",\n            name: spec.name, version: spec.version.to_s))\n        end\n        ui.info(\"\\n\")\n        # Force halt after installation and require command to be run again. This\n        # will properly load any new locally installed plugins which are now available.\n        ui.warn(I18n.t(\"vagrant.plugins.local.install_rerun_command\"))\n        exit(-1)\n      end\n      if Vagrant::Plugin::Manager.instance.local_file\n        Vagrant::Plugin::Manager.instance.local_file.installed_plugins\n      else\n        {}\n      end\n    end\n\n    # This method copies the private keys into the home directory if they\n    # do not already exist. The `default_private_key_path` references the\n    # original rsa based private key and is retained for compatibility. The\n    # `default_private_keys_directory` contains the list of valid private\n    # keys supported by Vagrant.\n    #\n    # NOTE: The keys are copied because `ssh` requires that the key is chmod\n    # 0600, but if Vagrant is installed as a separate user, then the\n    # effective uid won't be able to read the key. So the key is copied\n    # to the home directory and chmod 0600.\n    def copy_insecure_private_keys\n      # First setup the deprecated single key path\n      if !@default_private_key_path.exist?\n        @logger.info(\"Copying private key to home directory\")\n\n        source      = File.expand_path(\"keys/vagrant\", Vagrant.source_root)\n        destination = @default_private_key_path\n\n        begin\n          FileUtils.cp(source, destination)\n        rescue Errno::EACCES\n          raise Errors::CopyPrivateKeyFailed,\n            source: source,\n            destination: destination\n        end\n      end\n\n      if !Util::Platform.windows?\n        # On Windows, permissions don't matter as much, so don't worry\n        # about doing chmod.\n        if Util::FileMode.from_octal(@default_private_key_path.stat.mode) != \"600\"\n          @logger.info(\"Changing permissions on private key to 0600\")\n          @default_private_key_path.chmod(0600)\n        end\n      end\n\n      # Now setup the key directory\n      Dir.glob(File.expand_path(\"keys/vagrant.key.*\", Vagrant.source_root)).each do |source|\n        destination = default_private_keys_directory.join(File.basename(source))\n        default_private_key_paths << destination\n        next if File.exist?(destination)\n        begin\n          FileUtils.cp(source, destination)\n        rescue Errno::EACCES\n          raise Errors::CopyPrivateKeyFailed,\n            source: source,\n            destination: destination\n        end\n      end\n\n      if !Util::Platform.windows?\n        default_private_key_paths.each do |key_path|\n          if Util::FileMode.from_octal(key_path.stat.mode) != \"600\"\n            @logger.info(\"Changing permissions on private key (#{key_path}) to 0600\")\n            key_path.chmod(0600)\n          end\n        end\n      end\n    end\n\n    # Finds the Vagrantfile in the given directory.\n    #\n    # @param [Pathname] path Path to search in.\n    # @return [Pathname]\n    def find_vagrantfile(search_path, filenames=nil)\n      filenames ||= [\"Vagrantfile\", \"vagrantfile\"]\n      filenames.each do |vagrantfile|\n        current_path = search_path.join(vagrantfile)\n        return current_path if current_path.file?\n      end\n\n      nil\n    end\n\n    # Returns the key used for the host capability for provider installs\n    # of the given name.\n    def provider_install_key(name)\n      \"provider_install_#{name}\".to_sym\n    end\n\n    # This upgrades a home directory that was in the v1.1 format to the\n    # v1.5 format. It will raise exceptions if anything fails.\n    def upgrade_home_path_v1_1\n      if !ENV[\"VAGRANT_UPGRADE_SILENT_1_5\"]\n        @ui.ask(I18n.t(\"vagrant.upgrading_home_path_v1_5\"))\n      end\n\n      collection = BoxCollection.new(\n        @home_path.join(\"boxes\"), temp_dir_root: tmp_path)\n      collection.upgrade_v1_1_v1_5\n    end\n\n    # This upgrades a Vagrant 1.0.x \"dotfile\" to the new V2 format.\n    #\n    # This is a destructive process. Once the upgrade is complete, the\n    # old dotfile is removed, and the environment becomes incompatible for\n    # Vagrant 1.0 environments.\n    #\n    # @param [Pathname] path The path to the dotfile\n    def upgrade_v1_dotfile(path)\n      @logger.info(\"Upgrading V1 dotfile to V2 directory structure...\")\n\n      # First, verify the file isn't empty. If it is an empty file, we\n      # just delete it and go on with life.\n      contents = path.read.strip\n      if contents.strip == \"\"\n        @logger.info(\"V1 dotfile was empty. Removing and moving on.\")\n        path.delete\n        return\n      end\n\n      # Otherwise, verify there is valid JSON in here since a Vagrant\n      # environment would always ensure valid JSON. This is a sanity check\n      # to make sure we don't nuke a dotfile that is not ours...\n      @logger.debug(\"Attempting to parse JSON of V1 file\")\n      json_data = nil\n      begin\n        json_data = JSON.parse(contents)\n        @logger.debug(\"JSON parsed successfully. Things are okay.\")\n      rescue JSON::ParserError\n        # The file could've been tampered with since Vagrant 1.0.x is\n        # supposed to ensure that the contents are valid JSON. Show an error.\n        raise Errors::DotfileUpgradeJSONError,\n          state_file: path.to_s\n      end\n\n      # Alright, let's upgrade this guy to the new structure. Start by\n      # backing up the old dotfile.\n      backup_file = path.dirname.join(\".vagrant.v1.#{Time.now.to_i}\")\n      @logger.info(\"Renaming old dotfile to: #{backup_file}\")\n      path.rename(backup_file)\n\n      # Now, we create the actual local data directory. This should succeed\n      # this time since we renamed the old conflicting V1.\n      setup_local_data_path(true)\n\n      if json_data[\"active\"]\n        @logger.debug(\"Upgrading to V2 style for each active VM\")\n        json_data[\"active\"].each do |name, id|\n          @logger.info(\"Upgrading dotfile: #{name} (#{id})\")\n\n          # Create the machine configuration directory\n          directory = @local_data_path.join(\"machines/#{name}/virtualbox\")\n          FileUtils.mkdir_p(directory)\n\n          # Write the ID file\n          directory.join(\"id\").open(\"w+\") do |f|\n            f.write(id)\n          end\n        end\n      end\n\n      # Upgrade complete! Let the user know\n      @ui.info(I18n.t(\"vagrant.general.upgraded_v1_dotfile\",\n                      backup_path: backup_file.to_s))\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# This file contains all of the internal errors in Vagrant's core\n# commands, actions, etc.\n\nmodule Vagrant\n  # This module contains all of the internal errors in Vagrant's core.\n  # These errors are _expected_ errors and as such don't typically represent\n  # bugs in Vagrant itself. These are meant as a way to detect errors and\n  # display them in a user-friendly way.\n  #\n  # # Defining a new Error\n  #\n  # To define a new error, inherit from {VagrantError}, which lets Vagrant\n  # know that this is an expected error, and also gives you some helpers for\n  # providing exit codes and error messages. An example is shown below, then\n  # it is explained:\n  #\n  #     class MyError < Vagrant::Errors::VagrantError\n  #       error_key \"my_error\"\n  #     end\n  #\n  # This creates an error with an I18n error key of \"my_error.\" {VagrantError}\n  # uses I18n to look up error messages, in the \"vagrant.errors\" namespace. So\n  # in the above, the error message would be the translation of \"vagrant.errors.my_error\"\n  #\n  # If you don't want to use I18n, you can override the {#initialize} method and\n  # set your own error message.\n  #\n  # # Raising an Error\n  #\n  # To raise an error, it is nothing special, just raise it like any normal\n  # exception:\n  #\n  #     raise MyError.new\n  #\n  # Eventually this exception will bubble out to the `vagrant` binary which\n  # will show a nice error message. And if it is raised in the middle of a\n  # middleware sequence, then {Action::Warden} will catch it and begin the\n  # recovery process prior to exiting.\n  module Errors\n    # Main superclass of any errors in Vagrant. This provides some\n    # convenience methods for setting the status code and error key.\n    # The status code is used by the `vagrant` executable as the\n    # error code, and the error key is used as a default message from\n    # I18n.\n    class VagrantError < StandardError\n      # This is extra data passed into the message for translation.\n      attr_accessor :extra_data\n\n      def self.error_key(key=nil, namespace=nil)\n        define_method(:error_key) { key }\n        error_namespace(namespace) if namespace\n      end\n\n      def self.error_message(message)\n        define_method(:error_message) { message }\n      end\n\n      def self.error_namespace(namespace)\n        define_method(:error_namespace) { namespace }\n      end\n\n      def initialize(*args)\n        key     = args.shift if args.first.is_a?(Symbol)\n        message = args.shift if args.first.is_a?(Hash)\n        message ||= {}\n        @extra_data    = message.dup\n        message[:_key] ||= error_key\n        message[:_namespace] ||= error_namespace\n        message[:_key] = key if key\n\n        if message[:_key]\n          message = translate_error(message)\n        else\n          message = error_message\n        end\n\n        super(message)\n      end\n\n      # The error message for this error. This is used if no error_key\n      # is specified for a translatable error message.\n      def error_message; \"No error message\"; end\n\n      # The default error namespace which is used for the error key.\n      # This can be overridden here or by calling the \"error_namespace\"\n      # class method.\n      def error_namespace; \"vagrant.errors\"; end\n\n      # The key for the error message. This should be set using the\n      # {error_key} method but can be overridden here if needed.\n      def error_key; nil; end\n\n      # This is the exit code that should be used when exiting from\n      # this exception.\n      #\n      # @return [Integer]\n      def status_code; 1; end\n\n      protected\n\n      def translate_error(opts)\n        return nil if !opts[:_key]\n        I18n.t(\"#{opts[:_namespace]}.#{opts[:_key]}\", **opts)\n      end\n    end\n\n    class ActiveMachineWithDifferentProvider < VagrantError\n      error_key(:active_machine_with_different_provider)\n    end\n\n    class AliasInvalidError < VagrantError\n      error_key(:alias_invalid_error)\n    end\n\n    class BatchMultiError < VagrantError\n      error_key(:batch_multi_error)\n    end\n\n    class BoxAddDirectVersion < VagrantError\n      error_key(:box_add_direct_version)\n    end\n\n    class BoxAddMetadataMultiURL < VagrantError\n      error_key(:box_add_metadata_multi_url)\n    end\n\n    class BoxAddNameMismatch < VagrantError\n      error_key(:box_add_name_mismatch)\n    end\n\n    class BoxAddNameRequired < VagrantError\n      error_key(:box_add_name_required)\n    end\n\n    class BoxAddNoMatchingProvider < VagrantError\n      error_key(:box_add_no_matching_provider)\n    end\n\n    class BoxAddNoArchitectureSupport < VagrantError\n      error_key(:box_add_no_architecture_support)\n    end\n\n    class BoxAddNoMatchingArchitecture < VagrantError\n      error_key(:box_add_no_matching_architecture)\n    end\n\n    class BoxAddNoMatchingProviderVersion < VagrantError\n      error_key(:box_add_no_matching_provider_version)\n    end\n\n    class BoxAddNoMatchingVersion < VagrantError\n      error_key(:box_add_no_matching_version)\n    end\n\n    class BoxAddShortNotFound < VagrantError\n      error_key(:box_add_short_not_found)\n    end\n\n    class BoxAlreadyExists < VagrantError\n      error_key(:box_add_exists)\n    end\n\n    class BoxChecksumInvalidType < VagrantError\n      error_key(:box_checksum_invalid_type)\n    end\n\n    class BoxChecksumMismatch < VagrantError\n      error_key(:box_checksum_mismatch)\n    end\n\n    class BoxConfigChangingBox < VagrantError\n      error_key(:box_config_changing_box)\n    end\n\n    class BoxFileNotExist < VagrantError\n      error_key(:box_file_not_exist)\n    end\n\n    class BoxMetadataCorrupted < VagrantError\n      error_key(:box_metadata_corrupted)\n    end\n\n    class BoxMetadataMissingRequiredFields < VagrantError\n      error_key(:box_metadata_missing_required_fields)\n    end\n\n    class BoxMetadataDownloadError < VagrantError\n      error_key(:box_metadata_download_error)\n    end\n\n    class BoxMetadataFileNotFound < VagrantError\n      error_key(:box_metadata_file_not_found)\n    end\n\n    class BoxMetadataMalformed < VagrantError\n      error_key(:box_metadata_malformed)\n    end\n\n    class BoxMetadataMalformedVersion < VagrantError\n      error_key(:box_metadata_malformed_version)\n    end\n\n    class BoxNotFound < VagrantError\n      error_key(:box_not_found)\n    end\n\n    class BoxNotFoundWithProvider < VagrantError\n      error_key(:box_not_found_with_provider)\n    end\n\n    class BoxNotFoundWithProviderArchitecture < VagrantError\n      error_key(:box_not_found_with_provider_architecture)\n    end\n\n    class BoxNotFoundWithProviderAndVersion < VagrantError\n      error_key(:box_not_found_with_provider_and_version)\n    end\n\n    class BoxProviderDoesntMatch < VagrantError\n      error_key(:box_provider_doesnt_match)\n    end\n\n    class BoxRemoveNotFound < VagrantError\n      error_key(:box_remove_not_found)\n    end\n\n    class BoxRemoveArchitectureNotFound < VagrantError\n      error_key(:box_remove_architecture_not_found)\n    end\n\n    class BoxRemoveProviderNotFound < VagrantError\n      error_key(:box_remove_provider_not_found)\n    end\n\n    class BoxRemoveVersionNotFound < VagrantError\n      error_key(:box_remove_version_not_found)\n    end\n\n    class BoxRemoveMultiArchitecture < VagrantError\n      error_key(:box_remove_multi_architecture)\n    end\n\n    class BoxRemoveMultiProvider < VagrantError\n      error_key(:box_remove_multi_provider)\n    end\n\n    class BoxRemoveMultiVersion < VagrantError\n      error_key(:box_remove_multi_version)\n    end\n\n    class BoxServerNotSet < VagrantError\n      error_key(:box_server_not_set)\n    end\n\n    class BoxUnpackageFailure < VagrantError\n      error_key(:untar_failure, \"vagrant.actions.box.unpackage\")\n    end\n\n    class BoxUpdateMultiProvider < VagrantError\n      error_key(:box_update_multi_provider)\n    end\n\n    class BoxUpdateMultiArchitecture < VagrantError\n      error_key(:box_update_multi_architecture)\n    end\n\n    class BoxUpdateNoMetadata < VagrantError\n      error_key(:box_update_no_metadata)\n    end\n\n    class BoxVerificationFailed < VagrantError\n      error_key(:failed, \"vagrant.actions.box.verify\")\n    end\n\n    class BoxVersionInvalid < VagrantError\n      error_key(:box_version_invalid)\n    end\n\n    class BundlerDisabled < VagrantError\n      error_key(:bundler_disabled)\n    end\n\n    class BundlerError < VagrantError\n      error_key(:bundler_error)\n    end\n\n    class SourceSpecNotFound < BundlerError\n      error_key(:source_spec_not_found)\n    end\n\n    class CantReadMACAddresses < VagrantError\n      error_key(:cant_read_mac_addresses)\n    end\n\n    class CapabilityHostExplicitNotDetected < VagrantError\n      error_key(:capability_host_explicit_not_detected)\n    end\n\n    class CapabilityHostNotDetected < VagrantError\n      error_key(:capability_host_not_detected)\n    end\n\n    class CapabilityInvalid < VagrantError\n      error_key(:capability_invalid)\n    end\n\n    class CapabilityNotFound < VagrantError\n      error_key(:capability_not_found)\n    end\n\n    class CFEngineBootstrapFailed < VagrantError\n      error_key(:cfengine_bootstrap_failed)\n    end\n\n    class CFEngineCantAutodetectIP < VagrantError\n      error_key(:cfengine_cant_autodetect_ip)\n    end\n\n    class CFEngineInstallFailed < VagrantError\n      error_key(:cfengine_install_failed)\n    end\n\n    class CFEngineNotInstalled < VagrantError\n      error_key(:cfengine_not_installed)\n    end\n\n    class CLIInvalidUsage < VagrantError\n      error_key(:cli_invalid_usage)\n    end\n\n    class CLIInvalidOptions < VagrantError\n      error_key(:cli_invalid_options)\n    end\n\n    class CloneNotFound < VagrantError\n      error_key(:clone_not_found)\n    end\n\n    class CloneMachineNotFound < VagrantError\n      error_key(:clone_machine_not_found)\n    end\n\n    class CloudInitNotFound < VagrantError\n      error_key(:cloud_init_not_found)\n    end\n\n    class CloudInitCommandFailed < VagrantError\n      error_key(:cloud_init_command_failed)\n    end\n\n    class CommandDeprecated < VagrantError\n      error_key(:command_deprecated)\n    end\n\n    class CommandSuspendAllArgs < VagrantError\n      error_key(:command_suspend_all_arguments)\n    end\n\n    class CommandUnavailable < VagrantError\n      error_key(:command_unavailable)\n    end\n\n    class CommandUnavailableWindows < CommandUnavailable\n      error_key(:command_unavailable_windows)\n    end\n\n    class CommunicatorNotFound < VagrantError\n      error_key(:communicator_not_found)\n    end\n\n    class ConfigInvalid < VagrantError\n      error_key(:config_invalid)\n    end\n\n    class ConfigUpgradeErrors < VagrantError\n      error_key(:config_upgrade_errors)\n    end\n\n    class CopyPrivateKeyFailed < VagrantError\n      error_key(:copy_private_key_failed)\n    end\n\n    class CorruptMachineIndex < VagrantError\n      error_key(:corrupt_machine_index)\n    end\n\n    class CreateIsoHostCapNotFound < VagrantError\n      error_key(:create_iso_host_cap_not_found)\n    end\n\n    class DarwinMountFailed < VagrantError\n      error_key(:darwin_mount_failed)\n    end\n\n    class DarwinVersionFailed < VagrantError\n      error_key(:darwin_version_failed)\n    end\n\n    class DestroyRequiresForce < VagrantError\n      error_key(:destroy_requires_force)\n    end\n\n    class DotfileUpgradeJSONError < VagrantError\n      error_key(:dotfile_upgrade_json_error)\n    end\n\n    class DownloadAlreadyInProgress < VagrantError\n      error_key(:download_already_in_progress_error)\n    end\n\n    class DownloaderError < VagrantError\n      error_key(:downloader_error)\n    end\n\n    class DownloaderInterrupted < DownloaderError\n      error_key(:downloader_interrupted)\n    end\n\n    class DownloaderChecksumError < VagrantError\n      error_key(:downloader_checksum_error)\n    end\n\n    class EnvInval < VagrantError\n      error_key(:env_inval)\n    end\n\n    class EnvironmentNonExistentCWD < VagrantError\n      error_key(:environment_non_existent_cwd)\n    end\n\n    class EnvironmentLockedError < VagrantError\n      error_key(:environment_locked)\n    end\n\n    class HomeDirectoryLaterVersion < VagrantError\n      error_key(:home_dir_later_version)\n    end\n\n    class HomeDirectoryNotAccessible < VagrantError\n      error_key(:home_dir_not_accessible)\n    end\n\n    class HomeDirectoryUnknownVersion < VagrantError\n      error_key(:home_dir_unknown_version)\n    end\n\n    class HypervVirtualBoxError < VagrantError\n      error_key(:hyperv_virtualbox_error)\n    end\n\n    class ForwardPortAdapterNotFound < VagrantError\n      error_key(:forward_port_adapter_not_found)\n    end\n\n    class ForwardPortAutolistEmpty < VagrantError\n      error_key(:auto_empty, \"vagrant.actions.vm.forward_ports\")\n    end\n\n    class ForwardPortHostIPNotFound < VagrantError\n      error_key(:host_ip_not_found, \"vagrant.actions.vm.forward_ports\")\n    end\n\n    class ForwardPortCollision < VagrantError\n      error_key(:collision_error, \"vagrant.actions.vm.forward_ports\")\n    end\n\n    class GuestCapabilityInvalid < VagrantError\n      error_key(:guest_capability_invalid)\n    end\n\n    class GuestCapabilityNotFound < VagrantError\n      error_key(:guest_capability_not_found)\n    end\n\n    class GuestExplicitNotDetected < VagrantError\n      error_key(:guest_explicit_not_detected)\n    end\n\n    class GuestNotDetected < VagrantError\n      error_key(:guest_not_detected)\n    end\n\n    class HostExplicitNotDetected < VagrantError\n      error_key(:host_explicit_not_detected)\n    end\n\n    class ISOBuildFailed < VagrantError\n      error_key(:iso_build_failed)\n    end\n\n    class LinuxMountFailed < VagrantError\n      error_key(:linux_mount_failed)\n    end\n\n    class LinuxRDPClientNotFound < VagrantError\n      error_key(:linux_rdp_client_not_found)\n    end\n\n    class LocalDataDirectoryNotAccessible < VagrantError\n      error_key(:local_data_dir_not_accessible)\n    end\n\n    class MachineActionLockedError < VagrantError\n      error_key(:machine_action_locked)\n    end\n\n    class MachineFolderNotAccessible < VagrantError\n      error_key(:machine_folder_not_accessible)\n    end\n\n    class MachineGuestNotReady < VagrantError\n      error_key(:machine_guest_not_ready)\n    end\n\n    class MachineLocked < VagrantError\n      error_key(:machine_locked)\n    end\n\n    class MachineNotFound < VagrantError\n      error_key(:machine_not_found)\n    end\n\n    class MachineStateInvalid < VagrantError\n      error_key(:machine_state_invalid)\n    end\n\n    class MultiVMTargetRequired < VagrantError\n      error_key(:multi_vm_target_required)\n    end\n\n    class NetplanNoAvailableRenderers < VagrantError\n      error_key(:netplan_no_available_renderers)\n    end\n\n    class NetSSHException < VagrantError\n      error_key(:net_ssh_exception)\n    end\n\n    class NetworkCollision < VagrantError\n      error_key(:collides, \"vagrant.actions.vm.host_only_network\")\n    end\n\n    class NetworkAddressInvalid < VagrantError\n      error_key(:network_address_invalid)\n    end\n\n    class NetworkDHCPAlreadyAttached < VagrantError\n      error_key(:dhcp_already_attached, \"vagrant.actions.vm.network\")\n    end\n\n    class NetworkNotFound < VagrantError\n      error_key(:not_found, \"vagrant.actions.vm.host_only_network\")\n    end\n\n    class NetworkTypeNotSupported < VagrantError\n      error_key(:network_type_not_supported)\n    end\n\n    class NetworkManagerNotInstalled < VagrantError\n      error_key(:network_manager_not_installed)\n    end\n\n    class NFSBadExports < VagrantError\n      error_key(:nfs_bad_exports)\n    end\n\n    class NFSDupePerms < VagrantError\n      error_key(:nfs_dupe_permissions)\n    end\n\n    class NFSExportsFailed < VagrantError\n      error_key(:nfs_exports_failed)\n    end\n\n    class NFSCantReadExports < VagrantError\n      error_key(:nfs_cant_read_exports)\n    end\n\n    class NFSMountFailed < VagrantError\n      error_key(:nfs_mount_failed)\n    end\n\n    class NFSNoGuestIP < VagrantError\n      error_key(:nfs_no_guest_ip)\n    end\n\n    class NFSNoHostIP < VagrantError\n      error_key(:nfs_no_host_ip)\n    end\n\n    class NFSNoHostonlyNetwork < VagrantError\n      error_key(:nfs_no_hostonly_network)\n    end\n\n    class NFSNoValidIds < VagrantError\n      error_key(:nfs_no_valid_ids)\n    end\n\n    class NFSNotSupported < VagrantError\n      error_key(:nfs_not_supported)\n    end\n\n    class NFSClientNotInstalledInGuest < VagrantError\n      error_key(:nfs_client_not_installed_in_guest)\n    end\n\n    class NoDefaultProvider < VagrantError\n      error_key(:no_default_provider)\n    end\n\n    class NoDefaultSyncedFolderImpl < VagrantError\n      error_key(:no_default_synced_folder_impl)\n    end\n\n    class NoEnvironmentError < VagrantError\n      error_key(:no_env)\n    end\n\n    class OscdimgCommandMissingError < VagrantError\n      error_key(:oscdimg_command_missing)\n    end\n\n    class PackageIncludeMissing < VagrantError\n      error_key(:include_file_missing, \"vagrant.actions.general.package\")\n    end\n\n    class PackageIncludeSymlink < VagrantError\n      error_key(:package_include_symlink)\n    end\n\n    class PackageOutputDirectory < VagrantError\n      error_key(:output_is_directory, \"vagrant.actions.general.package\")\n    end\n\n    class PackageOutputExists < VagrantError\n      error_key(:output_exists, \"vagrant.actions.general.package\")\n    end\n\n    class PackageRequiresDirectory < VagrantError\n      error_key(:requires_directory, \"vagrant.actions.general.package\")\n    end\n\n    class PackageInvalidInfo < VagrantError\n      error_key(:package_invalid_info)\n    end\n\n    class PowerShellNotFound < VagrantError\n      error_key(:powershell_not_found)\n    end\n\n    class PowerShellInvalidVersion < VagrantError\n      error_key(:powershell_invalid_version)\n    end\n\n    class PowerShellError < VagrantError\n      error_key(:powershell_error, \"vagrant_ps.errors.powershell_error\")\n    end\n\n    class ProviderCantInstall < VagrantError\n      error_key(:provider_cant_install)\n    end\n\n    class ProviderChecksumMismatch < VagrantError\n      error_key(:provider_checksum_mismatch)\n    end\n\n    class ProviderInstallFailed < VagrantError\n      error_key(:provider_install_failed)\n    end\n\n    class ProviderNotFound < VagrantError\n      error_key(:provider_not_found)\n    end\n\n    class ProviderNotFoundSuggestion < VagrantError\n      error_key(:provider_not_found_suggestion)\n    end\n\n    class ProviderNotUsable < VagrantError\n      error_key(:provider_not_usable)\n    end\n\n    class ProvisionerFlagInvalid < VagrantError\n      error_key(:provisioner_flag_invalid)\n    end\n\n    class ProvisionerWinRMUnsupported < VagrantError\n      error_key(:provisioner_winrm_unsupported)\n    end\n\n    class PluginNeedsDeveloperTools < VagrantError\n      error_key(:plugin_needs_developer_tools)\n    end\n\n    class PluginMissingLibrary < VagrantError\n      error_key(:plugin_missing_library)\n    end\n\n    class PluginMissingRubyDev < VagrantError\n      error_key(:plugin_missing_ruby_dev)\n    end\n\n    class PluginGemNotFound < VagrantError\n      error_key(:plugin_gem_not_found)\n    end\n\n    class PluginInstallLicenseNotFound < VagrantError\n      error_key(:plugin_install_license_not_found)\n    end\n\n    class PluginInstallFailed < VagrantError\n      error_key(:plugin_install_failed)\n    end\n\n    class PluginInstallSpace < VagrantError\n      error_key(:plugin_install_space)\n    end\n\n    class PluginInstallVersionConflict < VagrantError\n      error_key(:plugin_install_version_conflict)\n    end\n\n    class PluginLoadError < VagrantError\n      error_key(:plugin_load_error)\n    end\n\n    class PluginNotInstalled < VagrantError\n      error_key(:plugin_not_installed)\n    end\n\n    class PluginStateFileParseError < VagrantError\n      error_key(:plugin_state_file_not_parsable)\n    end\n\n    class PluginUninstallSystem < VagrantError\n      error_key(:plugin_uninstall_system)\n    end\n\n    class PluginInitError < VagrantError\n      error_key(:plugin_init_error)\n    end\n\n    class PluginSourceError < VagrantError\n      error_key(:plugin_source_error)\n    end\n\n    class PluginNoLocalError < VagrantError\n      error_key(:plugin_no_local_error)\n    end\n\n    class PluginMissingLocalError < VagrantError\n      error_key(:plugin_missing_local_error)\n    end\n\n    class PushesNotDefined < VagrantError\n      error_key(:pushes_not_defined)\n    end\n\n    class PushStrategyNotDefined < VagrantError\n      error_key(:push_strategy_not_defined)\n    end\n\n    class PushStrategyNotLoaded < VagrantError\n      error_key(:push_strategy_not_loaded)\n    end\n\n    class PushStrategyNotProvided < VagrantError\n      error_key(:push_strategy_not_provided)\n    end\n\n    class RSyncPostCommandError < VagrantError\n      error_key(:rsync_post_command_error)\n    end\n\n    class RSyncError < VagrantError\n      error_key(:rsync_error)\n    end\n\n    class RSyncNotFound < VagrantError\n      error_key(:rsync_not_found)\n    end\n\n    class RSyncNotInstalledInGuest < VagrantError\n      error_key(:rsync_not_installed_in_guest)\n    end\n\n    class RSyncGuestInstallError < VagrantError\n      error_key(:rsync_guest_install_error)\n    end\n\n    class SCPPermissionDenied < VagrantError\n      error_key(:scp_permission_denied)\n    end\n\n    class SCPUnavailable < VagrantError\n      error_key(:scp_unavailable)\n    end\n\n    class SharedFolderCreateFailed < VagrantError\n      error_key(:shared_folder_create_failed)\n    end\n\n    class ShellExpandFailed < VagrantError\n      error_key(:shell_expand_failed)\n    end\n\n    class SnapshotConflictFailed < VagrantError\n      error_key(:snapshot_force)\n    end\n\n    class SnapshotNotFound < VagrantError\n      error_key(:snapshot_not_found)\n    end\n\n    class SnapshotNotSupported < VagrantError\n      error_key(:snapshot_not_supported)\n    end\n\n    class SSHAuthenticationFailed < VagrantError\n      error_key(:ssh_authentication_failed)\n    end\n\n    class SSHChannelOpenFail < VagrantError\n      error_key(:ssh_channel_open_fail)\n    end\n\n    class SSHConnectEACCES < VagrantError\n      error_key(:ssh_connect_eacces)\n    end\n\n    class SSHConnectionRefused < VagrantError\n      error_key(:ssh_connection_refused)\n    end\n\n    class SSHConnectionAborted < VagrantError\n      error_key(:ssh_connection_aborted)\n    end\n\n    class SSHConnectionReset < VagrantError\n      error_key(:ssh_connection_reset)\n    end\n\n    class SSHConnectionTimeout < VagrantError\n      error_key(:ssh_connection_timeout)\n    end\n\n    class SSHDisconnected < VagrantError\n      error_key(:ssh_disconnected)\n    end\n\n    class SSHHostDown < VagrantError\n      error_key(:ssh_host_down)\n    end\n\n    class SSHInvalidShell< VagrantError\n      error_key(:ssh_invalid_shell)\n    end\n\n    class SSHInsertKeyUnsupported < VagrantError\n      error_key(:ssh_insert_key_unsupported)\n    end\n\n    class SSHIsPuttyLink < VagrantError\n      error_key(:ssh_is_putty_link)\n    end\n\n    class SSHKeyBadOwner < VagrantError\n      error_key(:ssh_key_bad_owner)\n    end\n\n    class SSHKeyBadPermissions < VagrantError\n      error_key(:ssh_key_bad_permissions)\n    end\n\n    class SSHKeyTypeNotSupported < VagrantError\n      error_key(:ssh_key_type_not_supported)\n    end\n\n    class SSHKeyTypeNotSupportedByServer < VagrantError\n      error_key(:ssh_key_type_not_supported_by_server)\n    end\n\n    class SSHNoExitStatus < VagrantError\n      error_key(:ssh_no_exit_status)\n    end\n\n    class SSHNoRoute < VagrantError\n      error_key(:ssh_no_route)\n    end\n\n    class SSHNotReady < VagrantError\n      error_key(:ssh_not_ready)\n    end\n\n    class SSHRunRequiresKeys < VagrantError\n      error_key(:ssh_run_requires_keys)\n    end\n\n    class SSHUnavailable < VagrantError\n      error_key(:ssh_unavailable)\n    end\n\n    class SSHUnavailableWindows < VagrantError\n      error_key(:ssh_unavailable_windows)\n    end\n\n    class SyncedFolderUnusable < VagrantError\n      error_key(:synced_folder_unusable)\n    end\n\n    class TriggersBadExitCodes < VagrantError\n      error_key(:triggers_bad_exit_codes)\n    end\n\n    class TriggersGuestNotExist < VagrantError\n      error_key(:triggers_guest_not_exist)\n    end\n\n    class TriggersGuestNotRunning < VagrantError\n      error_key(:triggers_guest_not_running)\n    end\n\n    class TriggersNoBlockGiven < VagrantError\n      error_key(:triggers_no_block_given)\n    end\n\n    class TriggersNoStageGiven < VagrantError\n      error_key(:triggers_no_stage_given)\n    end\n\n    class UIExpectsTTY < VagrantError\n      error_key(:ui_expects_tty)\n    end\n\n    class UnimplementedProviderAction < VagrantError\n      error_key(:unimplemented_provider_action)\n    end\n\n    class UploadInvalidCompressionType < VagrantError\n      error_key(:upload_invalid_compression_type)\n    end\n\n    class UploadMissingExtractCapability < VagrantError\n      error_key(:upload_missing_extract_capability)\n    end\n\n    class UploadMissingTempCapability < VagrantError\n      error_key(:upload_missing_temp_capability)\n    end\n\n    class UploadSourceMissing < VagrantError\n      error_key(:upload_source_missing)\n    end\n\n    class UploaderError < VagrantError\n      error_key(:uploader_error)\n    end\n\n    class UploaderInterrupted < UploaderError\n      error_key(:uploader_interrupted)\n    end\n\n    class VagrantLocked < VagrantError\n      error_key(:vagrant_locked)\n    end\n\n    class VagrantInterrupt < VagrantError\n      error_key(:interrupted)\n    end\n\n    class VagrantfileExistsError < VagrantError\n      error_key(:vagrantfile_exists)\n    end\n\n    class VagrantfileLoadError < VagrantError\n      error_key(:vagrantfile_load_error)\n    end\n\n    class VagrantfileNameError < VagrantError\n      error_key(:vagrantfile_name_error)\n    end\n\n    class VagrantfileSyntaxError < VagrantError\n      error_key(:vagrantfile_syntax_error)\n    end\n\n    class VagrantfileTemplateNotFoundError < VagrantError\n      error_key(:vagrantfile_template_not_found_error)\n    end\n\n    class VagrantfileWriteError < VagrantError\n      error_key(:vagrantfile_write_error)\n    end\n\n    class VagrantVersionBad < VagrantError\n      error_key(:vagrant_version_bad)\n    end\n\n    class VBoxManageError < VagrantError\n      error_key(:vboxmanage_error)\n    end\n\n    class VBoxManageLaunchError < VagrantError\n      error_key(:vboxmanage_launch_error)\n    end\n\n    class VBoxManageNotFoundError < VagrantError\n      error_key(:vboxmanage_not_found_error)\n    end\n\n    class VirtualBoxBrokenVersion040214 < VagrantError\n      error_key(:virtualbox_broken_version_040214)\n    end\n\n    class VirtualBoxConfigNotFound < VagrantError\n      error_key(:virtualbox_config_not_found)\n    end\n\n    class VirtualBoxDisksDefinedExceedLimit < VagrantError\n      error_key(:virtualbox_disks_defined_exceed_limit)\n    end\n\n    class VirtualBoxDisksControllerNotFound < VagrantError\n      error_key(:virtualbox_disks_controller_not_found)\n    end\n\n    class VirtualBoxDisksNoSupportedControllers < VagrantError\n      error_key(:virtualbox_disks_no_supported_controllers)\n    end\n\n    class VirtualBoxDisksPrimaryNotFound < VagrantError\n      error_key(:virtualbox_disks_primary_not_found)\n    end\n\n    class VirtualBoxDisksUnsupportedController < VagrantError\n      error_key(:virtualbox_disks_unsupported_controller)\n    end\n\n    class VirtualBoxGuestPropertyNotFound < VagrantError\n      error_key(:virtualbox_guest_property_not_found)\n    end\n\n    class VirtualBoxInvalidVersion < VagrantError\n      error_key(:virtualbox_invalid_version)\n    end\n\n    class VirtualBoxNoRoomForHighLevelNetwork < VagrantError\n      error_key(:virtualbox_no_room_for_high_level_network)\n    end\n\n    class VirtualBoxNotDetected < VagrantError\n      error_key(:virtualbox_not_detected)\n    end\n\n    class VirtualBoxKernelModuleNotLoaded < VagrantError\n      error_key(:virtualbox_kernel_module_not_loaded)\n    end\n\n    class VirtualBoxInstallIncomplete < VagrantError\n      error_key(:virtualbox_install_incomplete)\n    end\n\n    class VirtualBoxMachineFolderNotFound < VagrantError\n      error_key(:virtualbox_machine_folder_not_found)\n    end\n\n    class VirtualBoxNoName < VagrantError\n      error_key(:virtualbox_no_name)\n    end\n\n    class VirtualBoxMountFailed < VagrantError\n      error_key(:virtualbox_mount_failed)\n    end\n\n    class VirtualBoxMountNotSupportedBSD < VagrantError\n      error_key(:virtualbox_mount_not_supported_bsd)\n    end\n\n    class VirtualBoxNameExists < VagrantError\n      error_key(:virtualbox_name_exists)\n    end\n\n    class VirtualBoxUserMismatch < VagrantError\n      error_key(:virtualbox_user_mismatch)\n    end\n\n    class VirtualBoxVersionEmpty < VagrantError\n      error_key(:virtualbox_version_empty)\n    end\n\n    class VirtualBoxInvalidHostSubnet < VagrantError\n      error_key(:virtualbox_invalid_host_subnet)\n    end\n\n    class VMBaseMacNotSpecified < VagrantError\n      error_key(:no_base_mac, \"vagrant.actions.vm.match_mac\")\n    end\n\n    class VMBootBadState < VagrantError\n      error_key(:boot_bad_state)\n    end\n\n    class VMBootTimeout < VagrantError\n      error_key(:boot_timeout)\n    end\n\n    class VMCloneFailure < VagrantError\n      error_key(:failure, \"vagrant.actions.vm.clone\")\n    end\n\n    class VMCreateMasterFailure < VagrantError\n      error_key(:failure, \"vagrant.actions.vm.clone.create_master\")\n    end\n\n    class VMCustomizationFailed < VagrantError\n      error_key(:failure, \"vagrant.actions.vm.customize\")\n    end\n\n    class VMImportFailure < VagrantError\n      error_key(:failure, \"vagrant.actions.vm.import\")\n    end\n\n    class VMInaccessible < VagrantError\n      error_key(:vm_inaccessible)\n    end\n\n    class VMNameExists < VagrantError\n      error_key(:vm_name_exists)\n    end\n\n    class VMNoMatchError < VagrantError\n      error_key(:vm_no_match)\n    end\n\n    class VMNotCreatedError < VagrantError\n      error_key(:vm_creation_required)\n    end\n\n    class VMNotFoundError < VagrantError\n      error_key(:vm_not_found)\n    end\n\n    class VMNotRunningError < VagrantError\n      error_key(:vm_not_running)\n    end\n\n    class VMPowerOffToPackage < VagrantError\n      error_key(:power_off, \"vagrant.actions.vm.export\")\n    end\n\n    class WinRMInvalidCommunicator < VagrantError\n      error_key(:winrm_invalid_communicator)\n    end\n\n    class WSLVagrantVersionMismatch < VagrantError\n      error_key(:wsl_vagrant_version_mismatch)\n    end\n\n    class WSLVagrantAccessError < VagrantError\n      error_key(:wsl_vagrant_access_error)\n    end\n\n    class WSLVirtualBoxWindowsAccessError < VagrantError\n      error_key(:wsl_virtualbox_windows_access)\n    end\n\n    class WSLRootFsNotFoundError < VagrantError\n      error_key(:wsl_rootfs_not_found_error)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"vagrant/capability_host\"\n\nmodule Vagrant\n  # This class handles guest-OS specific interactions with a machine.\n  # It is primarily responsible for detecting the proper guest OS\n  # implementation and then delegating capabilities.\n  #\n  # Vagrant has many tasks which require specific guest OS knowledge.\n  # These are implemented using a guest/capability system. Various plugins\n  # register as \"guests\" which determine the underlying OS of the system.\n  # Then, \"guest capabilities\" register themselves for a specific OS (one\n  # or more), and these capabilities are called.\n  #\n  # Example capabilities might be \"mount_virtualbox_shared_folder\" or\n  # \"configure_networks\".\n  #\n  # This system allows for maximum flexibility and pluginability for doing\n  # guest OS specific operations.\n  class Guest\n    include CapabilityHost\n\n    def initialize(machine, guests, capabilities)\n      @capabilities = capabilities\n      @guests       = guests\n      @machine      = machine\n    end\n\n    # This will detect the proper guest OS for the machine and set up\n    # the class to actually execute capabilities.\n    def detect!\n      guest_name = @machine.config.vm.guest\n      initialize_capabilities!(guest_name, @guests, @capabilities, @machine)\n    rescue Errors::CapabilityHostExplicitNotDetected => e\n      raise Errors::GuestExplicitNotDetected, value: e.extra_data[:value]\n    rescue Errors::CapabilityHostNotDetected\n      raise Errors::GuestNotDetected\n    end\n\n    # See {CapabilityHost#capability}\n    def capability(*args)\n      super\n    rescue Errors::CapabilityNotFound => e\n      raise Errors::GuestCapabilityNotFound,\n        cap: e.extra_data[:cap],\n        guest: name\n    rescue Errors::CapabilityInvalid => e\n      raise Errors::GuestCapabilityInvalid,\n        cap: e.extra_data[:cap],\n        guest: name\n    end\n\n    # Returns the specified or detected guest type name.\n    #\n    # @return [Symbol]\n    def name\n      capability_host_chain[0][0]\n    end\n\n    # This returns whether the guest is ready to work. If this returns\n    # `false`, then {#detect!} should be called in order to detect the\n    # guest OS.\n    #\n    # @return [Boolean]\n    def ready?\n      !!capability_host_chain\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/capability_host\"\n\nmodule Vagrant\n  # This class handles host-OS specific interactions. It is responsible for\n  # detecting the proper host OS implementation and delegating capabilities\n  # to plugins.\n  #\n  # See {Guest} for more information on capabilities.\n  class Host\n    include CapabilityHost\n\n    def initialize(host, hosts, capabilities, env)\n      initialize_capabilities!(host, hosts, capabilities, env)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/machine.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"./util/ssh\"\nrequire_relative \"./action/builtin/mixin_synced_folders\"\n\nrequire \"digest/md5\"\nrequire \"thread\"\nrequire \"log4r\"\n\nmodule Vagrant\n  # This represents a machine that Vagrant manages. This provides a singular\n  # API for querying the state and making state changes to the machine, which\n  # is backed by any sort of provider (VirtualBox, VMware, etc.).\n  class Machine\n    extend Vagrant::Action::Builtin::MixinSyncedFolders\n\n    # The box that is backing this machine.\n    #\n    # @return [Box]\n    attr_accessor :box\n\n    # Configuration for the machine.\n    #\n    # @return [Object]\n    attr_accessor :config\n\n    # Directory where machine-specific data can be stored.\n    #\n    # @return [Pathname]\n    attr_reader :data_dir\n\n    # The environment that this machine is a part of.\n    #\n    # @return [Environment]\n    attr_reader :env\n\n    # ID of the machine. This ID comes from the provider and is not\n    # guaranteed to be of any particular format except that it is\n    # a string.\n    #\n    # @return [String]\n    attr_reader :id\n\n    # Name of the machine. This is assigned by the Vagrantfile.\n    #\n    # @return [Symbol]\n    attr_reader :name\n\n    # The provider backing this machine.\n    #\n    # @return [Object]\n    attr_reader :provider\n\n    # The provider-specific configuration for this machine.\n    #\n    # @return [Object]\n    attr_accessor :provider_config\n\n    # The name of the provider.\n    #\n    # @return [Symbol]\n    attr_reader :provider_name\n\n    # The options given to the provider when registering the plugin.\n    #\n    # @return [Hash]\n    attr_reader :provider_options\n\n    # The triggers with machine specific configuration applied\n    #\n    # @return [Vagrant::Plugin::V2::Trigger]\n    attr_reader :triggers\n\n    # The UI for outputting in the scope of this machine.\n    #\n    # @return [UI]\n    attr_reader :ui\n\n    # The Vagrantfile that this machine is attached to.\n    #\n    # @return [Vagrantfile]\n    attr_reader :vagrantfile\n\n    # Initialize a new machine.\n    #\n    # @param [String] name Name of the virtual machine.\n    # @param [Class] provider The provider backing this machine. This is\n    #   currently expected to be a V1 `provider` plugin.\n    # @param [Object] provider_config The provider-specific configuration for\n    #   this machine.\n    # @param [Hash] provider_options The provider-specific options from the\n    #   plugin definition.\n    # @param [Object] config The configuration for this machine.\n    # @param [Pathname] data_dir The directory where machine-specific data\n    #   can be stored. This directory is ensured to exist.\n    # @param [Box] box The box that is backing this virtual machine.\n    # @param [Environment] env The environment that this machine is a\n    #   part of.\n    def initialize(name, provider_name, provider_cls, provider_config, provider_options, config, data_dir, box, env, vagrantfile, base=false)\n      @logger = Log4r::Logger.new(\"vagrant::machine\")\n      @logger.info(\"Initializing machine: #{name}\")\n      @logger.info(\"  - Provider: #{provider_cls}\")\n      @logger.info(\"  - Box: #{box}\")\n      @logger.info(\"  - Data dir: #{data_dir}\")\n\n      @box             = box\n      @config          = config\n      @data_dir        = data_dir\n      @env             = env\n      @vagrantfile     = vagrantfile\n      @guest           = Guest.new(\n        self,\n        Vagrant.plugin(\"2\").manager.guests,\n        Vagrant.plugin(\"2\").manager.guest_capabilities)\n      @name            = name\n      @provider_config = provider_config\n      @provider_name   = provider_name\n      @provider_options = provider_options\n      @ui              = Vagrant::UI::Prefixed.new(@env.ui, @name)\n      @ui_mutex        = Mutex.new\n      @state_mutex     = Mutex.new\n      @triggers        = Vagrant::Plugin::V2::Trigger.new(@env, @config.trigger, self, @ui)\n\n      # Read the ID, which is usually in local storage\n      @id = nil\n\n      # XXX: This is temporary. This will be removed very soon.\n      if base\n        @id = name\n\n        # For base setups, we don't want to insert the key\n        @config.ssh.insert_key = false\n      else\n        reload\n      end\n\n      # Keep track of where our UUID should be placed\n      @index_uuid_file = nil\n      @index_uuid_file = @data_dir.join(\"index_uuid\") if @data_dir\n\n      # Initializes the provider last so that it has access to all the\n      # state we setup on this machine.\n      @provider = provider_cls.new(self)\n      @provider._initialize(@provider_name, self)\n\n      # If we're using WinRM, we eager load the plugin because of\n      # GH-3390\n      if @config.vm.communicator == :winrm\n        @logger.debug(\"Eager loading WinRM communicator to avoid GH-3390\")\n        communicate\n      end\n\n      # If the ID is the special not created ID, then set our ID to\n      # nil so that we destroy all our data.\n      if state.id == MachineState::NOT_CREATED_ID\n        self.id = nil\n      end\n\n      # Output a bunch of information about this machine in\n      # machine-readable format in case someone is listening.\n      @ui.machine(\"metadata\", \"provider\", provider_name)\n    end\n\n    # This calls an action on the provider. The provider may or may not\n    # actually implement the action.\n    #\n    # @param [Symbol] name Name of the action to run.\n    # @param [Hash] extra_env This data will be passed into the action runner\n    #   as extra data set on the environment hash for the middleware\n    #   runner.\n    def action(name, opts=nil)\n      @logger.info(\"Calling action: #{name} on provider #{@provider}\")\n\n      opts ||= {}\n\n      # Determine whether we lock or not\n      lock = true\n      lock = opts.delete(:lock) if opts.key?(:lock)\n\n      # Extra env keys are the remaining opts\n      extra_env = opts.dup\n\n      check_cwd # Warns the UI if the machine was last used on a different dir\n\n      # Create a deterministic ID for this machine\n      vf = nil\n      vf = @env.vagrantfile_name[0] if @env.vagrantfile_name\n      id = Digest::MD5.hexdigest(\n        \"#{@env.root_path}#{vf}#{@env.local_data_path}#{@name}\")\n\n      # We only lock if we're not executing an SSH action. In the future\n      # we will want to do more fine-grained unlocking in actions themselves\n      # but for a 1.6.2 release this will work.\n      locker = Proc.new { |*args, &block| block.call }\n      locker = @env.method(:lock) if lock && !name.to_s.start_with?(\"ssh\")\n\n      # Lock this machine for the duration of this action\n      return_env = locker.call(\"machine-action-#{id}\") do\n        # Get the callable from the provider.\n        callable = @provider.action(name)\n\n        # If this action doesn't exist on the provider, then an exception\n        # must be raised.\n        if callable.nil?\n          raise Errors::UnimplementedProviderAction,\n            action: name,\n            provider: @provider.to_s\n        end\n\n        # Call the action\n        ui.machine(\"action\", name.to_s, \"start\")\n        action_result = action_raw(name, callable, extra_env)\n        ui.machine(\"action\", name.to_s, \"end\")\n        action_result\n      end\n      # preserve returning environment after machine action runs\n      return return_env\n    rescue Errors::EnvironmentLockedError\n      raise Errors::MachineActionLockedError,\n        action: name,\n        name: @name\n    end\n\n    # This calls a raw callable in the proper context of the machine using\n    # the middleware stack.\n    #\n    # @param [Symbol] name Name of the action\n    # @param [Proc] callable\n    # @param [Hash] extra_env Extra env for the action env.\n    # @return [Hash] The resulting env\n    def action_raw(name, callable, extra_env={})\n      if !extra_env.is_a?(Hash)\n        extra_env = {}\n      end\n\n      # Run the action with the action runner on the environment\n      env = {ui: @ui}.merge(extra_env).merge(\n        raw_action_name: name,\n        action_name: \"machine_action_#{name}\".to_sym,\n        machine: self,\n        machine_action: name\n      )\n      @env.action_runner.run(callable, env)\n    end\n\n    # Returns a communication object for executing commands on the remote\n    # machine. Note that the _exact_ semantics of this are up to the\n    # communication provider itself. Despite this, the semantics are expected\n    # to be consistent across operating systems. For example, all linux-based\n    # systems should have similar communication (usually a shell). All\n    # Windows systems should have similar communication as well. Therefore,\n    # prior to communicating with the machine, users of this method are\n    # expected to check the guest OS to determine their behavior.\n    #\n    # This method will _always_ return some valid communication object.\n    # The `ready?` API can be used on the object to check if communication\n    # is actually ready.\n    #\n    # @return [Object]\n    def communicate\n      if !@communicator\n        requested  = @config.vm.communicator\n        requested ||= :ssh\n        klass = Vagrant.plugin(\"2\").manager.communicators[requested]\n        raise Errors::CommunicatorNotFound, comm: requested.to_s if !klass\n        @communicator = klass.new(self)\n      end\n\n      @communicator\n    end\n\n    # Returns a guest implementation for this machine. The guest implementation\n    # knows how to do guest-OS specific tasks, such as configuring networks,\n    # mounting folders, etc.\n    #\n    # @return [Guest]\n    def guest\n      raise Errors::MachineGuestNotReady if !communicate.ready?\n      @guest.detect! if !@guest.ready?\n      @guest\n    end\n\n    # This sets the unique ID associated with this machine. This will\n    # persist this ID so that in the future Vagrant will be able to find\n    # this machine again. The unique ID must be absolutely unique to the\n    # virtual machine, and can be used by providers for finding the\n    # actual machine associated with this instance.\n    #\n    # **WARNING:** Only providers should ever use this method.\n    #\n    # @param [String] value The ID.\n    def id=(value)\n      @logger.info(\"New machine ID: #{value.inspect}\")\n\n      id_file = nil\n      if @data_dir\n        # The file that will store the id if we have one. This allows the\n        # ID to persist across Vagrant runs. Also, store the UUID for the\n        # machine index.\n        id_file = @data_dir.join(\"id\")\n      end\n\n      if value\n        if id_file\n          # Write the \"id\" file with the id given.\n          id_file.open(\"w+\") do |f|\n            f.write(value)\n          end\n        end\n\n        if uid_file\n          # Write the user id that created this machine\n          uid_file.open(\"w+\") do |f|\n            f.write(Process.uid.to_s)\n          end\n        end\n\n        # If we don't have a UUID, then create one\n        if index_uuid.nil?\n          # Create the index entry and save it\n          entry = MachineIndex::Entry.new\n          entry.local_data_path = @env.local_data_path\n          entry.name = @name.to_s\n          entry.provider = @provider_name.to_s\n          entry.architecture = @architecture\n          entry.state = \"preparing\"\n          entry.vagrantfile_path = @env.root_path\n          entry.vagrantfile_name = @env.vagrantfile_name\n\n          if @box\n            entry.extra_data[\"box\"] = {\n              \"name\"     => @box.name,\n              \"provider\" => @box.provider.to_s,\n              \"architecture\" => @box.architecture,\n              \"version\"  => @box.version.to_s,\n            }\n          end\n\n          entry = @env.machine_index.set(entry)\n          @env.machine_index.release(entry)\n\n          # Store our UUID so we can access it later\n          if @index_uuid_file\n            @index_uuid_file.open(\"w+\") do |f|\n              f.write(entry.id)\n            end\n          end\n        end\n      else\n        @logger.debug(\"machine ID has been unset, deregistering machine and removing data directory\")\n        # Delete the file, since the machine is now destroyed\n        id_file.delete if id_file && id_file.file?\n        uid_file.delete if uid_file && uid_file.file?\n\n        # If we have a UUID associated with the index, remove it\n        uuid = index_uuid\n        if uuid\n          entry = @env.machine_index.get(uuid)\n          @env.machine_index.delete(entry) if entry\n        end\n\n        if @data_dir\n          # Delete the entire data directory contents since all state\n          # associated with the VM is now gone.\n          @data_dir.children.each do |child|\n            begin\n              child.rmtree\n            rescue Errno::EACCES\n              @logger.info(\"EACCESS deleting file: #{child}\")\n            end\n          end\n        end\n      end\n\n      # Store the ID locally\n      @id = value.nil? ? nil : value.to_s\n\n      # Notify the provider that the ID changed in case it needs to do\n      # any accounting from it.\n      @provider.machine_id_changed\n    end\n\n    # Returns the UUID associated with this machine in the machine\n    # index. We only have a UUID if an ID has been set.\n    #\n    # @return [String] UUID or nil if we don't have one yet.\n    def index_uuid\n      return nil if !@index_uuid_file\n      return @index_uuid_file.read.chomp if @index_uuid_file.file?\n      return nil\n    end\n\n    # This returns a clean inspect value so that printing the value via\n    # a pretty print (`p`) results in a readable value.\n    #\n    # @return [String]\n    def inspect\n      \"#<#{self.class}: #{@name} (#{@provider.class})>\"\n    end\n\n    # This reloads the ID of the underlying machine.\n    def reload\n      old_id = @id\n      @id = nil\n\n      if @data_dir\n        # Read the id file from the data directory if it exists as the\n        # ID for the pre-existing physical representation of this machine.\n        id_file = @data_dir.join(\"id\")\n        id_content = id_file.read.strip if id_file.file?\n        if !id_content.to_s.empty?\n          @id = id_content\n        end\n      end\n\n      if @id != old_id && @provider\n        # It changed, notify the provider\n        @provider.machine_id_changed\n      end\n\n      @id\n    end\n\n    # This returns the SSH info for accessing this machine. This SSH info\n    # is queried from the underlying provider. This method returns `nil` if\n    # the machine is not ready for SSH communication.\n    #\n    # The structure of the resulting hash is guaranteed to contain the\n    # following structure, although it may return other keys as well\n    # not documented here:\n    #\n    #     {\n    #       host: \"1.2.3.4\",\n    #       port: \"22\",\n    #       username: \"mitchellh\",\n    #       private_key_path: \"/path/to/my/key\"\n    #     }\n    #\n    # Note that Vagrant makes no guarantee that this info works or is\n    # correct. This is simply the data that the provider gives us or that\n    # is configured via a Vagrantfile. It is still possible after this\n    # point when attempting to connect via SSH to get authentication\n    # errors.\n    #\n    # @return [Hash] SSH information.\n    def ssh_info\n      # First, ask the provider for their information. If the provider\n      # returns nil, then the machine is simply not ready for SSH, and\n      # we return nil as well.\n      info = @provider.ssh_info\n      return nil if info.nil?\n\n      # Delete out the nil entries.\n      info.dup.each do |key, value|\n        info.delete(key) if value.nil?\n      end\n\n      # We set the defaults\n      info[:host] ||= @config.ssh.default.host\n      info[:port] ||= @config.ssh.default.port\n      info[:private_key_path] ||= @config.ssh.default.private_key_path\n      info[:keys_only] ||= @config.ssh.default.keys_only\n      info[:verify_host_key] ||= @config.ssh.default.verify_host_key\n      info[:username] ||= @config.ssh.default.username\n      info[:remote_user] ||= @config.ssh.default.remote_user\n      info[:compression] ||= @config.ssh.default.compression\n      info[:dsa_authentication] ||= @config.ssh.default.dsa_authentication\n      info[:extra_args] ||= @config.ssh.default.extra_args\n      info[:config] ||= @config.ssh.default.config\n\n      # We set overrides if they are set. These take precedence over\n      # provider-returned data.\n      info[:host] = @config.ssh.host if @config.ssh.host\n      info[:port] = @config.ssh.port if @config.ssh.port\n      info[:keys_only] = @config.ssh.keys_only\n      info[:verify_host_key] = @config.ssh.verify_host_key\n      info[:compression] = @config.ssh.compression\n      info[:dsa_authentication] = @config.ssh.dsa_authentication\n      info[:username] = @config.ssh.username if @config.ssh.username\n      info[:password] = @config.ssh.password if @config.ssh.password\n      info[:remote_user] = @config.ssh.remote_user if @config.ssh.remote_user\n      info[:extra_args] = @config.ssh.extra_args if @config.ssh.extra_args\n      info[:config] = @config.ssh.config if @config.ssh.config\n\n      # We also set some fields that are purely controlled by Vagrant\n      info[:forward_agent] = @config.ssh.forward_agent\n      info[:forward_x11] = @config.ssh.forward_x11\n      info[:forward_env] = @config.ssh.forward_env\n      info[:connect_timeout] = @config.ssh.connect_timeout\n      info[:connect_retries] = @config.ssh.connect_retries\n      info[:connect_retry_delay] = @config.ssh.connect_retry_delay\n\n      info[:ssh_command] = @config.ssh.ssh_command if @config.ssh.ssh_command\n\n      # Add in provided proxy command config\n      info[:proxy_command] = @config.ssh.proxy_command if @config.ssh.proxy_command\n\n      # Set the private key path. If a specific private key is given in\n      # the Vagrantfile we set that. Otherwise, we use the default (insecure)\n      # private key, but only if the provider didn't give us one.\n      if !info[:private_key_path] && !info[:password]\n        if @config.ssh.private_key_path\n          info[:private_key_path] = @config.ssh.private_key_path\n        else\n          info[:private_key_path] = @env.default_private_key_paths\n        end\n      end\n\n      # If we have a private key in our data dir, then use that\n      if @data_dir && !@config.ssh.private_key_path\n        data_private_key = @data_dir.join(\"private_key\")\n        if data_private_key.file?\n          info[:private_key_path] = [data_private_key.to_s]\n        end\n      end\n\n      # Setup the keys\n      info[:private_key_path] ||= []\n      info[:private_key_path] = Array(info[:private_key_path])\n\n      # Expand the private key path relative to the root path\n      info[:private_key_path].map! do |path|\n        File.expand_path(path, @env.root_path)\n      end\n\n      # Check that the private key permissions are valid\n      info[:private_key_path].each do |path|\n        key_path = Pathname.new(path)\n        if key_path.exist?\n          Vagrant::Util::SSH.check_key_permissions(key_path)\n        end\n      end\n\n      # Return the final compiled SSH info data\n      info\n    end\n\n    # Returns the state of this machine. The state is queried from the\n    # backing provider, so it can be any arbitrary symbol.\n    #\n    # @return [MachineState]\n    def state\n      result = @provider.state\n      raise Errors::MachineStateInvalid if !result.is_a?(MachineState)\n\n      # Update our state cache if we have a UUID and an entry in the\n      # master index.\n      uuid = index_uuid\n      if uuid\n        # active_machines provides access to query this info on each machine\n        # from a different thread, ensure multiple machines do not access\n        # the locked entry simultaneously as this triggers a locked machine\n        # exception.\n        @state_mutex.synchronize do\n          entry = @env.machine_index.get(uuid)\n          if entry\n            entry.state = result.short_description\n            @env.machine_index.set(entry)\n            @env.machine_index.release(entry)\n          end\n        end\n      end\n\n      result\n    end\n\n    # Returns the state of this machine. The state is queried from the\n    # backing provider, so it can be any arbitrary symbol.\n    #\n    # @param [Symbol] state of machine\n    # @return [Entry] entry of recovered machine\n    def recover_machine(state)\n      entry = @env.machine_index.get(index_uuid)\n      if entry\n        @env.machine_index.release(entry)\n        return entry\n      end\n\n      entry = MachineIndex::Entry.new(id=index_uuid, {})\n      entry.local_data_path = @env.local_data_path\n      entry.name = @name.to_s\n      entry.provider = @provider_name.to_s\n      entry.state = state\n      entry.vagrantfile_path = @env.root_path\n      entry.vagrantfile_name = @env.vagrantfile_name\n\n      if @box\n        entry.extra_data[\"box\"] = {\n          \"name\"     => @box.name,\n          \"provider\" => @box.provider.to_s,\n          \"architecture\" => @box.architecture,\n          \"version\"  => @box.version.to_s,\n        }\n      end\n\n      @state_mutex.synchronize do\n        entry = @env.machine_index.recover(entry)\n        @env.machine_index.release(entry)\n      end\n      return entry\n    end\n\n    # Returns the user ID that created this machine. This is specific to\n    # the host machine that this was created on.\n    #\n    # @return [String]\n    def uid\n      path = uid_file\n      return nil if !path\n      return nil if !path.file?\n      return uid_file.read.chomp\n    end\n\n    # Temporarily changes the machine UI. This is useful if you want\n    # to execute an {#action} with a different UI.\n    def with_ui(ui)\n      @ui_mutex.synchronize do\n        begin\n          old_ui = @ui\n          @ui    = ui\n          yield\n        ensure\n          @ui = old_ui\n        end\n      end\n    end\n\n    # This returns the set of shared folders that should be done for\n    # this machine. It returns the folders in a hash keyed by the\n    # implementation class for the synced folders.\n    #\n    # @return [Hash<Symbol, Hash<String, Hash>>]\n    def synced_folders\n      self.class.synced_folders(self)\n    end\n\n    protected\n\n    # Returns the path to the file that stores the UID.\n    def uid_file\n      return nil if !@data_dir\n      @data_dir.join(\"creator_uid\")\n    end\n\n    # Checks the current directory for a given machine\n    # and displays a warning if that machine has moved\n    # from its previous location on disk. If the machine\n    # has moved, it prints a warning to the user.\n    def check_cwd\n      desired_encoding = @env.root_path.to_s.encoding\n      vagrant_cwd_filepath = @data_dir.join('vagrant_cwd')\n      vagrant_cwd = if File.exist?(vagrant_cwd_filepath)\n                      File.read(vagrant_cwd_filepath,\n                        external_encoding: desired_encoding\n                      ).chomp\n                    end\n\n      if !File.identical?(vagrant_cwd.to_s, @env.root_path.to_s)\n        if vagrant_cwd\n          ui.warn(I18n.t(\n            'vagrant.moved_cwd',\n            old_wd:     \"#{vagrant_cwd}\",\n            current_wd: \"#{@env.root_path.to_s}\"))\n        end\n        File.write(vagrant_cwd_filepath, @env.root_path.to_s,\n          external_encoding: desired_encoding\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/machine_index.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\nrequire \"pathname\"\nrequire \"securerandom\"\nrequire \"thread\"\n\nrequire \"vagrant/util/silence_warnings\"\n\nmodule Vagrant\n  # MachineIndex is able to manage the index of created Vagrant environments\n  # in a central location.\n  #\n  # The MachineIndex stores a mapping of UUIDs to basic information about\n  # a machine. The UUIDs are stored with the Vagrant environment and are\n  # looked up in the machine index.\n  #\n  # The MachineIndex stores information such as the name of a machine,\n  # the directory it was last seen at, its last known state, etc. Using\n  # this information, we can load the entire {Machine} object for a machine,\n  # or we can just display metadata if needed.\n  #\n  # The internal format of the data file is currently JSON in the following\n  # structure:\n  #\n  #   {\n  #     \"version\": 1,\n  #     \"machines\": {\n  #       \"uuid\": {\n  #         \"name\": \"foo\",\n  #         \"provider\": \"vmware_fusion\",\n  #         \"architecture\": \"amd64\",\n  #         \"data_path\": \"/path/to/data/dir\",\n  #         \"vagrantfile_path\": \"/path/to/Vagrantfile\",\n  #         \"state\": \"running\",\n  #         \"updated_at\": \"2014-03-02 11:11:44 +0100\"\n  #       }\n  #     }\n  #   }\n  #\n  class MachineIndex\n\n    include Enumerable\n\n    # Initializes a MachineIndex at the given file location.\n    #\n    # @param [Pathname] data_dir Path to the directory where data for the\n    #   index can be stored. This folder should exist and must be writable.\n    def initialize(data_dir)\n      @data_dir   = data_dir\n      @index_file = data_dir.join(\"index\")\n      @lock       = Monitor.new\n      @machines  = {}\n      @machine_locks = {}\n\n      with_index_lock do\n        unlocked_reload\n      end\n    end\n\n    # Deletes a machine by UUID.\n    #\n    # The machine being deleted with this UUID must either be locked\n    # by this index or must be unlocked.\n    #\n    # @param [Entry] entry The entry to delete.\n    # @return [Boolean] true if delete is successful\n    def delete(entry)\n      return true if !entry.id\n\n      @lock.synchronize do\n        with_index_lock do\n          return true if !@machines[entry.id]\n\n          # If we don't have the lock, then we need to acquire it.\n          if !@machine_locks[entry.id]\n            raise \"Unlocked delete on machine: #{entry.id}\"\n          end\n\n          # Reload so we have the latest data, then delete and save\n          unlocked_reload\n          @machines.delete(entry.id)\n          unlocked_save\n\n          # Release access on this machine\n          unlocked_release(entry.id)\n        end\n      end\n\n      true\n    end\n\n    # Iterate over every machine in the index. The yielded {Entry} objects\n    # will NOT be locked, so you'll have to call {#get} manually to acquire\n    # the lock on them.\n    def each(reload=false)\n      if reload\n        @lock.synchronize do\n          with_index_lock do\n            unlocked_reload\n          end\n        end\n      end\n\n      @machines.each do |uuid, data|\n        yield Entry.new(uuid, data.merge(\"id\" => uuid))\n      end\n    end\n\n    # Accesses a machine by UUID and returns a {MachineIndex::Entry}\n    #\n    # The entry returned is locked and can't be read again or updated by\n    # this process or any other. To unlock the machine, call {#release}\n    # with the entry.\n    #\n    # You can only {#set} an entry (update) when the lock is held.\n    #\n    # @param [String] uuid UUID for the machine to access.\n    # @return [MachineIndex::Entry]\n    def get(uuid)\n      entry = nil\n\n      @lock.synchronize do\n        with_index_lock do\n          # Reload the data\n          unlocked_reload\n\n          data = find_by_prefix(uuid)\n          return nil if !data\n          uuid = data[\"id\"]\n\n          entry = Entry.new(uuid, data)\n\n          # Lock this machine\n          lock_file = lock_machine(uuid)\n          if !lock_file\n            raise Errors::MachineLocked,\n              name: entry.name,\n              provider: entry.provider\n          end\n\n          @machine_locks[uuid] = lock_file\n        end\n      end\n\n      entry\n    end\n\n    # Tests if the index has the given UUID.\n    #\n    # @param [String] uuid\n    # @return [Boolean]\n    def include?(uuid)\n      @lock.synchronize do\n        with_index_lock do\n          unlocked_reload\n          return !!find_by_prefix(uuid)\n        end\n      end\n    end\n\n    # Releases an entry, unlocking it.\n    #\n    # This is an idempotent operation. It is safe to call this even if you're\n    # unsure if an entry is locked or not.\n    #\n    # After calling this, the previous entry should no longer be used.\n    #\n    # @param [Entry] entry\n    def release(entry)\n      @lock.synchronize do\n        unlocked_release(entry.id)\n      end\n    end\n\n    # Creates/updates an entry object and returns the resulting entry.\n    #\n    # If the entry was new (no UUID), then the UUID will be set on the\n    # resulting entry and can be used. Additionally, the a lock will\n    # be created for the resulting entry, so you must {#release} it\n    # if you want others to be able to access it.\n    #\n    # If the entry isn't new (has a UUID). then this process must hold\n    # that entry's lock or else this set will fail.\n    #\n    # @param [Entry] entry\n    # @return [Entry]\n    def set(entry)\n      # Get the struct and update the updated_at attribute\n      struct = entry.to_json_struct\n\n      # Set an ID if there isn't one already set\n      id     = entry.id\n\n      @lock.synchronize do\n        with_index_lock do\n          # Reload so we have the latest machine data. This allows other\n          # processes to update their own machines without conflicting\n          # with our own.\n          unlocked_reload\n\n          # If we don't have a machine ID, try to look one up\n          if !id\n            self.each do |other|\n              if entry.name == other.name &&\n                entry.provider == other.provider &&\n                entry.vagrantfile_path.to_s == other.vagrantfile_path.to_s\n                id = other.id\n                break\n              end\n            end\n\n            # If we still don't have an ID, generate a random one\n            id = SecureRandom.uuid.gsub(\"-\", \"\") if !id\n\n            # Get a lock on this machine\n            lock_file = lock_machine(id)\n            if !lock_file\n              raise \"Failed to lock new machine: #{entry.name}\"\n            end\n\n            @machine_locks[id] = lock_file\n          end\n\n          if !@machine_locks[id]\n            raise \"Unlocked write on machine: #{id}\"\n          end\n\n          # Set our machine and save\n          @machines[id] = struct\n          unlocked_save\n        end\n      end\n\n      Entry.new(id, struct)\n    end\n\n    # Reinsert a machine into the global index if it has\n    # a valid existing uuid but does not currently exist\n    # in the index.\n    #\n    # @param [Entry] entry\n    # @return [Entry]\n    def recover(entry)\n      @lock.synchronize do\n        with_index_lock do\n          # Reload the data\n          unlocked_reload\n          # Don't recover if entry already exists in the global\n          return entry if find_by_prefix(entry.id)\n\n          lock_file = lock_machine(entry.id)\n          if !lock_file\n            raise Errors::MachineLocked,\n              name: entry.name,\n              provider: entry.provider\n          end\n          @machine_locks[entry.id] = lock_file\n        end\n      end\n      return set(entry)\n    end\n\n    protected\n\n    # Finds a machine where the UUID is prefixed by the given string.\n    #\n    # @return [Hash]\n    def find_by_prefix(prefix)\n      return if !prefix || prefix == \"\"\n      @machines.each do |uuid, data|\n        return data.merge(\"id\" => uuid) if uuid.start_with?(prefix)\n      end\n\n      nil\n    end\n\n    # Locks a machine exclusively to us, returning the file handle\n    # that holds the lock.\n    #\n    # If the lock cannot be acquired, then nil is returned.\n    #\n    # This should be called within an index lock.\n    #\n    # @return [File]\n    def lock_machine(uuid)\n      lock_path = @data_dir.join(\"#{uuid}.lock\")\n      lock_file = lock_path.open(\"w+\")\n      if lock_file.flock(File::LOCK_EX | File::LOCK_NB) === false\n        lock_file.close\n        lock_file = nil\n      end\n\n      lock_file\n    end\n\n    # Releases a local lock on a machine. This does not acquire any locks\n    # so make sure to lock around it.\n    #\n    # @param [String] id\n    def unlocked_release(id)\n      lock_file = @machine_locks[id]\n      if lock_file\n        lock_file.close\n        begin\n          File.delete(lock_file.path)\n        rescue Errno::EACCES\n          # Another process is probably opened it, no problem.\n        end\n\n        @machine_locks.delete(id)\n      end\n    end\n\n    # This will reload the data without locking the index. It is assumed\n    # the caller with lock the index outside of this call.\n    #\n    # @param [File] f\n    def unlocked_reload\n      return if !@index_file.file?\n\n      data = nil\n      begin\n        data = JSON.load(@index_file.read)\n      rescue JSON::ParserError\n        raise Errors::CorruptMachineIndex, path: @index_file.to_s\n      end\n\n      if data\n        if !data[\"version\"] || data[\"version\"].to_i != 1\n          raise Errors::CorruptMachineIndex, path: @index_file.to_s\n        end\n\n        @machines = data[\"machines\"] || {}\n      end\n    end\n\n    # Saves the index.\n    def unlocked_save\n      @index_file.open(\"w\") do |f|\n        f.write(JSON.dump({\n          \"version\"  => 1,\n          \"machines\" => @machines,\n        }))\n      end\n    end\n\n\n    # This will hold a lock to the index so it can be read or updated.\n    def with_index_lock\n      lock_path = \"#{@index_file}.lock\"\n      File.open(lock_path, \"w+\") do |f|\n        f.flock(File::LOCK_EX)\n        yield\n      end\n    end\n\n    # An entry in the MachineIndex.\n    class Entry\n      # The unique ID for this entry. This is _not_ the ID for the\n      # machine itself (which is provider-specific and in the data directory).\n      #\n      # @return [String]\n      attr_reader :id\n\n      # The path for the \"local data\" directory for the environment.\n      #\n      # @return [Pathname]\n      attr_accessor :local_data_path\n\n      # The name of the machine.\n      #\n      # @return [String]\n      attr_accessor :name\n\n      # The name of the provider.\n      #\n      # @return [String]\n      attr_accessor :provider\n\n      # The name of the architecture.\n      #\n      # @return [String]\n      attr_accessor :architecture\n\n      # The last known state of this machine.\n      #\n      # @return [String]\n      attr_accessor :state\n\n      # The last known state of this machine.\n      #\n      # @return [MachineState]\n      attr_accessor :full_state\n\n      # The valid Vagrantfile filenames for this environment.\n      #\n      # @return [Array<String>]\n      attr_accessor :vagrantfile_name\n\n      # The path to the Vagrantfile that manages this machine.\n      #\n      # @return [Pathname]\n      attr_accessor :vagrantfile_path\n\n      # The last time this entry was updated.\n      #\n      # @return [DateTime]\n      attr_reader :updated_at\n\n      # Extra data to store with the index entry. This can be anything\n      # and is treated like a general global state bag.\n      #\n      # @return [Hash]\n      attr_accessor :extra_data\n\n      # Initializes an entry.\n      #\n      # The parameter given should be nil if this is being created\n      # publicly.\n      def initialize(id=nil, raw=nil)\n        @logger = Log4r::Logger.new(\"vagrant::machine_index::entry\")\n\n        @extra_data = {}\n        @id = id\n        # Do nothing if we aren't given a raw value. Otherwise, parse it.\n        return if !raw\n\n        @local_data_path  = raw[\"local_data_path\"]\n        @name             = raw[\"name\"]\n        @provider         = raw[\"provider\"]\n        @architecture     = raw[\"architecture\"]\n        @state            = raw[\"state\"]\n        @full_state       = raw[\"full_state\"]\n        @vagrantfile_name = raw[\"vagrantfile_name\"]\n        @vagrantfile_path = raw[\"vagrantfile_path\"]\n        # TODO(mitchellh): parse into a proper datetime\n        @updated_at       = raw[\"updated_at\"]\n        @extra_data       = raw[\"extra_data\"] || {}\n\n        # Be careful with the paths\n        @local_data_path = nil  if @local_data_path == \"\"\n        @vagrantfile_path = nil if @vagrantfile_path == \"\"\n\n        # Convert to proper types\n        @local_data_path = Pathname.new(@local_data_path) if @local_data_path\n        @vagrantfile_path = Pathname.new(@vagrantfile_path) if @vagrantfile_path\n      end\n\n      # Returns boolean true if this entry appears to be valid.\n      # The criteria for being valid:\n      #\n      #   * Vagrantfile directory exists\n      #   * Vagrant environment contains a machine with this\n      #     name and provider.\n      #\n      # This method is _slow_. It should be used with care.\n      #\n      # @param [Pathname] home_path The home path for the Vagrant\n      #   environment.\n      # @return [Boolean]\n      def valid?(home_path)\n        return false if !vagrantfile_path\n        return false if !vagrantfile_path.directory?\n\n        # Create an environment so we can determine the active\n        # machines...\n        found = false\n        env = vagrant_env(home_path)\n        env.active_machines.each do |name, provider|\n          if name.to_s == self.name.to_s &&\n            provider.to_s == self.provider.to_s\n            found = true\n            break\n          end\n        end\n\n        # If an active machine of the same name/provider was not\n        # found, it is already false.\n        return false if !found\n\n        # Get the machine\n        machine = nil\n        begin\n          machine = env.machine(self.name.to_sym, self.provider.to_sym)\n        rescue Errors::MachineNotFound\n          return false\n        end\n\n        # Refresh the machine state\n        return false if machine.state.id == MachineState::NOT_CREATED_ID\n\n        true\n      end\n\n      # Creates a {Vagrant::Environment} for this entry.\n      #\n      # @return [Vagrant::Environment]\n      def vagrant_env(home_path, opts={})\n        Vagrant::Util::SilenceWarnings.silence! do\n          Environment.new({\n            cwd: @vagrantfile_path,\n            home_path: home_path,\n            local_data_path: @local_data_path,\n            vagrantfile_name: @vagrantfile_name,\n          }.merge(opts))\n        end\n      end\n\n      # Converts to the structure used by the JSON\n      def to_json_struct\n        {\n          \"local_data_path\"  => @local_data_path.to_s,\n          \"name\"             => @name,\n          \"provider\"         => @provider,\n          \"architecture\"     => @architecture,\n          \"state\"            => @state,\n          \"vagrantfile_name\" => @vagrantfile_name,\n          \"vagrantfile_path\" => @vagrantfile_path.to_s,\n          \"updated_at\"       => @updated_at,\n          \"extra_data\"       => @extra_data,\n        }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/machine_state.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  # This represents the state of a given machine. This is a very basic\n  # class that simply stores a short and long description of the state\n  # of a machine.\n  #\n  # The state also stores a state \"id\" which can be used as a unique\n  # identifier for a state. This should be a symbol. This allows internal\n  # code to compare state such as \":not_created\" instead of using\n  # string comparison.\n  #\n  # The short description should be a single word description of the\n  # state of the machine such as \"running\" or \"not created\".\n  #\n  # The long description can span multiple lines describing what the\n  # state actually means.\n  class MachineState\n    # This is a special ID that can be set for the state ID that\n    # tells Vagrant that the machine is not created. If this is the\n    # case, then Vagrant will set the ID to nil which will automatically\n    # clean out the machine data directory.\n    NOT_CREATED_ID = :not_created\n\n    # Unique ID for this state.\n    #\n    # @return [Symbol]\n    attr_reader :id\n\n    # Short description for this state.\n    #\n    # @return [String]\n    attr_reader :short_description\n\n    # Long description for this state.\n    #\n    # @return [String]\n    attr_reader :long_description\n\n    # Creates a new instance to represent the state of a machine.\n    #\n    # @param [Symbol] id Unique identifier for this state.\n    # @param [String] short Short (preferably one-word) description of\n    #   the state.\n    # @param [String] long Long description (can span multiple lines)\n    #   of the state.\n    def initialize(id, short, long)\n      @id                = id\n      @short_description = short\n      @long_description  = long\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/patches/builder/mkmf.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# This custom mkmf.rb file is used on Windows platforms\n# to handle common path related build failures where\n# a space is included in the path. The default installation\n# location being in Program Files results in most many\n# extensions failing to build. These patches will attempt\n# to find unquoted paths in flags and quote them prior to\n# usage.\n\n# Start with locating the real mkmf.rb file and\n# loading it\nmkmf_paths = $LOAD_PATH.find_all { |x|\n  !x.start_with?(__dir__) &&\n  File.exist?(File.join(x, \"mkmf.rb\"))\n}.uniq\n\n# At this point the path collection should only consist\n# of a single entry. If there's more than one, load all\n# of them but include a warning message that more than\n# one was encountered. If none are found, then something\n# bad is going on so just bail.\nif mkmf_paths.size > 1\n  $stderr.puts \"WARNING: Multiple mkmf.rb files located: #{mkmf_paths.inspect}\"\nelsif mkmf_paths.empty?\n  raise \"Failed to locate mkmf.rb file\"\nend\n\nmkmf_paths.each do |mpath|\n  require File.join(mpath, \"mkmf.rb\")\nend\n\n# Attempt to detect and quote Windos paths found within\n# the given string of flags\n#\n# @param [String] flags Compiler/linker flags\n# @return [String] flags with paths quoted\ndef flag_cleaner(flags)\n  parts = flags.split(\" -\")\n  parts.map! do |p|\n    if p !~ %r{[A-Za-z]:(/|\\\\)}\n      next p\n    elsif p =~ %r{\"[A-Za-z]:(/|\\\\).+\"$}\n      next p\n    end\n\n    p.gsub(%r{([A-Za-z]:(/|\\\\).+)$}, '\"\\1\"')\n  end\n\n  parts.join(\" -\")\nend\n\n# Check values defined for CFLAGS, CPPFLAGS, LDFLAGS,\n# and INCFLAGS for unquoted Windows paths and quote\n# them.\ndef clean_flags!\n  $CFLAGS = flag_cleaner($CFLAGS)\n  $CPPFLAGS = flag_cleaner($CPPFLAGS)\n  $LDFLAGS = flag_cleaner($LDFLAGS)\n  $INCFLAGS = flag_cleaner($INCFLAGS)\nend\n\n# Since mkmf loads the MakeMakefile module directly into the\n# current scope, apply patches directly in the scope\ndef vagrant_create_makefile(*args)\n  clean_flags!\n\n  ruby_create_makefile(*args)\nend\nalias :ruby_create_makefile :create_makefile\nalias :create_makefile :vagrant_create_makefile\n\ndef vagrant_append_cflags(*args)\n  result = ruby_append_cflags(*args)\n  clean_flags!\n  result\nend\nalias :ruby_append_cflags :append_cflags\nalias :append_cflags :vagrant_append_cflags\n\ndef vagrant_append_cppflags(*args)\n  result = ruby_append_cppflags(*args)\n  clean_flags!\n  result\nend\nalias :ruby_append_cppflags :append_cppflags\nalias :append_cppflags :vagrant_append_cppflags\n\ndef vagrant_append_ldflags(*args)\n  result = ruby_append_ldflags(*args)\n  clean_flags!\n  result\nend\nalias :ruby_append_ldflags :append_ldflags\nalias :append_ldflags :vagrant_append_ldflags\n\ndef vagrant_cc_config(*args)\n  clean_flags!\n  ruby_cc_config(*args)\nend\nalias :ruby_cc_config :cc_config\nalias :cc_config :vagrant_cc_config\n\ndef vagrant_link_config(*args)\n  clean_flags!\n  ruby_link_config(*args)\nend\nalias :ruby_link_config :link_config\nalias :link_config :vagrant_link_config\n\n# Finally, always append the flags that Vagrant has\n# defined via the environment\nappend_cflags(ENV[\"CFLAGS\"]) if ENV[\"CFLAGS\"]\nappend_cppflags(ENV[\"CPPFLAGS\"]) if ENV[\"CPPFLAGS\"]\nappend_ldflags(ENV[\"LDFLAGS\"]) if ENV[\"LDFLAGS\"]\n"
  },
  {
    "path": "lib/vagrant/patches/fake_ftp.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fake_ftp\"\n\nmodule FakeFtp\n  class File\n    def initialize(name = nil, data = nil, type = nil,\n      last_modified_time = Time.now)\n      @created = Time.now\n      @name = name\n      @data = data\n      @bytes = data_is_bytes(data) ? data : data.bytes.length\n      @data = data_is_bytes(data) ? nil : data\n      @type = type\n      @last_modified_time = last_modified_time.utc\n    end\n\n    def data_is_bytes(d)\n      d.nil? || d.is_a?(Integer)\n    end\n\n    def data=(data)\n      @bytes = data_is_bytes(data) ? data : data.bytes.length\n      @data = data_is_bytes(data) ? nil : data\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/patches/log4r.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# This adds trace level support to log4r. Since log4r\n# loggers use the trace method for checking if trace\n# information should be included in the output, we\n# make some modifications to allow the trace check to\n# still work while also supporting trace as a valid level\nrequire \"log4r/loggerfactory\"\n\nif !Log4r::Logger::LoggerFactory.respond_to?(:fake_define_methods)\n  class Log4r::Logger::LoggerFactory\n    class << self\n      def fake_set_log(logger, lname)\n        real_set_log(logger, lname)\n        if lname == \"TRACE\"\n          logger.instance_eval do\n            alias :trace_as_level :trace\n            def trace(*args)\n              return @trace if args.empty?\n              trace_as_level(*args)\n            end\n          end\n        end\n      end\n\n      def fake_undefine_methods(logger)\n        real_undefine_methods(logger)\n        logger.instance_eval do\n          def trace(*_)\n            @trace\n          end\n        end\n      end\n\n      alias_method :real_undefine_methods, :undefine_methods\n      alias_method :undefine_methods, :fake_undefine_methods\n      alias_method :real_set_log, :set_log\n      alias_method :set_log, :fake_set_log\n    end\n  end\n\n  class Log4r::Logger\n    # The factory allows using a previously created logger\n    # instance if it exists. Doing this prevents knocking\n    # out configuration that may have already been applied\n    # to the logger instance (like log level)\n    def self.factory(name, *args)\n      l = Log4r::Logger::Repository[name]\n      return l unless l.nil?\n      Log4r::Logger.new(name, *args)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/patches/net-ssh.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"net/ssh\"\nrequire \"net/ssh/buffer\"\n\n# Set the version requirement for when net-ssh should be patched\nNET_SSH_PATCH_REQUIREMENT = Gem::Requirement.new(\">= 7.0.0\", \"<= 7.3\")\n\n# This patch provides support for properly loading ECDSA private keys\nif NET_SSH_PATCH_REQUIREMENT.satisfied_by?(Gem::Version.new(Net::SSH::Version::STRING))\n  Net::SSH::Buffer.class_eval do\n    def vagrant_read_private_keyblob(type)\n      case type\n      when /^ecdsa\\-sha2\\-(\\w*)$/\n        curve_name_in_type = $1\n        curve_name_in_key = read_string\n\n        unless curve_name_in_type == curve_name_in_key\n          raise Net::SSH::Exception, \"curve name mismatched (`#{curve_name_in_key}' with `#{curve_name_in_type}')\"\n        end\n\n        public_key_oct = read_string\n        priv_key_bignum = read_bignum\n        begin\n          curvename = OpenSSL::PKey::EC::CurveNameAlias[curve_name_in_key]\n          group = OpenSSL::PKey::EC::Group.new(curvename)\n          point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(public_key_oct, 2))\n          priv_bn = OpenSSL::BN.new(priv_key_bignum, 2)\n          asn1 = OpenSSL::ASN1::Sequence(\n            [\n              OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(0)),\n              OpenSSL::ASN1::Sequence.new(\n                [\n                  OpenSSL::ASN1::ObjectId(\"id-ecPublicKey\"),\n                  OpenSSL::ASN1::ObjectId(curvename)\n                ]\n              ),\n              OpenSSL::ASN1::OctetString.new(\n                OpenSSL::ASN1::Sequence.new(\n                  [\n                    OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(1)),\n                    OpenSSL::ASN1::OctetString.new(priv_bn.to_s(2)),\n                    OpenSSL::ASN1::ASN1Data.new(\n                      [\n                        OpenSSL::ASN1::BitString.new(point.to_octet_string(:uncompressed)),\n                      ], 1, :CONTEXT_SPECIFIC,\n                    )\n                  ]\n                ).to_der\n              )\n            ]\n          )\n\n          key = OpenSSL::PKey::EC.new(asn1.to_der)\n\n          return key\n        rescue OpenSSL::PKey::ECError\n          raise NotImplementedError, \"unsupported key type `#{type}'\"\n        end\n      else\n        netssh_read_private_keyblob(type)\n      end\n    end\n\n    alias_method :netssh_read_private_keyblob, :read_private_keyblob\n    alias_method :read_private_keyblob, :vagrant_read_private_keyblob\n  end\n\n  OpenSSL::PKey::EC::Point.class_eval do\n    include Net::SSH::Authentication::PubKeyFingerprint\n    def to_pem\n      \"#{ssh_type} #{self.to_bn.to_s(2)}\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/patches/rubygems.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# This allows for effective monkey patching of the MakeMakefile\n# module when building gem extensions. When gem extensions are\n# built, the extconf.rb file is executed as a separate process.\n# To support monkey patching the MakeMakefile module, the ruby\n# executable path is adjusted to add a custom load path allowing\n# a customized mkmf.rb file to load the proper mkmf.rb file, and\n# then applying the proper patches.\nif Gem.win_platform?\n  Gem.class_eval do\n    class << self\n      def vagrant_ruby\n        cmd = ruby_ruby\n        \"#{cmd} -I\\\"#{Vagrant.source_root.join(\"lib/vagrant/patches/builder\")}\\\"\"\n      end\n\n      alias_method :ruby_ruby, :ruby\n      alias_method :ruby, :vagrant_ruby\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/patches/timeout_error.rb",
    "content": "# Adds an IO::TimeoutError for versions of ruby where it isn't defined (< 3.2). \nif !defined?(IO::TimeoutError)\n  class IO::TimeoutError < StandardError\n  end\nend"
  },
  {
    "path": "lib/vagrant/plugin/manager.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"set\"\n\nrequire_relative \"../bundler\"\nrequire_relative \"../shared_helpers\"\nrequire_relative \"state_file\"\n\nmodule Vagrant\n  module Plugin\n    # The Manager helps with installing, listing, and initializing plugins.\n    class Manager\n      # Returns the path to the [StateFile] for user plugins.\n      #\n      # @return [Pathname]\n      def self.user_plugins_file\n        Vagrant.user_data_path.join(\"plugins.json\")\n      end\n\n      # Returns the path to the [StateFile] for system plugins.\n      def self.system_plugins_file\n        dir = Vagrant.installer_embedded_dir\n        return nil if !dir\n        Pathname.new(dir).join(\"plugins.json\")\n      end\n\n      def self.instance\n        @instance ||= self.new(user_plugins_file)\n      end\n\n      attr_reader :user_file\n      attr_reader :system_file\n      attr_reader :local_file\n\n      # @param [Pathname] user_file\n      def initialize(user_file)\n        @logger = Log4r::Logger.new(\"vagrant::plugin::manager\")\n        @user_file   = StateFile.new(user_file)\n\n        system_path  = self.class.system_plugins_file\n        @system_file = nil\n        @system_file = StateFile.new(system_path) if system_path && system_path.file?\n\n        @local_file = nil\n        @globalized = @localized = false\n      end\n\n      # Enable global plugins\n      #\n      # @return [Hash] list of plugins\n      def globalize!\n        @globalized = true\n        @logger.debug(\"Enabling globalized plugins\")\n        plugins = installed_plugins\n        bundler_init(plugins, global: user_file.path)\n        plugins\n      end\n\n      # Enable environment local plugins\n      #\n      # @param [Environment] env Vagrant environment\n      # @return [Hash, nil] list of plugins\n      def localize!(env)\n        @localized = true\n        if env.local_data_path\n          @logger.debug(\"Enabling localized plugins\")\n          @local_file = StateFile.new(env.local_data_path.join(\"plugins.json\"))\n          Vagrant::Bundler.instance.environment_path = env.local_data_path\n          plugins = local_file.installed_plugins\n          bundler_init(plugins, local: local_file.path)\n          plugins\n        end\n      end\n\n      # @return [Boolean] local and global plugins are loaded\n      def ready?\n        @globalized && @localized\n      end\n\n      # Initialize bundler with given plugins\n      #\n      # @param [Hash] plugins List of plugins\n      # @return [nil]\n      def bundler_init(plugins, **opts)\n        if !Vagrant.plugins_init?\n          @logger.warn(\"Plugin initialization is disabled\")\n          return nil\n        end\n\n        @logger.info(\"Plugins:\")\n        plugins.each do |plugin_name, plugin_info|\n          installed_version = plugin_info[\"installed_gem_version\"]\n          version_constraint = plugin_info[\"gem_version\"]\n          installed_version = 'undefined' if installed_version.to_s.empty?\n          version_constraint = '> 0' if version_constraint.to_s.empty?\n          @logger.info(\n            \"  - #{plugin_name} = [installed: \" \\\n              \"#{installed_version} constraint: \" \\\n              \"#{version_constraint}]\"\n          )\n        end\n        begin\n          Vagrant::Bundler.instance.init!(plugins, **opts)\n        rescue StandardError, ScriptError => err\n          @logger.error(\"Plugin initialization error - #{err.class}: #{err}\")\n          err.backtrace.each do |backtrace_line|\n            @logger.debug(backtrace_line)\n          end\n          raise Vagrant::Errors::PluginInitError, message: err.to_s\n        end\n      end\n\n      # Installs another plugin into our gem directory.\n      #\n      # @param [String] name Name of the plugin (gem)\n      # @return [Gem::Specification]\n      def install_plugin(name, **opts)\n        if opts[:env_local] && @local_file.nil?\n          raise Errors::PluginNoLocalError\n        end\n\n        if name =~ /\\.gem$/\n          # If this is a gem file, then we install that gem locally.\n          local_spec = Vagrant::Bundler.instance.install_local(name, opts)\n          name       = local_spec.name\n          opts[:version] = local_spec.version.to_s\n        end\n\n        plugins = installed_plugins\n        plugins[name] = {\n          \"require\"     => opts[:require],\n          \"gem_version\" => opts[:version],\n          \"sources\"     => opts[:sources],\n        }\n\n        if local_spec.nil?\n          result = nil\n          install_lambda = lambda do\n            Vagrant::Bundler.instance.install(plugins, opts[:env_local]).each do |spec|\n              next if spec.name != name\n              next if result && result.version >= spec.version\n              result = spec\n            end\n          end\n\n          if opts[:verbose]\n            Vagrant::Bundler.instance.verbose(&install_lambda)\n          else\n            install_lambda.call\n          end\n        else\n          result = local_spec\n        end\n\n        if result\n          # Add the plugin to the state file\n          plugin_file = opts[:env_local] ? @local_file : @user_file\n          plugin_file.add_plugin(\n            result.name,\n            version: opts[:version],\n            require: opts[:require],\n            sources: opts[:sources],\n            env_local: !!opts[:env_local],\n            installed_gem_version: result.version.to_s\n          )\n        else\n          r = Gem::Dependency.new(name, opts[:version])\n          result = Gem::Specification.find { |s|\n            s.satisfies_requirement?(r) &&\n              s.activated?\n          }\n          raise Errors::PluginInstallFailed,\n            name: name if result.nil?\n          @logger.warn(\"Plugin install returned no result as no new plugins were installed.\")\n        end\n        # After install clean plugin gems to remove any cruft. This is useful\n        # for removing outdated dependencies or other versions of an installed\n        # plugin if the plugin is upgraded/downgraded\n        Vagrant::Bundler.instance.clean(installed_plugins, local: !!opts[:local])\n        result\n      rescue Gem::GemNotFoundException\n        raise Errors::PluginGemNotFound, name: name\n      rescue Gem::Exception => err\n        @logger.warn(\"Failed to install plugin: #{err}\")\n        @logger.debug(\"#{err.class}: #{err}\\n#{err.backtrace.join(\"\\n\")}\")\n        # Try and determine a cause for the failure\n        case err.message\n        when /install development tools first/\n          raise Errors::PluginNeedsDeveloperTools\n        when /library not found in default locations/\n          lib = err.message.match(/(\\w+) library not found in default locations/)\n          if lib.nil?\n            raise Errors::BundlerError, message: err.message\n          end\n          raise Errors::PluginMissingLibrary,\n            library: lib.captures.first,\n            name: name\n        when /find header files for ruby/\n          raise Errors::PluginMissingRubyDev\n        else\n          raise Errors::BundlerError, message: err.message\n        end\n      end\n\n      # Uninstalls the plugin with the given name.\n      #\n      # @param [String] name\n      def uninstall_plugin(name, **opts)\n        if @system_file\n          if !@user_file.has_plugin?(name) && @system_file.has_plugin?(name)\n            raise Errors::PluginUninstallSystem,\n              name: name\n          end\n        end\n\n        if opts[:env_local] && @local_file.nil?\n          raise Errors::PluginNoLocalError\n        end\n\n        plugin_file = opts[:env_local] ? @local_file : @user_file\n\n        if !plugin_file.has_plugin?(name)\n          raise Errors::PluginNotInstalled,\n            name: name\n        end\n\n        plugin_file.remove_plugin(name)\n\n        # Clean the environment, removing any old plugins\n        Vagrant::Bundler.instance.clean(installed_plugins)\n      rescue Gem::Exception => e\n        raise Errors::BundlerError, message: e.to_s\n      end\n\n      # Updates all or a specific set of plugins.\n      def update_plugins(specific, **opts)\n        if opts[:env_local] && @local_file.nil?\n          raise Errors::PluginNoLocalError\n        end\n\n        plugin_file = opts[:env_local] ? @local_file : @user_file\n\n        result = Vagrant::Bundler.instance.update(plugin_file.installed_plugins, specific)\n        plugin_file.installed_plugins.each do |name, info|\n          matching_spec = result.detect{|s| s.name == name}\n          info = Hash[\n            info.map do |key, value|\n              [key.to_sym, value]\n            end\n          ]\n          if matching_spec\n            plugin_file.add_plugin(name, **info.merge(\n              version: \"> 0\",\n              installed_gem_version: matching_spec.version.to_s\n            ))\n          end\n        end\n        Vagrant::Bundler.instance.clean(installed_plugins)\n        result\n      rescue Gem::Exception => e\n        raise Errors::BundlerError, message: e.to_s\n      end\n\n      # This returns the list of plugins that should be enabled.\n      #\n      # @return [Hash]\n      def installed_plugins\n        system = {}\n        if @system_file\n          @system_file.installed_plugins.each do |k, v|\n            system[k] = v.merge(\"system\" => true)\n          end\n        end\n        plugin_list = Util::DeepMerge.deep_merge(system, @user_file.installed_plugins)\n\n        if @local_file\n          plugin_list = Util::DeepMerge.deep_merge(plugin_list,\n            @local_file.installed_plugins)\n        end\n\n        # Sort plugins by name\n        Hash[\n          plugin_list.map{|plugin_name, plugin_info|\n            [plugin_name, plugin_info]\n          }.sort_by(&:first)\n        ]\n      end\n\n      # This returns the list of plugins that are installed as\n      # Gem::Specifications.\n      #\n      # @return [Array<Gem::Specification>]\n      def installed_specs\n        installed_plugin_info = installed_plugins\n        installed = Set.new(installed_plugin_info.keys)\n        installed_versions = Hash[\n          installed_plugin_info.map{|plugin_name, plugin_info|\n            gem_version = plugin_info[\"gem_version\"].to_s\n            gem_version = \"> 0\" if gem_version.empty?\n            [plugin_name, Gem::Requirement.new(gem_version)]\n          }\n        ]\n\n        # Go through the plugins installed in this environment and\n        # get the latest version of each.\n        installed_map = {}\n        Gem::Specification.find_all.each do |spec|\n          # Ignore specs that aren't in our installed list\n          next if !installed.include?(spec.name)\n\n          next if installed_versions[spec.name] &&\n            !installed_versions[spec.name].satisfied_by?(spec.version)\n\n          # If we already have a newer version in our list of installed,\n          # then ignore it\n          next if installed_map.key?(spec.name) &&\n            installed_map[spec.name].version >= spec.version\n\n          installed_map[spec.name] = spec\n        end\n\n        installed_map.values\n      end\n\n      # Loads the requested plugins into the Vagrant runtime\n      #\n      # @param [Hash] plugins List of plugins to load\n      # @return [nil]\n      def load_plugins(plugins)\n        if !Vagrant.plugins_enabled?\n          @logger.warn(\"Plugin loading is disabled\")\n          return\n        end\n\n        if plugins.nil?\n          @logger.debug(\"No plugins provided for loading\")\n          return\n        end\n\n        begin\n          @logger.info(\"Loading plugins...\")\n          plugins.each do |plugin_name, plugin_info|\n            if plugin_info[\"require\"].to_s.empty?\n              begin\n                @logger.info(\"Loading plugin `#{plugin_name}` with default require: `#{plugin_name}`\")\n                require plugin_name\n              rescue LoadError => err\n                if plugin_name.include?(\"-\")\n                  plugin_slash = plugin_name.gsub(\"-\", \"/\")\n                  @logger.error(\"Failed to load plugin `#{plugin_name}` with default require. - #{err.class}: #{err}\")\n                  @logger.info(\"Loading plugin `#{plugin_name}` with slash require: `#{plugin_slash}`\")\n                  require plugin_slash\n                else\n                  raise\n                end\n              end\n            else\n              @logger.debug(\"Loading plugin `#{plugin_name}` with custom require: `#{plugin_info[\"require\"]}`\")\n              require plugin_info[\"require\"]\n            end\n            @logger.debug(\"Successfully loaded plugin `#{plugin_name}`.\")\n          end\n          if defined?(::Bundler)\n            @logger.debug(\"Bundler detected in use. Loading `:plugins` group.\")\n            ::Bundler.require(:plugins)\n          end\n        rescue ScriptError, StandardError => err\n          @logger.error(\"Plugin loading error: #{err.class} - #{err}\")\n          err.backtrace.each do |backtrace_line|\n            @logger.debug(backtrace_line)\n          end\n          raise Vagrant::Errors::PluginLoadError, message: err.to_s\n        end\n        nil\n      end\n\n      # Check if the requested plugin is installed\n      #\n      # @param [String] name Name of plugin\n      # @param [String] version Specific version of the plugin\n      # @return [Boolean]\n      def plugin_installed?(name, version=nil)\n        # Make the requirement object\n        version = Gem::Requirement.new([version.to_s]) if version\n\n        # If plugins are loaded, check for match in loaded specs\n        if ready?\n          return installed_specs.any? do |s|\n            match = s.name == name\n            next match if !version\n            next match && version.satisfied_by?(s.version)\n          end\n        end\n\n        # Plugins are not loaded yet so check installed plugin data\n        plugin_info = installed_plugins[name]\n        return false if !plugin_info\n        return !!plugin_info if version.nil? || plugin_info[\"installed_gem_version\"].nil?\n        installed_version = Gem::Version.new(plugin_info[\"installed_gem_version\"])\n        version.satisfied_by?(installed_version)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/state_file.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\nrequire \"fileutils\"\nrequire \"tempfile\"\n\nmodule Vagrant\n  module Plugin\n    # This is a helper to deal with the plugin state file that Vagrant\n    # uses to track what plugins are installed and activated and such.\n    class StateFile\n\n      # @return [Pathname] path to file\n      attr_reader :path\n\n      def initialize(path)\n        @path = path\n\n        @data = {}\n        if @path.exist?\n          begin\n            @data = JSON.parse(@path.read)\n          rescue JSON::ParserError => e\n            raise Vagrant::Errors::PluginStateFileParseError,\n              path: path, message: e.message\n          end\n\n          upgrade_v0! if !@data[\"version\"]\n        end\n\n        @data[\"version\"] ||= \"1\"\n        @data[\"installed\"] ||= {}\n      end\n\n      # Add a plugin that is installed to the state file.\n      #\n      # @param [String] name The name of the plugin\n      def add_plugin(name, **opts)\n        @data[\"installed\"][name] = {\n          \"ruby_version\"          => RUBY_VERSION,\n          \"vagrant_version\"       => Vagrant::VERSION,\n          \"gem_version\"           => opts[:version] || \"\",\n          \"require\"               => opts[:require] || \"\",\n          \"sources\"               => opts[:sources] || [],\n          \"installed_gem_version\" => opts[:installed_gem_version],\n          \"env_local\"             => !!opts[:env_local]\n        }\n\n        save!\n      end\n\n      # Adds a RubyGems index source to look up gems.\n      #\n      # @param [String] url URL of the source.\n      def add_source(url)\n        @data[\"sources\"] ||= []\n        @data[\"sources\"] << url if !@data[\"sources\"].include?(url)\n        save!\n      end\n\n      # This returns a hash of installed plugins according to the state\n      # file. Note that this may _not_ directly match over to actually\n      # installed gems.\n      #\n      # @return [Hash]\n      def installed_plugins\n        @data[\"installed\"]\n      end\n\n      # Returns true/false if the plugin is present in this state file.\n      #\n      # @return [Boolean]\n      def has_plugin?(name)\n        @data[\"installed\"].key?(name)\n      end\n\n      # Remove a plugin that is installed from the state file.\n      #\n      # @param [String] name The name of the plugin.\n      def remove_plugin(name)\n        @data[\"installed\"].delete(name)\n        save!\n      end\n\n      # Remove a source for RubyGems.\n      #\n      # @param [String] url URL of the source\n      def remove_source(url)\n        @data[\"sources\"] ||= []\n        @data[\"sources\"].delete(url)\n        save!\n      end\n\n      # Returns the list of RubyGems sources that will be searched for\n      # plugins.\n      #\n      # @return [Array<String>]\n      def sources\n        @data[\"sources\"] || []\n      end\n\n      # This saves the state back into the state file.\n      def save!\n        Tempfile.open(@path.basename.to_s, @path.dirname.to_s) do |f|\n          f.binmode\n          f.write(JSON.dump(@data))\n          f.fsync\n          f.chmod(0644)\n          f.close\n          FileUtils.mv(f.path, @path)\n        end\n      end\n\n      protected\n\n      # This upgrades the internal data representation from V0 (the initial\n      # version) to V1.\n      def upgrade_v0!\n        @data[\"version\"] = \"1\"\n\n        new_installed = {}\n        (@data[\"installed\"] || []).each do |plugin|\n          new_installed[plugin] = {\n            \"ruby_version\"    => \"0\",\n            \"vagrant_version\" => \"0\",\n          }\n        end\n\n        @data[\"installed\"] = new_installed\n\n        save!\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v1/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'log4r'\nrequire \"vagrant/util/safe_puts\"\n\nmodule Vagrant\n  module Plugin\n    module V1\n      # This is the base class for a CLI command.\n      class Command\n        include Util::SafePuts\n\n        def initialize(argv, env)\n          @argv = argv\n          @env  = env\n          @logger = Log4r::Logger.new(\"vagrant::command::#{self.class.to_s.downcase}\")\n        end\n\n        # This is what is called on the class to actually execute it. Any\n        # subclasses should implement this method and do any option parsing\n        # and validation here.\n        def execute\n        end\n\n        protected\n\n        # Parses the options given an OptionParser instance.\n        #\n        # This is a convenience method that properly handles duping the\n        # originally argv array so that it is not destroyed.\n        #\n        # This method will also automatically detect \"-h\" and \"--help\"\n        # and print help. And if any invalid options are detected, the help\n        # will be printed, as well.\n        #\n        # If this method returns `nil`, then you should assume that help\n        # was printed and parsing failed.\n        def parse_options(opts=nil)\n          # Creating a shallow copy of the arguments so the OptionParser\n          # doesn't destroy the originals.\n          argv = @argv.dup\n\n          # Default opts to a blank optionparser if none is given\n          opts ||= OptionParser.new\n\n          # Add the help option, which must be on every command.\n          opts.on_tail(\"-h\", \"--help\", \"Print this help\") do\n            safe_puts(opts.help)\n            return nil\n          end\n\n          opts.parse!(argv)\n          return argv\n        rescue OptionParser::InvalidOption\n          raise Errors::CLIInvalidOptions, help: opts.help.chomp\n        end\n\n        # Yields a VM for each target VM for the command.\n        #\n        # This is a convenience method for easily implementing methods that\n        # take a target VM (in the case of multi-VM) or every VM if no\n        # specific VM name is specified.\n        #\n        # @param [String] name The name of the VM. Nil if every VM.\n        # @param [Boolean] single_target If true, then an exception will be\n        #   raised if more than one target is found.\n        def with_target_vms(names=nil, options=nil)\n          # Using VMs requires a Vagrant environment to be properly setup\n          raise Errors::NoEnvironmentError if !@env.root_path\n\n          # Setup the options hash\n          options ||= {}\n\n          # Require that names be an array\n          names ||= []\n          names = [names] if !names.is_a?(Array)\n\n          # First determine the proper array of VMs.\n          vms = []\n          if names.length > 0\n            names.each do |name|\n              if pattern = name[/^\\/(.+?)\\/$/, 1]\n                # This is a regular expression name, so we convert to a regular\n                # expression and allow that sort of matching.\n                regex = Regexp.new(pattern)\n\n                @env.vms.each do |name, vm|\n                  vms << vm if name =~ regex\n                end\n\n                raise Errors::VMNoMatchError if vms.empty?\n              else\n                # String name, just look for a specific VM\n                vms << @env.vms[name.to_sym]\n                raise Errors::VMNotFoundError, name: name if !vms[0]\n              end\n            end\n          else\n            vms = @env.vms_ordered\n          end\n\n          # Make sure we're only working with one VM if single target\n          if options[:single_target] && vms.length != 1\n            vm = @env.primary_vm\n            raise Errors::MultiVMTargetRequired if !vm\n            vms = [vm]\n          end\n\n          # If we asked for reversed ordering, then reverse it\n          vms.reverse! if options[:reverse]\n\n          # Go through each VM and yield it!\n          vms.each do |old_vm|\n            # We get a new VM from the environment here to avoid potentially\n            # stale VMs (if there was a config reload on the environment\n            # or something).\n            vm = @env.vms[old_vm.name]\n            yield vm\n          end\n        end\n\n        # This method will split the argv given into three parts: the\n        # flags to this command, the subcommand, and the flags to the\n        # subcommand. For example:\n        #\n        #     -v status -h -v\n        #\n        # The above would yield 3 parts:\n        #\n        #     [\"-v\"]\n        #     \"status\"\n        #     [\"-h\", \"-v\"]\n        #\n        # These parts are useful because the first is a list of arguments\n        # given to the current command, the second is a subcommand, and the\n        # third are the commands given to the subcommand.\n        #\n        # @return [Array] The three parts.\n        def split_main_and_subcommand(argv)\n          # Initialize return variables\n          main_args   = nil\n          sub_command = nil\n          sub_args    = []\n\n          # We split the arguments into two: One set containing any\n          # flags before a word, and then the rest. The rest are what\n          # get actually sent on to the subcommand.\n          argv.each_index do |i|\n            if !argv[i].start_with?(\"-\")\n              # We found the beginning of the sub command. Split the\n              # args up.\n              main_args   = argv[0, i]\n              sub_command = argv[i]\n              sub_args    = argv[i + 1, argv.length - i + 1]\n\n              # Break so we don't find the next non flag and shift our\n              # main args.\n              break\n            end\n          end\n\n          # Handle the case that argv was empty or didn't contain any subcommand\n          main_args = argv.dup if main_args.nil?\n\n          return [main_args, sub_command, sub_args]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v1/communicator.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Plugin\n    module V1\n      # Base class for a communicator in Vagrant. A communicator is\n      # responsible for communicating with a machine in some way. There\n      # are various stages of Vagrant that require things such as uploading\n      # files to the machine, executing shell commands, etc. Implementors\n      # of this class are expected to provide this functionality in some\n      # way.\n      #\n      # Note that a communicator must provide **all** of the methods\n      # in this base class. There is currently no way for one communicator\n      # to provide say a more efficient way of uploading a file, but not\n      # provide shell execution. This sort of thing will come in a future\n      # version.\n      class Communicator\n        # This returns true/false depending on if the given machine\n        # can be communicated with using this communicator. If this returns\n        # `true`, then this class will be used as the primary communication\n        # method for the machine.\n        #\n        # @return [Boolean]\n        def self.match?(machine)\n          false\n        end\n\n        # Initializes the communicator with the machine that we will be\n        # communicating with. This base method does nothing (it doesn't\n        # even store the machine in an instance variable for you), so you're\n        # expected to override this and do something with the machine if\n        # you care about it.\n        #\n        # @param [Machine] machine The machine this instance is expected to\n        #   communicate with.\n        def initialize(machine)\n        end\n\n        # Checks if the target machine is ready for communication. If this\n        # returns true, then all the other methods for communicating with\n        # the machine are expected to be functional.\n        #\n        # @return [Boolean]\n        def ready?\n          false\n        end\n\n        # Download a file from the remote machine to the local machine.\n        #\n        # @param [String] from Path of the file on the remote machine.\n        # @param [String] to Path of where to save the file locally.\n        def download(from, to)\n        end\n\n        # Upload a file to the remote machine.\n        #\n        # @param [String] from Path of the file locally to upload.\n        # @param [String] to Path of where to save the file on the remote\n        #   machine.\n        def upload(from, to)\n        end\n\n        # Execute a command on the remote machine. The exact semantics\n        # of this method are up to the implementor, but in general the\n        # users of this class will expect this to be a shell.\n        #\n        # This method gives you no way to write data back to the remote\n        # machine, so only execute commands that don't expect input.\n        #\n        # @param [String] command Command to execute.\n        # @yield [type, data] Realtime output of the command being executed.\n        # @yieldparam [String] type Type of the output. This can be\n        #   `:stdout`, `:stderr`, etc. The exact types are up to the\n        #   implementor.\n        # @yieldparam [String] data Data for the given output.\n        # @return [Integer] Exit code of the command.\n        def execute(command, opts=nil)\n        end\n\n        # Executes a command on the remote machine with administrative\n        # privileges. See {#execute} for documentation, as the API is the\n        # same.\n        #\n        # @see #execute\n        def sudo(command, opts=nil)\n        end\n\n        # Executes a command and returns true if the command succeeded,\n        # and false otherwise. By default, this executes as a normal user,\n        # and it is up to the communicator implementation if they expose an\n        # option for running tests as an administrator.\n        #\n        # @see #execute\n        def test(command, opts=nil)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v1/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Plugin\n    module V1\n      # This is the base class for a configuration key defined for\n      # V1. Any configuration key plugins for V1 should inherit from this\n      # class.\n      class Config\n        # This constant represents an unset value. This is useful so it is\n        # possible to know the difference between a configuration value that\n        # was never set, and a value that is nil (explicitly). Best practice\n        # is to initialize all variables to this value, then the {#merge}\n        # method below will \"just work\" in many cases.\n        UNSET_VALUE = Object.new\n\n        # This is called as a last-minute hook that allows the configuration\n        # object to finalize itself before it will be put into use. This is\n        # a useful place to do some defaults in the case the user didn't\n        # configure something or so on.\n        #\n        # An example of where this sort of thing is used or has been used:\n        # the \"vm\" configuration key uses this to make sure that at least\n        # one sub-VM has been defined: the default VM.\n        #\n        # The configuration object is expected to mutate itself.\n        def finalize!\n          # Default implementation is to do nothing.\n        end\n\n        # Merge another configuration object into this one. This assumes that\n        # the other object is the same class as this one. This should not\n        # mutate this object, but instead should return a new, merged object.\n        #\n        # The default implementation will simply iterate over the instance\n        # variables and merge them together, with this object overriding\n        # any conflicting instance variables of the older object. Instance\n        # variables starting with \"__\" (double underscores) will be ignored.\n        # This lets you set some sort of instance-specific state on your\n        # configuration keys without them being merged together later.\n        #\n        # @param [Object] other The other configuration object to merge from,\n        #   this must be the same type of object as this one.\n        # @return [Object] The merged object.\n        def merge(other)\n          result = self.class.new\n\n          # Set all of our instance variables on the new class\n          [self, other].each do |obj|\n            obj.instance_variables.each do |key|\n              # Ignore keys that start with a double underscore. This allows\n              # configuration classes to still hold around internal state\n              # that isn't propagated.\n              if !key.to_s.start_with?(\"@__\")\n                result.instance_variable_set(key, obj.instance_variable_get(key))\n              end\n            end\n          end\n\n          result\n        end\n\n        # Allows setting options from a hash. By default this simply calls\n        # the `#{key}=` method on the config class with the value, which is\n        # the expected behavior most of the time.\n        #\n        # This is expected to mutate itself.\n        #\n        # @param [Hash] options A hash of options to set on this configuration\n        #   key.\n        def set_options(options)\n          options.each do |key, value|\n            send(\"#{key}=\", value)\n          end\n        end\n\n        # Converts this configuration object to JSON.\n        def to_json(*a)\n          instance_variables_hash.to_json(*a)\n        end\n\n        # Returns the instance variables as a hash of key-value pairs.\n        def instance_variables_hash\n          instance_variables.inject({}) do |acc, iv|\n            acc[iv.to_s[1..-1]] = instance_variable_get(iv)\n            acc\n          end\n        end\n\n        # This is called to upgrade this V1 config to V2. The parameter given\n        # is the full V2 configuration object, so you can do anything to it\n        # that you want.\n        #\n        # No return value is expected, modifications should be made directly\n        # to the new V2 object.\n        #\n        # @param [V2::Root] new\n        def upgrade(new)\n        end\n\n        # Called after the configuration is finalized and loaded to validate\n        # this object.\n        #\n        # @param [Environment] env Vagrant::Environment object of the\n        #   environment that this configuration has been loaded into. This\n        #   gives you convenient access to things like the root path\n        #   and so on.\n        # @param [ErrorRecorder] errors\n        def validate(env, errors)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v1/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# This file contains all the errors that the V1 plugin interface\n# may throw.\n\nmodule Vagrant\n  module Plugin\n    module V1\n      # Exceptions that can be thrown within the plugin interface all\n      # inherit from this parent exception.\n      class Error < StandardError; end\n\n      # This is thrown when a command name given is invalid.\n      class InvalidCommandName < Error; end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v1/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Plugin\n    module V1\n      # The base class for a guest. A guest represents an installed system\n      # within a machine that Vagrant manages. There are some portions of\n      # Vagrant which are OS-specific such as mounting shared folders and\n      # halting the machine, and this abstraction allows the implementation\n      # for these to be separate from the core of Vagrant.\n      class Guest\n        class BaseError < Errors::VagrantError\n          error_namespace(\"vagrant.guest.base\")\n        end\n\n        include Vagrant::Util\n\n        # The VM which this system is tied to.\n        attr_reader :vm\n\n        # Initializes the system. Any subclasses MUST make sure this\n        # method is called on the parent. Therefore, if a subclass overrides\n        # `initialize`, then you must call `super`.\n        def initialize(vm)\n          @vm = vm\n        end\n\n        # This method is automatically called when the system is available (when\n        # Vagrant can successfully SSH into the machine) to give the system a chance\n        # to determine the distro and return a distro-specific system.\n        #\n        # If this method returns nil, then this instance is assumed to be\n        # the most specific guest implementation.\n        def distro_dispatch\n        end\n\n        # Halt the machine. This method should gracefully shut down the\n        # operating system. This method will cause `vagrant halt` and associated\n        # commands to _block_, meaning that if the machine doesn't halt\n        # in a reasonable amount of time, this method should just return.\n        #\n        # If when this method returns, the machine's state isn't \"powered_off,\"\n        # Vagrant will proceed to forcefully shut the machine down.\n        def halt\n          raise BaseError, _key: :unsupported_halt\n        end\n\n        # Mounts a shared folder.\n        #\n        # This method should create, mount, and properly set permissions\n        # on the shared folder. This method should also properly\n        # adhere to any configuration values such as `shared_folder_uid`\n        # on `config.vm`.\n        #\n        # @param [String] name The name of the shared folder.\n        # @param [String] guestpath The path on the machine which the user\n        #   wants the folder mounted.\n        # @param [Hash] options Additional options for the shared folder\n        #   which can be honored.\n        def mount_shared_folder(name, guestpath, options)\n          raise BaseError, _key: :unsupported_shared_folder\n        end\n\n        # Mounts a shared folder via NFS. This assumes that the exports\n        # via the host are already done.\n        def mount_nfs(ip, folders)\n          raise BaseError, _key: :unsupported_nfs\n        end\n\n        # Configures the given list of networks on the virtual machine.\n        #\n        # The networks parameter will be an array of hashes where the hashes\n        # represent the configuration of a network interface. The structure\n        # of the hash will be roughly the following:\n        #\n        # {\n        #   type:      :static,\n        #   ip:        \"192.168.33.10\",\n        #   netmask:   \"255.255.255.0\",\n        #   interface: 1\n        # }\n        #\n        def configure_networks(networks)\n          raise BaseError, _key: :unsupported_configure_networks\n        end\n\n        # Called to change the hostname of the virtual machine.\n        def change_host_name(name)\n          raise BaseError, _key: :unsupported_host_name\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v1/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Plugin\n    module V1\n      # Base class for a host in Vagrant. A host class contains functionality\n      # that is specific to a specific OS that is running Vagrant. This\n      # abstraction is done because there is some host-specific logic that\n      # Vagrant must do in some cases.\n      class Host\n        # This returns true/false depending on if the current running system\n        # matches the host class.\n        #\n        # @return [Boolean]\n        def self.match?\n          nil\n        end\n\n        # The precedence of the host when checking for matches. This is to\n        # allow certain host such as generic OS's (\"Linux\", \"BSD\", etc.)\n        # to be specified last.\n        #\n        # The hosts with the higher numbers will be checked first.\n        #\n        # If you're implementing a basic host, you can probably ignore this.\n        def self.precedence\n          5\n        end\n\n        # Initializes a new host class.\n        #\n        # The only required parameter is a UI object so that the host\n        # objects have some way to communicate with the outside world.\n        #\n        # @param [UI] ui UI for the hosts to output to.\n        def initialize(ui)\n          @ui = ui\n        end\n\n        # Returns true of false denoting whether or not this host supports\n        # NFS shared folder setup. This method ideally should verify that\n        # NFS is installed.\n        #\n        # @return [Boolean]\n        def nfs?\n          false\n        end\n\n        # Exports the given hash of folders via NFS.\n        #\n        # @param [String] id A unique ID that is guaranteed to be unique to\n        #   match these sets of folders.\n        # @param [String] ip IP of the guest machine.\n        # @param [Hash] folders Shared folders to sync.\n        def nfs_export(id, ip, folders)\n        end\n\n        # Prunes any NFS exports made by Vagrant which aren't in the set\n        # of valid ids given.\n        #\n        # @param [Array<String>] valid_ids Valid IDs that should not be\n        #   pruned.\n        def nfs_prune(valid_ids)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v1/manager.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule Vagrant\n  module Plugin\n    module V1\n      # This class maintains a list of all the registered plugins as well\n      # as provides methods that allow querying all registered components of\n      # those plugins as a single unit.\n      class Manager\n        attr_reader :registered\n\n        def initialize\n          @logger = Log4r::Logger.new(\"vagrant::plugin::v1::manager\")\n          @registered = []\n        end\n\n        # This returns all the registered communicators.\n        #\n        # @return [Hash]\n        def communicators\n          result = {}\n\n          @registered.each do |plugin|\n            result.merge!(plugin.communicator.to_hash)\n          end\n\n          result\n        end\n\n        # This returns all the registered configuration classes.\n        #\n        # @return [Hash]\n        def config\n          result = {}\n\n          @registered.each do |plugin|\n            plugin.config.each do |key, klass|\n              result[key] = klass\n            end\n          end\n\n          result\n        end\n\n        # This returns all the registered configuration classes that were\n        # marked as \"upgrade safe.\"\n        #\n        # @return [Hash]\n        def config_upgrade_safe\n          result = {}\n\n          @registered.each do |plugin|\n            configs = plugin.data[:config_upgrade_safe]\n            if configs\n              configs.each do |key|\n                result[key] = plugin.config.get(key)\n              end\n            end\n          end\n\n          result\n        end\n\n        # This returns all the registered guests.\n        #\n        # @return [Hash]\n        def guests\n          result = {}\n\n          @registered.each do |plugin|\n            result.merge!(plugin.guest.to_hash)\n          end\n\n          result\n        end\n\n        # This returns all registered host classes.\n        #\n        # @return [Hash]\n        def hosts\n          hosts = {}\n\n          @registered.each do |plugin|\n            hosts.merge!(plugin.host.to_hash)\n          end\n\n          hosts\n        end\n\n        # This returns all registered providers.\n        #\n        # @return [Hash]\n        def providers\n          providers = {}\n\n          @registered.each do |plugin|\n            providers.merge!(plugin.provider.to_hash)\n          end\n\n          providers\n        end\n\n        # This registers a plugin. This should _NEVER_ be called by the public\n        # and should only be called from within Vagrant. Vagrant will\n        # automatically register V1 plugins when a name is set on the\n        # plugin.\n        def register(plugin)\n          if !@registered.include?(plugin)\n            @logger.info(\"Registered plugin: #{plugin.name}\")\n            @registered << plugin\n          end\n        end\n\n        # This clears out all the registered plugins. This is only used by\n        # unit tests and should not be called directly.\n        def reset!\n          @registered.clear\n        end\n\n        # This unregisters a plugin so that its components will no longer\n        # be used. Note that this should only be used for testing purposes.\n        def unregister(plugin)\n          if @registered.include?(plugin)\n            @logger.info(\"Unregistered: #{plugin.name}\")\n            @registered.delete(plugin)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v1/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"set\"\nrequire \"log4r\"\n\nmodule Vagrant\n  module Plugin\n    module V1\n      # This is the superclass for all V1 plugins.\n      class Plugin\n        # Special marker that can be used for action hooks that matches\n        # all action sequences.\n        ALL_ACTIONS = :__all_actions__\n\n        # The logger for this class.\n        LOGGER = Log4r::Logger.new(\"vagrant::plugin::v1::plugin\")\n\n        # Set the root class up to be ourself, so that we can reference this\n        # from within methods which are probably in subclasses.\n        ROOT_CLASS = self\n\n        # This returns the manager for all V1 plugins.\n        #\n        # @return [V1::Manager]\n        def self.manager\n          @manager ||= Manager.new\n        end\n\n        # Set the name of the plugin. The moment that this is called, the\n        # plugin will be registered and available. Before this is called, a\n        # plugin does not exist. The name must be unique among all installed\n        # plugins.\n        #\n        # @param [String] name Name of the plugin.\n        # @return [String] The name of the plugin.\n        def self.name(name=UNSET_VALUE)\n          # Get or set the value first, so we have a name for logging when\n          # we register.\n          result = get_or_set(:name, name)\n\n          # The plugin should be registered if we're setting a real name on it\n          Plugin.manager.register(self) if name != UNSET_VALUE\n\n          # Return the result\n          result\n        end\n\n        # Sets a human-friendly description of the plugin.\n        #\n        # @param [String] value Description of the plugin.\n        # @return [String] Description of the plugin.\n        def self.description(value=UNSET_VALUE)\n          get_or_set(:description, value)\n        end\n\n        # Registers a callback to be called when a specific action sequence\n        # is run. This allows plugin authors to hook into things like VM\n        # bootup, VM provisioning, etc.\n        #\n        # @param [Symbol] name Name of the action.\n        # @return [Array] List of the hooks for the given action.\n        def self.action_hook(name, &block)\n          # Get the list of hooks for the given hook name\n          data[:action_hooks] ||= {}\n          hooks = data[:action_hooks][name.to_sym] ||= []\n\n          # Return the list if we don't have a block\n          return hooks if !block_given?\n\n          # Otherwise add the block to the list of hooks for this action.\n          hooks << block\n        end\n\n        # Defines additional command line commands available by key. The key\n        # becomes the subcommand, so if you register a command \"foo\" then\n        # \"vagrant foo\" becomes available.\n        #\n        # @param [String] name Subcommand key.\n        def self.command(name=UNSET_VALUE, &block)\n          data[:command] ||= Registry.new\n\n          if name != UNSET_VALUE\n            # Validate the name of the command\n            if name.to_s !~ /^[-a-z0-9]+$/i\n              raise InvalidCommandName, \"Commands can only contain letters, numbers, and hyphens\"\n            end\n\n            # Register a new command class only if a name was given.\n            data[:command].register(name.to_sym, &block)\n          end\n\n          # Return the registry\n          data[:command]\n        end\n\n        # Defines additional communicators to be available. Communicators\n        # should be returned by a block passed to this method. This is done\n        # to ensure that the class is lazy loaded, so if your class inherits\n        # from or uses any Vagrant internals specific to Vagrant 1.0, then\n        # the plugin can still be defined without breaking anything in future\n        # versions of Vagrant.\n        #\n        # @param [String] name Communicator name.\n        def self.communicator(name=UNSET_VALUE, &block)\n          data[:communicator] ||= Registry.new\n\n          # Register a new communicator class only if a name was given.\n          data[:communicator].register(name.to_sym, &block) if name != UNSET_VALUE\n\n          # Return the registry\n          data[:communicator]\n        end\n\n        # Defines additional configuration keys to be available in the\n        # Vagrantfile. The configuration class should be returned by a\n        # block passed to this method. This is done to ensure that the class\n        # is lazy loaded, so if your class inherits from any classes that\n        # are specific to Vagrant 1.0, then the plugin can still be defined\n        # without breaking anything in future versions of Vagrant.\n        #\n        # @param [String] name Configuration key.\n        # @param [Boolean] upgrade_safe If this is true, then this configuration\n        #   key is safe to load during an upgrade, meaning that it depends\n        #   on NO Vagrant internal classes. Do _not_ set this to true unless\n        #   you really know what you're doing, since you can cause Vagrant\n        #   to crash (although Vagrant will output a user-friendly error\n        #   message if this were to happen).\n        def self.config(name=UNSET_VALUE, upgrade_safe=false, &block)\n          data[:config] ||= Registry.new\n\n          # Register a new config class only if a name was given.\n          if name != UNSET_VALUE\n            data[:config].register(name.to_sym, &block)\n\n            # If we were told this is an upgrade safe configuration class\n            # then we add it to the set.\n            if upgrade_safe\n              data[:config_upgrade_safe] ||= Set.new\n              data[:config_upgrade_safe].add(name.to_sym)\n            end\n          end\n\n          # Return the registry\n          data[:config]\n        end\n\n        # Defines an additionally available guest implementation with\n        # the given key.\n        #\n        # @param [String] name Name of the guest.\n        def self.guest(name=UNSET_VALUE, &block)\n          data[:guests] ||= Registry.new\n\n          # Register a new guest class only if a name was given\n          data[:guests].register(name.to_sym, &block) if name != UNSET_VALUE\n\n          # Return the registry\n          data[:guests]\n        end\n\n        # Defines an additionally available host implementation with\n        # the given key.\n        #\n        # @param [String] name Name of the host.\n        def self.host(name=UNSET_VALUE, &block)\n          data[:hosts] ||= Registry.new\n\n          # Register a new host class only if a name was given\n          data[:hosts].register(name.to_sym, &block) if name != UNSET_VALUE\n\n          # Return the registry\n          data[:hosts]\n        end\n\n        # Registers additional providers to be available.\n        #\n        # @param [Symbol] name Name of the provider.\n        def self.provider(name=UNSET_VALUE, &block)\n          data[:providers] ||= Registry.new\n\n          # Register a new provider class only if a name was given\n          data[:providers].register(name.to_sym, &block) if name != UNSET_VALUE\n\n          # Return the registry\n          data[:providers]\n        end\n\n        # Registers additional provisioners to be available.\n        #\n        # @param [String] name Name of the provisioner.\n        def self.provisioner(name=UNSET_VALUE, &block)\n          data[:provisioners] ||= Registry.new\n\n          # Register a new provisioner class only if a name was given\n          data[:provisioners].register(name.to_sym, &block) if name != UNSET_VALUE\n\n          # Return the registry\n          data[:provisioners]\n        end\n\n        # Returns the internal data associated with this plugin. This\n        # should NOT be called by the general public.\n        #\n        # @return [Hash]\n        def self.data\n          @data ||= {}\n        end\n\n        protected\n\n        # Sentinel value denoting that a value has not been set.\n        UNSET_VALUE = Object.new\n\n        # Helper method that will set a value if a value is given, or otherwise\n        # return the already set value.\n        #\n        # @param [Symbol] key Key for the data\n        # @param [Object] value Value to store.\n        # @return [Object] Stored value.\n        def self.get_or_set(key, value=UNSET_VALUE)\n          # If no value is to be set, then return the value we have already set\n          return data[key] if value.eql?(UNSET_VALUE)\n\n          # Otherwise set the value\n          data[key] = value\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v1/provider.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Plugin\n    module V1\n      # This is the base class for a provider for the V1 API. A provider\n      # is responsible for creating compute resources to match the needs\n      # of a Vagrant-configured system.\n      class Provider\n        # Initialize the provider to represent the given machine.\n        #\n        # @param [Vagrant::Machine] machine The machine that this provider\n        #   is responsible for.\n        def initialize(machine)\n        end\n\n        # This should return an action callable for the given name.\n        #\n        # @param [Symbol] name Name of the action.\n        # @return [Object] A callable action sequence object, whether it\n        #   is a proc, object, etc.\n        def action(name)\n          nil\n        end\n\n        # This method is called if the underlying machine ID changes. Providers\n        # can use this method to load in new data for the actual backing\n        # machine or to realize that the machine is now gone (the ID can\n        # become `nil`). No parameters are given, since the underlying machine\n        # is simply the machine instance given to this object. And no\n        # return value is necessary.\n        def machine_id_changed\n        end\n\n        # This should return a hash of information that explains how to\n        # SSH into the machine. If the machine is not at a point where\n        # SSH is even possible, then `nil` should be returned.\n        #\n        # The general structure of this returned hash should be the\n        # following:\n        #\n        #     {\n        #       host: \"1.2.3.4\",\n        #       port: \"22\",\n        #       username: \"mitchellh\",\n        #       private_key_path: \"/path/to/my/key\"\n        #     }\n        #\n        # **Note:** Vagrant only supports private key based authentication,\n        # mainly for the reason that there is no easy way to exec into an\n        # `ssh` prompt with a password, whereas we can pass a private key\n        # via commandline.\n        #\n        # @return [Hash] SSH information. For the structure of this hash\n        #   read the accompanying documentation for this method.\n        def ssh_info\n          nil\n        end\n\n        # This should return the state of the machine within this provider.\n        # The state can be any symbol.\n        #\n        # @return [Symbol]\n        def state\n          nil\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v1/provisioner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Plugin\n    module V1\n      # This is the base class for a provisioner for the V1 API. A provisioner\n      # is primarily responsible for installing software on a Vagrant guest.\n      class Provisioner\n        # The environment which provisioner is running in. This is the\n        # action environment, not a Vagrant::Environment.\n        attr_reader :env\n\n        # The configuration for this provisioner. This will be an instance of\n        # the `Config` class which is part of the provisioner.\n        attr_reader :config\n\n        def initialize(env, config)\n          @env    = env\n          @config = config\n        end\n\n        # This method is expected to return a class that is used for\n        # configuring the provisioner. This return value is expected to be\n        # a subclass of {Config}.\n        #\n        # @return [Config]\n        def self.config_class\n        end\n\n        # This is the method called to \"prepare\" the provisioner. This is called\n        # before any actions are run by the action runner (see {Vagrant::Actions::Runner}).\n        # This can be used to setup shared folders, forward ports, etc. Whatever is\n        # necessary on a \"meta\" level.\n        #\n        # No return value is expected.\n        def prepare\n        end\n\n        # This is the method called to provision the system. This method\n        # is expected to do whatever necessary to provision the system (create files,\n        # SSH, etc.)\n        def provision!\n        end\n\n        # This is the method called to when the system is being destroyed\n        # and allows the provisioners to engage in any cleanup tasks necessary.\n        def cleanup\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v1.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"vagrant/plugin/v1/errors\"\n\nmodule Vagrant\n  module Plugin\n    module V1\n      autoload :Command, \"vagrant/plugin/v1/command\"\n      autoload :Communicator, \"vagrant/plugin/v1/communicator\"\n      autoload :Config, \"vagrant/plugin/v1/config\"\n      autoload :Guest,  \"vagrant/plugin/v1/guest\"\n      autoload :Host,   \"vagrant/plugin/v1/host\"\n      autoload :Manager, \"vagrant/plugin/v1/manager\"\n      autoload :Plugin, \"vagrant/plugin/v1/plugin\"\n      autoload :Provider, \"vagrant/plugin/v1/provider\"\n      autoload :Provisioner, \"vagrant/plugin/v1/provisioner\"\n\n      # Errors\n      autoload :Error, \"vagrant/plugin/v1/error\"\n      autoload :InvalidCommandName, \"vagrant/plugin/v1/error\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'log4r'\nrequire \"vagrant/util/safe_puts\"\n\nmodule Vagrant\n  module Plugin\n    module V2\n      # This is the base class for a CLI command.\n      class Command\n        include Util::SafePuts\n\n        # This should return a brief (60 characters or less) synopsis of what\n        # this command does. It will be used in the output of the help.\n        #\n        # @return [String]\n        def self.synopsis\n          \"\"\n        end\n\n        def initialize(argv, env)\n          @argv = argv\n          @env  = env\n          @logger = Log4r::Logger.new(\"vagrant::command::#{self.class.to_s.downcase}\")\n        end\n\n        # This is what is called on the class to actually execute it. Any\n        # subclasses should implement this method and do any option parsing\n        # and validation here.\n        def execute\n        end\n\n        protected\n\n        # Parses the options given an OptionParser instance.\n        #\n        # This is a convenience method that properly handles duping the\n        # originally argv array so that it is not destroyed.\n        #\n        # This method will also automatically detect \"-h\" and \"--help\"\n        # and print help. And if any invalid options are detected, the help\n        # will be printed, as well.\n        #\n        # If this method returns `nil`, then you should assume that help\n        # was printed and parsing failed.\n        def parse_options(opts=nil)\n          # make sure optparse doesn't use POSIXLY_CORRECT parsing\n          ENV[\"POSIXLY_CORRECT\"] = nil\n\n          # Creating a shallow copy of the arguments so the OptionParser\n          # doesn't destroy the originals.\n          argv = @argv.dup\n\n          # Default opts to a blank optionparser if none is given\n          opts ||= Vagrant::OptionParser.new\n\n          # Add the help option, which must be on every command.\n          opts.on_tail(\"-h\", \"--help\", \"Print this help\") do\n            safe_puts(opts.help)\n            return nil\n          end\n\n          opts.parse!(argv)\n          return argv\n        rescue OptionParser::InvalidOption, OptionParser::MissingArgument, OptionParser::AmbiguousOption\n          raise Errors::CLIInvalidOptions, help: opts.help.chomp\n        end\n\n        # Yields a VM for each target VM for the command.\n        #\n        # This is a convenience method for easily implementing methods that\n        # take a target VM (in the case of multi-VM) or every VM if no\n        # specific VM name is specified.\n        #\n        # @param [String] name The name of the VM. Nil if every VM.\n        # @param [Hash] options Additional tweakable settings.\n        # @option options [Symbol] :provider The provider to back the\n        #   machines with. All machines will be backed with this\n        #   provider. If none is given, a sensible default is chosen.\n        # @option options [Boolean] :reverse If true, the resulting order\n        #   of machines is reversed.\n        # @option options [Boolean] :single_target If true, then an\n        #   exception will be raised if more than one target is found.\n        def with_target_vms(names=nil, options=nil)\n          @logger.debug(\"Getting target VMs for command. Arguments:\")\n          @logger.debug(\" -- names: #{names.inspect}\")\n          @logger.debug(\" -- options: #{options.inspect}\")\n\n          # Setup the options hash\n          options ||= {}\n\n          # Require that names be an array\n          names ||= []\n          names = [names] if !names.is_a?(Array)\n\n          # Determine if we require a local Vagrant environment. There are\n          # two cases that we require a local environment:\n          #\n          #   * We're asking for ANY/EVERY VM (no names given).\n          #\n          #   * We're asking for specific VMs, at least once of which\n          #     is NOT in the local machine index.\n          #\n          requires_local_env = false\n          requires_local_env = true if names.empty?\n          requires_local_env ||= names.any? { |n|\n            !@env.machine_index.include?(n)\n          }\n          raise Errors::NoEnvironmentError if requires_local_env && !@env.root_path\n\n          @logger.info(\"getting active machines\")\n          # Cache the active machines outside the loop\n          active_machines = @env.active_machines\n\n          # This is a helper that gets a single machine with the proper\n          # provider. The \"proper provider\" in this case depends on what was\n          # given:\n          #\n          #   * If a provider was explicitly specified, then use that provider.\n          #     But if an active machine exists with a DIFFERENT provider,\n          #     then throw an error (for now), since we don't yet support\n          #     bringing up machines with different providers.\n          #\n          #   * If no provider was specified, then use the active machine's\n          #     provider if it exists, otherwise use the default provider.\n          #\n          get_machine = lambda do |name|\n            # Check for an active machine with the same name\n            provider_to_use = options[:provider]\n            provider_to_use = provider_to_use.to_sym if provider_to_use\n\n            # If we have this machine in our index, load that.\n            entry = @env.machine_index.get(name.to_s)\n            if entry\n              @env.machine_index.release(entry)\n\n              # Create an environment for this location and yield the\n              # machine in that environment. We silence warnings here because\n              # Vagrantfiles often have constants, so people would otherwise\n              # constantly (heh) get \"already initialized constant\" warnings.\n              begin\n                env = entry.vagrant_env(\n                  @env.home_path, ui_class: @env.ui_class)\n              rescue Vagrant::Errors::EnvironmentNonExistentCWD\n                # This means that this environment working directory\n                # no longer exists, so delete this entry.\n                entry = @env.machine_index.get(name.to_s)\n                @env.machine_index.delete(entry) if entry\n                raise\n              end\n\n              next env.machine(entry.name.to_sym, entry.provider.to_sym)\n            end\n\n            active_machines.each do |active_name, active_provider|\n              if name == active_name\n                # We found an active machine with the same name\n\n                if provider_to_use && provider_to_use != active_provider\n                  # We found an active machine with a provider that doesn't\n                  # match the requested provider. Show an error.\n                  raise Errors::ActiveMachineWithDifferentProvider,\n                    name: active_name.to_s,\n                    active_provider: active_provider.to_s,\n                    requested_provider: provider_to_use.to_s\n                else\n                  # Use this provider and exit out of the loop. One of the\n                  # invariants [for now] is that there shouldn't be machines\n                  # with multiple providers.\n                  @logger.info(\"Active machine found with name #{active_name}. \" +\n                               \"Using provider: #{active_provider}\")\n                  provider_to_use = active_provider\n                  break\n                end\n              end\n            end\n\n            # Use the default provider if nothing else\n            provider_to_use ||= @env.default_provider(machine: name)\n\n            # Get the right machine with the right provider\n            @env.machine(name, provider_to_use)\n          end\n\n          # First determine the proper array of VMs.\n          machines = []\n          if names.length > 0\n            names.each do |name|\n              if pattern = name[/^\\/(.+?)\\/$/, 1]\n                @logger.debug(\"Finding machines that match regex: #{pattern}\")\n\n                # This is a regular expression name, so we convert to a regular\n                # expression and allow that sort of matching.\n                regex = Regexp.new(pattern)\n\n                @env.machine_names.each do |machine_name|\n                  if machine_name =~ regex\n                    machines << get_machine.call(machine_name)\n                  end\n                end\n\n                raise Errors::VMNoMatchError if machines.empty?\n              else\n                # String name, just look for a specific VM\n                @logger.debug(\"Finding machine that match name: #{name}\")\n                machines << get_machine.call(name.to_sym)\n                raise Errors::VMNotFoundError, name: name if !machines[0]\n              end\n            end\n          else\n            # No name was given, so we return every VM in the order\n            # configured.\n            @logger.debug(\"Loading all machines...\")\n            machines = @env.machine_names.map do |machine_name|\n              get_machine.call(machine_name)\n            end\n          end\n\n          @logger.debug(\"have machine list to process\")\n\n          # Make sure we're only working with one VM if single target\n          if options[:single_target] && machines.length != 1\n            @logger.debug(\"Using primary machine since single target\")\n            primary_name = @env.primary_machine_name\n            raise Errors::MultiVMTargetRequired if !primary_name\n            machines = [get_machine.call(primary_name)]\n          end\n\n          # If we asked for reversed ordering, then reverse it\n          machines.reverse! if options[:reverse]\n\n          # Go through each VM and yield it!\n          color_order = [:default]\n          color_index = 0\n\n          machines.each do |machine|\n            if (machine.state && machine.state.id != :not_created &&\n                !machine.index_uuid.nil? && !@env.machine_index.include?(machine.index_uuid))\n              machine.recover_machine(machine.state.id)\n            end\n\n            # Set the machine color\n            machine.ui.opts[:color] = color_order[color_index % color_order.length]\n            color_index += 1\n\n            @logger.info(\"With machine: #{machine.name} (#{machine.provider.inspect})\")\n            yield machine\n\n            # Call the state method so that we update our index state. Don't\n            # worry about exceptions here, since we just care about updating\n            # the cache.\n            begin\n              # Called for side effects\n              machine.state\n            rescue Errors::VagrantError\n            end\n          end\n        end\n\n        # This method will split the argv given into three parts: the\n        # flags to this command, the subcommand, and the flags to the\n        # subcommand. For example:\n        #\n        #     -v status -h -v\n        #\n        # The above would yield 3 parts:\n        #\n        #     [\"-v\"]\n        #     \"status\"\n        #     [\"-h\", \"-v\"]\n        #\n        # These parts are useful because the first is a list of arguments\n        # given to the current command, the second is a subcommand, and the\n        # third are the commands given to the subcommand.\n        #\n        # @return [Array] The three parts.\n        def split_main_and_subcommand(argv)\n          # Initialize return variables\n          main_args   = nil\n          sub_command = nil\n          sub_args    = []\n\n          # We split the arguments into two: One set containing any\n          # flags before a word, and then the rest. The rest are what\n          # get actually sent on to the subcommand.\n          argv.each_index do |i|\n            if !argv[i].start_with?(\"-\")\n              # We found the beginning of the sub command. Split the\n              # args up.\n              main_args   = argv[0, i]\n              sub_command = argv[i]\n              sub_args    = argv[i + 1, argv.length - i + 1]\n\n              # Break so we don't find the next non flag and shift our\n              # main args.\n              break\n            end\n          end\n\n          # Handle the case that argv was empty or didn't contain any subcommand\n          main_args = argv.dup if main_args.nil?\n\n          return [main_args, sub_command, sub_args]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/communicator.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"timeout\"\n\nmodule Vagrant\n  module Plugin\n    module V2\n      # Base class for a communicator in Vagrant. A communicator is\n      # responsible for communicating with a machine in some way. There\n      # are various stages of Vagrant that require things such as uploading\n      # files to the machine, executing shell commands, etc. Implementors\n      # of this class are expected to provide this functionality in some\n      # way.\n      #\n      # Note that a communicator must provide **all** of the methods\n      # in this base class. There is currently no way for one communicator\n      # to provide say a more efficient way of uploading a file, but not\n      # provide shell execution. This sort of thing will come in a future\n      # version.\n      class Communicator\n        # This returns true/false depending on if the given machine\n        # can be communicated with using this communicator. If this returns\n        # `true`, then this class will be used as the primary communication\n        # method for the machine.\n        #\n        # @return [Boolean]\n        def self.match?(machine)\n          true\n        end\n\n        # Initializes the communicator with the machine that we will be\n        # communicating with. This base method does nothing (it doesn't\n        # even store the machine in an instance variable for you), so you're\n        # expected to override this and do something with the machine if\n        # you care about it.\n        #\n        # @param [Machine] machine The machine this instance is expected to\n        #   communicate with.\n        def initialize(machine)\n        end\n\n        # Checks if the target machine is ready for communication. If this\n        # returns true, then all the other methods for communicating with\n        # the machine are expected to be functional.\n        #\n        # @return [Boolean]\n        def ready?\n          false\n        end\n\n        # wait_for_ready waits until the communicator is ready, blocking\n        # until then. It will wait up to the given duration or raise an\n        # exception if something goes wrong.\n        #\n        # @param [Integer] duration Timeout in seconds.\n        # @return [Boolean] Will return true on successful connection\n        #   or false on timeout.\n        def wait_for_ready(duration)\n          # By default, we implement a naive solution.\n          begin\n            Timeout.timeout(duration) do\n              while true\n                return true if ready?\n                sleep 0.5\n              end\n            end\n          rescue Timeout::Error\n            # We timed out, we failed.\n          end\n\n          return false\n        end\n\n        # Download a file from the remote machine to the local machine.\n        #\n        # @param [String] from Path of the file on the remote machine.\n        # @param [String] to Path of where to save the file locally.\n        def download(from, to)\n        end\n\n        # Upload a file to the remote machine.\n        #\n        # @param [String] from Path of the file locally to upload.\n        # @param [String] to Path of where to save the file on the remote\n        #   machine.\n        def upload(from, to)\n        end\n\n        # Execute a command on the remote machine. The exact semantics\n        # of this method are up to the implementor, but in general the\n        # users of this class will expect this to be a shell.\n        #\n        # This method gives you no way to write data back to the remote\n        # machine, so only execute commands that don't expect input.\n        #\n        # @param [String] command Command to execute.\n        # @yield [type, data] Realtime output of the command being executed.\n        # @yieldparam [String] type Type of the output. This can be\n        #   `:stdout`, `:stderr`, etc. The exact types are up to the\n        #   implementor.\n        # @yieldparam [String] data Data for the given output.\n        # @return [Integer] Exit code of the command.\n        def execute(command, opts=nil)\n        end\n\n        # Executes a command on the remote machine with administrative\n        # privileges. See {#execute} for documentation, as the API is the\n        # same.\n        #\n        # @see #execute\n        def sudo(command, opts=nil)\n        end\n\n        # Executes a command and returns true if the command succeeded,\n        # and false otherwise. By default, this executes as a normal user,\n        # and it is up to the communicator implementation if they expose an\n        # option for running tests as an administrator.\n        #\n        # @see #execute\n        def test(command, opts=nil)\n        end\n\n        # Reset the communicator. For communicators which establish\n        # a persistent connection to the remote machine, this connection\n        # should be terminated and re-established. The communicator\n        # instance should be in a \"fresh\" state after calling this method.\n        def reset!\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/components.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Plugin\n    module V2\n      # This is the container class for the components of a single plugin.\n      # This allows us to separate the plugin class which defines the\n      # components, and the actual container of those components. This\n      # removes a bit of state overhead from the plugin class itself.\n      class Components\n        # This contains all the action hooks.\n        #\n        # @return [Hash<Symbol, Array>]\n        attr_reader :action_hooks\n\n        # This contains all the command plugins by name, and returns\n        # the command class and options. The command class is wrapped\n        # in a Proc so that it can be lazy loaded.\n        #\n        # @return [Registry<Symbol, Array<Proc, Hash>>]\n        attr_reader :commands\n\n        # This contains all the configuration plugins by scope.\n        #\n        # @return [Hash<Symbol, Registry>]\n        attr_reader :configs\n\n        # This contains all the guests and their parents.\n        #\n        # @return [Registry<Symbol, Array<Class, Symbol>>]\n        attr_reader :guests\n\n        # This contains all the registered guest capabilities.\n        #\n        # @return [Hash<Symbol, Registry>]\n        attr_reader :guest_capabilities\n\n        # This contains all the hosts and their parents.\n        #\n        # @return [Registry<Symbol, Array<Class, Symbol>>]\n        attr_reader :hosts\n\n        # This contains all the registered host capabilities.\n        #\n        # @return [Hash<Symbol, Registry>]\n        attr_reader :host_capabilities\n\n        # This contains all the provider plugins by name, and returns\n        # the provider class and options.\n        #\n        # @return [Hash<Symbol, Registry>]\n        attr_reader :providers\n\n        # This contains all the registered provider capabilities.\n        #\n        # @return [Hash<Symbol, Registry>]\n        attr_reader :provider_capabilities\n\n        # This contains all the push implementations by name.\n        #\n        # @return [Registry<Symbol, Array<Class, Hash>>]\n        attr_reader :pushes\n\n        # This contains all the synced folder implementations by name.\n        #\n        # @return [Registry<Symbol, Array<Class, Integer>>]\n        attr_reader :synced_folders\n\n        # This contains all the registered synced folder capabilities.\n        #\n        # @return [Hash<Symbol, Registry>]\n        attr_reader :synced_folder_capabilities\n\n        def initialize\n          # The action hooks hash defaults to []\n          @action_hooks = Hash.new { |h, k| h[k] = [] }\n\n          @commands = Registry.new\n          @configs = Hash.new { |h, k| h[k] = Registry.new }\n          @guests  = Registry.new\n          @guest_capabilities = Hash.new { |h, k| h[k] = Registry.new }\n          @hosts   = Registry.new\n          @host_capabilities = Hash.new { |h, k| h[k] = Registry.new }\n          @providers = Registry.new\n          @provider_capabilities = Hash.new { |h, k| h[k] = Registry.new }\n          @pushes = Registry.new\n          @synced_folders = Registry.new\n          @synced_folder_capabilities = Hash.new { |h, k| h[k] = Registry.new }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"set\"\n\nmodule Vagrant\n  module Plugin\n    module V2\n      # This is the base class for a configuration key defined for\n      # V2. Any configuration key plugins for V2 should inherit from this\n      # class.\n      class Config\n        # This constant represents an unset value. This is useful so it is\n        # possible to know the difference between a configuration value that\n        # was never set, and a value that is nil (explicitly). Best practice\n        # is to initialize all variables to this value, then the {#merge}\n        # method below will \"just work\" in many cases.\n\n        UNSET_VALUE = :__UNSET__VALUE__\n\n        # This is called as a last-minute hook that allows the configuration\n        # object to finalize itself before it will be put into use. This is\n        # a useful place to do some defaults in the case the user didn't\n        # configure something or so on.\n        #\n        # An example of where this sort of thing is used or has been used:\n        # the \"vm\" configuration key uses this to make sure that at least\n        # one sub-VM has been defined: the default VM.\n        #\n        # The configuration object is expected to mutate itself.\n        def finalize!\n          # Default implementation is to do nothing.\n        end\n\n        # Merge another configuration object into this one. This assumes that\n        # the other object is the same class as this one. This should not\n        # mutate this object, but instead should return a new, merged object.\n        #\n        # The default implementation will simply iterate over the instance\n        # variables and merge them together, with this object overriding\n        # any conflicting instance variables of the older object. Instance\n        # variables starting with \"__\" (double underscores) will be ignored.\n        # This lets you set some sort of instance-specific state on your\n        # configuration keys without them being merged together later.\n        #\n        # @param [Object] other The other configuration object to merge from,\n        #   this must be the same type of object as this one.\n        # @return [Object] The merged object.\n        def merge(other)\n          result = self.class.new\n\n          # Set all of our instance variables on the new class\n          [self, other].each do |obj|\n            obj.instance_variables.each do |key|\n              # Ignore keys that start with a double underscore. This allows\n              # configuration classes to still hold around internal state\n              # that isn't propagated.\n              if !key.to_s.start_with?(\"@__\")\n                # Don't set the value if it is the unset value, either.\n                value = obj.instance_variable_get(key)\n                result.instance_variable_set(key, value) if value != UNSET_VALUE\n              end\n            end\n          end\n\n          # Persist through the set of invalid methods\n          this_invalid  = @__invalid_methods || Set.new\n          other_invalid = other.instance_variable_get(:\"@__invalid_methods\") || Set.new\n          result.instance_variable_set(:\"@__invalid_methods\", this_invalid + other_invalid)\n\n          result\n        end\n\n        # Capture all bad configuration calls and save them for an error\n        # message later during validation.\n        def method_missing(name, *args, &block)\n          return super if @__finalized\n\n          # There are a few scenarios where ruby will attempt to implicity\n          # coerce a given object into a certain type. Configs can end up\n          # in some of these scenarios when they're being shipped around in\n          # callbacks with splats. If method_missing allows these methods to be\n          # called but continues to return Config back, Ruby will raise a\n          # TypeError. Doing the normal thing of raising NoMethodError allows\n          # Config to behave normally as its being passed through splats.\n          #\n          # For a bit more detail and some keywords for further searching, see:\n          # https://ruby-doc.org/core-2.7.2/doc/implicit_conversion_rdoc.html\n          if [:to_hash, :to_ary].include?(name)\n            return super\n          end\n\n          name = name.to_s\n          name = name[0...-1] if name.end_with?(\"=\")\n\n          @__invalid_methods ||= Set.new\n          @__invalid_methods.add(name)\n\n          # Return the dummy object so that anything else works\n          ::Vagrant::Config::V2::DummyConfig.new\n        end\n\n        # Allows setting options from a hash. By default this simply calls\n        # the `#{key}=` method on the config class with the value, which is\n        # the expected behavior most of the time.\n        #\n        # This is expected to mutate itself.\n        #\n        # @param [Hash] options A hash of options to set on this configuration\n        #   key.\n        def set_options(options)\n          options.each do |key, value|\n            send(\"#{key}=\", value)\n          end\n        end\n\n        # Converts this configuration object to JSON.\n        def to_json(*a)\n          instance_variables_hash.to_json(*a)\n        end\n\n        # A default to_s implementation.\n        def to_s\n          self.class.to_s\n        end\n\n        # Returns the instance variables as a hash of key-value pairs.\n        def instance_variables_hash\n          instance_variables.inject({}) do |acc, iv|\n            acc[iv.to_s[1..-1]] = instance_variable_get(iv)\n            acc\n          end\n        end\n\n        # Called after the configuration is finalized and loaded to validate\n        # this object.\n        #\n        # @param [Machine] machine Access to the machine that is being\n        #   validated.\n        # @return [Hash]\n        def validate(machine)\n          return { self.to_s => _detected_errors }\n        end\n\n        # This returns any automatically detected errors.\n        #\n        # @return [Array<String>]\n        def _detected_errors\n          return [] if !@__invalid_methods || @__invalid_methods.empty?\n          return [I18n.t(\"vagrant.config.common.bad_field\",\n                         fields: @__invalid_methods.to_a.sort.join(\", \"))]\n        end\n\n        # An internal finalize call that no subclass should override.\n        def _finalize!\n          @__finalized = true\n        end\n\n        def clean_up_config_object(config)\n          # Remote variables that are internal\n          config.delete_if{|k,_| k.start_with?(\"_\") }\n          config\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# This file contains all the errors that the V2 plugin interface\n# may throw.\n\nmodule Vagrant\n  module Plugin\n    module V2\n      # Exceptions that can be thrown within the plugin interface all\n      # inherit from this parent exception.\n      class Error < StandardError; end\n\n      # This is thrown when a command name given is invalid.\n      class InvalidCommandName < Error; end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Plugin\n    module V2\n      # A base class for a guest OS. A guest OS is responsible for detecting\n      # that the guest operating system running within the machine. The guest\n      # can then be extended with various \"guest capabilities\" which are their\n      # own form of plugin.\n      #\n      # The guest class itself is only responsible for detecting itself,\n      # and may provide helpers for the capabilities.\n      class Guest\n        # This method is called when the machine is booted and has communication\n        # capabilities in order to detect whether this guest operating system\n        # is running within the machine.\n        #\n        # @return [Boolean]\n        def detect?(machine)\n          false\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Plugin\n    module V2\n      # Base class for a host in Vagrant. A host class contains functionality\n      # that is specific to a specific OS that is running Vagrant. This\n      # abstraction is done because there is some host-specific logic that\n      # Vagrant must do in some cases.\n      class Host\n        # This returns true/false depending on if the current running system\n        # matches the host class.\n        #\n        # @return [Boolean]\n        def detect?(env)\n          false\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/manager.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule Vagrant\n  module Plugin\n    module V2\n      # This class maintains a list of all the registered plugins as well\n      # as provides methods that allow querying all registered components of\n      # those plugins as a single unit.\n      class Manager\n        attr_reader :registered\n\n        def initialize\n          @logger = Log4r::Logger.new(\"vagrant::plugin::v2::manager\")\n          @registered = []\n        end\n\n        # This returns all the action hooks.\n        #\n        # @return [Array]\n        def action_hooks(hook_name)\n          result = []\n\n          @registered.each do |plugin|\n            result += plugin.components.action_hooks[Plugin::ALL_ACTIONS]\n            result += plugin.components.action_hooks[hook_name]\n          end\n\n          result\n        end\n\n        # Find all hooks that are applicable for the given key. This\n        # lookup does not include hooks which are defined for ALL_ACTIONS.\n        # Key lookups will match on either string or symbol values. The\n        # provided keys is broken down into multiple parts for lookups,\n        # which allows defining hooks with an entire namespaced name,\n        # or a short suffx. For example:\n        #\n        #  Assume we are given an action class\n        #    key = Vagrant::Action::Builtin::SyncedFolders\n        #\n        #  The list of keys that will be checked for hooks:\n        #    [\"Vagrant::Action::Builtin::SyncedFolders\", \"vagrant_action_builtin_synced_folders\",\n        #     \"Action::Builtin::SyncedFolders\", \"action_builtin_synced_folders\",\n        #     \"Builtin::SyncedFolders\", \"builtin_synced_folders\",\n        #     \"SyncedFolders\", \"synced_folders\"]\n        #\n        # @param key [Class, String] key Key for hook lookups\n        # @return [Array<Proc>]\n        def find_action_hooks(key)\n          result = []\n\n          generate_hook_keys(key).each do |k|\n            @registered.each do |plugin|\n              result += plugin.components.action_hooks[k]\n              result += plugin.components.action_hooks[k.to_sym]\n            end\n          end\n\n          result\n        end\n\n        # Generate all valid lookup keys for given key\n        #\n        # @param [Class, String] key Base key for generation\n        # @return [Array<String>] all valid keys\n        def generate_hook_keys(key)\n          if key.is_a?(Class)\n            key = key.name.to_s\n          else\n            key = key.to_s\n          end\n          parts = key.split(\"::\")\n          [].tap do |keys|\n            until parts.empty?\n              x = parts.join(\"::\")\n              keys << x\n              y = x.gsub(/([a-z])([A-Z])/, '\\1_\\2').gsub('::', '_').downcase\n              keys << y if x != y\n              parts.shift\n            end\n          end\n        end\n\n        # This returns all the registered commands.\n        #\n        # @return [Registry<Symbol, Array<Proc, Hash>>]\n        def commands\n          Registry.new.tap do |result|\n            @registered.each do |plugin|\n              result.merge!(plugin.components.commands)\n            end\n          end\n        end\n\n        # This returns all the registered communicators.\n        #\n        # @return [Hash]\n        def communicators\n          Registry.new.tap do |result|\n            @registered.each do |plugin|\n              result.merge!(plugin.communicator)\n            end\n          end\n        end\n\n        # This returns all the registered configuration classes.\n        #\n        # @return [Hash]\n        def config\n          Registry.new.tap do |result|\n            @registered.each do |plugin|\n              result.merge!(plugin.components.configs[:top])\n            end\n          end\n        end\n\n        # This returns all the registered guests.\n        #\n        # @return [Hash]\n        def guests\n          Registry.new.tap do |result|\n            @registered.each do |plugin|\n              result.merge!(plugin.components.guests)\n            end\n          end\n        end\n\n        # This returns all the registered guest capabilities.\n        #\n        # @return [Hash]\n        def guest_capabilities\n          results = Hash.new { |h, k| h[k] = Registry.new }\n\n          @registered.each do |plugin|\n            plugin.components.guest_capabilities.each do |guest, caps|\n              results[guest].merge!(caps)\n            end\n          end\n\n          results\n        end\n\n        # This returns all the registered guests.\n        #\n        # @return [Hash]\n        def hosts\n          Registry.new.tap do |result|\n            @registered.each do |plugin|\n              result.merge!(plugin.components.hosts)\n            end\n          end\n        end\n\n        # This returns all the registered host capabilities.\n        #\n        # @return [Hash]\n        def host_capabilities\n          results = Hash.new { |h, k| h[k] = Registry.new }\n\n          @registered.each do |plugin|\n            plugin.components.host_capabilities.each do |host, caps|\n              results[host].merge!(caps)\n            end\n          end\n\n          results\n        end\n\n        # This returns all registered providers.\n        #\n        # @return [Hash]\n        def providers\n          Registry.new.tap do |result|\n            @registered.each do |plugin|\n              result.merge!(plugin.components.providers)\n            end\n          end\n        end\n\n        # This returns all the registered provider capabilities.\n        #\n        # @return [Hash]\n        def provider_capabilities\n          results = Hash.new { |h, k| h[k] = Registry.new }\n\n          @registered.each do |plugin|\n            plugin.components.provider_capabilities.each do |provider, caps|\n              results[provider].merge!(caps)\n            end\n          end\n\n          results\n        end\n\n        # This returns all the config classes for the various providers.\n        #\n        # @return [Hash]\n        def provider_configs\n          Registry.new.tap do |result|\n            @registered.each do |plugin|\n              result.merge!(plugin.components.configs[:provider])\n            end\n          end\n        end\n\n        # This returns all the config classes for the various provisioners.\n        #\n        # @return [Registry]\n        def provisioner_configs\n          Registry.new.tap do |result|\n            @registered.each do |plugin|\n              result.merge!(plugin.components.configs[:provisioner])\n            end\n          end\n        end\n\n        # This returns all registered provisioners.\n        #\n        # @return [Hash]\n        def provisioners\n          Registry.new.tap do |result|\n            @registered.each do |plugin|\n              result.merge!(plugin.provisioner)\n            end\n          end\n        end\n\n        # This returns all registered pushes.\n        #\n        # @return [Registry]\n        def pushes\n          Registry.new.tap do |result|\n            @registered.each do |plugin|\n              result.merge!(plugin.components.pushes)\n            end\n          end\n        end\n\n        # This returns all the config classes for the various pushes.\n        #\n        # @return [Registry]\n        def push_configs\n          Registry.new.tap do |result|\n            @registered.each do |plugin|\n              result.merge!(plugin.components.configs[:push])\n            end\n          end\n        end\n\n        # This returns all synced folder implementations.\n        #\n        # @return [Registry]\n        def synced_folders\n          Registry.new.tap do |result|\n            @registered.each do |plugin|\n              result.merge!(plugin.components.synced_folders)\n            end\n          end\n        end\n        \n        # This returns all the registered synced folder capabilities.\n        #\n        # @return [Hash]\n        def synced_folder_capabilities\n          results = Hash.new { |h, k| h[k] = Registry.new }\n\n          @registered.each do |plugin|\n            plugin.components.synced_folder_capabilities.each do |synced_folder, caps|\n              results[synced_folder].merge!(caps)\n            end\n          end\n\n          results\n        end\n        # This registers a plugin. This should _NEVER_ be called by the public\n        # and should only be called from within Vagrant. Vagrant will\n        # automatically register V2 plugins when a name is set on the\n        # plugin.\n        def register(plugin)\n          if !@registered.include?(plugin)\n            @logger.info(\"Registered plugin: #{plugin.name}\")\n            @registered << plugin\n          end\n        end\n\n        # This clears out all the registered plugins. This is only used by\n        # unit tests and should not be called directly.\n        def reset!\n          @registered.clear\n        end\n\n        # This unregisters a plugin so that its components will no longer\n        # be used. Note that this should only be used for testing purposes.\n        def unregister(plugin)\n          if @registered.include?(plugin)\n            @logger.info(\"Unregistered: #{plugin.name}\")\n            @registered.delete(plugin)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"set\"\nrequire \"log4r\"\nrequire \"vagrant/plugin/v2/components\"\n\nmodule Vagrant\n  module Plugin\n    module V2\n      # This is the superclass for all V2 plugins.\n      class Plugin\n        # Special marker that can be used for action hooks that matches\n        # all action sequences.\n        ALL_ACTIONS = :__all_actions__\n\n        # The logger for this class.\n        LOGGER = Log4r::Logger.new(\"vagrant::plugin::v2::plugin\")\n\n        # Set the root class up to be ourself, so that we can reference this\n        # from within methods which are probably in subclasses.\n        ROOT_CLASS = self\n\n        # This returns the manager for all V2 plugins.\n        #\n        # @return [V2::Manager]\n        def self.manager\n          @manager ||= local_manager\n        end\n\n        def self.local_manager\n          @_manager ||= Manager.new\n        end\n\n        # Returns the {Components} for this plugin.\n        #\n        # @return [Components]\n        def self.components\n          @components ||= Components.new\n        end\n\n        # Set the name of the plugin. The moment that this is called, the\n        # plugin will be registered and available. Before this is called, a\n        # plugin does not exist. The name must be unique among all installed\n        # plugins.\n        #\n        # @param [String] name Name of the plugin.\n        # @return [String] The name of the plugin.\n        def self.name(name=UNSET_VALUE)\n          # Get or set the value first, so we have a name for logging when\n          # we register.\n          result = get_or_set(:name, name)\n\n          # The plugin should be registered if we're setting a real name on it\n          Plugin.manager.register(self) if name != UNSET_VALUE\n\n          # Return the result\n          result\n        end\n\n        # Sets a human-friendly description of the plugin.\n        #\n        # @param [String] value Description of the plugin.\n        # @return [String] Description of the plugin.\n        def self.description(value=UNSET_VALUE)\n          get_or_set(:description, value)\n        end\n\n        # Registers a callback to be called when a specific action sequence\n        # is run. This allows plugin authors to hook into things like VM\n        # bootup, VM provisioning, etc.\n        #\n        # @param [String] name Name of the action.\n        # @param [Symbol] hook_name The location to hook. If this isn't\n        #   set, every middleware action is hooked.\n        # @return [Array] List of the hooks for the given action.\n        def self.action_hook(name, hook_name=nil, &block)\n          # The name is currently not used but we want it for the future.\n          hook_name = hook_name.to_s if hook_name\n\n          hook_name ||= ALL_ACTIONS\n          components.action_hooks[hook_name.to_sym] << block\n        end\n\n        # Defines additional command line commands available by key. The key\n        # becomes the subcommand, so if you register a command \"foo\" then\n        # \"vagrant foo\" becomes available.\n        #\n        # @param [String] name Subcommand key.\n        def self.command(name, **opts, &block)\n          # Validate the name of the command\n          if name.to_s !~ /^[-a-z0-9]+$/i\n            raise InvalidCommandName, \"Commands can only contain letters, numbers, and hyphens\"\n          end\n\n          # By default, the command is primary\n          opts[:primary] = true if !opts.key?(:primary)\n\n          # Register the command\n          components.commands.register(name.to_sym) do\n            [block, opts]\n          end\n\n          nil\n        end\n\n        # Defines additional communicators to be available. Communicators\n        # should be returned by a block passed to this method. This is done\n        # to ensure that the class is lazy loaded, so if your class inherits\n        # from or uses any Vagrant internals specific to Vagrant 1.0, then\n        # the plugin can still be defined without breaking anything in future\n        # versions of Vagrant.\n        #\n        # @param [String] name Communicator name.\n        def self.communicator(name=UNSET_VALUE, &block)\n          data[:communicator] ||= Registry.new\n\n          # Register a new communicator class only if a name was given.\n          data[:communicator].register(name.to_sym, &block) if name != UNSET_VALUE\n\n          # Return the registry\n          data[:communicator]\n        end\n\n        # Defines additional configuration keys to be available in the\n        # Vagrantfile. The configuration class should be returned by a\n        # block passed to this method. This is done to ensure that the class\n        # is lazy loaded, so if your class inherits from any classes that\n        # are specific to Vagrant 1.0, then the plugin can still be defined\n        # without breaking anything in future versions of Vagrant.\n        #\n        # @param [String] name Configuration key.\n        def self.config(name, scope=nil, &block)\n          scope ||= :top\n          components.configs[scope].register(name.to_sym, &block)\n          nil\n        end\n\n        # Defines an additionally available guest implementation with\n        # the given key.\n        #\n        # @param [String] name Name of the guest.\n        # @param [String] parent Name of the parent guest (if any)\n        def self.guest(name, parent=nil, &block)\n          components.guests.register(name.to_sym) do\n            parent = parent.to_sym if parent\n\n            [block.call, parent]\n          end\n          nil\n        end\n\n        # Defines a capability for the given guest. The block should return\n        # a class/module that has a method with the capability name, ready\n        # to be executed. This means that if it is an instance method,\n        # the block should return an instance of the class.\n        #\n        # @param [String] guest The name of the guest\n        # @param [String] cap The name of the capability\n        def self.guest_capability(guest, cap, &block)\n          components.guest_capabilities[guest.to_sym].register(cap.to_sym, &block)\n          nil\n        end\n\n        # Defines an additionally available host implementation with\n        # the given key.\n        #\n        # @param [String] name Name of the host.\n        # @param [String] parent Name of the parent host (if any)\n        def self.host(name, parent=nil, &block)\n          components.hosts.register(name.to_sym) do\n            parent = parent.to_sym if parent\n\n            [block.call, parent]\n          end\n          nil\n        end\n\n        # Defines a capability for the given host. The block should return\n        # a class/module that has a method with the capability name, ready\n        # to be executed. This means that if it is an instance method,\n        # the block should return an instance of the class.\n        #\n        # @param [String] host The name of the host\n        # @param [String] cap The name of the capability\n        def self.host_capability(host, cap, &block)\n          components.host_capabilities[host.to_sym].register(cap.to_sym, &block)\n          nil\n        end\n\n        # Registers additional providers to be available.\n        #\n        # @param [Symbol] name Name of the provider.\n        def self.provider(name=UNSET_VALUE, options=nil, &block)\n          options ||= {}\n          options[:priority] ||= 5\n\n          components.providers.register(name.to_sym) do\n            [block.call, options]\n          end\n\n          nil\n        end\n\n        # Defines a capability for the given provider. The block should return\n        # a class/module that has a method with the capability name, ready\n        # to be executed. This means that if it is an instance method,\n        # the block should return an instance of the class.\n        #\n        # @param [String] provider The name of the provider\n        # @param [String] cap The name of the capability\n        def self.provider_capability(provider, cap, &block)\n          components.provider_capabilities[provider.to_sym].register(cap.to_sym, &block)\n          nil\n        end\n\n        # Registers additional provisioners to be available.\n        #\n        # @param [String] name Name of the provisioner.\n        def self.provisioner(name=UNSET_VALUE, &block)\n          data[:provisioners] ||= Registry.new\n\n          # Register a new provisioner class only if a name was given\n          data[:provisioners].register(name.to_sym, &block) if name != UNSET_VALUE\n\n          # Return the registry\n          data[:provisioners]\n        end\n\n        # Registers additional pushes to be available.\n        #\n        # @param [String] name Name of the push.\n        # @param [Hash] options List of options for the push.\n        def self.push(name, options=nil, &block)\n          components.pushes.register(name.to_sym) do\n            [block.call, options]\n          end\n\n          nil\n        end\n\n        # Registers additional synced folder implementations.\n        #\n        # @param [String] name Name of the implementation.\n        # @param [Integer] priority The priority of the implementation,\n        # higher (big) numbers are tried before lower (small) numbers.\n        def self.synced_folder(name, priority=10, &block)\n          components.synced_folders.register(name.to_sym) do\n            [block.call, priority]\n          end\n\n          nil\n        end\n\n        # Defines a capability for the given synced folder. The block should return\n        # a class/module that has a method with the capability name, ready\n        # to be executed. This means that if it is an instance method,\n        # the block should return an instance of the class.\n        #\n        # @param [String] synced_folder The name of the synced folder\n        # @param [String] cap The name of the capability\n        def self.synced_folder_capability(synced_folder, cap, &block)\n          components.synced_folder_capabilities[synced_folder.to_sym].register(cap.to_sym, &block)\n          nil\n        end\n\n        # Returns the internal data associated with this plugin. This\n        # should NOT be called by the general public.\n        #\n        # @return [Hash]\n        def self.data\n          @data ||= {}\n        end\n\n        protected\n\n        # Sentinel value denoting that a value has not been set.\n        UNSET_VALUE = :__UNSET__VALUE__\n\n        # Helper method that will set a value if a value is given, or otherwise\n        # return the already set value.\n        #\n        # @param [Symbol] key Key for the data\n        # @param [Object] value Value to store.\n        # @return [Object] Stored value.\n        def self.get_or_set(key, value=UNSET_VALUE)\n          # If no value is to be set, then return the value we have already set\n          return data[key] if value.eql?(UNSET_VALUE)\n\n          # Otherwise set the value\n          data[key] = value\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/provider.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/capability_host\"\n\nmodule Vagrant\n  module Plugin\n    module V2\n      # This is the base class for a provider for the V2 API. A provider\n      # is responsible for creating compute resources to match the needs\n      # of a Vagrant-configured system.\n      class Provider\n        include CapabilityHost\n\n        # This is called early, before a machine is instantiated, to check\n        # if this provider is usable. This should return true or false.\n        #\n        # If raise_error is true, then instead of returning false, this\n        # should raise an error with a helpful message about why this\n        # provider cannot be used.\n        #\n        # @param [Boolean] raise_error If true, raise exception if not usable.\n        # @return [Boolean]\n        def self.usable?(raise_error=false)\n          # Return true by default for backwards compat since this was\n          # introduced long after providers were being written.\n          true\n        end\n\n        # This is called early, before a machine is instantiated, to check\n        # if this provider is installed. This should return true or false.\n        #\n        # If the provider is not installed and Vagrant determines it is\n        # able to install this provider, then it will do so. Installation\n        # is done by calling Environment.install_provider.\n        #\n        # If Environment.can_install_provider? returns false, then an error\n        # will be shown to the user.\n        def self.installed?\n          # By default return true for backwards compat so all providers\n          # continue to work.\n          true\n        end\n\n        # Initialize the provider to represent the given machine.\n        #\n        # @param [Vagrant::Machine] machine The machine that this provider\n        #   is responsible for.\n        def initialize(machine)\n        end\n\n        # This should return an action callable for the given name.\n        #\n        # @param [Symbol] name Name of the action.\n        # @return [Object] A callable action sequence object, whether it\n        #   is a proc, object, etc.\n        def action(name)\n          nil\n        end\n\n        # This method is called if the underlying machine ID changes. Providers\n        # can use this method to load in new data for the actual backing\n        # machine or to realize that the machine is now gone (the ID can\n        # become `nil`). No parameters are given, since the underlying machine\n        # is simply the machine instance given to this object. And no\n        # return value is necessary.\n        def machine_id_changed\n        end\n\n        # This should return a hash of information that explains how to\n        # SSH into the machine. If the machine is not at a point where\n        # SSH is even possible, then `nil` should be returned.\n        #\n        # The general structure of this returned hash should be the\n        # following:\n        #\n        #     {\n        #       host: \"1.2.3.4\",\n        #       port: \"22\",\n        #       username: \"mitchellh\",\n        #       private_key_path: \"/path/to/my/key\"\n        #     }\n        #\n        # **Note:** Vagrant only supports private key based authentication,\n        # mainly for the reason that there is no easy way to exec into an\n        # `ssh` prompt with a password, whereas we can pass a private key\n        # via commandline.\n        #\n        # @return [Hash] SSH information. For the structure of this hash\n        #   read the accompanying documentation for this method.\n        def ssh_info\n          nil\n        end\n\n        # This should return the state of the machine within this provider.\n        # The state must be an instance of {MachineState}. Please read the\n        # documentation of that class for more information.\n        #\n        # @return [MachineState]\n        def state\n          nil\n        end\n\n        # This is an internal initialize function that should never be\n        # overridden. It is used to initialize some common internal state\n        # that is used in a provider.\n        def _initialize(name, machine)\n          initialize_capabilities!(\n            name.to_sym,\n            { name.to_sym => [Class.new, nil] },\n            Vagrant.plugin(\"2\").manager.provider_capabilities,\n            machine,\n          )\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/provisioner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Plugin\n    module V2\n      # This is the base class for a provisioner for the V2 API. A provisioner\n      # is primarily responsible for installing software on a Vagrant guest.\n      class Provisioner\n        attr_reader :machine\n        attr_reader :config\n\n        # Initializes the provisioner with the machine that it will be\n        # provisioning along with the provisioner configuration (if there\n        # is any).\n        #\n        # The provisioner should _not_ do anything at this point except\n        # initialize internal state.\n        #\n        # @param [Machine] machine The machine that this will be provisioning.\n        # @param [Object] config Provisioner configuration, if one was set.\n        def initialize(machine, config)\n          @machine = machine\n          @config  = config\n        end\n\n        # Called with the root configuration of the machine so the provisioner\n        # can add some configuration on top of the machine.\n        #\n        # During this step, and this step only, the provisioner should modify\n        # the root machine configuration to add any additional features it\n        # may need. Examples include sharing folders, networking, and so on.\n        # This step is guaranteed to be called before any of those steps are\n        # done so the provisioner may do that.\n        #\n        # No return value is expected.\n        def configure(root_config)\n        end\n\n        # This is the method called when the actual provisioning should be\n        # done. The communicator is guaranteed to be ready at this point,\n        # and any shared folders or networks are already setup.\n        #\n        # No return value is expected.\n        def provision\n        end\n\n        # This is the method called when destroying a machine that allows\n        # for any state related to the machine created by the provisioner\n        # to be cleaned up.\n        def cleanup\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/push.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Plugin\n    module V2\n      class Push\n        attr_reader :env\n        attr_reader :config\n\n        # Initializes the pusher with the given environment the push\n        # configuration.\n        #\n        # @param [Environment] env\n        # @param [Object] config Push configuration\n        def initialize(env, config)\n          @env     = env\n          @config  = config\n        end\n\n        # This is the method called when the actual pushing should be\n        # done.\n        #\n        # No return value is expected.\n        def push\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/synced_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Plugin\n    module V2\n      # This is the base class for a synced folder implementation.\n      class SyncedFolder\n        class Collection < Hash\n\n          # @return [Array<Symbol>] names of synced folder types\n          def types\n            keys\n          end\n\n          # Fetch the synced plugin folder of the given type\n          #\n          # @param [Symbol] t Synced folder type\n          # @return [Vagrant::Plugin::V2::SyncedFolder]\n          def type(t)\n            f = detect { |k, _| k.to_sym == t.to_sym }.last\n            raise KeyError, \"Unknown synced folder type\" if !f\n            f.values.first[:plugin]\n          end\n\n          # Converts to a regular Hash and removes\n          # plugin instances so the result is ready\n          # for serialization\n          #\n          # @return [Hash]\n          def to_h\n            c = lambda do |h|\n              h.keys.each do |k|\n                if h[k].is_a?(Hash)\n                  h[k] = c.call(h[k].to_h.clone)\n                end\n              end\n              h\n            end\n            h = c.call(super)\n            h.values.each do |f|\n              f.values.each do |g|\n                g.delete(:plugin)\n              end\n            end\n            h\n          end\n        end\n\n        include CapabilityHost\n\n        # This is called early when the synced folder is set to determine\n        # if this implementation can be used for this machine. This should\n        # return true or false.\n        #\n        # @param [Machine] machine\n        # @param [Boolean] raise_error If true, should raise an exception\n        #   if it isn't usable.\n        # @return [Boolean]\n        def usable?(machine, raise_error=false)\n        end\n\n        # DEPRECATED: This will be removed.\n        #\n        # @deprecated\n        def prepare(machine, folders, opts)\n        end\n\n        # This is called after the machine is booted and after networks\n        # are setup.\n        #\n        # This might be called with new folders while the machine is running.\n        # If so, then this should add only those folders without removing\n        # any existing ones.\n        #\n        # No return value.\n        def enable(machine, folders, opts)\n        end\n\n        # This is called to remove the synced folders from a running\n        # machine.\n        #\n        # This is not guaranteed to be called, but this should be implemented\n        # by every synced folder implementation.\n        #\n        # @param [Machine] machine The machine to modify.\n        # @param [Hash] folders The folders to remove. This will not contain\n        #   any folders that should remain.\n        # @param [Hash] opts Any options for the synced folders.\n        def disable(machine, folders, opts)\n        end\n\n        # This is called after destroying the machine during a\n        # `vagrant destroy` and also prior to syncing folders during\n        # a `vagrant up`.\n        #\n        # No return value.\n        #\n        # @param [Machine] machine\n        # @param [Hash] opts\n        def cleanup(machine, opts)\n        end\n\n        def _initialize(machine, synced_folder_type)\n          plugins = Vagrant.plugin(\"2\").manager.synced_folders\n          capabilities = Vagrant.plugin(\"2\").manager.synced_folder_capabilities\n          initialize_capabilities!(synced_folder_type, plugins, capabilities, machine)\n          self\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2/trigger.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'fileutils'\nrequire 'log4r'\nrequire 'shellwords'\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/shell/provisioner\")\nrequire \"vagrant/util/subprocess\"\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/powershell\"\n\nmodule Vagrant\n  module Plugin\n    module V2\n      class Trigger\n        # @return [Kernel_V2::Config::Trigger]\n        attr_reader :config\n\n        # This class is responsible for setting up basic triggers that were\n        # defined inside a Vagrantfile.\n        #\n        # @param [Vagrant::Environment] env Vagrant environment\n        # @param [Kernel_V2::TriggerConfig] config Trigger configuration\n        # @param [Vagrant::Machine] machine Active Machine\n        # @param [Vagrant::UI] ui Class for printing messages to user\n        def initialize(env, config, machine, ui)\n          @env        = env\n          @config     = config\n          @machine    = machine\n          @ui         = ui\n\n          @logger = Log4r::Logger.new(\"vagrant::trigger::#{self.class.to_s.downcase}\")\n        end\n\n        # Fires all triggers, if any are defined for the named type and guest. Returns early\n        # and logs a warning if the community plugin `vagrant-triggers` is installed\n        #\n        # @param [Symbol] name Name of `type` thing to fire trigger on\n        # @param [Symbol] stage :before or :after\n        # @param [String] guest The guest that invoked firing the triggers\n        # @param [Symbol] type Type of trigger to fire (:action, :hook, :command)\n        def fire(name, stage, guest, type, all: false)\n          if community_plugin_detected?\n            @logger.warn(\"Community plugin `vagrant-triggers detected, so core triggers will not fire\")\n            return\n          end\n\n          return @logger.warn(\"Name given is nil, no triggers will fire\") if !name\n          return @logger.warn(\"Name given cannot be symbolized, no triggers will fire\") if\n            !name.respond_to?(:to_sym)\n\n          name = name.to_sym\n\n          # get all triggers matching action\n          triggers = find(name, stage, guest, type, all: all)\n\n          if !triggers.empty?\n            @logger.info(\"Firing trigger for #{type} #{name} on guest #{guest}\")\n            @ui.info(I18n.t(\"vagrant.trigger.start\", type: type, stage: stage, name: name))\n            execute(triggers)\n          end\n        end\n\n        # Find all triggers defined for the named type and guest.\n        #\n        # @param [Symbol] name Name of `type` thing to fire trigger on\n        # @param [Symbol] stage :before or :after\n        # @param [String] guest The guest that invoked firing the triggers\n        # @param [Symbol] type Type of trigger to fire\n        # @return [Array]\n        def find(name, stage, guest, type, all: false)\n          triggers = nil\n          name = nameify(name)\n\n          if stage == :before\n            triggers = config.before_triggers.select do |t|\n              (all && t.command.respond_to?(:to_sym) && t.command.to_sym == :all && !t.ignore.include?(name.to_sym)) ||\n                (type == :hook && matched_hook?(t.command, name)) ||\n                nameify(t.command) == name\n            end\n          elsif stage == :after\n            triggers = config.after_triggers.select do |t|\n              (all && t.command.respond_to?(:to_sym) && t.command.to_sym == :all && !t.ignore.include?(name.to_sym)) ||\n                (type == :hook && matched_hook?(t.command, name)) ||\n                nameify(t.command) == name\n            end\n          else\n            raise Errors::TriggersNoStageGiven,\n              name: name,\n              stage: stage,\n              type: type,\n              guest_name: guest\n          end\n\n          filter_triggers(triggers, guest, type)\n        end\n\n        protected\n\n        # Convert object into name\n        #\n        # @param [Object, Class] object Object to name\n        # @return [String]\n        def nameify(object)\n          if object.is_a?(Class)\n            object.name.to_s\n          else\n            object.to_s\n          end\n        end\n\n        #-------------------------------------------------------------------\n        # Internal methods, don't call these.\n        #-------------------------------------------------------------------\n\n        # Generate all valid lookup keys for given action key\n        #\n        # @param [Class, String] key Base key for generation\n        # @return [Array<String>] all valid keys\n        def matched_hook?(key, subject)\n          subject = nameify(subject)\n          Vagrant.plugin(\"2\").manager.generate_hook_keys(key).any? do |k|\n            k == subject\n          end\n        end\n\n        # Looks up if the community plugin `vagrant-triggers` is installed\n        # and also caches the result\n        #\n        # @return [Boolean]\n        def community_plugin_detected?\n          if !defined?(@_triggers_enabled)\n            plugins = Vagrant::Plugin::Manager.instance.installed_plugins\n            @_triggers_enabled = plugins.keys.include?(\"vagrant-triggers\")\n          end\n          @_triggers_enabled\n        end\n\n        # Filters triggers to be fired based on configured restraints\n        #\n        # @param [Array] triggers An array of triggers to be filtered\n        # @param [String] guest_name The name of the current guest\n        # @param [Symbol] type The type of trigger (:command or :type)\n        # @return [Array] The filtered array of triggers\n        def filter_triggers(triggers, guest_name, type)\n          # look for only_on trigger constraint and if it doesn't match guest\n          # name, throw it away also be sure to preserve order\n          filter = triggers.dup\n\n          filter.each do |trigger|\n            index = nil\n            match = false\n            if trigger.only_on\n              trigger.only_on.each do |o|\n                if o.match(guest_name.to_s)\n                  # trigger matches on current guest, so we're fine to use it\n                  match = true\n                  break\n                end\n              end\n              # no matches found, so don't use trigger for guest\n              index = triggers.index(trigger) unless match == true\n            end\n\n            if trigger.type != type\n              index = triggers.index(trigger)\n            end\n\n            if index\n              @logger.debug(\"Trigger #{trigger.id} will be ignored for #{guest_name}\")\n              triggers.delete_at(index)\n            end\n          end\n\n          return triggers\n        end\n\n        # Execute all triggers in the given array\n        #\n        # @param [Array] triggers An array of triggers to be fired\n        def execute(triggers)\n          # ensure on_error is respected by exiting or continuing\n          triggers.each do |trigger|\n            @logger.debug(\"Running trigger #{trigger.id}...\")\n\n            if trigger.name\n              @ui.info(I18n.t(\"vagrant.trigger.fire_with_name\",\n                                      name: trigger.name))\n            else\n              @ui.info(I18n.t(\"vagrant.trigger.fire\"))\n            end\n\n            if trigger.info\n              info(trigger.info)\n            end\n\n            if trigger.warn\n              warn(trigger.warn)\n            end\n\n            if trigger.abort\n              trigger_abort(trigger.abort)\n            end\n\n            if trigger.run\n              run(trigger.run, trigger.on_error, trigger.exit_codes)\n            end\n\n            if trigger.run_remote\n              run_remote(trigger.run_remote, trigger.on_error, trigger.exit_codes)\n            end\n\n            if trigger.ruby_block\n              execute_ruby(trigger.ruby_block)\n            end\n          end\n        end\n\n        # Prints the given message at info level for a trigger\n        #\n        # @param [String] message The string to be printed\n        def info(message)\n          @ui.info(message)\n        end\n\n        # Prints the given message at warn level for a trigger\n        #\n        # @param [String] message The string to be printed\n        def warn(message)\n          @ui.warn(message)\n        end\n\n        # Runs a script on a guest\n        #\n        # @param [Provisioners::Shell::Config] config A Shell provisioner config\n        def run(config, on_error, exit_codes)\n          if config.inline\n            if Vagrant::Util::Platform.windows?\n              cmd = config.inline\n            else\n              cmd = Shellwords.split(config.inline)\n            end\n\n            @ui.detail(I18n.t(\"vagrant.trigger.run.inline\", command: config.inline))\n          else\n            cmd = File.expand_path(config.path, @env.root_path).shellescape\n            args = Array(config.args)\n            cmd << \" #{args.join(' ')}\" if !args.empty?\n            cmd = Shellwords.split(cmd)\n\n            @ui.detail(I18n.t(\"vagrant.trigger.run.script\", path: config.path))\n          end\n\n          # Pick an execution method to run the script or inline string with\n          # Default to Subprocess::Execute\n          exec_method = Vagrant::Util::Subprocess.method(:execute)\n\n          if Vagrant::Util::Platform.windows?\n            if config.inline\n              exec_method = Vagrant::Util::PowerShell.method(:execute_inline)\n            else\n              exec_method = Vagrant::Util::PowerShell.method(:execute)\n            end\n          end\n\n          begin\n            result = exec_method.call(*cmd, :notify => [:stdout, :stderr]) do |type,data|\n              options = {}\n              case type\n              when :stdout\n                options[:color] = :green if !config.keep_color\n              when :stderr\n                options[:color] = :red if !config.keep_color\n              end\n\n              @ui.detail(data, **options)\n            end\n            if !exit_codes.include?(result.exit_code)\n              raise Errors::TriggersBadExitCodes,\n                code: result.exit_code\n            end\n          rescue => e\n            @ui.error(I18n.t(\"vagrant.errors.triggers_run_fail\"))\n            @ui.error(e.message)\n\n            if on_error == :halt\n              @logger.debug(\"Trigger run encountered an error. Halting on error...\")\n              raise e\n            else\n              @logger.debug(\"Trigger run encountered an error. Continuing on anyway...\")\n              @ui.warn(I18n.t(\"vagrant.trigger.on_error_continue\"))\n            end\n          end\n        end\n\n        # Runs a script on the guest\n        #\n        # @param [ShellProvisioner/Config] config A Shell provisioner config\n        def run_remote(config, on_error, exit_codes)\n          if !@machine\n            # machine doesn't even exist.\n            if on_error == :halt\n              raise Errors::TriggersGuestNotExist\n            else\n              @ui.warn(I18n.t(\"vagrant.errors.triggers_guest_not_exist\"))\n              @ui.warn(I18n.t(\"vagrant.trigger.on_error_continue\"))\n              return\n            end\n          elsif @machine.state.id != :running\n            if on_error == :halt\n              raise Errors::TriggersGuestNotRunning,\n                machine_name: @machine.name,\n                state: @machine.state.id\n            else\n              @machine.ui.error(I18n.t(\"vagrant.errors.triggers_guest_not_running\",\n                                        machine_name: @machine.name,\n                                        state: @machine.state.id))\n              @machine.ui.warn(I18n.t(\"vagrant.trigger.on_error_continue\"))\n              return\n            end\n          end\n\n          prov = VagrantPlugins::Shell::Provisioner.new(@machine, config)\n\n          begin\n            prov.provision\n          rescue => e\n            @machine.ui.error(I18n.t(\"vagrant.errors.triggers_run_fail\"))\n\n            if on_error == :halt\n              @logger.debug(\"Trigger run encountered an error. Halting on error...\")\n              raise e\n            else\n              @logger.debug(\"Trigger run encountered an error. Continuing on anyway...\")\n              @machine.ui.error(e.message)\n            end\n          end\n        end\n\n        # Exits Vagrant immediately\n        #\n        # @param [Integer] code Code to exit Vagrant on\n        def trigger_abort(exit_code)\n          if Thread.current[:batch_parallel_action]\n            @ui.warn(I18n.t(\"vagrant.trigger.abort_threaded\"))\n            @logger.debug(\"Trigger abort within parallel batch action. \" \\\n              \"Setting exit code and terminating.\")\n            Thread.current[:exit_code] = exit_code\n            Thread.current.terminate\n          else\n            @ui.warn(I18n.t(\"vagrant.trigger.abort\"))\n            @logger.debug(\"Trigger abort within non-parallel action, exiting directly\")\n            Process.exit!(exit_code)\n          end\n        end\n\n        # Calls the given ruby block for execution\n        #\n        # @param [Proc] ruby_block\n        def execute_ruby(ruby_block)\n          ruby_block.call(@env, @machine)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin/v2.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\n# We don't autoload components because if we're loading anything in the\n# V2 namespace anyways, then we're going to need the Components class.\nrequire \"vagrant/plugin/v2/components\"\nrequire \"vagrant/plugin/v2/errors\"\n\nmodule Vagrant\n  module Plugin\n    module V2\n      autoload :Command, \"vagrant/plugin/v2/command\"\n      autoload :Communicator, \"vagrant/plugin/v2/communicator\"\n      autoload :Components, \"vagrant/plugin/v2/components\"\n      autoload :Config, \"vagrant/plugin/v2/config\"\n      autoload :Guest,  \"vagrant/plugin/v2/guest\"\n      autoload :Host,   \"vagrant/plugin/v2/host\"\n      autoload :Manager, \"vagrant/plugin/v2/manager\"\n      autoload :Plugin, \"vagrant/plugin/v2/plugin\"\n      autoload :Provider, \"vagrant/plugin/v2/provider\"\n      autoload :Push, \"vagrant/plugin/v2/push\"\n      autoload :Provisioner, \"vagrant/plugin/v2/provisioner\"\n      autoload :SyncedFolder, \"vagrant/plugin/v2/synced_folder\"\n      autoload :Trigger, \"vagrant/plugin/v2/trigger\"\n\n      # Errors\n      autoload :Error, \"vagrant/plugin/v2/error\"\n      autoload :InvalidCommandName, \"vagrant/plugin/v2/error\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Plugin\n    autoload :V1,        \"vagrant/plugin/v1\"\n    autoload :V2,        \"vagrant/plugin/v2\"\n    autoload :Manager,   \"vagrant/plugin/manager\"\n    autoload :StateFile, \"vagrant/plugin/state_file\"\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/registry.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  # Register components in a single location that can be queried.\n  #\n  # This allows certain components (such as guest systems, configuration\n  # pieces, etc.) to be registered and queried, lazily.\n  class Registry\n    def initialize\n      @items = {}\n      @results_cache = {}\n    end\n\n    # Register a key with a lazy-loaded value.\n    #\n    # If a key with the given name already exists, it is overwritten.\n    def register(key, &block)\n      raise ArgumentError, \"block required\" if !block_given?\n      @items[key] = block\n    end\n\n    # Get a value by the given key.\n    #\n    # This will evaluate the block given to `register` and return the\n    # resulting value.\n    def get(key)\n      return nil if !@items.key?(key)\n      return @results_cache[key] if @results_cache.key?(key)\n      @results_cache[key] = @items[key].call\n    end\n    alias :[] :get\n\n    # Checks if the given key is registered with the registry.\n    #\n    # @return [Boolean]\n    def key?(key)\n      @items.key?(key)\n    end\n    alias_method :has_key?, :key?\n\n    # Returns an array populated with the keys of this object.\n    #\n    # @return [Array]\n    def keys\n      @items.keys\n    end\n\n    # Iterate over the keyspace.\n    def each(&block)\n      @items.each do |key, _|\n        yield key, get(key)\n      end\n    end\n\n    # Iterate over the keyspace and return result\n    #\n    # @return [Array]\n    def map(&block)\n      @items.map do |key, _|\n        yield key, get(key)\n      end\n    end\n\n    # Return the number of elements in this registry.\n    #\n    # @return [Integer]\n    def length\n      @items.keys.length\n    end\n    alias_method :size, :length\n\n    # Checks if this registry has any items.\n    #\n    # @return [Boolean]\n    def empty?\n      @items.keys.empty?\n    end\n\n    # Merge one registry with another and return a completely new\n    # registry. Note that the result cache is completely busted, so\n    # any gets on the new registry will result in a cache miss.\n    def merge(other)\n      self.class.new.tap do |result|\n        result.merge!(self)\n        result.merge!(other)\n      end\n    end\n\n    # Like #{merge} but merges into self.\n    def merge!(other)\n      @items.merge!(other.__internal_state[:items])\n      self\n    end\n\n    # Converts this registry to a hash\n    def to_hash\n      result = {}\n      self.each do |key, value|\n        result[key] = value\n      end\n\n      result\n    end\n\n    def __internal_state\n      {\n        items: @items,\n        results_cache: @results_cache\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/shared_helpers.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tempfile\"\nrequire \"thread\"\n\nmodule Vagrant\n  @@global_lock = Mutex.new\n\n  # This is the default endpoint of the Vagrant Cloud in\n  # use. API calls will be made to this for various functions\n  # of Vagrant that may require remote access.\n  #\n  # @return [String]\n  DEFAULT_SERVER_URL = \"https://vagrantcloud.com\".freeze\n\n  # Max number of seconds to wait for joining an active thread.\n  #\n  # @return [Integer]\n  # @note This is not the maximum time for a thread to complete.\n  THREAD_MAX_JOIN_TIMEOUT = 60\n\n  # List of required external tools that are expected to be\n  # present when running outside of the installers\n  REQUIRED_EXTERNAL_TOOLS = [\"bsdtar\", \"curl\", \"ssh\"].map(&:freeze).freeze\n\n  # This holds a global lock for the duration of the block. This should\n  # be invoked around anything that is modifying process state (such as\n  # environmental variables).\n  def self.global_lock\n    @@global_lock.synchronize do\n      return yield\n    end\n  end\n\n  # This returns a true/false showing whether we're running from the\n  # environment setup by the Vagrant installers.\n  #\n  # @return [Boolean]\n  def self.in_installer?\n    !!ENV[\"VAGRANT_INSTALLER_ENV\"]\n  end\n\n  # This returns a true/false if we are running within a bundler environment\n  #\n  # @return [Boolean]\n  def self.in_bundler?\n    !!ENV[\"BUNDLE_GEMFILE\"] &&\n      !defined?(::Bundler).nil?\n  end\n\n  # Returns the path to the embedded directory of the Vagrant installer,\n  # if there is one (if we're running in an installer).\n  #\n  # @return [String]\n  def self.installer_embedded_dir\n    return nil if !Vagrant.in_installer?\n    ENV[\"VAGRANT_INSTALLER_EMBEDDED_DIR\"]\n  end\n\n  # Should the plugin system be initialized\n  #\n  # @return [Boolean]\n  def self.plugins_init?\n    !ENV['VAGRANT_DISABLE_PLUGIN_INIT']\n  end\n\n  # This returns whether or not 3rd party plugins should and can be loaded.\n  #\n  # @return [Boolean]\n  def self.plugins_enabled?\n    !ENV[\"VAGRANT_NO_PLUGINS\"]\n  end\n\n  # Whether or not super quiet mode is enabled. This is ill-advised.\n  #\n  # @return [Boolean]\n  def self.very_quiet?\n    !!ENV[\"VAGRANT_I_KNOW_WHAT_IM_DOING_PLEASE_BE_QUIET\"]\n  end\n\n  # The current log level for Vagrant\n  #\n  # @return [String]\n  def self.log_level\n    ENV.fetch(\"VAGRANT_LOG\", \"fatal\").downcase\n  end\n\n  # Returns the URL prefix to the server.\n  #\n  # @return [String]\n  def self.server_url(config_server_url=nil)\n    result = ENV[\"VAGRANT_SERVER_URL\"]\n    result = config_server_url if result == \"\" or result == nil\n    result || DEFAULT_SERVER_URL\n  end\n\n  # The source root is the path to the root directory of the Vagrant source.\n  #\n  # @return [Pathname]\n  def self.source_root\n    @source_root ||= Pathname.new(File.expand_path('../../../', __FILE__))\n  end\n\n  # This returns the path to the ~/.vagrant.d folder where Vagrant's\n  # per-user state is stored.\n  #\n  # @return [Pathname]\n  def self.user_data_path\n    # Use user specified env var if available\n    path = ENV[\"VAGRANT_HOME\"]\n\n    # On Windows, we default to the USERPROFILE directory if it\n    # is available. This is more compatible with Cygwin and sharing\n    # the home directory across shells.\n    if !path && ENV[\"USERPROFILE\"]\n      path = \"#{ENV[\"USERPROFILE\"]}/.vagrant.d\"\n    end\n\n    # Fallback to the default\n    path ||= \"~/.vagrant.d\"\n\n    Pathname.new(path).expand_path\n  end\n\n  # This returns true/false if the running version of Vagrant is\n  # a pre-release version (development)\n  #\n  # @return [Boolean]\n  def self.prerelease?\n    Gem::Version.new(Vagrant::VERSION).prerelease?\n  end\n\n  # This returns true/false if the Vagrant should allow prerelease\n  # versions when resolving plugin dependency constraints\n  #\n  # @return [Boolean]\n  def self.allow_prerelease_dependencies?\n    !!ENV[\"VAGRANT_ALLOW_PRERELEASE\"]\n  end\n\n  # This allows control over dependency resolution when installing\n  # plugins into vagrant. When true, dependency libraries that Vagrant\n  # core relies upon will be hard constraints.\n  #\n  # @return [Boolean]\n  def self.strict_dependency_enforcement\n    if ENV[\"VAGRANT_DISABLE_STRICT_DEPENDENCY_ENFORCEMENT\"]\n      false\n    else\n      true\n    end\n  end\n\n  # Automatically install locally defined plugins instead of\n  # waiting for user confirmation.\n  #\n  # @return [Boolean]\n  def self.auto_install_local_plugins?\n    if ENV[\"VAGRANT_INSTALL_LOCAL_PLUGINS\"]\n      true\n    else\n      false\n    end\n  end\n\n  # Use Ruby Resolv in place of libc\n  #\n  # @return [boolean] enabled or not\n  def self.enable_resolv_replace\n    if ENV[\"VAGRANT_ENABLE_RESOLV_REPLACE\"]\n      if !ENV[\"VAGRANT_DISABLE_RESOLV_REPLACE\"]\n        begin\n          require \"resolv-replace\"\n          true\n        rescue\n          false\n        end\n      else\n        false\n      end\n    end\n  end\n\n  # Set the global logger\n  #\n  # @param log Logger\n  # @return [Logger]\n  def self.global_logger=(log)\n    @_global_logger = log\n  end\n\n  # Get the global logger instance\n  #\n  # @return [Logger]\n  def self.global_logger\n    if @_global_logger.nil?\n      require \"log4r\"\n      @_global_logger = Log4r::Logger.new(\"vagrant::global\")\n    end\n    @_global_logger\n  end\n\n  # Add a new block of default CLI options which\n  # should be automatically added to all commands\n  #\n  # @param [Proc] block Proc instance containing OptParser configuration\n  # @return [nil]\n  def self.add_default_cli_options(block)\n    if !block.is_a?(Proc)\n      raise TypeError,\n        \"Expecting type `Proc` but received `#{block.class}`\"\n    end\n    if block.arity != 1 && block.arity != -1\n      raise ArgumentError,\n        \"Proc must accept OptionParser argument\"\n    end\n    @_default_cli_options = [] if !@_default_cli_options\n    @_default_cli_options << block\n    nil\n  end\n\n  # Array of default CLI options to automatically\n  # add to commands.\n  #\n  # @return [Array<Proc>] Default optparse options\n  def self.default_cli_options\n    @_default_cli_options = [] if !@_default_cli_options\n    @_default_cli_options.dup\n  end\n\n  def self.detect_missing_tools\n    REQUIRED_EXTERNAL_TOOLS.find_all do |tool|\n      !Vagrant::Util::Which.which(tool)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/ui.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"delegate\"\nrequire \"io/console\"\nrequire \"thread\"\nrequire \"log4r\"\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/safe_puts\"\n\nmodule Vagrant\n  module UI\n    # Vagrant UIs handle communication with the outside world (typically\n    # through a shell). They must respond to the following methods:\n    #\n    # * `info`\n    # * `warn`\n    # * `error`\n    # * `success`\n    class Interface\n      # Opts can be used to set some options. These options are implementation\n      # specific. See the implementation for more docs.\n      attr_accessor :opts\n\n      # @return [IO] UI input. Defaults to `$stdin`.\n      attr_accessor :stdin\n\n      # @return [IO] UI output. Defaults to `$stdout`.\n      attr_accessor :stdout\n\n      # @return [IO] UI error output. Defaults to `$stderr`.\n      attr_accessor :stderr\n\n      def initialize\n        @logger   = Log4r::Logger.new(\"vagrant::ui::interface\")\n        @opts     = {}\n\n        @stdin  = $stdin\n        @stdout = $stdout\n        @stderr = $stderr\n      end\n\n      def initialize_copy(original)\n        super\n        @opts = original.opts.dup\n      end\n\n      [:ask, :detail, :warn, :error, :info, :output, :success].each do |method|\n        define_method(method) do |message, **opts|\n          # Log normal console messages\n          begin\n            @logger.info { \"#{method}: #{message}\" }\n          rescue ThreadError\n            # We're being called in a trap-context. Wrap in a thread.\n            Thread.new do\n              @logger.info { \"#{method}: #{message}\" }\n            end.join(THREAD_MAX_JOIN_TIMEOUT)\n          end\n        end\n      end\n\n      [:clear_line, :report_progress].each do |method|\n        # By default do nothing, these aren't logged\n        define_method(method) { |*args| }\n      end\n\n      # @return [false]\n      def color?\n        return false\n      end\n\n      # For machine-readable output.\n      #\n      # @param [String] type The type of the data\n      # @param [Array] data The data associated with the type\n      def machine(type, *data)\n        @logger.info(\"Machine: #{type} #{data.inspect}\")\n      end\n\n      # Yields self (UI)\n      # Provides a way for selectively displaying or not displaying\n      # updating content like download progress.\n      def rewriting\n        yield self\n      end\n\n      def to_proto\n        raise NotImplementedError,\n          \"Vagrant::UI::Interface#to_proto\"\n      end\n    end\n\n    # This is a UI implementation that does nothing.\n    class Silent < Interface\n      def ask(*args, **opts)\n        super\n\n        # Silent can't do this, obviously.\n        raise Errors::UIExpectsTTY\n      end\n    end\n\n    class MachineReadable < Interface\n      include Util::SafePuts\n\n      def initialize\n        super\n\n        @lock = Mutex.new\n      end\n\n      def ask(*args, **opts)\n        super\n\n        # Machine-readable can't ask for input\n        raise Errors::UIExpectsTTY\n      end\n\n      [:detail, :warn, :error, :info, :output, :success].each do |method|\n        define_method(method) do |message, **opts|\n          machine(\"ui\", method.to_s, message, **opts)\n        end\n      end\n\n      def machine(type, *data)\n        opts = {}\n        opts = data.pop if data.last.kind_of?(Hash)\n\n        target = opts[:target] || \"\"\n\n        # Prepare the data by replacing characters that aren't outputted\n        data.each_index do |i|\n          data[i] = data[i].to_s.dup\n          data[i].gsub!(\",\", \"%!(VAGRANT_COMMA)\")\n          data[i].gsub!(\"\\n\", \"\\\\n\")\n          data[i].gsub!(\"\\r\", \"\\\\r\")\n        end\n\n        # Avoid locks in a trap context introduced from Ruby 2.0\n        Thread.new do\n          @lock.synchronize do\n            safe_puts(\"#{Time.now.utc.to_i},#{target},#{type},#{data.join(\",\")}\")\n          end\n        end.join(THREAD_MAX_JOIN_TIMEOUT)\n      end\n    end\n\n    # This is a UI implementation that outputs the text as is. It\n    # doesn't add any color.\n    class Basic < Interface\n      include Util::SafePuts\n\n      def initialize\n        super\n\n        @lock = Mutex.new\n      end\n\n      # Use some light meta-programming to create the various methods to\n      # output text to the UI. These all delegate the real functionality\n      # to `say`.\n      [:detail, :info, :warn, :error, :output, :success].each do |method|\n        class_eval <<-CODE\n          def #{method}(message, **opts)\n            super(message)\n            say(#{method.inspect}, message, **opts)\n          end\n        CODE\n      end\n\n      def ask(message, **opts)\n        super(message)\n\n        # We can't ask questions when the output isn't a TTY.\n        raise Errors::UIExpectsTTY if !@stdin.tty? && !Vagrant::Util::Platform.windows?\n\n        # Setup the options so that the new line is suppressed\n        opts ||= {}\n        opts[:echo]     = true  if !opts.key?(:echo)\n        opts[:new_line] = false if !opts.key?(:new_line)\n        opts[:prefix]   = false if !opts.key?(:prefix)\n\n        # Output the data\n        say(:info, message, opts)\n\n        input = nil\n        if opts[:echo] || !@stdin.respond_to?(:noecho)\n          input = @stdin.gets\n        else\n          begin\n            input = @stdin.noecho(&:gets)\n\n            # Output a newline because without echo, the newline isn't\n            # echoed either.\n            say(:info, \"\\n\", opts)\n          rescue Errno::EBADF\n            # This means that stdin doesn't support echoless input.\n            say(:info, \"\\n#{I18n.t(\"vagrant.stdin_cant_hide_input\")}\\n \", opts)\n\n            # Ask again, with echo enabled\n            input = ask(message, **opts.merge(echo: true))\n          end\n        end\n\n        # Get the results and chomp off the newline. We do a logical OR\n        # here because `gets` can return a nil, for example in the case\n        # that ctrl-D is pressed on the input.\n        (input || \"\").chomp\n      end\n\n      # This is used to output progress reports to the UI.\n      # Send this method progress/total and it will output it\n      # to the UI. Send `clear_line` to clear the line to show\n      # a continuous progress meter.\n      def report_progress(progress, total, show_parts=true)\n        if total && total > 0\n          percent = (progress.to_f / total.to_f) * 100\n          line    = \"Progress: #{percent.to_i}%\"\n          line   << \" (#{progress} / #{total})\" if show_parts\n        else\n          line    = \"Progress: #{progress}\"\n        end\n\n        info(line, new_line: false)\n      end\n\n      def clear_line\n        # See: https://en.wikipedia.org/wiki/ANSI_escape_code\n        reset = \"\\r\\033[K\"\n\n        info(reset, new_line: false)\n      end\n\n      # This method handles actually outputting a message of a given type\n      # to the console.\n      def say(type, message, opts={})\n        defaults = { new_line: true, prefix: true }\n        opts     = defaults.merge(@opts).merge(opts)\n\n        # Don't output if we're hiding details\n        return if type == :detail && opts[:hide_detail]\n\n        # Determine whether we're expecting to output our\n        # own new line or not.\n        printer = opts[:new_line] ? :puts : :print\n\n        # Determine the proper IO channel to send this message\n        # to based on the type of the message\n        channel = type == :error || opts[:channel] == :error ? @stderr : @stdout\n\n        # Output! We wrap this in a lock so that it safely outputs only\n        # one line at a time. We wrap this in a thread because as of Ruby 2.0\n        # we can't acquire locks in a trap context (ctrl-c), so we have to\n        # do this.\n        Thread.new do\n          @lock.synchronize do\n            safe_puts(format_message(type, message, **opts),\n                      io: channel, printer: printer)\n          end\n        end.join(THREAD_MAX_JOIN_TIMEOUT)\n      end\n\n      def format_message(type, message, **opts)\n        Util::CredentialScrubber.desensitize(message)\n      end\n    end\n\n    class NonInteractive < Basic\n      def initialize\n        super\n      end\n\n      def rewriting\n        # no-op\n      end\n\n      def report_progress(progress, total, show_parts=true)\n        # no-op\n      end\n\n      def clear_line\n        @logger.warn(\"Using `clear line` in a non interactive ui\")\n        say(:info, \"\\n\", opts)\n      end\n\n      def ask(*args, **opts)\n        # Non interactive can't ask for input\n        raise Errors::UIExpectsTTY\n      end\n    end\n\n    # Prefixed wraps an existing UI and adds a prefix to it.\n    class Prefixed < Interface\n      # The prefix for `output` messages.\n      OUTPUT_PREFIX = \"==> \"\n\n      def initialize(ui, prefix)\n        super()\n\n        @prefix = prefix\n        @ui     = ui\n      end\n\n      def to_proto\n        @ui.to_proto\n      end\n\n      def client\n        @ui.client\n      end\n\n      def initialize_copy(original)\n        super\n        @ui = original.instance_variable_get(:@ui).dup\n      end\n\n      # Use some light meta-programming to create the various methods to\n      # output text to the UI. These all delegate the real functionality\n      # to `say`.\n      [:ask, :detail, :info, :warn, :error, :output, :success].each do |method|\n        class_eval <<-CODE\n          def #{method}(message, **opts)\n            super(message)\n            if !@ui.opts.key?(:bold) && !opts.key?(:bold)\n              opts[:bold] = #{method.inspect} != :detail && \\\n                #{method.inspect} != :ask\n            end\n            if !opts.key?(:target)\n              opts[:target] = @prefix\n            end\n            @ui.#{method}(format_message(#{method.inspect}, message, **opts), **opts)\n          end\n        CODE\n      end\n\n      [:clear_line, :report_progress].each do |method|\n        # By default do nothing, these aren't formatted\n        define_method(method) do |*args|\n          @ui.send(method, *args)\n        end\n      end\n\n      # For machine-readable output, set the prefix in the\n      # options hash and continue it on.\n      def machine(type, *data)\n        opts = {}\n        opts = data.pop if data.last.is_a?(Hash)\n        opts[:target] = @prefix\n        data << opts\n        @ui.machine(type, *data)\n      end\n\n      # Return the parent's opts.\n      #\n      # @return [Hash]\n      def opts\n        @ui.opts\n      end\n\n      def format_message(type, message, **opts)\n        opts = self.opts.merge(opts)\n\n        prefix = \"\"\n        if !opts.key?(:prefix) || opts[:prefix]\n          prefix = OUTPUT_PREFIX\n          prefix = \" \" * OUTPUT_PREFIX.length if \\\n            type == :detail || type == :ask || opts[:prefix_spaces]\n        end\n\n        message = Util::CredentialScrubber.desensitize(message)\n\n        # Fast-path if there is no prefix\n        return message if prefix.empty?\n\n        target = @prefix\n        target = opts[:target] if opts.key?(:target)\n        target = \"#{target}:\" if target != \"\"\n\n        lines = [message]\n        if message != \"\"\n          lines = [].tap do |l|\n            message.scan(/(.*?)(\\n|$)/).each do |m|\n              l << m.first if m.first != \"\" || (m.first == \"\" && m.last == \"\\n\")\n            end\n          end\n          lines << \"\" if message.end_with?(\"\\n\")\n        end\n\n        # Otherwise, make sure to prefix every line properly\n        lines.map do |line|\n          \"#{prefix}#{target} #{line}\"\n        end.join(\"\\n\")\n      end\n\n      def rewriting\n        @ui.rewriting do |ui|\n          yield ui\n        end\n      end\n\n    end\n\n    # This is a UI implementation that outputs color for various types\n    # of messages. This should only be used with a TTY that supports color,\n    # but is up to the user of the class to verify this is the case.\n    class Colored < Basic\n      # Terminal colors\n      COLORS = {\n        red:     31,\n        green:   32,\n        yellow:  33,\n        blue:    34,\n        magenta: 35,\n        cyan:    36,\n        white:   37,\n      }\n\n      # @return [true]\n      def color?\n        return true\n      end\n\n      # This is called by `say` to format the message for output.\n      def format_message(type, message, **opts)\n        # Get the format of the message before adding color.\n        message = super\n\n        opts = @opts.merge(opts)\n\n        # Special case some colors for certain message types\n        opts[:color] = :red if type == :error\n        opts[:color] = :green if type == :success\n        opts[:color] = :yellow if type == :warn\n\n        # If it is a detail, it is not bold. Every other message type\n        # is bolded.\n        bold  = !!opts[:bold]\n        colorseq = \"#{bold ? 1 : 0 }\"\n        if opts[:color] && opts[:color] != :default\n          color = COLORS[opts[:color]]\n          colorseq += \";#{color}\"\n        end\n\n        # Color the message and make sure to reset the color at the end\n        \"\\033[#{colorseq}m#{message}\\033[0m\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/ansi_escape_code_remover.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    module ANSIEscapeCodeRemover\n      # Removes ANSI escape code sequences from the text and returns\n      # it.\n      #\n      # This removes all the ANSI escape codes listed here along with\n      # the escape codes for VT100 terminals:\n      #\n      # http://ascii-table.com/ansi-escape-sequences.php\n      def remove_ansi_escape_codes(text)\n        # An array of regular expressions which match various kinds\n        # of escape sequences. I can't think of a better single regular\n        # expression or any faster way to do this.\n        matchers = [/\\e\\[\\d*[ABCD]/,       # Matches things like \\e[4D\n                    /\\e\\[(\\d*;)?\\d*[HF]/,  # Matches \\e[1;2H or \\e[H\n                    /\\e\\[(s|u|2J|K)/,      # Matches \\e[s, \\e[2J, etc.\n                    /\\e\\[=\\d*[hl]/,        # Matches \\e[=24h\n                    /\\e\\[\\?[1-9][hl]/,     # Matches \\e[?2h\n                    /\\e\\[20[hl]/,          # Matches \\e[20l]\n                    /\\e[DME78H]/,          # Matches \\eD, \\eH, etc.\n                    /\\e\\[[0-3]?[JK]/,      # Matches \\e[0J, \\e[K, etc.\n                    ]\n\n        # Take each matcher and replace it with emptiness.\n        matchers.each do |matcher|\n          text.gsub!(matcher, \"\")\n        end\n\n        text\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/busy.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # Utility class which allows blocks of code to be marked as \"busy\"\n    # with a specified interrupt handler. During busy areas of code, it\n    # is often undesirable for SIGINTs to immediately kill the application.\n    # This class is a helper to cleanly register callbacks to handle this\n    # situation.\n    class Busy\n      @@registered = []\n      @@mutex = Mutex.new\n\n      class << self\n        # Mark a given block of code as a \"busy\" block of code, which will\n        # register a SIGINT handler for the duration of the block. When a\n        # SIGINT occurs, the `sig_callback` proc will be called. It is up\n        # to the callback to behave properly and exit the application.\n        def busy(sig_callback)\n          register(sig_callback)\n          return yield\n        ensure\n          unregister(sig_callback)\n        end\n\n        # Registers a SIGINT handler. This typically is called from {busy}.\n        # Callbacks are only registered once, so calling this multiple times\n        # with the same callback has no consequence.\n        def register(sig_callback)\n          @@mutex.synchronize do\n            registered << sig_callback\n            registered.uniq!\n\n            # Register the handler if this is our first callback.\n            Signal.trap(\"INT\") { fire_callbacks } if registered.length == 1\n          end\n        end\n\n        # Unregisters a SIGINT handler.\n        def unregister(sig_callback)\n          @@mutex.synchronize do\n            registered.delete(sig_callback)\n\n            # Remove the signal trap if no more registered callbacks exist\n            Signal.trap(\"INT\", \"DEFAULT\") if registered.empty?\n          end\n        end\n\n        # Fires all the registered callbacks.\n        def fire_callbacks\n          registered.reverse.each { |r| r.call }\n        end\n\n        # Helper method to get access to the class variable. This is mostly\n        # exposed for tests. This shouldn't be mucked with directly, since it's\n        # structure may change at any time.\n        def registered; @@registered; end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/caps.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\nrequire \"fileutils\"\nrequire \"pathname\"\nrequire \"vagrant/util/directory\"\nrequire \"vagrant/util/subprocess\"\n\nmodule Vagrant\n  module Util\n    module Caps\n      module BuildISO\n\n        # Builds an iso given a compatible iso_command\n        #\n        # @param [List<String>] command to build iso\n        # @param [Pathname] input directory for iso build\n        # @param [Pathname] output file for iso build\n        def build_iso(iso_command, source_directory, file_destination)\n          FileUtils.mkdir_p(file_destination.dirname)\n          if !file_destination.exist? || Vagrant::Util::Directory.directory_changed?(source_directory, file_destination.mtime)\n            result = Vagrant::Util::Subprocess.execute(*iso_command)\n            if result.exit_code != 0\n              raise Vagrant::Errors::ISOBuildFailed, cmd: iso_command.join(\" \"), stdout: result.stdout, stderr: result.stderr\n            end\n          end\n        end\n\n        protected\n\n        def ensure_output_iso(file_destination)\n          if file_destination.nil?\n            tmpfile = Tempfile.new([\"vagrant\", \".iso\"])\n            file_destination = Pathname.new(tmpfile.path)\n            tmpfile.close\n            tmpfile.unlink\n          else\n            file_destination = Pathname.new(file_destination.to_s)\n            # If the file destination path is a folder, target the output to a randomly named\n            # file in that dir\n            if file_destination.extname != \".iso\"\n              file_destination = file_destination.join(\"#{SecureRandom.hex(3)}_vagrant.iso\")\n            end\n          end\n          file_destination\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/checkpoint_client.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"singleton\"\n\nmodule Vagrant\n  module Util\n    class CheckpointClient\n\n      include Singleton\n\n      # Maximum number of seconds to wait for check to complete\n      CHECKPOINT_TIMEOUT = 10\n\n      # @return [Log4r::Logger]\n      attr_reader :logger\n\n      # @return [Boolean]\n      attr_reader :enabled\n\n      # @return [Hash]\n      attr_reader :files\n\n      # @return [Vagrant::Environment]\n      attr_reader :env\n\n      def initialize\n        @logger = Log4r::Logger.new(\"vagrant::checkpoint_client\")\n        @enabled = false\n      end\n\n      # Setup will attempt to load the checkpoint library and define\n      # required paths\n      #\n      # @param [Vagrant::Environment] env\n      # @return [self]\n      def setup(env)\n        begin\n          require \"checkpoint\"\n          @enabled = true\n        rescue LoadError\n          @logger.warn(\"checkpoint library not found. disabling.\")\n        end\n        if ENV[\"VAGRANT_CHECKPOINT_DISABLE\"]\n          @logger.debug(\"checkpoint disabled via explicit user request\")\n          @enabled = false\n        end\n        @files = {\n          signature: env.data_dir.join(\"checkpoint_signature\"),\n          cache: env.data_dir.join(\"checkpoint_cache\")\n        }\n        @checkpoint_thread = nil\n        @env = env\n        self\n      end\n\n      # Check has completed\n      def complete?\n        !@checkpoint_thread.nil? && !@checkpoint_thread.alive?\n      end\n\n      # Result of check\n      #\n      # @return [Hash, nil]\n      def result\n        if !enabled || @checkpoint_thread.nil?\n          nil\n        elsif !defined?(@result)\n          @checkpoint_thread.join(CHECKPOINT_TIMEOUT)\n          @result = @checkpoint_thread[:result]\n        else\n          @result\n        end\n      end\n\n      # Run check\n      #\n      # @return [self]\n      def check\n        if enabled && @checkpoint_thread.nil?\n          logger.debug(\"starting plugin check\")\n          @checkpoint_thread = Thread.new do\n            Thread.current.abort_on_exception = false\n            if Thread.current.respond_to?(:report_on_exception=)\n              Thread.current.report_on_exception = false\n            end\n            begin\n              Thread.current[:result] = Checkpoint.check(\n                product: \"vagrant\",\n                version: VERSION,\n                signature_file: files[:signature],\n                cache_file: files[:cache]\n              )\n              if !Thread.current[:result].is_a?(Hash)\n                Thread.current[:result] = nil\n              end\n              logger.debug(\"plugin check complete\")\n            rescue => e\n              logger.debug(\"plugin check failure - #{e}\")\n            end\n          end\n        end\n        self\n      end\n\n      # Display any alerts or version update information\n      #\n      # @return [boolean] true if displayed, false if not\n      def display\n        if !defined?(@displayed)\n          if !complete?\n            @logger.debug(\"waiting for checkpoint to complete...\")\n          end\n          # Don't display if information is cached\n          if result && !result[\"cached\"]\n            version_check\n            alerts_check\n          else\n            @logger.debug(\"no information received from checkpoint\")\n          end\n          @displayed = true\n        else\n          false\n        end\n      end\n\n      def alerts_check\n        if result[\"alerts\"] && !result[\"alerts\"].empty?\n          result[\"alerts\"].group_by{|a| a[\"level\"]}.each_pair do |_, alerts|\n            alerts.each do |alert|\n              date = nil\n              begin\n                date = Time.at(alert[\"date\"])\n              rescue\n                date = Time.now\n              end\n              output = I18n.t(\"vagrant.alert\",\n                message: alert[\"message\"],\n                date: date,\n                url: alert[\"url\"]\n              )\n              case alert[\"level\"]\n              when \"info\"\n                alert_ui = Vagrant::UI::Prefixed.new(env.ui, \"vagrant\")\n                alert_ui.info(output)\n              when \"warn\"\n                alert_ui = Vagrant::UI::Prefixed.new(env.ui, \"vagrant-warning\")\n                alert_ui.warn(output)\n              when \"critical\"\n                alert_ui = Vagrant::UI::Prefixed.new(env.ui, \"vagrant-alert\")\n                alert_ui.error(output)\n              end\n            end\n            env.ui.info(\"\")\n          end\n        else\n          @logger.debug(\"no alert notifications to display\")\n        end\n      end\n\n      def version_check\n        latest_version = Gem::Version.new(result[\"current_version\"])\n        installed_version = Gem::Version.new(VERSION)\n        ui = Vagrant::UI::Prefixed.new(env.ui, \"vagrant\")\n        if latest_version > installed_version\n          @logger.info(\"new version of Vagrant available - #{latest_version}\")\n          ui.info(I18n.t(\"vagrant.version_upgrade_available\", latest_version: latest_version, installed_version: installed_version), channel: :error)\n          env.ui.info(\"\", channel: :error)\n        else\n          @logger.debug(\"vagrant is currently up to date\")\n        end\n      end\n\n      # @private\n      # Reset the cached values for platform. This is not considered a public\n      # API and should only be used for testing.\n      def reset!\n        logger = @logger\n        instance_variables.each(&method(:remove_instance_variable))\n        @logger = logger\n        @enabled = false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/command_deprecation.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # Automatically add deprecation notices to commands\n    module CommandDeprecation\n\n      # @return [String] generated name of command\n      def deprecation_command_name\n        name_parts = self.class.name.split(\"::\")\n        [\n          name_parts[1].sub('Command', ''),\n          name_parts[3]\n        ].compact.map(&:downcase).join(\" \")\n      end\n\n      def self.included(klass)\n        klass.class_eval do\n          class << self\n            if method_defined?(:synopsis)\n              alias_method :non_deprecated_synopsis, :synopsis\n\n              def synopsis\n                if !non_deprecated_synopsis.to_s.empty?\n                  \"#{non_deprecated_synopsis} [DEPRECATED]\"\n                else\n                  non_deprecated_synopsis\n                end\n              end\n            end\n          end\n          alias_method :non_deprecated_execute, :execute\n\n          def execute(*args, &block)\n            @env[:ui].warn(I18n.t(\"vagrant.commands.deprecated\",\n              name: deprecation_command_name\n            ) + \"\\n\")\n            non_deprecated_execute(*args, &block)\n          end\n        end\n      end\n\n      # Mark command deprecation complete and fully disable\n      # the command's functionality\n      module Complete\n        def self.included(klass)\n          klass.include(CommandDeprecation)\n          klass.class_eval do\n            def execute(*_)\n              raise Vagrant::Errors::CommandDeprecated,\n                name: deprecation_command_name\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/counter.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'thread'\n\nmodule Vagrant\n  module Util\n    # Atomic counter implementation. This is useful for incrementing\n    # a counter which is guaranteed to only be used once in its class.\n    module Counter\n      def get_and_update_counter(name=nil)\n        name ||= :global\n\n        mutex.synchronize do\n          @__counter ||= Hash.new(1)\n          result = @__counter[name]\n          @__counter[name] += 1\n          result\n        end\n      end\n\n      def mutex\n        @__counter_mutex ||= Mutex.new\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/credential_scrubber.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # Utility class to remove credential information from strings\n    class CredentialScrubber\n      # String used to replace credential information\n      REPLACEMENT_TEXT = \"*****\".freeze\n\n      # Attempt to remove detected credentials from string\n      #\n      # @param [String] string\n      # @return [String]\n      def self.scrub(string)\n        string = url_scrubber(string)\n      end\n\n      # Detect URLs and remove any embedded credentials\n      #\n      # @param [String] string\n      # @return [String]\n      def self.url_scrubber(string)\n        string.gsub(%r{(ftp|https?)://[^\\s]+@[^\\s]+}) do |address|\n          uri = URI.parse(address)\n          uri.user = uri.password = REPLACEMENT_TEXT\n          uri.to_s\n        end\n      end\n\n      # Remove sensitive information from string\n      #\n      # @param [String] string\n      # @return [String]\n      def self.desensitize(string)\n        string = string.to_s.dup\n        sensitive_strings.each do |remove|\n          string.gsub!(/(\\W|^)#{Regexp.escape(remove)}(\\W|$)/, \"\\\\1#{REPLACEMENT_TEXT}\\\\2\")\n        end\n        string\n      end\n\n      # Register a sensitive string to be scrubbed\n      def self.sensitive(string)\n        string = string.to_s.dup\n        if string.length > 0\n          sensitive_strings.push(string).uniq!\n        end\n        nil\n      end\n\n      # Deregister a sensitive string and allow output\n      def self.unsensitive(string)\n        sensitive_strings.delete(string)\n        nil\n      end\n\n      # @return [Array<string>]\n      def self.sensitive_strings\n        if !defined?(@_sensitive_strings)\n          @_sensitive_strings = []\n        end\n        @_sensitive_strings\n      end\n\n      # @private\n      # Reset the cached values for scrubber. This is not considered a public\n      # API and should only be used for testing.\n      def self.reset!\n        instance_variables.each(&method(:remove_instance_variable))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/curl_helper.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    class CurlHelper\n\n      # Hosts that do not require notification on redirect\n      SILENCED_HOSTS = [\n        \"vagrantcloud-files-production.s3-accelerate.amazonaws.com\".freeze,\n        \"vagrantcloud.com\".freeze,\n        \"vagrantup.com\".freeze,\n        \"cloud.hashicorp.com\".freeze,\n      ].freeze\n\n      def self.capture_output_proc(logger, ui, source=nil)\n        progress_data = \"\"\n        progress_regexp = /^\\r\\s*(\\d.+?)\\r/m\n\n        # Setup the proc that'll receive the real-time data from\n        # the downloader.\n        data_proc = Proc.new do |type, data|\n          # Type will always be \"stderr\" because that is the only\n          # type of data we're subscribed for notifications.\n\n          # Accumulate progress_data\n          progress_data << data\n\n          redirect_notify = false\n          while true\n            # If the download has been redirected and we are no longer downloading\n            # from the original host, notify the user that the target host has\n            # changed from the source.\n            if progress_data.include?(\"Location\")\n              location = progress_data.scan(/(^|[^\\w-])Location: (.+?)$/m).flatten.compact.last.to_s.strip\n              if !location.empty?\n                location_uri = URI.parse(location)\n\n                if !location_uri.host.nil? && !redirect_notify\n                  logger.info(\"download redirected to #{location}\")\n                  source_uri = URI.parse(source)\n                  source_host = source_uri.host.to_s.split(\".\", 2).last\n                  location_host = location_uri.host.to_s.split(\".\", 2).last\n                  if location_host != source_host && !SILENCED_HOSTS.include?(location_host) && !SILENCED_HOSTS.include?(location_uri.host.to_s)\n                    ui.rewriting do |_ui|\n                      _ui.clear_line\n                      _ui.detail \"Download redirected to host: #{location_uri.host}\"\n                    end\n                  end\n                  redirect_notify = true\n                end\n              end\n              progress_data.replace(\"\")\n              break\n            end\n            # If we have a full amount of column data (two \"\\r\") then\n            # we report new progress reports. Otherwise, just keep\n            # accumulating.\n            match = nil\n            check_match = true\n\n            while check_match\n              check_match = progress_regexp.match(progress_data)\n              if check_match\n                data = check_match[1].to_s\n                stop = progress_data.index(data) + data.length\n                progress_data.slice!(0, stop)\n\n                match = check_match\n              end\n            end\n\n            break if !match\n\n            # Ignore the first \\r and split by whitespace to grab the columns\n            columns = data.strip.split(/\\s+/)\n\n            # COLUMN DATA:\n            #\n            # 0 - % total\n            # 1 - Total size\n            # 2 - % received\n            # 3 - Received size\n            # 4 - % transferred\n            # 5 - Transferred size\n            # 6 - Average download speed\n            # 7 - Average upload speed\n            # 9 - Total time\n            # 9 - Time spent\n            # 10 - Time left\n            # 11 - Current speed\n            output = \"Progress: #{columns[0]}% (Rate: #{columns[11]}/s, Estimated time remaining: #{columns[10]})\"\n            ui.rewriting do |ui|\n              ui.clear_line\n              ui.detail(output, new_line: false)\n            end\n          end\n        end\n\n        return data_proc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/deep_merge.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    module DeepMerge\n      # This was lifted straight from Rails 4.0.2 as a basic Hash\n      # deep merge.\n      def self.deep_merge(myself, other_hash, &block)\n        myself = myself.dup\n        other_hash.each_pair do |k,v|\n          tv = myself[k]\n          if tv.is_a?(Hash) && v.is_a?(Hash)\n            myself[k] = deep_merge(tv, v, &block)\n          else\n            myself[k] = block && tv ? block.call(k, tv, v) : v\n          end\n        end\n        myself\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/directory.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'pathname'\n\nmodule Vagrant\n  module Util\n    class Directory\n      # Check if directory has any new updates\n      #\n      # @param [Pathname, String] Path to directory\n      # @param [Time] time to compare to eg. has any file in dir_path\n      #               changed since this time\n      # @return [Boolean]\n      def self.directory_changed?(dir_path, threshold_time)\n        Dir.glob(Pathname.new(dir_path).join(\"**\", \"*\")).any? do |path|\n          Pathname.new(path).mtime > threshold_time\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/downloader.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"cgi\"\nrequire \"uri\"\n\nrequire \"log4r\"\nrequire \"digest\"\nrequire \"digest/md5\"\nrequire \"digest/sha1\"\nrequire \"vagrant/util/busy\"\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/subprocess\"\nrequire \"vagrant/util/curl_helper\"\nrequire \"vagrant/util/file_checksum\"\n\nmodule Vagrant\n  module Util\n    # This class downloads files using various protocols by subprocessing\n    # to cURL. cURL is a much more capable and complete download tool than\n    # a hand-rolled Ruby library, so we defer to its expertise.\n    class Downloader\n      # Custom user agent provided to cURL so that requests to URL shorteners\n      # are properly tracked.\n      #\n      #     Vagrant/1.7.4 (+https://www.vagrantup.com; ruby2.1.0)\n      USER_AGENT = \"Vagrant/#{VERSION} (+https://www.vagrantup.com; #{RUBY_ENGINE}#{RUBY_VERSION}) #{ENV['VAGRANT_USER_AGENT_PROVISIONAL_STRING']}\".strip.freeze\n\n      attr_accessor :source\n      attr_reader :destination\n      attr_accessor :headers\n\n      def initialize(source, destination, options=nil)\n        options     ||= {}\n\n        @logger      = Log4r::Logger.new(\"vagrant::util::downloader\")\n        @source      = source.to_s\n        @destination = destination.to_s\n\n        begin\n          url = URI.parse(@source)\n          if url.scheme && url.scheme.start_with?(\"http\") && url.user\n            auth = \"#{CGI.unescape(url.user)}\"\n            auth += \":#{CGI.unescape(url.password)}\" if url.password\n            url.user = nil\n            url.password = nil\n            options[:auth] ||= auth\n            @source = url.to_s\n          end\n        rescue URI::InvalidURIError\n          # Ignore, since its clearly not HTTP\n        end\n\n        # Get the various optional values\n        @auth        = options[:auth]\n        @ca_cert     = options[:ca_cert]\n        @ca_path     = options[:ca_path]\n        @continue    = options[:continue]\n        @headers     = Array(options[:headers])\n        @insecure    = options[:insecure]\n        @ui          = options[:ui]\n        @client_cert = options[:client_cert]\n        @location_trusted = options[:location_trusted]\n        @checksums   = {\n          :md5 => options[:md5],\n          :sha1 => options[:sha1],\n          :sha256 => options[:sha256],\n          :sha384 => options[:sha384],\n          :sha512 => options[:sha512]\n        }.compact\n        @extra_download_options = options[:box_extra_download_options] || []\n        # If on Windows SSL revocation checks should be best effort. More context\n        # for this usage can be found in the following links:\n        #\n        # https://github.com/curl/curl/issues/3727\n        # https://github.com/curl/curl/pull/4981\n        if Platform.windows?\n          @ssl_revoke_best_effort = !options[:disable_ssl_revoke_best_effort]\n        end\n      end\n\n      # This executes the actual download, downloading the source file\n      # to the destination with the given options used to initialize this\n      # class.\n      #\n      # If this method returns without an exception, the download\n      # succeeded. An exception will be raised if the download failed.\n      def download!\n        # This variable can contain the proc that'll be sent to\n        # the subprocess execute.\n        data_proc = nil\n\n        extra_subprocess_opts = {}\n        if @ui\n          # If we're outputting progress, then setup the subprocess to\n          # tell us output so we can parse it out.\n          extra_subprocess_opts[:notify] = :stderr\n\n          data_proc = Vagrant::Util::CurlHelper.capture_output_proc(@logger, @ui, @source)\n        end\n\n        @logger.info(\"Downloader starting download: \")\n        @logger.info(\"  -- Source: #{@source}\")\n        @logger.info(\"  -- Destination: #{@destination}\")\n\n        retried = false\n        begin\n          # Get the command line args and the subprocess opts based\n          # on our downloader settings.\n          options, subprocess_options = self.options\n          options += [\"--output\", @destination]\n          options << @source\n\n          # Merge in any extra options we set\n          subprocess_options.merge!(extra_subprocess_opts)\n\n          # Go!\n          execute_curl(options, subprocess_options, &data_proc)\n        rescue Errors::DownloaderError => e\n          # If we already retried, raise it.\n          raise if retried\n\n          @logger.error(\"Exit code: #{e.extra_data[:code]}\")\n\n          # If its any error other than 33, it is an error.\n          raise if e.extra_data[:code].to_i != 33\n\n          # Exit code 33 means that the server doesn't support ranges.\n          # In this case, try again without resume.\n          @logger.error(\"Error is server doesn't support byte ranges. Retrying from scratch.\")\n          @continue = false\n          retried = true\n          retry\n        ensure\n          # If we're outputting to the UI, clear the output to\n          # avoid lingering progress meters.\n          if @ui\n            @ui.clear_line\n\n            # Windows doesn't clear properly for some reason, so we just\n            # output one more newline.\n            @ui.detail(\"\") if Platform.windows?\n          end\n        end\n\n        validate_download!(@source, @destination, @checksums)\n\n        # Everything succeeded\n        true\n      end\n\n      # Does a HEAD request of the URL and returns the output.\n      def head\n        options, subprocess_options = self.options\n        options.unshift(\"-I\")\n        options << @source\n\n        @logger.info(\"HEAD: #{@source}\")\n        result = execute_curl(options, subprocess_options)\n        result.stdout\n      end\n\n      protected\n\n      # Apply any checksum validations based on provided\n      # options content\n      #\n      # @param source [String] Source of file\n      # @param path [String, Pathname] local file path\n      # @param checksums [Hash] User provided options\n      # @option checksums [String] :md5 Compare MD5 checksum\n      # @option checksums [String] :sha1 Compare SHA1 checksum\n      # @return [Boolean]\n      def validate_download!(source, path, checksums)\n        checksums.each do |type, expected|\n          actual = FileChecksum.new(path, type).checksum\n          @logger.debug(\"Validating checksum (#{type}) for #{source}. \" \\\n            \"expected: #{expected} actual: #{actual}\")\n          if actual.casecmp(expected) != 0\n            raise Errors::DownloaderChecksumError.new(\n              source: source,\n              path: path,\n              type: type,\n              expected_checksum: expected,\n              actual_checksum: actual\n            )\n          end\n        end\n        true\n      end\n\n      def execute_curl(options, subprocess_options, &data_proc)\n        options = options.dup\n        options.unshift(\"-q\")\n        options << subprocess_options\n\n        # Create the callback that is called if we are interrupted\n        interrupted  = false\n        int_callback = Proc.new do\n          @logger.info(\"Downloader interrupted!\")\n          interrupted = true\n        end\n\n        # Execute!\n        result = Busy.busy(int_callback) do\n          Subprocess.execute(\"curl\", *options, &data_proc)\n        end\n\n        # If the download was interrupted, then raise a specific error\n        raise Errors::DownloaderInterrupted if interrupted\n\n        # If it didn't exit successfully, we need to parse the data and\n        # show an error message.\n        if result.exit_code != 0\n          @logger.warn(\"Downloader exit code: #{result.exit_code}\")\n          check = result.stderr.match(/\\n*curl:\\s+\\((?<code>\\d+)\\)\\s*(?<error>.*)$/)\n          if check && check[:code] == \"416\"\n            # All good actually. 416 means there is no more bytes to download\n            @logger.warn(\"Downloader got a 416, but is likely fine. Continuing on...\")\n          else\n            if !check\n              err_msg = result.stderr\n            else\n              err_msg = check[:error]\n            end\n\n            raise Errors::DownloaderError,\n              code: result.exit_code,\n              message: err_msg\n          end\n        end\n\n        result\n      end\n\n      # Returns the various cURL and subprocess options.\n      #\n      # @return [Array<Array, Hash>]\n      def options\n        # Build the list of parameters to execute with cURL\n        options = [\n          \"--fail\",\n          \"--location\",\n          \"--max-redirs\", \"10\", \"--verbose\",\n          \"--user-agent\", USER_AGENT,\n        ]\n\n        options += [\"--cacert\", @ca_cert] if @ca_cert\n        options += [\"--capath\", @ca_path] if @ca_path\n        options += [\"--continue-at\", \"-\"] if @continue\n        options << \"--insecure\" if @insecure\n        options << \"--cert\" << @client_cert if @client_cert\n        options << \"-u\" << @auth if @auth\n        options << \"--location-trusted\" if @location_trusted\n        options << \"--ssl-revoke-best-effort\" if @ssl_revoke_best_effort\n\n        options.concat(@extra_download_options)\n\n        if @headers\n          Array(@headers).each do |header|\n            options << \"-H\" << header\n          end\n        end\n\n        # Specify some options for the subprocess\n        subprocess_options = {}\n\n        # If we're in Vagrant, then we use the packaged CA bundle\n        if Vagrant.in_installer?\n          subprocess_options[:env] ||= {}\n          subprocess_options[:env][\"CURL_CA_BUNDLE\"] = ENV[\"CURL_CA_BUNDLE\"]\n        end\n\n        return [options, subprocess_options]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/env.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    class Env\n      def self.with_original_env\n        original_env = ENV.to_hash\n        if defined?(::Bundler) && defined?(::Bundler::ORIGINAL_ENV)\n          ENV.replace(::Bundler::ORIGINAL_ENV)\n        end\n        ENV.update(Vagrant.original_env)\n        yield\n      ensure\n        ENV.replace(original_env.to_hash)\n      end\n\n      # Execute the given command, removing any Ruby-specific environment\n      # variables. This is an \"enhanced\" version of `Bundler.with_clean_env`,\n      # which only removes Bundler-specific values. We need to remove all\n      # values, specifically:\n      #\n      # - _ORIGINAL_GEM_PATH\n      # - GEM_PATH\n      # - GEM_HOME\n      # - GEM_ROOT\n      # - BUNDLE_BIN_PATH\n      # - BUNDLE_GEMFILE\n      # - RUBYLIB\n      # - RUBYOPT\n      # - RUBY_ENGINE\n      # - RUBY_ROOT\n      # - RUBY_VERSION\n      #\n      # This will escape Vagrant's environment entirely, which is required if\n      # calling an executable that lives in another Ruby environment. The\n      # original environment restored at the end of this call.\n      #\n      # @param [Proc] block\n      #   the block to execute with the cleaned environment\n      def self.with_clean_env\n        with_original_env do\n          if ENV[\"BUNDLE_ORIG_MANPATH\"]\n            ENV[\"MANPATH\"] = ENV[\"BUNDLE_ORIG_MANPATH\"]\n          end\n          ENV.delete_if { |k,_| k[0,7] == \"BUNDLE_\" }\n          if ENV.has_key? \"RUBYOPT\"\n            ENV[\"RUBYOPT\"] = ENV[\"RUBYOPT\"].sub(\"-rbundler/setup\", \"\")\n            ENV[\"RUBYOPT\"] = ENV[\"RUBYOPT\"].sub(\"-I#{File.expand_path('..', __FILE__)}\", \"\")\n          end\n          yield\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/experimental.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    class Experimental\n      class << self\n        # A method for determining if the experimental flag has been enabled with\n        # any features\n        #\n        # @return [Boolean]\n        def enabled?\n          if !defined?(@_experimental)\n            experimental = features_requested\n            if experimental.size >= 1 && experimental.first != \"0\"\n              @_experimental = true\n            else\n              @_experimental = false\n            end\n          end\n          @_experimental\n        end\n\n        # A method for determining if all experimental features have been enabled\n        # by either a global enabled value \"1\" or all features explicitly enabled.\n        #\n        # @return [Boolean]\n        def global_enabled?\n          if !defined?(@_global_enabled)\n            experimental = features_requested\n            if experimental.size == 1 && experimental.first == \"1\"\n              @_global_enabled = true\n            else\n              @_global_enabled = false\n            end\n          end\n          @_global_enabled\n        end\n\n        # A method for Vagrant internals to determine if a given feature\n        # has been abled by the user, is a valid feature flag and can be used.\n        #\n        # @param [String] feature\n        # @return [Boolean] - A hash containing the original array and if it is valid\n        def feature_enabled?(feature)\n          experimental = features_requested\n          feature = feature.to_s\n\n          return global_enabled? || experimental.include?(feature)\n        end\n\n        # Returns the features requested for the experimental flag\n        #\n        # @return [Array] - Returns an array of requested experimental features\n        def features_requested\n          if !defined?(@_requested_features)\n            @_requested_features = ENV[\"VAGRANT_EXPERIMENTAL\"].to_s.downcase.split(',')\n          end\n          @_requested_features\n        end\n\n        # A function to guard experimental blocks of code from being executed\n        #\n        # @param [Array] features - Array of features to guard a method with\n        # @param [Block] block - Block of ruby code to be guarded against\n        def guard_with(*features, &block)\n          yield if block_given? && features.any? {|f| feature_enabled?(f)}\n        end\n\n        # @private\n        # Reset the cached values for platform. This is not considered a public\n        # API and should only be used for testing.\n        def reset!\n          instance_variables.each(&method(:remove_instance_variable))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/file_checksum.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# This is an \"interface\" that should be implemented by any digest class\n# passed into FileChecksum. Note that this isn't strictly enforced at\n# the moment, and this class isn't directly used. It is merely here for\n# documentation of structure of the class.\n\nrequire \"vagrant/errors\"\n\nclass DigestClass\n  def update(string); end\n  def hexdigest; end\nend\n\nmodule Vagrant\n  module Util\n    class FileChecksum\n      BUFFER_SIZE = 1024 * 8\n\n      # Supported file checksum\n      CHECKSUM_MAP = {\n        :md5 => Digest::MD5,\n        :sha1 => Digest::SHA1,\n        :sha256 => Digest::SHA256,\n        :sha384 => Digest::SHA384,\n        :sha512 => Digest::SHA512\n      }.freeze\n\n      # Initializes an object to calculate the checksum of a file. The given\n      # ``digest_klass`` should implement the ``DigestClass`` interface. Note\n      # that the built-in Ruby digest classes duck type this properly:\n      # Digest::MD5, Digest::SHA1, etc.\n      def initialize(path, digest_klass)\n        if digest_klass.is_a?(Class)\n          @digest_klass = digest_klass\n        else\n          @digest_klass = load_digest(digest_klass)\n        end\n\n        @path = path\n      end\n\n      # This calculates the checksum of the file and returns it as a\n      # string.\n      #\n      # @return [String]\n      def checksum\n        digest = @digest_klass.new\n        buf = ''\n\n        File.open(@path, \"rb\") do |f|\n          while !f.eof\n            begin\n              f.readpartial(BUFFER_SIZE, buf)\n              digest.update(buf)\n            rescue EOFError\n              # Although we check for EOF earlier, this seems to happen\n              # sometimes anyways [GH-2716].\n              break\n            end\n          end\n        end\n\n        digest.hexdigest\n      end\n\n      private\n\n      def load_digest(type)\n        digest = CHECKSUM_MAP[type.to_s.downcase.to_sym]\n        if digest.nil?\n          raise Vagrant::Errors::BoxChecksumInvalidType,\n                type: type.to_s,\n                types: CHECKSUM_MAP.keys.join(', ')\n        end\n        digest\n      end\n    end\n  end\nend\n\n# NOTE: This class was not originally namespaced\n# with the Util module so this is left for backwards\n# compatibility.\nFileChecksum = Vagrant::Util::FileChecksum\n"
  },
  {
    "path": "lib/vagrant/util/file_mode.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    class FileMode\n      # This returns the file permissions as a string from\n      # an octal number.\n      def self.from_octal(octal)\n        perms = sprintf(\"%o\", octal)\n        perms.reverse[0..2].reverse\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/file_mutex.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # Utility to provide a simple mutex via file lock\n    class FileMutex\n      # Create a new FileMutex instance\n      #\n      # @param mutex_path [String] path for file\n      def initialize(mutex_path)\n        @mutex_path = mutex_path\n      end\n\n      # Execute provided block within lock and unlock\n      # when completed\n      def with_lock(&block)\n        lock\n        begin\n          block.call\n        rescue => e\n          raise e\n        ensure\n          unlock\n        end\n      end\n\n      # Attempt to acquire the lock\n      def lock\n        if lock_file.flock(File::LOCK_EX|File::LOCK_NB) === false\n          raise Errors::VagrantLocked, lock_file_path: @mutex_path\n        end\n      end\n\n      # Unlock the file\n      def unlock\n        lock_file.flock(File::LOCK_UN)\n        lock_file.close\n        File.delete(@mutex_path) if File.file?(@mutex_path)\n      end\n\n      protected\n\n      def lock_file\n        return @lock_file if @lock_file && !@lock_file.closed?\n        @lock_file = File.open(@mutex_path, \"w+\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/guest_hosts.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # Helper methods for modfiying guests /etc/hosts file\n    module GuestHosts\n\n      module Unix\n        DEAFAULT_LOOPBACK_CHECK_LIMIT = 5.freeze\n\n        # Add hostname to a loopback address on /etc/hosts if not already there\n        # Will insert name at the first free address of the form 127.0.X.1, up to\n        # the loop_bound\n        #\n        # @param [Communicator] \n        # @param [String] full hostanme\n        # @param [int] (option) defines the upper bound for searching for an available loopback address\n        def add_hostname_to_loopback_interface(comm, name, loop_bound=DEAFAULT_LOOPBACK_CHECK_LIMIT)\n          basename = name.split(\".\", 2)[0]\n          comm.sudo <<-EOH.gsub(/^ {14}/, '')\n          grep -w '#{name}' /etc/hosts || {\n            for i in #{[*1..loop_bound].join(' ')}; do\n              grep -w \"127.0.${i}.1\" /etc/hosts || {\n                echo \"127.0.${i}.1 #{name} #{basename}\" >> /etc/hosts\n                break\n              }\n            done\n          }\n          EOH\n        end\n      end\n\n      # Linux specific inspection helpers\n      module Linux\n        include Unix\n        # Remove any line in /etc/hosts that contains hostname,\n        # then add hostname with associated ip \n        #\n        # @param [Communicator] \n        # @param [String] full hostanme\n        # @param [String] target ip\n        def replace_host(comm, name, ip)\n          basename = name.split(\".\", 2)[0]\n          comm.sudo <<-EOH.gsub(/^ {14}/, '')\n          sed -i '/#{name}/d' /etc/hosts\n          sed -i'' '1i '#{ip}'\\\\t#{name}\\\\t#{basename}' /etc/hosts\n          EOH\n        end\n      end\n\n      # BSD specific inspection helpers\n      module BSD\n        include Unix\n        # Remove any line in /etc/hosts that contains hostname,\n        # then add hostname with associated ip \n        #\n        # @param [Communicator] \n        # @param [String] full hostanme\n        # @param [String] target ip\n        def replace_host(comm, name, ip)\n          basename = name.split(\".\", 2)[0]\n          comm.sudo <<-EOH.gsub(/^ {14}/, '')\n          sed -i.bak '/#{name}/d' /etc/hosts\n          sed -i.bak '1i\\\\\\n#{ip}\\t#{name}\\t#{basename}\\n' /etc/hosts\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/guest_inspection.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # Helper methods for inspecting guests to determine if specific services\n    # or applications are installed and in use\n    module GuestInspection\n      # Linux specific inspection helpers\n      module Linux\n\n        ## systemd helpers\n\n        # systemd is in use\n        #\n        # @return [Boolean]\n        def systemd?(comm)\n          comm.test(\"ps -o comm= 1 | grep systemd\", sudo: true)\n        end\n\n        # systemd-networkd.service is in use\n        #\n        # @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator\n        # @return [Boolean]\n        def systemd_networkd?(comm)\n          comm.test(\"systemctl -q is-active systemd-networkd.service\", sudo: true)\n        end\n\n        # NetworkManager.service is in use\n        #\n        # @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator\n        # @return [Boolean]\n        def systemd_network_manager?(comm)\n          comm.test(\"systemctl -q is-active NetworkManager.service\", sudo: true)\n        end\n\n        # Check if a unit file with the given name is defined. Name can\n        # be a pattern or explicit name.\n        #\n        # @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator\n        # @param [String] name Name or pattern to search\n        # @return [Boolean]\n        def systemd_unit_file?(comm, name)\n          comm.test(\"systemctl -q list-unit-files | grep \\\"#{name}\\\"\")\n        end\n\n        # Check if a unit is currently active within systemd\n        #\n        # @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator\n        # @param [String] name Name or pattern to search\n        # @return [Boolean]\n        def systemd_unit?(comm, name)\n          comm.test(\"systemctl -q list-units | grep \\\"#{name}\\\"\")\n        end\n\n        # Check if given service is controlled by systemd\n        #\n        # @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator\n        # @param [String] service_name Name of the service to check\n        # @return [Boolean]\n        def systemd_controlled?(comm, service_name)\n          comm.test(\"systemctl -q is-active #{service_name}\", sudo: true)\n        end\n\n        # systemd hostname set is via hostnamectl\n        #\n        # @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator\n        # @return [Boolean]\n        # NOTE: This test includes actually calling `hostnamectl` to verify\n        # that it is in working order. This prevents attempts to use the\n        # hostnamectl command when it is available, but dbus is not which\n        # renders the command useless\n        def hostnamectl?(comm)\n          comm.test(\"command -v hostnamectl && hostnamectl\")\n        end\n\n        ## netplan helpers\n\n        # netplan is installed\n        #\n        # @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator\n        # @return [Boolean]\n        def netplan?(comm)\n          comm.test(\"command -v netplan\")\n        end\n\n        # is networkd isntalled\n        #\n        # @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator\n        # @return [Boolean]\n        def networkd?(comm)\n          comm.test(\"command -v networkd\")\n        end\n\n        ## nmcli helpers\n\n        # nmcli is installed\n        #\n        # @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator\n        # @return [Boolean]\n        def nmcli?(comm)\n          comm.test(\"command -v nmcli\")\n        end\n\n        # NetworkManager currently controls device\n        #\n        # @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator\n        # @param device_name [String]\n        # @return [Boolean]\n        def nm_controlled?(comm, device_name)\n          comm.test(\"nmcli -t d show #{device_name}\") &&\n            !comm.test(\"nmcli -t d show #{device_name} | grep unmanaged\")\n        end\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/guest_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # Helper methods for configuring guest networks\n    module GuestNetworks\n      module Linux\n        NETWORK_MANAGER_DEVICE_DIRECTORY = \"/etc/NetworkManager/system-connections\".freeze\n\n        def configure_network_manager(machine, networks, **opts)\n          comm = machine.communicate\n          nm_directory = opts.fetch(:nm_directory, NETWORK_MANAGER_DEVICE_DIRECTORY)\n\n          interfaces = machine.guest.capability(:network_interfaces)\n          net_configs = machine.config.vm.networks.find_all { |type, _| type.to_s.end_with?(\"_network\") }.map(&:last)\n\n          # Get IDs of currently configured devices\n          current_devs = get_current_devices(comm)\n\n          networks.each.with_index do |network, i|\n            net_opts = (net_configs[i] || {}).merge(network)\n            net_opts[:type] = net_opts[:type].to_s\n            net_opts[:device] = interfaces[network[:interface]]\n\n            if !net_opts[:mac_address]\n              comm.execute(\"cat /sys/class/net/#{net_opts[:device]}/address\") do |type, data|\n                net_opts[:mac_address] = data if type == :stdout\n              end\n            end\n\n            tmpl_opts = {\n              interface_name: net_opts[:device],\n              type: net_opts[:type],\n              mac_address: net_opts[:mac_address],\n              uuid: SecureRandom.uuid\n            }\n\n            if net_opts[:type] != \"dhcp\"\n              begin\n                addr = IPAddr.new(\"#{net_opts[:ip]}\")\n                if addr.ipv4?\n                  tmpl_opts[:ipv4] = addr.to_string\n                  masked = addr.mask(net_opts[:netmask])\n\n                  tmpl_opts[:ipv4_mask] = masked.prefix\n                  tmpl_opts[:ipv4_gateway] = masked.succ.to_string\n                else\n                  tmpl_opts[:ipv6] = addr.to_string\n                  masked = addr.mask(net_opts[:netmask])\n\n                  tmpl_opts[:ipv6_mask] = masked.prefix\n                  tmpl_opts[:ipv6_gateway] = masked.succ.to_string\n                end\n              rescue IPAddr::Error => err\n                raise NetworkAddressInvalid,\n                      address: net_opts[:ip],\n                      mask: net_opts[:netmask],\n                      error: err.to_s\n              end\n            end\n\n            entry = TemplateRenderer.render(\"networking/network_manager/network_manager_device\", options: tmpl_opts)\n            remote_path = \"/tmp/vagrant-network-entry-#{net_opts[:device]}-#{Time.now.to_i}-#{i}\"\n            final_path = \"#{nm_directory}/#{net_opts[:device]}.nmconnection\"\n\n            Tempfile.open(\"vagrant-nm-configure-networks\") do |f|\n              f.binmode\n              f.write(entry)\n              f.fsync\n              f.close\n              comm.upload(f.path, remote_path)\n            end\n\n            # Remove the device if it already exists\n            if device_id = current_devs[net_opts[:device]]\n              [\n                \"nmcli d disconnect '#{net_opts[:device]}'\",\n                \"nmcli c delete '#{device_id}'\",\n              ].each do |cmd|\n                comm.sudo(cmd, error_check: false)\n              end\n            end\n\n            # Apply the config\n            [\n              \"chown root:root '#{remote_path}'\",\n              \"chmod 0600 '#{remote_path}'\",\n              \"mv '#{remote_path}' '#{final_path}'\",\n              \"nmcli c load '#{final_path}'\",\n              \"nmcli d connect '#{net_opts[:device]}'\"\n            ].each do |cmd|\n              comm.sudo(cmd)\n            end\n          end\n        end\n\n        # Get all network devices currently managed by NetworkManager.\n        # @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator\n        # @return [Hash] A hash of current device names and their associated IDs.\n        def get_current_devices(comm)\n          {}.tap do |cd|\n            comm.execute(\"nmcli -t c show\") do |type, data|\n              if type == :stdout\n                data.strip.lines.map(&:chomp).each do |line|\n                  next if line.strip.empty?\n                  _, id, _, dev = line.strip.split(':')\n                  cd[dev] = id\n                end\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/hash_with_indifferent_access.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # A hash with indifferent access. Mostly taken from Thor/Rails (thanks).\n    # Normally I'm not a fan of using an indifferent access hash since Symbols\n    # are basically memory leaks in Ruby, but since Vagrant is typically a quick\n    # one-off binary run and it doesn't use too many hash keys where this is\n    # used, the effect should be minimal.\n    #\n    #   hash[:foo]  #=> 'bar'\n    #   hash['foo'] #=> 'bar'\n    #\n    class HashWithIndifferentAccess < ::Hash\n      def initialize(hash={}, &block)\n        super(&block)\n\n        hash.each do |key, value|\n          self[convert_key(key)] = value\n        end\n      end\n\n      def [](key)\n        super(convert_key(key))\n      end\n\n      def []=(key, value)\n        super(convert_key(key), value)\n      end\n\n      def delete(key)\n        super(convert_key(key))\n      end\n\n      def values_at(*indices)\n        indices.collect { |key| self[convert_key(key)] }\n      end\n\n      def merge(other)\n        dup.merge!(other)\n      end\n\n      def merge!(other)\n        other.each do |key, value|\n          self[convert_key(key)] = value\n        end\n        self\n      end\n\n      def key?(key)\n        super(convert_key(key))\n      end\n\n      alias_method :include?, :key?\n      alias_method :has_key?, :key?\n      alias_method :member?, :key?\n\n      protected\n\n      def convert_key(key)\n        key.is_a?(Symbol) ? key.to_s : key\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/install_cli_autocomplete.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # Generic installation of content to shell config file\n    class InstallShellConfig\n\n      PREPEND_STRING = \"# >>>> Vagrant command completion (start)\".freeze\n      APPEND_STRING = \"# <<<<  Vagrant command completion (end)\".freeze\n\n      attr_accessor :prepend_string\n      attr_accessor :string_insert\n      attr_accessor :append_string\n      attr_accessor :config_paths\n\n      def initialize(string_insert, config_paths)\n        @prepend_string = PREPEND_STRING\n        @string_insert = string_insert\n        @append_string = APPEND_STRING\n        @config_paths = config_paths\n        @logger = Log4r::Logger.new(\"vagrant::util::install_shell_config\")\n      end\n\n      # Searches a users home dir for a shell config file based on a\n      # given home dir and a configured set of config paths. If there\n      # are multiple config paths, it will return the first match.\n      #\n      # @param [string] path to users home dir\n      # @return [string] path to shell config file if exists\n      def shell_installed(home)\n        @logger.info(\"Searching for config in home #{home}\")\n        @config_paths.each do |path|\n          config_file = File.join(home, path)\n          if File.exist?(config_file)\n            @logger.info(\"Found config file #{config_file}\")\n            return config_file\n          end\n        end\n        return nil\n      end\n\n      # Searches a given file for the existence of a set prepend string.\n      # This can be used to find if vagrant has inserted some strings to a file\n      #\n      # @param [string] path to a file (config file)\n      # @return [boolean] true if the prepend string is found in the file\n      def is_installed(path)\n        File.foreach(path) do |line|\n          if line.include?(@prepend_string)\n            @logger.info(\"Found completion already installed in #{path}\")\n            return true\n          end\n        end\n        return false\n      end\n\n      # Given a path to the users home dir, will install some given strings\n      # marked by a prepend and append string\n      #\n      # @param [string] path to users home dir\n      # @return [string] path to shell config file that was modified if exists\n      def install(home)\n        path = shell_installed(home)\n        if path && !is_installed(path)\n          File.open(path, \"a\") do |f|\n            f.write(\"\\n\")\n            f.write(@prepend_string)\n            f.write(\"\\n\")\n            f.write(@string_insert)\n            f.write(\"\\n\")\n            f.write(@append_string)\n            f.write(\"\\n\")\n          end\n        end\n        return path\n      end\n    end\n\n    # Install autocomplete script to zsh config located as .zshrc\n    class InstallZSHShellConfig < InstallShellConfig\n      def initialize\n        string_insert = \"\"\"fpath=(#{File.join(Vagrant.source_root, \"contrib\", \"zsh\")} $fpath)\\ncompinit\"\"\".freeze\n        config_paths = [\".zshrc\".freeze].freeze\n        super(string_insert, config_paths)\n      end\n    end\n\n    # Install autocomplete script to bash config located as .bashrc or .bash_profile\n    class InstallBashShellConfig < InstallShellConfig\n      def initialize\n        string_insert = \". #{File.join(Vagrant.source_root, 'contrib', 'bash', 'completion.sh')}\".freeze\n        config_paths = [\".bashrc\".freeze, \".bash_profile\".freeze].freeze\n        super(string_insert, config_paths)\n      end\n    end\n\n    # Install autocomplete script for supported shells\n    class InstallCLIAutocomplete\n      SUPPORTED_SHELLS = {\n        \"zsh\" => Vagrant::Util::InstallZSHShellConfig.new(),\n        \"bash\" => Vagrant::Util::InstallBashShellConfig.new()\n      }\n\n      def self.install(shells=[])\n        shells = SUPPORTED_SHELLS.keys() if shells.empty?\n        home = Dir.home\n        written_paths = []\n        \n        shells.map do |shell|\n          if SUPPORTED_SHELLS[shell]\n            written_paths.push(SUPPORTED_SHELLS[shell].install(home))\n          else\n            raise ArgumentError, \"shell must be in #{SUPPORTED_SHELLS.keys()}\"\n          end\n        end.compact\n        return written_paths\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/io.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/platform\"\n\nmodule Vagrant\n  module Util\n    class IO\n      # The chunk size for reading from subprocess IO.\n      READ_CHUNK_SIZE = 4096\n\n      # Reads data from an IO object while it can, returning the data it reads.\n      # When it encounters a case when it can't read anymore, it returns the\n      # data.\n      #\n      # @return [String]\n      def self.read_until_block(io)\n        data = \"\"\n\n        while true\n          begin\n            if Platform.windows?\n              # Windows doesn't support non-blocking reads on\n              # file descriptors or pipes so we have to get\n              # a bit more creative.\n\n              # Check if data is actually ready on this IO device.\n              # We have to do this since `readpartial` will actually block\n              # until data is available, which can cause blocking forever\n              # in some cases.\n              results = ::IO.select([io], nil, nil, 1.0)\n              break if !results || results[0].empty?\n\n              # Read!\n              data << io.readpartial(READ_CHUNK_SIZE).encode(\n                \"UTF-8\", Encoding.default_external,\n                invalid: :replace,\n                undef: :replace\n              )\n            else\n              # Do a simple non-blocking read on the IO object\n              data << io.read_nonblock(READ_CHUNK_SIZE)\n            end\n          rescue EOFError, Errno::EAGAIN, ::IO::WaitReadable\n            break\n          end\n        end\n\n        data\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/ipv4_interfaces.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    module IPv4Interfaces\n      def ipv4_interfaces\n        Socket.getifaddrs.select do |ifaddr|\n          ifaddr.addr && ifaddr.addr.ipv4?\n        end.map do |ifaddr|\n          [ifaddr.name, ifaddr.addr.ip_address]\n        end\n      end\n\n      extend IPv4Interfaces\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/is_port_open.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"socket\"\n\nmodule Vagrant\n  module Util\n    # Contains the method {#is_port_open?} to check if a port is open\n    # (listening) or closed (not in use). This method isn't completely\n    # fool-proof, but it works enough of the time to be useful.\n    module IsPortOpen\n      # Checks if a port is open (listening) on a given host and port.\n      #\n      # @param [String] host Hostname or IP address.\n      # @param [Integer] port Port to check.\n      # @return [Boolean] `true` if the port is open (listening), `false`\n      #   otherwise.\n      def is_port_open?(host, port)\n        begin\n          Socket.tcp(host, port, connect_timeout: 0.1).close\n          true\n        rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, \\\n            Errno::ENETUNREACH, Errno::EACCES, Errno::ENOTCONN, Errno::EALREADY\n          false\n        end\n      end\n\n      extend IsPortOpen\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/keypair.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"base64\"\nrequire \"ed25519\"\nrequire \"securerandom\"\n\nrequire \"vagrant/util/retryable\"\n\nmodule Vagrant\n  module Util\n    class Keypair\n      # Magic string header\n      AUTH_MAGIC = \"openssh-key-v1\".freeze\n      # Header of private key file content\n      PRIVATE_KEY_START = \"-----BEGIN OPENSSH PRIVATE KEY-----\\n\".freeze\n      # Footer of private key file content\n      PRIVATE_KEY_END = \"-----END OPENSSH PRIVATE KEY-----\\n\".freeze\n\n      # Check if provided key is a supported key type\n      #\n      # @param [Symbol] key Key type to check\n      # @return [Boolean] key type is supported\n      def self.valid_type?(key)\n        VALID_TYPES.keys.include?(key)\n      end\n\n      # @return [Array<Symbol>] list of supported key types\n      def self.available_types\n        PREFER_KEY_TYPES.values\n      end\n\n      # Create a new keypair\n      #\n      # @param [String] password Password for the key or nil for no password (only supported for rsa type)\n      # @param [Symbol] type Key type to generate\n      # @return [Array<String, String, String>] Public key, openssh private key, openssh public key with comment\n      def self.create(password=nil, type: :rsa)\n        if !VALID_TYPES.key?(type)\n          raise ArgumentError,\n                \"Invalid key type requested (supported types: #{available_types.map(&:inspect).join(\", \")})\"\n        end\n\n        VALID_TYPES[type].create(password)\n      end\n\n      class Ed25519\n        # Key type identifier\n        KEY_TYPE = \"ssh-ed25519\".freeze\n\n        # Encodes given string\n        #\n        # @param [String] s String to encode\n        # @return [String]\n        def self.string(s)\n          [s.length].pack(\"N\") + s\n        end\n\n        # Encodes given string with padding to block size\n        #\n        # @param [String] s String to encode\n        # @param [Integer] blocksize Defined block size\n        # @return [String]\n        def self.padded_string(s, blocksize)\n          pad = blocksize - (s.length % blocksize)\n          string(s + Array(1..pad).pack(\"c*\"))\n        end\n\n        # Creates an ed25519 SSH key pair\n        # @return [Array<String, String, String>] Public key, openssh private key, openssh public key with comment\n        # @note Password support was not included as it's not actively used anywhere. If it ends up being\n        # something that's needed, it can be revisited\n        def self.create(password=nil)\n          if password\n            raise NotImplementedError,\n                  \"Ed25519 key pair generation does not support passwords\"\n          end\n\n          # Generate the key\n          base_key = ::Ed25519::SigningKey.generate\n          # Define the comment used for the key\n          comment = \"vagrant\"\n\n          # Grab the raw public key\n          public_key = base_key.verify_key.to_bytes\n          # Encode the public key for use building the openssh private key\n          encoded_public_key = string(KEY_TYPE) + string(public_key)\n          # Format the public key into the openssh public key format for writing\n          openssh_public_key = \"#{KEY_TYPE} #{Base64.encode64(encoded_public_key).gsub(\"\\n\", \"\")} #{comment}\"\n\n          # Agent encoded private key is used when building the openssh private key\n          # (https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-4.2.3)\n          # (https://dnaeon.github.io/openssh-private-key-binary-format/)\n          agent_private_key = [\n            ([SecureRandom.random_number((2**32)-1)] * 2).pack(\"NN\"), # checkint, random uint32 value, twice (used for encryption verification)\n            encoded_public_key, # includes the key type and public key\n            string(base_key.seed + public_key), # private key with public key concatenated\n            string(comment), # comment for the key\n          ].join\n\n          # Build openssh private key data (https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key)\n          private_key = [\n            AUTH_MAGIC + \"\\0\", # Magic string\n            string(\"none\"), # cipher name, no encryption, so none\n            string(\"none\"), # kdf name, no encryption, so none\n            string(\"\"), # kdf options/data, no encryption, so empty string\n            [1].pack(\"N\"), # Number of keys (just one)\n            string(encoded_public_key), # The public key\n            padded_string(agent_private_key, 8) # Private key encoded with agent rules, padded for 8 byte block size\n          ].join\n\n          # Create the openssh private key content\n          openssh_private_key = [\n            PRIVATE_KEY_START,\n            Base64.encode64(private_key),\n            PRIVATE_KEY_END,\n          ].join\n\n          return [public_key, openssh_private_key, openssh_public_key]\n        end\n      end\n\n      class Rsa\n        extend Retryable\n\n        # Key type identifier\n        KEY_TYPE = \"ssh-rsa\"\n\n        # Creates an SSH keypair and returns it.\n        #\n        # @param [String] password Password for the key, or nil for no password.\n        # @return [Array<String, String, String>] PEM-encoded public and private key,\n        #   respectively. The final element is the OpenSSH encoded public\n        #   key.\n        def self.create(password=nil)\n          # This sometimes fails with RSAError. It is inconsistent and strangely\n          # sleeps seem to fix it. We just retry this a few times. See GH-5056\n          rsa_key = nil\n          retryable(on: OpenSSL::PKey::RSAError, sleep: 2, tries: 5) do\n            rsa_key = OpenSSL::PKey::RSA.new(2048)\n          end\n\n          public_key  = rsa_key.public_key\n          private_key = rsa_key.to_pem\n\n          if password\n            cipher      = OpenSSL::Cipher.new('des3')\n            private_key = rsa_key.to_pem(cipher, password)\n          end\n\n          # Generate the binary necessary for the OpenSSH public key.\n          binary = [7].pack(\"N\")\n          binary += \"ssh-rsa\"\n          [\"e\", \"n\"].each do |m|\n            val  = public_key.send(m)\n            data = val.to_s(2)\n\n            first_byte = data[0,1].unpack(\"c\").first\n            if val < 0\n              data[0] = [0x80 & first_byte].pack(\"c\")\n            elsif first_byte < 0\n              data = 0.chr + data\n            end\n\n            binary += [data.length].pack(\"N\") + data\n          end\n\n          openssh_key = \"ssh-rsa #{Base64.encode64(binary).gsub(\"\\n\", \"\")} vagrant\"\n          public_key  = public_key.to_pem\n          return [public_key, private_key, openssh_key]\n        end\n      end\n\n      # Base class for Ecdsa type keys to subclass\n      class Ecdsa\n        # Encodes given string\n        #\n        # @param [String] s String to encode\n        # @return [String]\n        def self.string(s)\n          [s.length].pack(\"N\") + s\n        end\n\n        # Encodes given string with padding to block size\n        #\n        # @param [String] s String to encode\n        # @param [Integer] blocksize Defined block size\n        # @return [String]\n        def self.padded_string(s, blocksize)\n          pad = blocksize - (s.length % blocksize)\n          string(s + Array(1..pad).pack(\"c*\"))\n        end\n\n        # Creates an ed25519 SSH key pair\n        # @return [Array<String, String, String>] Public key, openssh private key, openssh public key with comment\n        # @note Password support was not included as it's not actively used anywhere. If it ends up being\n        # something that's needed, it can be revisited\n        def self.create(password=nil)\n          if password\n            raise NotImplementedError,\n                  \"Ecdsa key pair generation does not support passwords\"\n          end\n\n          # Generate the key\n          base_key = OpenSSL::PKey::EC.generate(self.const_get(:OPENSSL_CURVE))\n          # Define the comment used for the key\n          comment = \"vagrant\"\n\n          # Grab the raw public key\n          public_key = base_key.public_key.to_bn.to_s(2)\n          # Encode the public key for use building the openssh private key\n          encoded_public_key = string(self.const_get(:KEY_TYPE)) + string(self.const_get(:OPENSSH_CURVE)) + string(public_key)\n          # Format the public key into the openssh public key format for writing\n          openssh_public_key = \"#{self.const_get(:KEY_TYPE)} #{Base64.encode64(encoded_public_key).gsub(\"\\n\", \"\")} #{comment}\"\n\n          pk_value = base_key.private_key.to_s(2)\n          # Pad the start of the key if required\n          if pk_value.length % 8 == 0\n            pk_value = \"\\0#{pk_value}\"\n          end\n\n          # Agent encoded private key is used when building the openssh private key\n          # (https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-4.2.3)\n          # (https://dnaeon.github.io/openssh-private-key-binary-format/)\n          agent_private_key = [\n            ([SecureRandom.random_number((2**32)-1)] * 2).pack(\"NN\"), # checkint, random uint32 value, twice (used for encryption verification)\n            encoded_public_key, # includes the key type and public key\n            string(pk_value), # private key\n            string(comment), # comment for the key\n          ].join\n\n          # Build openssh private key data (https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key)\n          private_key = [\n            AUTH_MAGIC + \"\\0\", # Magic string\n            string(\"none\"), # cipher name, no encryption, so none\n            string(\"none\"), # kdf name, no encryption, so none\n            string(\"\"), # kdf options/data, no encryption, so empty string\n            [1].pack(\"N\"), # Number of keys (just one)\n            string(encoded_public_key), # The public key\n            padded_string(agent_private_key, 8) # Private key encoded with agent rules, padded for 8 byte block size\n          ].join\n\n          # Create the openssh private key content\n          openssh_private_key = [\n            PRIVATE_KEY_START,\n            Base64.encode64(private_key),\n            PRIVATE_KEY_END,\n          ].join\n\n          return [public_key, openssh_private_key, openssh_public_key]\n        end\n      end\n\n      class Ecdsa256 < Ecdsa\n        KEY_TYPE = \"ecdsa-sha2-nistp256\".freeze\n        OPENSSH_CURVE = \"nistp256\".freeze\n        OPENSSL_CURVE = \"prime256v1\".freeze\n      end\n\n      class Ecdsa384 < Ecdsa\n        KEY_TYPE = \"ecdsa-sha2-nistp384\".freeze\n        OPENSSH_CURVE = \"nistp384\".freeze\n        OPENSSL_CURVE = \"secp384r1\".freeze\n      end\n\n      class Ecdsa521 < Ecdsa\n        KEY_TYPE = \"ecdsa-sha2-nistp521\".freeze\n        OPENSSH_CURVE = \"nistp521\".freeze\n        OPENSSL_CURVE = \"secp521r1\".freeze\n      end\n\n      # Supported key types.\n      VALID_TYPES = {\n        ecdsa256: Ecdsa256,\n        ecdsa384: Ecdsa384,\n        ecdsa521: Ecdsa521,\n        ed25519: Ed25519,\n        rsa: Rsa\n      }.freeze\n\n      # Ordered mapping of openssh key type name to lookup name. The\n      # order defined here is based on preference. Note that ecdsa\n      # ordering is based on performance\n      PREFER_KEY_TYPES = {\n        Ed25519::KEY_TYPE => :ed25519,\n        Ecdsa256::KEY_TYPE => :ecdsa256,\n        Ecdsa521::KEY_TYPE => :ecdsa521,\n        Ecdsa384::KEY_TYPE => :ecdsa384,\n        Rsa::KEY_TYPE => :rsa,\n      }.freeze\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/line_buffer.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    class LineBuffer\n\n      # Maximum number of characters to buffer before sending\n      # to callback without detecting a new line\n      MAX_LINE_LENGTH = 5000.freeze\n\n      # Create a new line buffer. The registered block\n      # will be called when a new line is encountered on\n      # provided input, or the max line length is reached\n      def initialize(&callback)\n        raise ArgumentError,\n          \"Expected callback but received none\" if callback.nil?\n        @mu = Mutex.new\n        @callback = callback\n        @buffer = \"\"\n      end\n\n      # Add string data to output\n      #\n      # @param [String] str String of data to output\n      # @return [self]\n      def <<(str)\n        @mu.synchronize do\n          while i = str.index(\"\\n\")\n            @callback.call((@buffer + str[0, i+1]).rstrip)\n            @buffer.clear\n            str = str[i+1, str.length].to_s\n          end\n\n          @buffer << str.to_s\n\n          if @buffer.length > MAX_LINE_LENGTH\n            @callback.call(@buffer.dup)\n            @buffer.clear\n          end\n        end\n        self\n      end\n\n      # Closes the buffer. Any remaining data that has\n      # been buffered will be given to the callback.\n      # Once closed the instance will no longer be usable.\n      #\n      # @return [self]\n      def close\n        @mu.synchronize do\n          # Send any remaining output on the buffer\n          @callback.call(@buffer.dup) if !@buffer.empty?\n          # Disable this buffer instance\n          @callback = nil\n          @buffer.clear\n          @buffer.freeze\n        end\n        self\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/line_ending_helpers.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    module LineEndingHelpers\n      # Converts line endings to unix-style line endings in the\n      # given string.\n      #\n      # @param [String] string Original string\n      # @return [String] The fixed string\n      def dos_to_unix(string)\n        string.gsub(\"\\r\\n\", \"\\n\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/logging_formatter.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/credential_scrubber\"\nrequire \"log4r/formatter/formatter\"\n\nmodule Vagrant\n  module Util\n    # Wrapper for logging formatting to provide\n    # information scrubbing prior to being written\n    # to output target\n    class LoggingFormatter < Log4r::BasicFormatter\n      # @return [Log4r::PatternFormatter]\n      attr_reader :formatter\n\n      # Creates a new formatter wrapper instance.\n      #\n      # @param [Log4r::Formatter]\n      def initialize(formatter)\n        @formatter = formatter\n      end\n\n      # Format event and scrub output\n      def format(event)\n        msg = formatter.format(event)\n        CredentialScrubber.desensitize(msg)\n      end\n    end\n\n    class HCLogFormatter < Log4r::BasicFormatter\n      MAX_MESSAGE_LENGTH = 4096\n\n      def format(event)\n        message = format_object(event.data).\n          force_encoding('UTF-8').\n          scrub(\"?\")\n        if message.length > MAX_MESSAGE_LENGTH\n          message = Array.new.tap { |a|\n            until message.empty?\n              a << \"continued...\" unless a.empty?\n              a << message.slice!(0, MAX_MESSAGE_LENGTH)\n            end\n          }\n        else\n          message = [message]\n        end\n\n        message.map do |msg|\n          d = {\n            \"@timestamp\" => Time.now.strftime(\"%Y-%m-%dT%H:%M:%S.%6N%:z\"),\n            \"@level\" => Log4r::LNAMES[event.level].downcase,\n            \"@module\" => event.fullname,\n            \"@name\" => event.name,\n            \"@message\" => msg,\n          }\n          d[\"@caller\"] = event.tracer[0] if event.tracer\n          d.to_json + \"\\n\"\n        end\n      end\n    end\n\n    class HCLogOutputter < Log4r::StderrOutputter\n      def write(data)\n        data.each do |d|\n          super(d)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/map_command_options.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    class MapCommandOptions\n      # Given a hash map of user specified argments, will generate\n      # a list. Set the key to the command flag, and the value to \n      # it's value. If the value is boolean (true), only the flag is\n      # added. eg.\n      # {a: \"opt-a\", b: true} -> [\"--a\", \"opt-a\", \"--b\"]\n      #\n      # @param [Hash]   map of commands\n      # @param [String] string prepended to cmd line flags (keys)\n      #\n      # @return[Array<String>] commands in list form\n      def self.map_to_command_options(map, cmd_flag=\"--\")\n        opt_list = []\n        if map == nil\n          return opt_list\n        end\n        map.each do |k, v|\n          # If the value is true (bool) add the key as the cmd flag\n          if v.is_a?(TrueClass)\n            opt_list.push(\"#{cmd_flag}#{k}\")\n          # If the value is a string, add the key as the flag, and value as the flags argument\n          elsif v.is_a?(String)\n            opt_list.push(\"#{cmd_flag}#{k}\")\n            opt_list.push(v)\n          end\n        end\n        return opt_list\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/mime.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'mime/types'\nrequire 'securerandom'\n\nmodule Vagrant\n  module Util\n    module Mime\n      class Multipart\n\n        # @return [Array<String>] collection of content part of the multipart mime\n        attr_accessor :content\n\n        # @return [String] type of the content\n        attr_accessor :content_type\n\n        # @return [Hash] headers for the mime\n        attr_accessor :headers\n\n        # @param [String] (optional) mime content type\n        # @param [String] (optional) mime version\n        def initialize(content_type=\"multipart/mixed\")\n          @content_id = \"#{Time.now.to_i}@#{SecureRandom.alphanumeric(24)}.local\"\n          @boundary = \"Boundary_#{SecureRandom.alphanumeric(24)}\"\n          @content_type = MIME::Types[content_type].first\n          @content = []\n          @headers = {\n            \"Content-ID\"=> \"<#{@content_id}>\",\n            \"Content-Type\"=> \"#{content_type}; boundary=#{@boundary}\",\n          }\n        end\n\n        # Add an entry to the multipart mime\n        #\n        # @param entry to add\n        def add(entry)\n          content << entry\n        end\n\n        # Output MimeEntity as a string\n        #\n        # @return [String] mime data\n        def to_s\n          output_string = \"\"\n          headers.each do |k, v|\n            output_string += \"#{k}: #{v}\\n\"\n          end\n          output_string += \"\\n--#{@boundary}\\n\"\n          @content.each do |entry|\n            output_string += entry.to_s\n            output_string += \"\\n--#{@boundary}\\n\"\n          end\n          output_string\n        end\n      end\n\n      class Entity\n\n        # @return [String] entity content \n        attr_reader :content\n\n        # @return [String] type of the entity content\n        attr_reader :content_type\n\n        # @return [String] content disposition\n        attr_accessor :disposition\n\n        # @param [String] entity content\n        # @param [String] type of the entity content\n        def initialize(content, content_type)\n          if !MIME::Types.include?(content_type)\n            MIME::Types.add(MIME::Type.new(\"content-type\" => content_type))\n          end\n          @content = content\n          @content_type = MIME::Types[content_type].first\n          @content_id = \"#{Time.now.to_i}@#{SecureRandom.alphanumeric(24)}.local\"\n        end\n\n        # Output MimeEntity as a string\n        #\n        # @return [String] mime data\n        def to_s\n          output_string = \"Content-ID: <#{@content_id}>\\n\"\n          output_string += \"Content-Type: #{@content_type}\\n\"\n          if disposition\n            output_string += \"Content-Disposition: #{@disposition}\\n\"\n          end\n          output_string += \"\\n#{content}\"\n          output_string\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/network_ip.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"ipaddr\"\n\nmodule Vagrant\n  module Util\n    module NetworkIP\n\n      DEFAULT_MASK = \"255.255.255.0\".freeze\n\n      LOGGER = Log4r::Logger.new(\"vagrant::util::NetworkIP\")\n\n      # Returns the network address of the given IP and subnet.\n      #\n      # @return [String]\n      def network_address(ip, subnet)\n        begin\n          IPAddr.new(ip).mask(subnet).to_s\n        rescue IPAddr::InvalidPrefixError\n          LOGGER.warn(\"Provided mask '#{subnet}' is invalid. Falling back to using mask '#{DEFAULT_MASK}'\")\n          IPAddr.new(ip).mask(DEFAULT_MASK).to_s\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/numeric.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule Vagrant\n  module Util\n    class Numeric\n\n      # Authors Note: This conversion has been borrowed from the ActiveSupport Numeric class\n      # Conversion helper constants\n      KILOBYTE = 1024\n      MEGABYTE = KILOBYTE * 1024\n      GIGABYTE = MEGABYTE * 1024\n      TERABYTE = GIGABYTE * 1024\n      PETABYTE = TERABYTE * 1024\n      EXABYTE  = PETABYTE * 1024\n\n      BYTES_CONVERSION_MAP = {KB: KILOBYTE, MB: MEGABYTE, GB: GIGABYTE, TB: TERABYTE,\n                              PB: PETABYTE, EB: EXABYTE}\n\n      # Regex borrowed from the vagrant-disksize config class\n      SHORTHAND_MATCH_REGEX = /^(?<number>[0-9]+)\\s?(?<unit>KB|MB|GB|TB)?$/\n\n      class << self\n        LOGGER = Log4r::Logger.new(\"vagrant::util::numeric\")\n\n        # A helper that converts a shortcut string to its bytes representation.\n        # The expected format of `str` is essentially: \"<Number>XX\"\n        # Where `XX` is shorthand for KB, MB, GB, TB, PB, or EB. For example, 50 megabytes:\n        #\n        # str = \"50MB\"\n        #\n        # @param [String] - str\n        # @return [Integer,nil] - bytes - returns nil if method fails to convert to bytes\n        def string_to_bytes(str)\n          bytes = nil\n\n          str = str.to_s.strip\n          matches = SHORTHAND_MATCH_REGEX.match(str)\n          if matches\n            number = matches[:number].to_i\n            unit = matches[:unit].to_sym\n\n            if BYTES_CONVERSION_MAP.key?(unit)\n              bytes = number * BYTES_CONVERSION_MAP[unit]\n            else\n              LOGGER.error(\"An invalid unit or format was given, string_to_bytes cannot convert #{str}\")\n            end\n          end\n\n          bytes\n        end\n\n        # Convert bytes to a user friendly string representation\n        #\n        # @param [Numeric] bytes Number of bytes to represent\n        # @return [String] user friendly output\n        def bytes_to_string(bytes)\n          # We want to locate the size that will return the\n          # smallest whole value number\n          BYTES_CONVERSION_MAP.sort { |a, b|\n            b.last <=> a.last\n          }.each do |suffix, size|\n            val = bytes.to_f / size\n            next if val < 1\n            val = sprintf(\"%.2f\", val)\n            val.slice!(-1, 1) while val.end_with?(\"0\")\n            val.slice!(-1, 1) if val.end_with?(\".\")\n            return \"#{val}#{suffix}\"\n          end\n          \"#{bytes} byte#{\"s\" if bytes > 1}\"\n        end\n\n        # Rounds actual value to two decimal places\n        #\n        # @param [Integer] bytes\n        # @return [Integer] megabytes - bytes representation in megabytes\n        def bytes_to_megabytes(bytes)\n          (bytes / MEGABYTE.to_f).round(2)\n        end\n\n        # @private\n        # Reset the cached values for platform. This is not considered a public\n        # API and should only be used for testing.\n        def reset!\n          instance_variables.each(&method(:remove_instance_variable))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/platform.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"rbconfig\"\nrequire \"shellwords\"\nrequire \"tempfile\"\nrequire \"tmpdir\"\nrequire \"log4r\"\n\nrequire \"vagrant/util/subprocess\"\nrequire \"vagrant/util/powershell\"\nrequire \"vagrant/util/which\"\n\nmodule Vagrant\n  module Util\n    # This class just contains some platform checking code.\n    class Platform\n      class << self\n\n        # Detect architecture of host system\n        #\n        # @return [String]\n        def architecture\n          if !defined?(@_host_architecture)\n            if ENV[\"VAGRANT_HOST_ARCHITECTURE\"].to_s != \"\"\n              return @_host_architecture = ENV[\"VAGRANT_HOST_ARCHITECTURE\"]\n            end\n\n            @_host_architecture = case RbConfig::CONFIG[\"target_cpu\"]\n              when \"amd64\", \"x86_64\", \"x64\"\n                \"amd64\"\n              when \"386\", \"i386\", \"x86\"\n                \"i386\"\n              when \"arm64\", \"aarch64\"\n                \"arm64\"\n              else\n                RbConfig::CONFIG[\"target_cpu\"]\n              end\n          end\n          @_host_architecture\n        end\n\n        def logger\n          if !defined?(@_logger)\n            @_logger = Log4r::Logger.new(\"vagrant::util::platform\")\n          end\n          @_logger\n        end\n\n        def cygwin?\n          if !defined?(@_cygwin)\n            @_cygwin = ENV[\"VAGRANT_DETECTED_OS\"].to_s.downcase.include?(\"cygwin\") ||\n              platform.include?(\"cygwin\") ||\n              ENV[\"OSTYPE\"].to_s.downcase.include?(\"cygwin\")\n          end\n          @_cygwin\n        end\n\n        def msys?\n          if !defined?(@_msys)\n            @_msys = ENV[\"VAGRANT_DETECTED_OS\"].to_s.downcase.include?(\"msys\") ||\n              platform.include?(\"msys\") ||\n              ENV[\"OSTYPE\"].to_s.downcase.include?(\"msys\")\n          end\n          @_msys\n        end\n\n        def wsl?\n          if !defined?(@_wsl)\n            @_wsl = false\n            SilenceWarnings.silence! do\n              # Find 'microsoft' in /proc/version indicative of WSL\n              if File.file?('/proc/version')\n                osversion = File.open('/proc/version', &:gets)\n                if osversion.downcase.include?(\"microsoft\")\n                  @_wsl = true\n                end\n              end\n            end\n          end\n          @_wsl\n        end\n\n        [:darwin, :bsd, :freebsd, :linux, :solaris].each do |type|\n          define_method(\"#{type}?\") do\n            platform.include?(type.to_s)\n          end\n        end\n\n        def windows?\n          return @_windows if defined?(@_windows)\n          @_windows = %w[mingw mswin].any? { |t| platform.include?(t) }\n          return @_windows\n        end\n\n        # Checks if the user running Vagrant on Windows has administrative\n        # privileges.\n        #\n        # From: https://support.microsoft.com/en-us/kb/243330\n        # SID: S-1-5-19\n        #\n        # @return [Boolean]\n        def windows_admin?\n          return @_windows_admin if defined?(@_windows_admin)\n\n          @_windows_admin = -> {\n            ps_cmd = '(new-object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)'\n            output = Vagrant::Util::PowerShell.execute_cmd(ps_cmd)\n            return output == 'True'\n          }.call\n\n          return @_windows_admin\n        end\n\n        # Checks if Hyper-V is accessible to the local user. It will check\n        # if user is in the \"Hyper-V Administrators\" group, is a Domain\n        # administrator, and finally will run a manual interaction with\n        # Hyper-V to determine if Hyper-V is usable for the current user.\n        #\n        # From: https://support.microsoft.com/en-us/kb/243330\n        # SID: S-1-5-32-578\n        # Name: BUILTIN\\Hyper-V Administrators\n        # SID: S-1-5-21DOMAIN-512\n        # Name: Domain Admins\n        #\n        # @return [Boolean]\n        def windows_hyperv_admin?\n          return @_windows_hyperv_admin if defined?(@_windows_hyperv_admin)\n\n          if ENV[\"VAGRANT_IS_HYPERV_ADMIN\"]\n            return @_windows_hyperv_admin = true\n          end\n\n          ps_cmd = \"Write-Output ([System.Security.Principal.WindowsIdentity]::GetCurrent().Groups | \" \\\n            \"Select-Object Value | ConvertTo-JSON)\"\n          output = Vagrant::Util::PowerShell.execute_cmd(ps_cmd)\n          if output\n            groups = begin\n                       JSON.load(output)\n                     rescue JSON::ParserError\n                       []\n                     end\n            admin_group = groups.detect do |g|\n              g[\"Value\"].to_s == \"S-1-5-32-578\" ||\n                (g[\"Value\"].start_with?(\"S-1-5-21\") && g[\"Value\"].to_s.end_with?(\"-512\"))\n            end\n\n            if admin_group\n              return @_windows_hyperv_admin = true\n            end\n          end\n\n          ps_cmd = \"$x = (Get-VMHost).Name; if($x -eq [System.Net.Dns]::GetHostName()){ Write-Output 'true'}\"\n          output = Vagrant::Util::PowerShell.execute_cmd(ps_cmd)\n          result = output == \"true\"\n\n          return @_windows_hyperv_admin = result\n        end\n\n        # Checks if Hyper-V is enabled on the host system and returns true\n        # if enabled.\n        #\n        # @return [Boolean]\n        def windows_hyperv_enabled?\n          return @_windows_hyperv_enabled if defined?(@_windows_hyperv_enabled)\n\n          @_windows_hyperv_enabled = -> {\n            check_commands = Array.new.tap do |c|\n              c << \"(Get-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V-Hypervisor -Online).State\"\n              c << \"(Get-WindowsFeature -FeatureName Microsoft-Hyper-V-Hypervisor).State\"\n            end\n            check_commands.each do |ps_cmd|\n              begin\n                output = Vagrant::Util::PowerShell.execute_cmd(ps_cmd)\n                return true if output == \"Enabled\"\n              rescue Errors::PowerShellInvalidVersion\n                logger.warn(\"Invalid PowerShell version detected during Hyper-V enable check\")\n                return false\n              rescue Errors::PowerShellError\n                logger.warn(\"Powershell command not found or error on execution of command\")\n                return false\n              end\n            end\n            return false\n          }.call\n\n          return @_windows_hyperv_enabled\n        end\n\n        # This takes any path and converts it from a Windows path to a\n        # Cygwin style path.\n        #\n        # @param [String] path\n        # @return [String]\n        def cygwin_path(path)\n          begin\n            cygpath = Vagrant::Util::Which.which(\"cygpath\")\n            if cygpath.nil?\n              # If Which can't find it, just attempt to invoke it directly\n              cygpath = \"cygpath\"\n            else\n              cygpath.gsub!(\"/\", '\\\\')\n            end\n\n            process = Subprocess.execute(\n              cygpath, \"-u\", \"-a\", path.to_s)\n            return process.stdout.chomp\n          rescue Errors::CommandUnavailableWindows => e\n            # Sometimes cygpath isn't available (msys). Instead, do what we\n            # can with bash tricks.\n            process = Subprocess.execute(\n              \"bash\",\n              \"--noprofile\",\n              \"--norc\",\n              \"-c\", \"cd #{Shellwords.escape(path)} && pwd\")\n            return process.stdout.chomp\n          end\n        end\n\n        # This takes any path and converts it from a Windows path to a\n        # msys style path.\n        #\n        # @param [String] path\n        # @return [String]\n        def msys_path(path)\n          begin\n            # We have to revert to the old env\n            # path here, otherwise it looks like\n            # msys2 ends up using the wrong cygpath\n            # binary and ends up with a `/cygdrive`\n            # when it doesn't exist in msys2\n            original_path_env = ENV['PATH']\n            ENV['PATH'] = ENV['VAGRANT_OLD_ENV_PATH']\n            cygwin_path(path)\n          ensure\n            ENV['PATH'] = original_path_env\n          end\n        end\n\n        # This takes any path and converts it to a full-length Windows\n        # path on Windows machines in Cygwin.\n        #\n        # @return [String]\n        def cygwin_windows_path(path)\n          return path if !cygwin?\n\n          # Replace all \"\\\" with \"/\", otherwise cygpath doesn't work.\n          path = unix_windows_path(path)\n\n          # Call out to cygpath and gather the result\n          process = Subprocess.execute(\"cygpath\", \"-w\", \"-l\", \"-a\", path.to_s)\n          return process.stdout.chomp\n        end\n\n        # This takes any path and converts Windows-style path separators\n        # to Unix-like path separators.\n        # @return [String]\n        def unix_windows_path(path)\n          path.gsub(\"\\\\\", \"/\")\n        end\n\n        # This checks if the filesystem is case sensitive. This is not a\n        # 100% correct check, since it is possible that the temporary\n        # directory runs a different filesystem than the root directory.\n        # However, this works in many cases.\n        def fs_case_sensitive?\n          return @_fs_case_sensitive if defined?(@_fs_case_sensitive)\n          @_fs_case_sensitive = Dir.mktmpdir(\"vagrant-fs-case-sensitive\") do |dir|\n            tmp_file = File.join(dir, \"FILE\")\n            File.open(tmp_file, \"w\") do |f|\n              f.write(\"foo\")\n            end\n\n            # The filesystem is case sensitive if the lowercased version\n            # of the filename is NOT reported as existing.\n            !File.file?(File.join(dir, \"file\"))\n          end\n          return @_fs_case_sensitive\n        end\n\n        # This expands the path and ensures proper casing of each part\n        # of the path.\n        def fs_real_path(path, **opts)\n          path = Pathname.new(File.expand_path(path))\n\n          if path.exist? && !fs_case_sensitive?\n            # If the path contains a Windows short path, then we attempt to\n            # expand. The require below is embedded here since it requires\n            # windows to work.\n            if windows? && path.to_s =~ /~\\d(\\/|\\\\)/\n              require_relative \"windows_path\"\n              path = Pathname.new(WindowsPath.longname(path.to_s))\n            end\n\n            # Build up all the parts of the path\n            original = []\n            while !path.root?\n              original.unshift(path.basename.to_s)\n              path = path.parent\n            end\n\n            # Traverse each part and join it into the resulting path\n            original.each do |single|\n              Dir.entries(path).each do |entry|\n                begin\n                  single = single.encode(\"filesystem\").to_s\n                rescue ArgumentError => err\n                  Vagrant.global_logger.warn(\"path encoding failed - part=#{single} err=#{err.class} msg=#{err}\")\n                  # NOTE: Depending on the Windows environment the above\n                  # encode will generate an \"input string invalid\" when\n                  # attempting to encode. If that happens, continue on\n                end\n                if entry.downcase == single.downcase\n                  path = path.join(entry)\n                end\n              end\n            end\n          end\n\n          if windows?\n            # Fix the drive letter to be uppercase.\n            path = path.to_s\n            if path[1] == \":\"\n              path[0] = path[0].upcase\n            end\n\n            path = Pathname.new(path)\n          end\n\n          path\n        end\n\n        # Converts a given path to UNC format by adding a prefix and converting slashes.\n        # @param [String] path Path to convert to UNC for Windows\n        # @return [String]\n        def windows_unc_path(path)\n          path = path.gsub(\"/\", \"\\\\\")\n\n          # Convert to UNC path\n          if path =~ /^[a-zA-Z]:\\\\?$/\n            # If the path is just a drive letter, then return that as-is\n            path + \"\\\\\"\n          elsif path.start_with?(\"\\\\\\\\\")\n            # If the path already starts with `\\\\` assume UNC and return as-is\n            path\n          else\n            \"\\\\\\\\?\\\\\" + path.gsub(\"/\", \"\\\\\")\n          end\n        end\n\n        # Returns a boolean noting whether the terminal supports color.\n        # output.\n        def terminal_supports_colors?\n          return @_terminal_supports_colors if defined?(@_terminal_supports_colors)\n          @_terminal_supports_colors = -> {\n            if windows?\n              return true if ENV.key?(\"ANSICON\")\n              return true if cygwin?\n              return true if ENV[\"TERM\"] == \"cygwin\"\n              return false\n            end\n\n            return true\n          }.call\n          return @_terminal_supports_colors\n        end\n\n        def platform\n          return @_platform if defined?(@_platform)\n          @_platform = RbConfig::CONFIG[\"host_os\"].downcase\n          return @_platform\n        end\n\n        # Determine if given path is within the WSL rootfs. Returns\n        # true if within the subsystem, or false if outside the subsystem.\n        #\n        # @param [String] path Path to check\n        # @return [Boolean] path is within subsystem\n        def wsl_path?(path)\n          wsl? && !path.to_s.downcase.start_with?(\"/mnt/\")\n        end\n\n        # Compute the path to rootfs of currently active WSL.\n        #\n        # @return [String] A path to rootfs of a current WSL instance.\n        def wsl_rootfs\n          return @_wsl_rootfs if defined?(@_wsl_rootfs)\n\n          if wsl?\n            # Mark our filesystem with a temporary file having an unique name.\n            marker = Tempfile.new(Time.now.to_i.to_s)\n            logger = Log4r::Logger.new(\"vagrant::util::platform::wsl\")\n\n            # Check for lxrun installation first\n            lxrun_path = [wsl_windows_appdata_local, \"lxss\"].join(\"\\\\\")\n            paths = [lxrun_path]\n\n            logger.debug(\"checking registry for WSL installation path\")\n            paths += PowerShell.execute_cmd(\n              '(Get-ChildItem HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Lxss ' \\\n                '| ForEach-Object {Get-ItemProperty $_.PSPath}).BasePath').to_s.split(\"\\r\\n\").map(&:strip)\n            paths.delete_if{|path| path.to_s.empty?}\n\n            paths.each do |path|\n              # Lowercase the drive letter, skip the next symbol (which is a\n              # colon from a Windows path) and convert path to UNIX style.\n              check_path = \"/mnt/#{path[0, 1].downcase}#{path[2..-1].tr('\\\\', '/')}/rootfs\"\n              begin\n                process = Subprocess.execute(\"wslpath\", \"-u\", \"-a\", path)\n                check_path = \"#{process.stdout.chomp}/rootfs\" if process.exit_code == 0\n              rescue Errors::CommandUnavailable => e\n                # pass\n              end\n\n              logger.debug(\"checking `#{path}` for current WSL instance\")\n              begin\n                # https://blogs.msdn.microsoft.com/wsl/2016/06/15/wsl-file-system-support\n                # Current WSL instance doesn't have an access to its mount from\n                # within itself despite all others are available. That's the\n                # hacky way we're using to determine current instance.\n                # For example we have three WSL instances:\n                # A -> C:\\User\\USER\\AppData\\Local\\Packages\\A\\LocalState\\rootfs\n                # B -> C:\\User\\USER\\AppData\\Local\\Packages\\B\\LocalState\\rootfs\n                # C -> C:\\User\\USER\\AppData\\Local\\Packages\\C\\LocalState\\rootfs\n                # If we're in \"A\" WSL at the moment, then its path will not be\n                # accessible since it's mounted for exactly the instance we're\n                # in. All others can be opened.\n                Dir.open(check_path) do |fs|\n                  # A fallback for a case if our trick will stop working. For\n                  # that we've created a temporary file with an unique name in\n                  # a current WSL and now seeking it among all WSL.\n                  if File.exist?(\"#{fs.path}/#{marker.path}\")\n                    @_wsl_rootfs = path\n                    break\n                  end\n                end\n              rescue Errno::EACCES\n                @_wsl_rootfs = path\n                # You can create and simultaneously run multiple WSL instances,\n                # comment out the \"break\", run this script within each one and\n                # it'll return only single value.\n                break\n              rescue Errno::ENOENT\n                # Warn about data discrepancy between Winreg and file system\n                # states. For the sake of justice, it's worth mentioning that\n                # it is possible only when someone will manually break WSL by\n                # removing a directory of its base path (kinda \"stupid WSL\n                # uninstallation by removing hidden and system directory\").\n                logger.warn(\"WSL instance at `#{path} is broken or no longer exists\")\n              end\n              # All other exceptions have to be raised since they will mean\n              # something unpredictably terrible.\n            end\n\n            marker.close!\n\n            raise Vagrant::Errors::WSLRootFsNotFoundError if @_wsl_rootfs.nil?\n          end\n\n          # Attach the rootfs leaf to the path\n          if @_wsl_rootfs != lxrun_path\n            @_wsl_rootfs = \"#{@_wsl_rootfs}\\\\rootfs\"\n          end\n\n          logger.debug(\"detected `#{@_wsl_rootfs}` as current WSL instance\")\n\n          @_wsl_rootfs\n        end\n\n        # Convert a WSL path to the local Windows path. This is useful\n        # for conversion when calling out to Windows executables from\n        # the WSL\n        #\n        # @param [String, Pathname] path Path to convert\n        # @return [String]\n        def wsl_to_windows_path(path)\n          path = path.to_s\n          if wsl? && wsl_windows_access? && !path.match(/^[a-zA-Z]:/)\n            path = File.expand_path(path)\n            begin\n              process = Subprocess.execute(\"wslpath\", \"-w\", \"-a\", path)\n              return process.stdout.chomp if process.exit_code == 0\n            rescue Errors::CommandUnavailable => e\n              # pass\n            end\n            if wsl_path?(path)\n              parts = path.split(\"/\")\n              parts.delete_if(&:empty?)\n              root_path = wsl_rootfs\n              # lxrun splits home separate so we need to account\n              # for it's specialness here when we build the path\n              if root_path.end_with?(\"lxss\") && !([\"root\", \"home\"].include?(parts.first))\n                root_path = \"#{root_path}\\\\rootfs\"\n              end\n              path = [root_path, *parts].join(\"\\\\\")\n            else\n              path = path.sub(\"/mnt/\", \"\")\n              parts = path.split(\"/\")\n              parts.first << \":\"\n              path = parts.join(\"\\\\\")\n            end\n          end\n          path\n        end\n\n        # Takes a windows path and formats it to the\n        # 'unix' style (i.e. `/cygdrive/c` or `/c/`)\n        #\n        # @param [Pathname, String] path Path to convert\n        # @param [Hash] hash of arguments\n        # @return [String]\n        def format_windows_path(path, *args)\n          path = cygwin_path(path) if cygwin?\n          path = msys_path(path) if msys?\n          path = wsl_to_windows_path(path) if wsl?\n          if windows? || wsl?\n            path = windows_unc_path(path) if !args.include?(:disable_unc)\n          end\n\n          path\n        end\n\n        # Automatically convert a given path to a Windows path. Will only\n        # be applied if running on a Windows host. If running on Windows\n        # host within the WSL, the actual Windows path will be returned.\n        #\n        # @param [Pathname, String] path Path to convert\n        # @return [String]\n        def windows_path(path)\n          path = cygwin_windows_path(path)\n          path = wsl_to_windows_path(path)\n          if windows? || wsl?\n            path = windows_unc_path(path)\n          end\n          path\n        end\n\n        # Allow Vagrant to access Vagrant managed machines outside the\n        # Windows Subsystem for Linux\n        #\n        # @return [Boolean]\n        def wsl_windows_access?\n          if !defined?(@_wsl_windows_access)\n            @_wsl_windows_access = wsl? && ENV[\"VAGRANT_WSL_ENABLE_WINDOWS_ACCESS\"]\n          end\n          @_wsl_windows_access\n        end\n\n        # The allowed windows system path Vagrant can manage from the Windows\n        # Subsystem for Linux\n        #\n        # @return [Pathname]\n        def wsl_windows_accessible_path\n          if !defined?(@_wsl_windows_accessible_path)\n            access_path = ENV[\"VAGRANT_WSL_WINDOWS_ACCESS_USER_HOME_PATH\"]\n            if access_path.to_s.empty?\n              begin\n                process = Subprocess.execute(\"wslpath\", \"-u\", \"-a\", wsl_windows_home)\n                access_path = process.stdout.chomp if process.exit_code == 0\n              rescue Errors::CommandUnavailable => e\n                # pass\n              end\n            end\n            if access_path.to_s.empty?\n              access_path = wsl_windows_home.gsub(\"\\\\\", \"/\").sub(\":\", \"\")\n              access_path[0] = access_path[0].downcase\n              access_path = \"/mnt/#{access_path}\"\n            end\n            @_wsl_windows_accessible_path = Pathname.new(access_path)\n          end\n          @_wsl_windows_accessible_path\n        end\n\n        # Checks given path to determine if Vagrant is allowed to bypass checks\n        #\n        # @param [String] path Path to check\n        # @return [Boolean] Vagrant is allowed to bypass checks\n        def wsl_windows_access_bypass?(path)\n          wsl? && wsl_windows_access? &&\n            path.to_s.start_with?(wsl_windows_accessible_path.to_s)\n        end\n\n        # Mount pattern for extracting local mount information\n        MOUNT_PATTERN = /^(?<device>.+?) on (?<mount>.+?) type (?<type>.+?) \\((?<options>.+)\\)/.freeze\n\n        # Get list of local mount paths that are DrvFs file systems\n        #\n        # @return [Array<String>]\n        # @todo(chrisroberts): Constantize types for check\n        def wsl_drvfs_mounts\n          if !defined?(@_wsl_drvfs_mounts)\n            @_wsl_drvfs_mounts = []\n            if wsl?\n              result = Util::Subprocess.execute(\"mount\")\n              result.stdout.each_line do |line|\n                info = line.match(MOUNT_PATTERN)\n                if info && (info[:type] == \"drvfs\" || info[:type] == \"9p\")\n                  @_wsl_drvfs_mounts << info[:mount]\n                end\n              end\n            end\n          end\n          @_wsl_drvfs_mounts\n        end\n\n        # Check if given path is located on DrvFs file system\n        #\n        # @param [String, Pathname] path Path to check\n        # @return [Boolean]\n        def wsl_drvfs_path?(path)\n          if wsl?\n            wsl_drvfs_mounts.each do |mount_path|\n              return true if path.to_s.start_with?(mount_path)\n            end\n          end\n          false\n        end\n\n        # If running within the Windows Subsystem for Linux, this will provide\n        # simple setup to allow sharing of the user's VAGRANT_HOME directory\n        # within the subsystem\n        #\n        # @param [Environment] env\n        # @param [Logger] logger Optional logger to display information\n        def wsl_init(env, logger=nil)\n          if wsl?\n            if ENV[\"VAGRANT_WSL_ENABLE_WINDOWS_ACCESS\"]\n              wsl_validate_matching_vagrant_versions!\n              shared_user = ENV[\"VAGRANT_WSL_WINDOWS_ACCESS_USER\"]\n              if shared_user.to_s.empty?\n                shared_user = wsl_windows_username\n              end\n              if logger\n                logger.warn(\"Windows Subsystem for Linux detected. Allowing access to user: #{shared_user}\")\n                logger.warn(\"Vagrant will be allowed to control Vagrant managed machines within the user's home path.\")\n              end\n              if ENV[\"VAGRANT_HOME\"] || ENV[\"VAGRANT_WSL_DISABLE_VAGRANT_HOME\"]\n                logger.warn(\"VAGRANT_HOME environment variable already set. Not overriding!\") if logger\n              else\n                home_path = wsl_windows_accessible_path.to_s\n                ENV[\"VAGRANT_HOME\"] = File.join(home_path, \".vagrant.d\")\n                if logger\n                  logger.info(\"Overriding VAGRANT_HOME environment variable to configured windows user. (#{ENV[\"VAGRANT_HOME\"]})\")\n                end\n                true\n              end\n            else\n              if env.local_data_path.to_s.start_with?(\"/mnt/\")\n                raise Vagrant::Errors::WSLVagrantAccessError\n              end\n            end\n          end\n        end\n\n        # Fetch the Windows username currently in use\n        #\n        # @return [String, Nil]\n        def wsl_windows_username\n          if !@_wsl_windows_username\n            result = Util::Subprocess.execute(\"cmd.exe\", \"/c\", \"echo %USERNAME%\")\n            if result.exit_code == 0\n              @_wsl_windows_username = result.stdout.strip\n            end\n          end\n          @_wsl_windows_username\n        end\n\n        # Fetch the Windows user home directory\n        #\n        # @return [String, Nil]\n        def wsl_windows_home\n          if !@_wsl_windows_home\n            result = Util::Subprocess.execute(\"cmd.exe\", \"/c\" \"echo %USERPROFILE%\")\n            if result.exit_code == 0\n              @_wsl_windows_home = result.stdout.gsub(\"\\\"\", \"\").strip\n            end\n          end\n          @_wsl_windows_home\n        end\n\n        # Fetch the Windows user local app data directory\n        #\n        # @return [String, Nil]\n        def wsl_windows_appdata_local\n          if !@_wsl_windows_appdata_local\n            result = Util::Subprocess.execute(\"cmd.exe\", \"/c\", \"echo %LOCALAPPDATA%\")\n            if result.exit_code == 0\n              @_wsl_windows_appdata_local = result.stdout.gsub(\"\\\"\", \"\").strip\n            end\n          end\n          @_wsl_windows_appdata_local\n        end\n\n        # Confirm Vagrant versions installed within the WSL and the Windows system\n        # are the same. Raise error if they do not match.\n        def wsl_validate_matching_vagrant_versions!\n          valid = false\n          if Util::Which.which(\"vagrant.exe\")\n            result = Util::Subprocess.execute(\"vagrant.exe\", \"--version\")\n            if result.exit_code == 0\n              windows_version = result.stdout.match(/Vagrant (?<version>[\\w.-]+)/)\n              if windows_version\n                windows_version = windows_version[:version].strip\n                valid = windows_version == Vagrant::VERSION\n              end\n            end\n            if !valid\n              raise Vagrant::Errors::WSLVagrantVersionMismatch,\n                wsl_version: Vagrant::VERSION,\n                windows_version: windows_version || \"unknown\"\n            end\n          end\n        end\n\n        # systemd is in use\n        def systemd?\n          if !defined?(@_systemd)\n            if !windows?\n              result = Vagrant::Util::Subprocess.execute(\"ps\", \"-o\", \"comm=\", \"1\")\n              @_systemd = result.stdout.chomp == \"systemd\"\n            else\n              @_systemd = false\n            end\n          end\n          @_systemd\n        end\n\n        # @private\n        # Reset the cached values for platform. This is not considered a public\n        # API and should only be used for testing.\n        def reset!\n          instance_variables.each(&method(:remove_instance_variable))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/powershell.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"base64\"\nrequire \"tmpdir\"\n\nrequire_relative \"subprocess\"\nrequire_relative \"which\"\n\nmodule Vagrant\n  module Util\n    # Executes PowerShell scripts.\n    #\n    # This is primarily a convenience wrapper around Subprocess that\n    # properly sets powershell flags for you.\n    class PowerShell\n      # NOTE: Version checks are only on Major\n      MINIMUM_REQUIRED_VERSION = 3\n      # Number of seconds to wait while attempting to get powershell version\n      DEFAULT_VERSION_DETECTION_TIMEOUT = 30\n      # Names of the powershell executable\n      POWERSHELL_NAMES = [\"pwsh\", \"powershell\"].map(&:freeze).freeze\n      # Paths to powershell executable\n      POWERSHELL_PATHS = [\n        \"%SYSTEMROOT%/System32/WindowsPowerShell/v1.0\",\n        \"%WINDIR%/System32/WindowsPowerShell/v1.0\",\n        \"%PROGRAMFILES%/PowerShell/7\",\n        \"%PROGRAMFILES%/PowerShell/6\"\n      ].map(&:freeze).freeze\n\n      LOGGER = Log4r::Logger.new(\"vagrant::util::powershell\")\n\n      # @return [String|nil] a powershell executable, depending on environment\n      def self.executable\n        if !defined?(@_powershell_executable)\n          prefer_name = ENV[\"VAGRANT_PREFERRED_POWERSHELL\"].to_s.sub(\".exe\", \"\")\n          if !POWERSHELL_NAMES.include?(prefer_name)\n            prefer_name = POWERSHELL_NAMES.first\n          end\n\n          LOGGER.debug(\"preferred powershell executable name: #{prefer_name}\")\n\n          # First start with detecting executable on configured path\n          found_shells = Hash.new.tap do |found|\n            POWERSHELL_NAMES.each do |psh|\n              psh_path = Which.which(psh)\n              psh_path = Which.which(psh + \".exe\") if !psh_path\n              next if !psh_path\n\n              LOGGER.debug(\"detected powershell for #{psh.inspect} - #{psh_path}\")\n              found[psh] = psh_path\n            end\n          end\n\n          # Done if preferred shell was found\n          if found_shells.key?(prefer_name)\n            LOGGER.debug(\"using preferred powershell #{prefer_name.inspect} - #{found_shells[prefer_name]}\")\n            return @_powershell_executable = found_shells[prefer_name]\n          end\n\n          # Now attempt with paths\n          paths = POWERSHELL_PATHS.map do |ppath|\n            result = Util::Subprocess.execute(\"cmd.exe\", \"/c\", \"echo #{ppath}\")\n            result.stdout.gsub(\"\\\"\", \"\").strip if result.exit_code == 0\n          end.compact\n\n          paths.each do |psh_path|\n            POWERSHELL_NAMES.each do |psh|\n              next if found_shells.key?(psh)\n\n              path = File.join(psh_path, psh)\n              [path, \"#{path}.exe\", path.sub(/^([A-Za-z]):/, \"/mnt/\\\\1\")].each do |full_path|\n                if File.executable?(full_path)\n                  found_shells[psh] = full_path\n                  break\n                end\n              end\n            end\n          end\n\n          # Done if preferred shell was found\n          if found_shells.key?(prefer_name)\n            LOGGER.debug(\"using preferred powershell #{prefer_name.inspect} - #{found_shells[prefer_name]}\")\n            return @_powershell_executable = found_shells[prefer_name]\n          end\n\n          # Iterate names and return first found\n          POWERSHELL_NAMES.each do |psh|\n            LOGGER.debug(\"using powershell #{prefer_name.inspect} - #{found_shells[prefer_name]}\")\n            return @_powershell_executable = found_shells[psh] if found_shells.key?(psh)\n          end\n        end\n        @_powershell_executable\n      end\n\n      # @return [Boolean] powershell executable available on PATH\n      def self.available?\n        !executable.nil?\n      end\n\n      # Execute a powershell script.\n      #\n      # @param [String] path Path to the PowerShell script to execute.\n      # @param [Array<String>] args Command arguments\n      # @param [Hash] opts Options passed to execute\n      # @option opts [Hash] :env Custom environment variables\n      # @return [Subprocess::Result]\n      def self.execute(path, *args, **opts, &block)\n        validate_install!\n        if opts.delete(:sudo) || opts.delete(:runas)\n          powerup_command(path, args, opts)\n        else\n          if mpath = opts.delete(:module_path)\n            m_env = opts.fetch(:env, {})\n            m_env[\"PSModulePath\"] = \"$env:PSModulePath+';#{mpath}'\"\n            opts[:env] = m_env\n          end\n          if env = opts.delete(:env)\n            env = env.map{|k,v| \"$env:#{k}=#{v}\"}.join(\";\") + \"; \"\n          end\n          command = [\n            executable,\n            \"-NoLogo\",\n            \"-NoProfile\",\n            \"-NonInteractive\",\n            \"-ExecutionPolicy\", \"Bypass\",\n            \"-Command\",\n            \"#{env}&('#{path}')\",\n            args\n          ].flatten\n\n          # Append on the options hash since Subprocess doesn't use\n          # Ruby 2.0 style options yet.\n          command << opts\n\n          Subprocess.execute(*command, &block)\n        end\n      end\n\n      # Execute a powershell command.\n      #\n      # @param [String] command PowerShell command to execute.\n      # @param [Hash] opts Extra options\n      # @option opts [Hash] :env Custom environment variables\n      # @return [nil, String] Returns nil if exit code is non-zero.\n      #   Returns stdout string if exit code is zero.\n      def self.execute_cmd(command, **opts)\n        validate_install!\n        if mpath = opts.delete(:module_path)\n          m_env = opts.fetch(:env, {})\n          m_env[\"PSModulePath\"] = \"$env:PSModulePath+';#{mpath}'\"\n          opts[:env] = m_env\n        end\n        if env = opts.delete(:env)\n          env = env.map{|k,v| \"$env:#{k}=#{v}\"}.join(\";\") + \"; \"\n        end\n        c = [\n          executable,\n          \"-NoLogo\",\n          \"-NoProfile\",\n          \"-NonInteractive\",\n          \"-ExecutionPolicy\", \"Bypass\",\n          \"-Command\",\n          \"#{env}#{command}\"\n        ].flatten.compact\n\n        r = Subprocess.execute(*c)\n        return nil if r.exit_code != 0\n        return r.stdout.chomp\n      end\n\n      # Execute a powershell command and return a result\n      #\n      # @param [String] command PowerShell command to execute.\n      # @param [Hash] opts A collection of options for subprocess::execute\n      # @option opts [Hash] :env Custom environment variables\n      # @param [Block] block Ruby block\n      def self.execute_inline(*command, **opts, &block)\n        validate_install!\n        if mpath = opts.delete(:module_path)\n          m_env = opts.fetch(:env, {})\n          m_env[\"PSModulePath\"] = \"$env:PSModulePath+';#{mpath}'\"\n          opts[:env] = m_env\n        end\n        if env = opts.delete(:env)\n          env = env.map{|k,v| \"$env:#{k}=#{v}\"}.join(\";\") + \"; \"\n        end\n\n        command = command.join(' ')\n\n        c = [\n          executable,\n          \"-NoLogo\",\n          \"-NoProfile\",\n          \"-NonInteractive\",\n          \"-ExecutionPolicy\", \"Bypass\",\n          \"-Command\",\n          \"#{env}#{command}\"\n        ].flatten.compact\n        c << opts\n\n        Subprocess.execute(*c, &block)\n      end\n\n      # Returns the version of PowerShell that is installed.\n      #\n      # @return [String]\n      def self.version\n        if !defined?(@_powershell_version)\n          command = [\n            executable,\n            \"-NoLogo\",\n            \"-NoProfile\",\n            \"-NonInteractive\",\n            \"-ExecutionPolicy\", \"Bypass\",\n            \"-Command\",\n            \"Write-Output $PSVersionTable.PSVersion.Major\"\n          ].flatten\n\n          version = nil\n          timeout = ENV[\"VAGRANT_POWERSHELL_VERSION_DETECTION_TIMEOUT\"].to_i\n          if timeout < 1\n            timeout = DEFAULT_VERSION_DETECTION_TIMEOUT\n          end\n          begin\n            r = Subprocess.execute(*command,\n              notify: [:stdout, :stderr],\n              timeout: timeout,\n            ) {|io_name,data| version = data}\n          rescue Vagrant::Util::Subprocess::TimeoutExceeded\n            LOGGER.debug(\"Timeout exceeded while attempting to determine version of Powershell.\")\n          end\n\n          @_powershell_version = version\n        end\n        @_powershell_version\n      end\n\n      # Validates that powershell is installed, available, and\n      # at or above minimum required version\n      #\n      # @return [Boolean]\n      # @raises []\n      def self.validate_install!\n        if !defined?(@_powershell_validation)\n          raise Errors::PowerShellNotFound if !available?\n          if version.to_i < MINIMUM_REQUIRED_VERSION\n            raise Errors::PowerShellInvalidVersion,\n              minimum_version: MINIMUM_REQUIRED_VERSION,\n              installed_version: version ? version : \"N/A\"\n          end\n          @_powershell_validation = true\n        end\n        @_powershell_validation\n      end\n\n      # Powerup the given command to perform privileged operations.\n      #\n      # @param [String] path\n      # @param [Array<String>] args\n      # @return [Array<String>]\n      def self.powerup_command(path, args, opts)\n        Dir.mktmpdir(\"vagrant\") do |dpath|\n          all_args = [path] + args.flatten.map{ |a|\n            a.gsub(/^['\"](.+)['\"]$/, \"\\\\1\")\n          }\n          arg_list = \"\\\"\" + all_args.join(\"\\\" \\\"\") + \"\\\"\"\n          stdout = File.join(dpath, \"stdout.txt\")\n          stderr = File.join(dpath, \"stderr.txt\")\n\n          script = \"& #{arg_list} ; exit $LASTEXITCODE;\"\n          script_content = Base64.strict_encode64(script.encode(\"UTF-16LE\", \"UTF-8\"))\n\n          # Wrap so we can redirect output to read later\n          wrapper = \"$p = Start-Process -FilePath powershell -ArgumentList @('-NoLogo', '-NoProfile', \" \\\n            \"'-NonInteractive', '-ExecutionPolicy', 'Bypass', '-EncodedCommand', '#{script_content}') \" \\\n            \"-PassThru -WindowStyle Hidden -Wait -RedirectStandardOutput '#{stdout}' -RedirectStandardError '#{stderr}'; \" \\\n            \"if($p){ exit $p.ExitCode; }else{ exit 1 }\"\n          wrapper_content = Base64.strict_encode64(wrapper.encode(\"UTF-16LE\", \"UTF-8\"))\n\n          powerup = \"$p = Start-Process -FilePath powershell -ArgumentList @('-NoLogo', '-NoProfile', \" \\\n            \"'-NonInteractive', '-ExecutionPolicy', 'Bypass', '-EncodedCommand', '#{wrapper_content}') \" \\\n            \"-PassThru -WindowStyle Hidden -Wait -Verb RunAs; if($p){ exit $p.ExitCode; }else{ exit 1 }\"\n\n          cmd = [\n            executable,\n            \"-NoLogo\",\n            \"-NoProfile\",\n            \"-NonInteractive\",\n            \"-ExecutionPolicy\", \"Bypass\",\n            \"-Command\", powerup\n          ]\n\n          result = Subprocess.execute(*cmd.push(opts))\n          r_stdout = result.stdout\n          if File.exist?(stdout)\n            r_stdout += File.read(stdout)\n          end\n          r_stderr = result.stderr\n          if File.exist?(stderr)\n            r_stderr += File.read(stderr)\n          end\n\n          Subprocess::Result.new(result.exit_code, r_stdout, r_stderr)\n        end\n      end\n\n      # @private\n      # Reset the cached values for platform. This is not considered a public\n      # API and should only be used for testing.\n      def self.reset!\n        instance_variables.each(&method(:remove_instance_variable))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/presence.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    module Presence\n      extend self\n\n      # Determines if the given object is \"present\". A String is considered\n      # present if the stripped contents are not empty. An Array/Hash is\n      # considered present if they have a length of more than 1. \"true\" is\n      # always present and `false` and `nil` are always not present. Any other\n      # object is considered to be present.\n      #\n      # @return [true, false]\n      def present?(obj)\n        case obj\n        when String\n          !obj.strip.empty?\n        when Symbol\n          !obj.to_s.strip.empty?\n        when Array\n          !obj.compact.empty?\n        when Hash\n          !obj.empty?\n        when TrueClass, FalseClass\n          obj\n        when NilClass\n          false\n        when Object\n          true\n        end\n      end\n\n      # Returns the presence of the object. If the object is {present?}, it is\n      # returned. Otherwise `false` is returned.\n      #\n      # @return [Object, false]\n      def presence(obj)\n        if present?(obj)\n          obj\n        else\n          false\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/retryable.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule Vagrant\n  module Util\n    module Retryable\n      # Retries a given block a specified number of times in the\n      # event the specified exception is raised. If the retries\n      # run out, the final exception is raised.\n      #\n      # This code is adapted slightly from the following blog post:\n      # http://blog.codefront.net/2008/01/14/retrying-code-blocks-in-ruby-on-exceptions-whatever/\n      def retryable(opts=nil)\n        logger = nil\n        opts   = { tries: 1, on: Exception }.merge(opts || {})\n\n        begin\n          return yield\n        rescue *opts[:on] => e\n          if (opts[:tries] -= 1) > 0\n            logger = Log4r::Logger.new(\"vagrant::util::retryable\")\n            logger.info(\"Retryable exception raised: #{e.inspect}\")\n\n            sleep opts[:sleep].to_f if opts[:sleep]\n            retry\n          end\n          raise\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/safe_chdir.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'thread'\n\nmodule Vagrant\n  module Util\n    class SafeChdir\n      @@chdir_lock  = Mutex.new\n\n      # Safely changes directory of this process by putting a lock around\n      # it so that it is thread safe. This will yield a block and when the\n      # block exits it changes back to the original directory.\n      #\n      # @param [String] dir Dir to change to temporarily\n      def self.safe_chdir(dir)\n        lock = @@chdir_lock\n\n        begin\n          @@chdir_lock.synchronize {}\n        rescue ThreadError\n          # If we already hold the lock, just create a new lock so we\n          # definitely don't block and don't get an error.\n          lock = Mutex.new\n        end\n\n        lock.synchronize do\n          Dir.chdir(dir) do\n            return yield\n          end\n        end\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/vagrant/util/safe_env.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    class SafeEnv\n      # This yields an environment hash to change and catches any issues\n      # while changing the environment variables and raises a helpful error\n      # to end users.\n      def self.change_env\n        yield ENV\n      rescue Errno::EINVAL\n        raise Errors::EnvInval\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/safe_exec.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # This module provides a `safe_exec` method which is a drop-in\n    # replacement for `Kernel.exec` which addresses a specific issue\n    # which manifests on OS X 10.5 (GH-51) and perhaps other operating systems.\n    # This issue causes `exec` to fail if there is more than one system\n    # thread. In that case, `safe_exec` automatically falls back to\n    # forking.\n    class SafeExec\n\n      @@logger = Log4r::Logger.new(\"vagrant::util::safe_exec\")\n\n      def self.exec(command, *args)\n        # Create a list of things to rescue from. Since this is OS\n        # specific, we need to do some defined? checks here to make\n        # sure they exist.\n        rescue_from = []\n        rescue_from << Errno::EOPNOTSUPP if defined?(Errno::EOPNOTSUPP)\n        rescue_from << Errno::E045 if defined?(Errno::E045)\n        rescue_from << SystemCallError\n\n        fork_instead = false\n        begin\n          if fork_instead\n            if Vagrant::Util::Platform.windows?\n              @@logger.debug(\"Using subprocess because windows platform\")\n              args = args.dup << {notify: [:stdout, :stderr]}\n              result = Vagrant::Util::Subprocess.execute(command, *args) do |type, data|\n                case type\n                when :stdout\n                  @@logger.info(data, new_line: false)\n                when :stderr\n                  @@logger.info(data, new_line: false)\n                end\n              end\n              Kernel.exit(result.exit_code)\n            else\n              pid = fork\n              Kernel.exec(command, *args)\n              Process.wait(pid)\n            end\n          else\n            if Vagrant::Util::Platform.windows?\n              # Re-generate strings to ensure common encoding\n              @@logger.debug(\"Converting command and arguments to common UTF-8 encoding for exec.\")\n              @@logger.debug(\"Command: `#{command.inspect}` Args: `#{args.inspect}`\")\n              begin\n                command = \"#{command}\".encode(\"UTF-8\")\n              rescue Encoding::UndefinedConversionError => e\n                @@logger.warn(\"Failed to convert command - #{e.class}: #{e} (`#{command}`)\")\n              end\n              args = args.map do |arg|\n                begin\n                  \"#{arg}\".encode(\"UTF-8\")\n                rescue Encoding::UndefinedConversionError => e\n                  @@logger.warn(\"Failed to convert command argument - #{e.class}: #{e} (`#{arg}`)\")\n                  arg\n                end\n              end\n              @@logger.debug(\"Converted - Command: `#{command.inspect}` Args: `#{args.inspect}`\")\n            end\n            Kernel.exec(command, *args)\n          end\n        rescue *rescue_from\n          # We retried already, raise the issue and be done\n          raise if fork_instead\n\n          # The error manifested itself, retry with a fork.\n          fork_instead = true\n          retry\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/safe_puts.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # This module provides a `safe_puts` method which outputs to\n    # the given IO object, and rescues any broken pipe errors and\n    # ignores them. This is useful in cases where you're outputting\n    # to stdout, for example, and the stdout is closed, but you want to\n    # keep running.\n    module SafePuts\n      # Uses `puts` on the given IO object and safely ignores any\n      # Errno::EPIPE.\n      #\n      # @param [String] message Message to output.\n      # @param [Hash] opts Options hash.\n      def safe_puts(message=nil, opts=nil)\n        message ||= \"\"\n        opts = {\n          io: $stdout,\n          printer: :puts\n        }.merge(opts || {})\n\n        begin\n          opts[:io].send(opts[:printer], message)\n        rescue Errno::EPIPE\n          # This is what makes this a `safe` puts.\n          return\n        end\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/vagrant/util/scoped_hash_override.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # This allows for hash options to be overridden by a scope key\n    # prefix. An example speaks best here. Imagine the following hash:\n    #\n    #     original = {\n    #       id: \"foo\",\n    #       mitchellh__id: \"bar\",\n    #       mitchellh__other: \"foo\"\n    #     }\n    #\n    #     scoped = scoped_hash_override(original, \"mitchellh\")\n    #\n    #     scoped == {\n    #       id: \"bar\",\n    #       other: \"foo\"\n    #     }\n    #\n    module ScopedHashOverride\n      def scoped_hash_override(original, scope)\n        # Convert the scope to a string in case a symbol was given since\n        # we use string comparisons for everything.\n        scope = scope.to_s\n\n        # Shallow copy the hash for the result\n        result = original.dup\n\n        original.each do |key, value|\n          parts = key.to_s.split(\"__\", 2)\n\n          # If we don't have the proper parts, then bail\n          next if parts.length != 2\n\n          # If this is our scope, then override\n          if parts[0] == scope\n            result[parts[1].to_sym] = value\n          end\n        end\n\n        result\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/shell_quote.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    module ShellQuote\n      # This will auto-escape the text with the given quote mark type.\n      #\n      # @param [String] text Text to escape\n      # @param [String] quote The quote character, such as \"\n      def self.escape(text, quote)\n        text.gsub(/#{quote}/) do |m|\n          \"#{m}\\\\#{m}#{m}\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/silence_warnings.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    module SilenceWarnings\n      # This silences any Ruby warnings.\n      def self.silence!\n        original = $VERBOSE\n        $VERBOSE = nil\n        return yield\n      ensure\n        $VERBOSE = original\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/ssh.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nrequire 'childprocess'\n\nrequire \"vagrant/util/file_mode\"\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/safe_exec\"\nrequire \"vagrant/util/safe_puts\"\nrequire \"vagrant/util/subprocess\"\nrequire \"vagrant/util/which\"\n\nmodule Vagrant\n  module Util\n    # This is a class that has helpers on it for dealing with SSH. These\n    # helpers don't depend on any part of Vagrant except what is given\n    # via the parameters.\n    class SSH\n      extend SafePuts\n\n      LOGGER = Log4r::Logger.new(\"vagrant::util::ssh\")\n\n      # Checks that the permissions for a private key are valid, and fixes\n      # them if possible. SSH requires that permissions on the private key\n      # are 0600 on POSIX based systems. This will make a best effort to\n      # fix these permissions if they are not properly set.\n      #\n      # @param [Pathname] key_path The path to the private key.\n      def self.check_key_permissions(key_path)\n        # Don't do anything if we're on Windows, since Windows doesn't worry\n        # about key permissions.\n        return if Platform.windows? || Platform.wsl_windows_access_bypass?(key_path)\n\n        LOGGER.debug(\"Checking key permissions: #{key_path}\")\n        stat = key_path.stat\n\n        if !stat.owned? && Process.uid != 0\n          # The SSH key must be owned by ourselves, unless we're root\n          raise Errors::SSHKeyBadOwner, key_path: key_path\n        end\n\n        if FileMode.from_octal(stat.mode) != \"600\"\n          LOGGER.info(\"Attempting to correct key permissions to 0600\")\n          key_path.chmod(0600)\n\n          # Re-stat the file to get the new mode, and verify it worked\n          stat = key_path.stat\n          if FileMode.from_octal(stat.mode) != \"600\"\n            raise Errors::SSHKeyBadPermissions, key_path: key_path\n          end\n        end\n      rescue Errno::EPERM\n        # This shouldn't happen since we verify we own the file, but\n        # it is possible in theory, so we raise an error.\n        raise Errors::SSHKeyBadPermissions, key_path: key_path\n      end\n\n      # Halts the running of this process and replaces it with a full-fledged\n      # SSH shell into a remote machine.\n      #\n      # Note: This method NEVER returns. The process ends after this.\n      #\n      # @param [Hash] ssh_info This is the SSH information. For the keys\n      #   required please see the documentation of {Machine#ssh_info}.\n      # @param [Hash] opts These are additional options that are supported\n      #   by exec.\n      def self.exec(ssh_info, opts={})\n        # Ensure the platform supports ssh. On Windows there are several programs which\n        # include ssh, notably git, mingw and cygwin, but make sure ssh is in the path!\n\n        # First try using the original path provided\n        if ENV[\"VAGRANT_PREFER_SYSTEM_BIN\"] != \"0\"\n          ssh_path = Which.which(\"ssh\", original_path: true)\n        end\n\n        # If we didn't find an ssh executable, see if we shipped one\n        if !ssh_path\n          ssh_path = Which.which(\"ssh\")\n          if ssh_path && Platform.windows? && (Platform.cygwin? || Platform.msys?)\n            LOGGER.warn(\"Failed to locate native SSH executable. Using vendored version.\")\n            LOGGER.warn(\"If display issues are encountered, install the ssh package for your environment.\")\n          end\n        end\n\n        if !ssh_path\n          if Platform.windows?\n            raise Errors::SSHUnavailableWindows,\n              host: ssh_info[:host],\n              port: ssh_info[:port],\n              username: ssh_info[:username],\n              key_path: ssh_info[:private_key_path].join(\", \")\n          end\n\n          raise Errors::SSHUnavailable\n        end\n\n        if Platform.windows?\n          # On Windows, we need to detect whether SSH is actually \"plink\"\n          # underneath the covers. In this case, we tell the user.\n          r = Subprocess.execute(ssh_path)\n          if r.stdout.include?(\"PuTTY Link\") || r.stdout.include?(\"Plink: command-line connection utility\")\n            raise Errors::SSHIsPuttyLink,\n              host: ssh_info[:host],\n              port: ssh_info[:port],\n              username: ssh_info[:username],\n              key_path: ssh_info[:private_key_path].join(\", \")\n          end\n        end\n\n        # If plain mode is enabled then we don't do any authentication (we don't\n        # set a user or an identity file)\n        plain_mode = opts[:plain_mode]\n\n        options = {}\n        options[:host] = ssh_info[:host]\n        options[:port] = ssh_info[:port]\n        options[:username] = ssh_info[:username]\n        options[:private_key_path] = ssh_info[:private_key_path]\n\n        log_level = ssh_info[:log_level] || \"FATAL\"\n\n        # Command line options\n        command_options = [\n          \"-p\", options[:port].to_s,\n          \"-o\", \"LogLevel=#{log_level}\"]\n\n        if ssh_info[:compression]\n          command_options += [\"-o\", \"Compression=yes\"]\n        end\n\n        if ssh_info[:dsa_authentication]\n          command_options += [\"-o\", \"DSAAuthentication=yes\"]\n        end\n\n        # Solaris/OpenSolaris/Illumos uses SunSSH which doesn't support the\n        # IdentitiesOnly option. Also, we don't enable it in plain mode or if\n        # if keys_only is false so that SSH and Net::SSH properly search our identities\n        # and tries to do it itself.\n        if !Platform.solaris? && !plain_mode && ssh_info[:keys_only]\n          command_options += [\"-o\", \"IdentitiesOnly=yes\"]\n        end\n\n        # no strict hostkey checking unless paranoid\n        if ssh_info[:verify_host_key] == :never || !ssh_info[:verify_host_key]\n          command_options += [\n            \"-o\", \"StrictHostKeyChecking=no\",\n            \"-o\", \"UserKnownHostsFile=/dev/null\"]\n        end\n\n        if !ssh_info[:disable_deprecated_algorithms]\n          command_options += [\n            \"-o\", \"PubkeyAcceptedKeyTypes=+ssh-rsa\",\n            \"-o\", \"HostKeyAlgorithms=+ssh-rsa\",\n          ]\n        end\n\n        # If we're not in plain mode and :private_key_path is set attach the private key path(s).\n        if !plain_mode && options[:private_key_path]\n          options[:private_key_path].each do |path|\n\n            private_key_arr = []\n\n            if path.include?('%')\n              if path.include?(' ') && Platform.windows?\n                LOGGER.warn(\"Paths with spaces and % on windows is not supported and will fail to read the file\")\n              end\n              # Use '-o' instead of '-i' because '-i' does not call\n              # percent_expand in misc.c, but '-o' does. when passing the path,\n              # replace '%' in the path with '%%' to escape the '%'\n              path = path.to_s.gsub('%', '%%')\n              private_key_arr = [\"-o\", \"IdentityFile=\\\"#{path}\\\"\"]\n            else\n              # Pass private key file directly with '-i', which properly supports\n              # paths with spaces on Windows guests\n              private_key_arr = [\"-i\", path]\n            end\n\n            command_options += private_key_arr\n          end\n        end\n\n        if ssh_info[:forward_x11]\n          # Both are required so that no warnings are shown regarding X11\n          command_options += [\n            \"-o\", \"ForwardX11=yes\",\n            \"-o\", \"ForwardX11Trusted=yes\"]\n        end\n\n        if ssh_info[:config]\n          command_options += [\"-F\", ssh_info[:config]]\n        end\n\n        if ssh_info[:proxy_command]\n          command_options += [\"-o\", \"ProxyCommand=#{ssh_info[:proxy_command]}\"]\n        end\n\n        if ssh_info[:forward_env]\n          command_options += [\"-o\", \"SendEnv=#{ssh_info[:forward_env].join(\" \")}\"]\n        end\n\n        # Configurables -- extra_args should always be last due to the way the\n        # ssh args parser works. e.g. if the user wants to use the -t option,\n        # any shell command(s) she'd like to run on the remote server would\n        # have to be the last part of the 'ssh' command:\n        #\n        #   $ ssh localhost -t -p 2222 \"cd mydirectory; bash\"\n        #\n        # Without having extra_args be last, the user loses this ability\n        command_options += [\"-o\", \"ForwardAgent=yes\"] if ssh_info[:forward_agent]\n\n        # Note about :extra_args\n        #   ssh_info[:extra_args] comes from a machines ssh config in a Vagrantfile,\n        #   where as opts[:extra_args] comes from running the ssh command\n        command_options += Array(ssh_info[:extra_args]) if ssh_info[:extra_args]\n\n        command_options.concat(opts[:extra_args]) if opts[:extra_args]\n\n        # Build up the host string for connecting\n        host_string = options[:host]\n        host_string = \"#{options[:username]}@#{host_string}\" if !plain_mode\n        command_options.unshift(host_string)\n\n        # On Cygwin we want to get rid of any DOS file warnings because\n        # we really don't care since both work.\n        ENV[\"nodosfilewarning\"] = \"1\" if Platform.cygwin?\n\n        # If an ssh command is defined, use that. If an ssh binary was\n        # discovered on the path, use that. Otherwise fail to just trying `ssh`\n        ssh = ssh_info[:ssh_command] || ssh_path || 'ssh'\n\n        # Invoke SSH with all our options\n        if !opts[:subprocess]\n          LOGGER.info(\"Invoking SSH: #{ssh} #{command_options.inspect}\")\n          _raw_exec(ssh, command_options, ssh_info, opts)\n          return\n        else\n          LOGGER.info(\"Executing SSH in subprocess: #{ssh} #{command_options.inspect}\")\n          return _raw_subprocess(ssh, command_options, ssh_info, opts)\n        end\n      end\n\n      def self._raw_exec(ssh, command_options, ssh_info, opts)\n        SafeExec.exec(ssh, *command_options)\n      end\n\n      def self._raw_subprocess(ssh, command_options, ssh_info, opts)\n        # If we're still here, it means we're supposed to subprocess\n        # out to ssh rather than exec it.\n        process = ChildProcess.build(ssh, *command_options)\n        process.io.inherit!\n\n        # Forward configured environment variables.\n        if ssh_info[:forward_env]\n          ssh_info[:forward_env].each do |key|\n            process.environment[key] = ENV[key]\n          end\n        end\n\n        process.start\n        process.wait\n        return process.exit_code\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/stacked_proc_runner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # Represents the \"stacked proc runner\" behavior which is used a\n    # couple places within Vagrant. This allows procs to \"stack\" on\n    # each other, then all execute in a single action. An example of\n    # its uses can be seen in the {Config} class.\n    module StackedProcRunner\n      # Returns the proc stack. This should always be called as the\n      # accessor of the stack. The instance variable itself should _never_\n      # be used.\n      #\n      # @return [Array<Proc>]\n      def proc_stack\n        @_proc_stack ||= []\n      end\n\n      # Adds (pushes) a proc to the stack. The actual proc added here is\n      # not executed, but merely stored.\n      #\n      # @param [Proc] block\n      def push_proc(&block)\n        proc_stack << block\n      end\n\n      # Executes all the procs on the stack, passing in the given arguments.\n      # The stack is not cleared afterwards. It is up to the user of this\n      # mixin to clear the stack by calling `proc_stack.clear`.\n      def run_procs!(*args)\n        proc_stack.each do |proc|\n          proc.call(*args)\n        end\n      end\n    end\n  end\nend"
  },
  {
    "path": "lib/vagrant/util/string_block_editor.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    # This class modifies strings by creating and managing Vagrant-owned\n    # \"blocks\" via wrapping them in specially formed comments.\n    #\n    # This is useful when modifying a file that someone else owns and adding\n    # automatic entries into it. Example: /etc/exports or some other\n    # configuration file.\n    #\n    # Vagrant marks ownership of a block in the string by wrapping it in\n    # VAGRANT-BEGIN and VAGRANT-END comments with a unique ID. Example:\n    #\n    #     foo\n    #     # VAGRANT-BEGIN: id\n    #     some contents\n    #     created by vagrant\n    #     # VAGRANT-END: id\n    #\n    # The goal of this class is to be able to insert and remove these\n    # blocks without modifying anything else in the string.\n    #\n    # The strings usually come from files but it is up to the caller to\n    # manage the file resource.\n    class StringBlockEditor\n      # The current string value. This is the value that is modified by\n      # the methods below.\n      #\n      # @return [String]\n      attr_reader :value\n\n      def initialize(string)\n        @value = string\n      end\n\n      # This returns the keys (or ids) that are in the string.\n      #\n      # @return [<Array<String>]\n      def keys\n        regexp = /^#\\s*VAGRANT-BEGIN:\\s*(.+?)$\\r?\\n?(.*)$\\r?\\n?^#\\s*VAGRANT-END:\\s(\\1)$/m\n        @value.scan(regexp).map do |match|\n          match[0]\n        end\n      end\n\n      # This deletes the block with the given key if it exists.\n      def delete(key)\n        key    = Regexp.quote(key)\n        regexp = /^#\\s*VAGRANT-BEGIN:\\s*#{key}$.*^#\\s*VAGRANT-END:\\s*#{key}$\\r?\\n?/m\n        @value.gsub!(regexp, \"\")\n      end\n\n      # This gets the value of the block with the given key.\n      def get(key)\n        key    = Regexp.quote(key)\n        regexp = /^#\\s*VAGRANT-BEGIN:\\s*#{key}$\\r?\\n?(.*?)\\r?\\n?^#\\s*VAGRANT-END:\\s*#{key}$\\r?\\n?/m\n        match  = regexp.match(@value)\n        return nil if !match\n        match[1]\n      end\n\n      # This inserts a block with the given key and value.\n      #\n      # @param [String] key\n      # @param [String] value\n      def insert(key, value)\n        # Insert the new block into the value\n        new_block = <<BLOCK\n# VAGRANT-BEGIN: #{key}\n#{value.strip}\n# VAGRANT-END: #{key}\nBLOCK\n\n        @value << new_block\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/subprocess.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'thread'\n\nrequire 'childprocess'\nrequire 'log4r'\n\nrequire 'vagrant/util/io'\nrequire 'vagrant/util/platform'\nrequire 'vagrant/util/safe_chdir'\nrequire 'vagrant/util/which'\n\nmodule Vagrant\n  module Util\n    # Execute a command in a subprocess, gathering the results and\n    # exit status.\n    #\n    # This class also allows you to read the data as it is outputted\n    # from the subprocess in real time, by simply passing a block to\n    # the execute method.\n    class Subprocess\n      # Convenience method for executing a method.\n      def self.execute(*command, &block)\n        new(*command).execute(&block)\n      end\n\n      def initialize(*command)\n        @options = command.last.is_a?(Hash) ? command.pop : {}\n        @command = command.dup\n        @command[0] = Which.which(@command[0]) if !File.file?(@command[0])\n        if !@command[0]\n          raise Errors::CommandUnavailableWindows, file: command[0] if Platform.windows?\n          raise Errors::CommandUnavailable, file: command[0]\n        end\n\n        @logger  = Log4r::Logger.new(\"vagrant::util::subprocess\")\n      end\n\n      # @return [TrueClass, FalseClass] subprocess is currently running\n      def running?\n        !!(@process && @process.alive?)\n      end\n\n      # Stop the subprocess if running\n      #\n      # @return [TrueClass] FalseClass] true if process was running and stopped\n      def stop\n        if @process && @process.alive?\n          @process.stop\n          true\n        else\n          false\n        end\n      end\n\n      # Start the process\n      #\n      # @return [Result]\n      def execute\n        # Get the timeout, if we have one\n        timeout = @options[:timeout]\n\n        # Get the working directory\n        workdir = @options[:workdir] || Dir.pwd\n\n        # Get what we're interested in being notified about\n        notify  = @options[:notify] || []\n        notify  = [notify] if !notify.is_a?(Array)\n        if notify.empty? && block_given?\n          # If a block is given, subscribers must be given, otherwise the\n          # block is never called. This is usually NOT what you want, so this\n          # is an error.\n          message = \"A list of notify subscriptions must be given if a block is given\"\n          raise ArgumentError, message\n        end\n\n        # Let's get some more useful booleans that we access a lot so\n        # we're not constantly calling an `include` check\n        notify_table = {}\n        notify_table[:stderr] = notify.include?(:stderr)\n        notify_table[:stdout] = notify.include?(:stdout)\n        notify_stdin  = notify.include?(:stdin)\n\n        # Build the ChildProcess\n        @logger.info(\"Starting process: #{@command.inspect}\")\n        @process = process = ChildProcess.build(*@command)\n\n        # Create the pipes so we can read the output in real time as\n        # we execute the command.\n        stdout, stdout_writer = ::IO.pipe\n        stderr, stderr_writer = ::IO.pipe\n        process.io.stdout = stdout_writer\n        process.io.stderr = stderr_writer\n        process.duplex = true\n        process.leader = true if @options[:detach]\n        process.detach = true if @options[:detach]\n\n        # Special installer-related things\n        if Vagrant.in_installer?\n          installer_dir = Vagrant.installer_embedded_dir.to_s.downcase\n\n          # If we're in an installer on Mac and we're executing a command\n          # in the installer context, then force DYLD_LIBRARY_PATH to look\n          # at our libs first.\n          if Platform.darwin?\n            if @command[0].downcase.include?(installer_dir)\n              @logger.info(\"Command in the installer. Specifying DYLD_LIBRARY_PATH...\")\n              process.environment[\"DYLD_LIBRARY_PATH\"] =\n                \"#{installer_dir}/lib:#{ENV[\"DYLD_LIBRARY_PATH\"]}\"\n            else\n              @logger.debug(\"Command not in installer, not touching env vars.\")\n            end\n\n            if File.setuid?(@command[0]) || File.setgid?(@command[0])\n              @logger.info(\"Command is setuid/setgid, clearing DYLD_LIBRARY_PATH\")\n              process.environment[\"DYLD_LIBRARY_PATH\"] = \"\"\n            end\n          end\n\n          # If the command that is being run is not inside the installer, reset\n          # the original environment - this is required for shelling out to\n          # other subprocesses that depend on environment variables (like Ruby\n          # and $GEM_PATH for example)\n          internal = [installer_dir, Vagrant.user_data_path.to_s.downcase].\n            any? { |path| @command[0].downcase.include?(path) }\n          if !internal\n            @logger.info(\"Command not in installer, restoring original environment...\")\n            jailbreak(process.environment)\n          end\n\n          # If running within an AppImage and calling external executable. When\n          # executable is external set the LD_LIBRARY_PATH to host values.\n          if ENV[\"VAGRANT_APPIMAGE\"]\n            embed_path = Pathname.new(Vagrant.installer_embedded_dir).expand_path.to_s\n            exec_path = Pathname.new(@command[0]).expand_path.to_s\n            if !exec_path.start_with?(embed_path) && ENV[\"VAGRANT_APPIMAGE_HOST_LD_LIBRARY_PATH\"]\n              @logger.info(\"Detected AppImage environment and request to external binary. Updating library path.\")\n              @logger.debug(\"Setting LD_LIBRARY_PATH to #{ENV[\"VAGRANT_APPIMAGE_HOST_LD_LIBRARY_PATH\"]}\")\n              process.environment[\"LD_LIBRARY_PATH\"] = ENV[\"VAGRANT_APPIMAGE_HOST_LD_LIBRARY_PATH\"].to_s\n            end\n          end\n        else\n          @logger.info(\"Vagrant not running in installer, restoring original environment...\")\n          jailbreak(process.environment)\n        end\n\n        # Set the environment on the process if we must\n        if @options[:env]\n          @options[:env].each do |k, v|\n            process.environment[k] = v\n          end\n        end\n\n        # Start the process\n        begin\n          SafeChdir.safe_chdir(workdir) do\n            process.start\n          end\n        rescue ChildProcess::LaunchError => ex\n          # Raise our own version of the error so that users of the class\n          # don't need to be aware of ChildProcess\n          raise LaunchError.new(ex.message)\n        end\n\n        # If running with the detach option, no need to capture IO or\n        # ensure program exists.\n        if @options[:detach]\n          return\n        end\n\n        # Make sure the stdin does not buffer\n        process.io.stdin.sync = true\n\n        if RUBY_PLATFORM != \"java\"\n          # On Java, we have to close after. See down the method...\n          # Otherwise, we close the writers right here, since we're\n          # not on the writing side.\n          stdout_writer.close\n          stderr_writer.close\n        end\n\n        # Create a dictionary to store all the output we see.\n        io_data = { stdout: \"\", stderr: \"\" }\n\n        # Record the start time for timeout purposes\n        start_time = Time.now.to_i\n\n        open_readers = [stdout, stderr]\n        open_writers = notify_stdin ? [process.io.stdin] : []\n        @logger.debug(\"Selecting on IO\")\n        while true\n          results = ::IO.select(open_readers, open_writers, nil, 0.1)\n          results ||= []\n          readers = results[0]\n          writers = results[1]\n\n          # Check if we have exceeded our timeout\n          raise TimeoutExceeded, process.pid if timeout && (Time.now.to_i - start_time) > timeout\n\n          # Check the readers to see if they're ready\n          if readers && !readers.empty?\n            readers.each do |r|\n              # Read from the IO object\n              data = IO.read_until_block(r)\n\n              # We don't need to do anything if the data is empty\n              next if data.empty?\n\n              io_name = r == stdout ? :stdout : :stderr\n              @logger.trace(\"#{io_name}: #{data.chomp}\")\n\n              io_data[io_name] += data\n              yield io_name, data if block_given? && notify_table[io_name]\n            end\n          end\n\n          # Break out if the process exited. We have to do this before\n          # attempting to write to stdin otherwise we'll get a broken pipe\n          # error.\n          break if process.exited?\n\n          # Check the writers to see if they're ready, and notify any listeners\n          if writers && !writers.empty? && block_given?\n            yield :stdin, process.io.stdin\n\n            # if the callback closed stdin, we should remove it, because\n            # IO.select() will throw if called with a closed io.\n            if process.io.stdin.closed?\n              open_writers = []\n            end\n          end\n        end\n\n        # Wait for the process to end.\n        begin\n          remaining = (timeout || 32000) - (Time.now.to_i - start_time)\n          remaining = 0 if remaining < 0\n          @logger.debug(\"Waiting for process to exit. Remaining to timeout: #{remaining}\")\n\n          process.poll_for_exit(remaining)\n        rescue ChildProcess::TimeoutError\n          raise TimeoutExceeded, process.pid\n        end\n\n        @logger.debug(\"Exit status: #{process.exit_code}\")\n\n        # Read the final output data, since it is possible we missed a small\n        # amount of text between the time we last read data and when the\n        # process exited.\n        [stdout, stderr].each do |io|\n          # Read the extra data, ignoring if there isn't any\n          extra_data = IO.read_until_block(io)\n          next if extra_data == \"\"\n\n          # Log it out and accumulate\n          io_name = io == stdout ? :stdout : :stderr\n          io_data[io_name] += extra_data\n          @logger.trace(\"#{io_name}: #{extra_data.chomp}\")\n\n          # Yield to any listeners any remaining data\n          yield io_name, extra_data if block_given? && notify_table[io_name]\n        end\n\n        if RUBY_PLATFORM == \"java\"\n          # On JRuby, we need to close the writers after the process,\n          # for some reason. See GH-711.\n          stdout_writer.close\n          stderr_writer.close\n        end\n\n        # Return an exit status container\n        return Result.new(process.exit_code, io_data[:stdout], io_data[:stderr])\n      ensure\n        if process && process.alive? && !@options[:detach]\n          # Make sure no matter what happens, the process exits\n          process.stop(2)\n        end\n      end\n\n      protected\n\n      # An error which raises when a process fails to start\n      class LaunchError < StandardError; end\n\n      # An error which occurs when the process doesn't end within\n      # the given timeout.\n      class TimeoutExceeded < StandardError\n        attr_reader :pid\n\n        def initialize(pid)\n          super()\n          @pid = pid\n        end\n      end\n\n      # Container class to store the results of executing a subprocess.\n      class Result\n        attr_reader :exit_code\n        attr_reader :stdout\n        attr_reader :stderr\n\n        def initialize(exit_code, stdout, stderr)\n          @exit_code = exit_code\n          @stdout    = stdout\n          @stderr    = stderr\n        end\n      end\n\n      private\n\n      # This is, quite possibly, the saddest function in all of Vagrant.\n      #\n      # If a user is running Vagrant via Bundler (but not via the official\n      # installer), we want to reset to the \"original\" environment so that when\n      # shelling out to other Ruby processes (specifically), the original\n      # environment is restored. This is super important for things like\n      # rbenv and chruby, who rely on environment variables to locate gems, but\n      # Bundler stomps on those environment variables like an angry T-Rex after\n      # watching Jurassic Park 2 and realizing they replaced you with CGI.\n      #\n      # If a user is running in Vagrant via the official installer, BUT trying\n      # to execute a subprocess *outside* of the installer, we want to reset to\n      # the \"original\" environment. In this case, the Vagrant installer actually\n      # knows what the original environment was and replaces it completely.\n      #\n      # Finally, we reset any Bundler-specific environment variables, since the\n      # subprocess being called could, itself, be Bundler. And Bundler does not\n      # behave very nicely in these circumstances.\n      #\n      # This function was added in Vagrant 1.7.3, but there is a failsafe\n      # because the author doesn't trust himself that this functionality won't\n      # break existing assumptions, so users can specify\n      # `VAGRANT_SKIP_SUBPROCESS_JAILBREAK` and none of the above will happen.\n      #\n      # This function modifies the given hash in place!\n      #\n      # @return [nil]\n      def jailbreak(env = {})\n        return if ENV.key?(\"VAGRANT_SKIP_SUBPROCESS_JAILBREAK\")\n\n        if defined?(::Bundler) && defined?(::Bundler::ORIGINAL_ENV)\n          env.replace(::Bundler::ORIGINAL_ENV)\n        end\n        env.merge!(Vagrant.original_env)\n\n        # Bundler does this, so I guess we should as well, since I think it\n        # other subprocesses that use Bundler will reload it\n        env[\"MANPATH\"] = ENV[\"BUNDLE_ORIG_MANPATH\"]\n\n        # Replace all current environment BUNDLE_ variables to nil\n        ENV.each do |k,_|\n          env[k] = nil if k[0,7] == \"BUNDLE_\"\n        end\n\n        # If RUBYOPT was set, unset it with Bundler\n        if ENV.key?(\"RUBYOPT\")\n          env[\"RUBYOPT\"] = ENV[\"RUBYOPT\"].sub(\"-rbundler/setup\", \"\")\n        end\n\n        nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/template_renderer.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'ostruct'\nrequire \"pathname\"\n\nrequire 'erubi'\n\nmodule Vagrant\n  module Util\n    # This class is used to render the ERB templates in the\n    # `GEM_ROOT/templates` directory.\n    class TemplateRenderer < OpenStruct\n      class << self\n        # Render a given template and return the result. This method optionally\n        # takes a block which will be passed the renderer prior to rendering, which\n        # allows the caller to set any view variables within the renderer itself.\n        #\n        # @return [String] Rendered template\n        def render(*args)\n          render_with(:render, *args)\n        end\n\n        # Render a given string and return the result. This method optionally\n        # takes a block which will be passed the renderer prior to rendering, which\n        # allows the caller to set any view variables within the renderer itself.\n        #\n        # @param [String] template The template data string.\n        # @return [String] Rendered template\n        def render_string(*args)\n          render_with(:render_string, *args)\n        end\n\n        # Method used internally to DRY out the other renderers. This method\n        # creates and sets up the renderer before calling a specified method on it.\n        def render_with(method, template, data={})\n          renderer = new(template, data)\n          yield renderer if block_given?\n          renderer.send(method.to_sym)\n        end\n      end\n\n      def initialize(template, data = {})\n        super()\n\n        @template_root = data.delete(:template_root)\n        @template_root ||= Vagrant.source_root.join(\"templates\")\n        @template_root = Pathname.new(@template_root)\n\n        data[:template] = template\n        data.each do |key, value|\n          send(\"#{key}=\", value)\n        end\n      end\n\n      # Renders the template using the class instance as the binding. Because the\n      # renderer inherits from `OpenStruct`, additional view variables can be\n      # added like normal accessors.\n      #\n      # @return [String]\n      def render\n        old_template = template\n        result = nil\n        File.open(full_template_path, 'r') do |f|\n          self.template = f.read\n          result = render_string\n        end\n\n        result\n      ensure\n        self.template = old_template\n      end\n\n      # Renders a template, handling the template as a string, but otherwise\n      # acting the same way as {#render}.\n      #\n      # @return [String]\n      def render_string\n        binding.eval(Erubi::Engine.new(template, trim: true).src)\n      end\n\n      # Returns the full path to the template, taking into account the gem directory\n      # and adding the `.erb` extension to the end.\n      #\n      # @return [String]\n      def full_template_path\n        @template_root.join(\"#{template}.erb\").to_s.squeeze(\"/\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/uploader.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"uri\"\n\nrequire \"log4r\"\nrequire \"vagrant/util/busy\"\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/subprocess\"\nrequire \"vagrant/util/curl_helper\"\n\nmodule Vagrant\n  module Util\n    # This class uploads files using various protocols by subprocessing\n    # to cURL. cURL is a much more capable and complete download tool than\n    # a hand-rolled Ruby library, so we defer to its expertise.\n    class Uploader\n\n      # @param [String] destination Valid URL to upload file to\n      # @param [String] file Location of file to upload on disk\n      # @param [Hash] options\n      # @option options [Vagrant::UI] :ui UI interface for output\n      # @option options [String, Symbol] :method Request method for upload\n      def initialize(destination, file, options=nil)\n        options ||= {}\n        @logger         = Log4r::Logger.new(\"vagrant::util::uploader\")\n        @destination    = destination.to_s\n        @file           = file.to_s\n        @ui             = options[:ui]\n        @request_method = options[:method]\n\n        if !@request_method\n          @request_method = \"PUT\"\n        end\n        @request_method = @request_method.to_s.upcase\n      end\n\n      def upload!\n        data_proc = Vagrant::Util::CurlHelper.capture_output_proc(@logger, @ui)\n\n        @logger.info(\"Uploader starting upload: \")\n        @logger.info(\"  -- Source: #{@file}\")\n        @logger.info(\"  -- Destination: #{@destination}\")\n\n        options = build_options\n        subprocess_options = {notify: :stderr}\n\n        begin\n          execute_curl(options, subprocess_options, &data_proc)\n        rescue Errors::UploaderError => e\n          raise\n        ensure\n          @ui.clear_line if @ui\n        end\n      end\n\n      protected\n\n      def build_options\n        options = [@destination, \"--request\", @request_method, \"--upload-file\", @file, \"--fail\"]\n        return options\n      end\n\n      def execute_curl(options, subprocess_options, &data_proc)\n        options = options.dup\n        options << subprocess_options\n\n        # Create the callback that is called if we are interrupted\n        interrupted  = false\n        int_callback = Proc.new do\n          @logger.info(\"Uploader interrupted!\")\n          interrupted = true\n        end\n\n        # Execute!\n        result = Busy.busy(int_callback) do\n          Subprocess.execute(\"curl\", *options, &data_proc)\n        end\n\n        # If the upload was interrupted, then raise a specific error\n        raise Errors::UploaderInterrupted if interrupted\n\n        # If it didn't exit successfully, we need to parse the data and\n        # show an error message.\n        if result.exit_code != 0\n          @logger.warn(\"Uploader exit code: #{result.exit_code}\")\n          check = result.stderr.match(/\\n*curl:\\s+\\((?<code>\\d+)\\)\\s*(?<error>.*)$/)\n          if !check\n            err_msg = result.stderr\n          else\n            err_msg = check[:error]\n          end\n\n          raise Errors::UploaderError,\n            exit_code: result.exit_code,\n            message: err_msg\n        end\n\n        if @ui\n          @ui.clear_line\n          # Windows doesn't clear properly for some reason, so we just\n          # output one more newline.\n          @ui.detail(\"\") if Platform.windows?\n        end\n        result\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/which.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/platform\"\n\nmodule Vagrant\n  module Util\n    class Which\n      # Cross-platform way of finding an executable in the PATH.\n      #\n      #   which('ruby') #=> /usr/bin/ruby\n      #\n      # This code is adapted from the following post by mislav:\n      #   http://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby\n      #\n      # @param [String] cmd The command to search for in the PATH.\n      # @param [Hash] opts Optional flags\n      #   @option [Boolean] :original_path Search within original path if available\n      # @return [String] The full path to the executable or `nil` if not found.\n      def self.which(cmd, **opts)\n        exts = nil\n\n        if !Platform.windows? || ENV['PATHEXT'].nil?\n          # If the PATHEXT variable is empty, we're on *nix and need to find\n          # the exact filename\n          exts = ['']\n        elsif File.extname(cmd).length != 0\n          # On Windows: if filename contains an extension, we must match that\n          # exact filename\n          exts = ['']\n        else\n          # On Windows: otherwise try to match all possible executable file\n          # extensions (.EXE .COM .BAT etc.)\n          exts = ENV['PATHEXT'].split(';')\n        end\n\n        if opts[:original_path]\n          search_path = ENV.fetch('VAGRANT_OLD_ENV_PATH', ENV['PATH'])\n        else\n          search_path = ENV['PATH']\n        end\n\n        SilenceWarnings.silence! do\n          search_path.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '').split(File::PATH_SEPARATOR).each do |path|\n            exts.each do |ext|\n              exe = \"#{path}#{File::SEPARATOR}#{cmd}#{ext}\"\n              return exe if File.executable? exe\n            end\n          end\n        end\n\n        return nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util/windows_path.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fiddle/import\"\n\nmodule Vagrant\n  module Util\n    module WindowsPath\n      module API\n        extend Fiddle::Importer\n        dlload 'kernel32.dll'\n        extern(\"int GetLongPathNameA(char*, char*, int)\", :stdcall)\n      end\n\n      # Converts a Windows shortname to a long name. This only works\n      # for ASCII paths currently and doesn't use the wide character\n      # support.\n      def self.longname(name)\n        # We loop over the API call in case we didn't allocate enough\n        # buffer space. In general it is usually enough.\n        bufferlen = 250\n        buffer    = nil\n        while true\n          buffer = ' ' * bufferlen\n          len    = API.GetLongPathNameA(name.to_s, buffer, buffer.size)\n          if bufferlen < len\n            # If the length returned is larger than our buffer length,\n            # it is the API telling us it needs more space. Allocate it\n            # and retry.\n            bufferlen = len\n            continue\n          end\n\n          break\n        end\n\n        return buffer.rstrip.chomp(\"\\0\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/util.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  module Util\n    autoload :ANSIEscapeCodeRemover,     'vagrant/util/ansi_escape_code_remover'\n    autoload :Busy,                      'vagrant/util/busy'\n    autoload :Caps,                      'vagrant/util/caps'\n    autoload :CheckpointClient,          'vagrant/util/checkpoint_client'\n    autoload :CommandDeprecation,        'vagrant/util/command_deprecation'\n    autoload :Counter,                   'vagrant/util/counter'\n    autoload :CredentialScrubber,        'vagrant/util/credential_scrubber'\n    autoload :CurlHelper,                'vagrant/util/curl_helper'\n    autoload :DeepMerge,                 'vagrant/util/deep_merge'\n    autoload :Directory,                 'vagrant/util/directory'\n    autoload :Downloader,                'vagrant/util/downloader'\n    autoload :Env,                       'vagrant/util/env'\n    autoload :Experimental,              'vagrant/util/experimental'\n    autoload :FileChecksum,              'vagrant/util/file_checksum'\n    autoload :FileMode,                  'vagrant/util/file_mode'\n    autoload :FileMutex,                 'vagrant/util/file_mutex'\n    autoload :GuestHosts,                'vagrant/util/guest_hosts'\n    autoload :GuestInspection,           'vagrant/util/guest_inspection'\n    autoload :GuestNetworks,             'vagrant/util/guest_networks'\n    autoload :HashWithIndifferentAccess, 'vagrant/util/hash_with_indifferent_access'\n    autoload :HCLogFormatter,            'vagrant/util/logging_formatter'\n    autoload :HCLogOutputter,            'vagrant/util/logging_formatter'\n    autoload :InstallShellConfig,        'vagrant/util/install_cli_autocomplete'\n    autoload :InstallZSHShellConfig,     'vagrant/util/install_cli_autocomplete'\n    autoload :InstallBashShellConfig,    'vagrant/util/install_cli_autocomplete'\n    autoload :InstallCLIShellConfig,     'vagrant/util/install_cli_autocomplete'\n    autoload :IO,                        'vagrant/util/io'\n    autoload :IPV4Interfaces,            'vagrant/util/ipv4_interfaces'\n    autoload :IsPortOpen,                'vagrant/util/is_port_open'\n    autoload :Keypair,                   'vagrant/util/keypair'\n    autoload :LineBuffer,                'vagrant/util/line_buffer'\n    autoload :LineEndingHelpers,         'vagrant/util/line_ending_helpers'\n    autoload :LoggingFormatter,          'vagrant/util/logging_formatter'\n    autoload :MapCommandOptions,         'vagrant/util/map_command_options'\n    autoload :Mime,                      'vagrant/util/mime'\n    autoload :NetworkIP,                 'vagrant/util/network_ip'\n    autoload :Numeric,                   'vagrant/util/numeric'\n    autoload :Platform,                  'vagrant/util/platform'\n    autoload :Powershell,                'vagrant/util/powershell'\n    autoload :Presence,                  'vagrant/util/presence'\n    autoload :Retryable,                 'vagrant/util/retryable'\n    autoload :SafeChdir,                 'vagrant/util/safe_chdir'\n    autoload :SafeEnv,                   'vagrant/util/safe_env'\n    autoload :SafeExec,                  'vagrant/util/safe_exec'\n    autoload :SafePuts,                  'vagrant/util/safe_puts'\n    autoload :ScopedHashOverride,        'vagrant/util/scoped_hash_override'\n    autoload :ShellQuote,                'vagrant/util/shell_quote'\n    autoload :SilenceWarnings,           'vagrant/util/silence_warnings'\n    autoload :SSH,                       'vagrant/util/ssh'\n    autoload :StackedProcRunner,         'vagrant/util/stacked_proc_runner'\n    autoload :StringBlockEditor,         'vagrant/util/string_block_editor'\n    autoload :Subprocess,                'vagrant/util/subprocess'\n    autoload :TemplateRenderer,          'vagrant/util/template_renderer'\n    autoload :Uploader,                  'vagrant/util/uploader'\n    autoload :Which,                     'vagrant/util/which'\n    autoload :WindowsPath,               'vagrant/util/windows_path'\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/vagrantfile.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/template_renderer\"\nrequire \"log4r\"\n\nmodule Vagrant\n  # This class provides a way to load and access the contents\n  # of a Vagrantfile.\n  #\n  # This class doesn't actually load Vagrantfiles, parse them,\n  # merge them, etc. That is the job of {Config::Loader}. This\n  # class, on the other hand, has higher-level operations on\n  # a loaded Vagrantfile such as looking up the defined machines,\n  # loading the configuration of a specific machine/provider combo,\n  # etc.\n  class Vagrantfile\n    # This is the configuration loaded as-is given the loader and\n    # keys to #initialize.\n    attr_reader :config\n\n    # Initializes by loading a Vagrantfile.\n    #\n    # @param [Config::Loader] loader Configuration loader that should\n    #   already be configured with the proper Vagrantfile locations.\n    #   This usually comes from {Vagrant::Environment}\n    # @param [Array<Symbol>] keys The Vagrantfiles to load and the\n    #   order to load them in (keys within the loader).\n    def initialize(loader, keys)\n      @keys   = keys\n      @loader = loader\n      @config, _ = loader.load(keys)\n      @logger = Log4r::Logger.new(\"vagrant::vagrantfile\")\n    end\n\n    # Returns a {Machine} for the given name and provider that\n    # is represented by this Vagrantfile.\n    #\n    # @param [Symbol] name Name of the machine.\n    # @param [Symbol] provider The provider the machine should\n    #   be backed by (required for provider overrides).\n    # @param [BoxCollection] boxes BoxCollection to look up the\n    #   box Vagrantfile.\n    # @param [Pathname] data_path Path where local machine data\n    #   can be stored.\n    # @param [Environment] env The environment running this machine\n    # @return [Machine]\n    def machine(name, provider, boxes, data_path, env)\n      # Load the configuration for the machine\n      results = machine_config(name, provider, boxes, data_path)\n      box             = results[:box]\n      config          = results[:config]\n      config_errors   = results[:config_errors]\n      config_warnings = results[:config_warnings]\n      provider_cls    = results[:provider_cls]\n      provider_options = results[:provider_options]\n\n      # If there were warnings or errors we want to output them\n      if !config_warnings.empty? || !config_errors.empty?\n        # The color of the output depends on whether we have warnings\n        # or errors...\n        level  = config_errors.empty? ? :warn : :error\n        output = Util::TemplateRenderer.render(\n          \"config/messages\",\n          warnings: config_warnings,\n          errors: config_errors).chomp\n        env.ui.send(level, I18n.t(\"vagrant.general.config_upgrade_messages\",\n                               name: name,\n                               output: output))\n\n        # If we had errors, then we bail\n        raise Errors::ConfigUpgradeErrors if !config_errors.empty?\n      end\n\n      # Get the provider configuration from the final loaded configuration\n      provider_config = config.vm.get_provider_config(provider)\n\n      # Create machine data directory if it doesn't exist\n      # XXX: Permissions error here.\n      FileUtils.mkdir_p(data_path)\n\n      # Create the machine and cache it for future calls. This will also\n      # return the machine from this method.\n      return Machine.new(name, provider, provider_cls, provider_config,\n        provider_options, config, data_path, box, env, self)\n    end\n    # Returns the configuration for a single machine.\n    #\n    # When loading a box Vagrantfile, it will be prepended to the\n    # key order specified when initializing this class. Sub-machine\n    # and provider-specific overrides are appended at the end. The\n    # actual order is:\n    #\n    # - box\n    # - keys specified for #initialize\n    # - sub-machine\n    # - provider\n    #\n    # The return value is a hash with the following keys (symbols)\n    # and values:\n    #\n    #   - box: the {Box} backing the machine\n    #   - config: the actual configuration\n    #   - config_errors: list of errors, if any\n    #   - config_warnings: list of warnings, if any\n    #   - provider_cls: class of the provider backing the machine\n    #   - provider_options: options for the provider\n    #\n    # @param [Symbol] name Name of the machine.\n    # @param [Symbol] provider The provider the machine should\n    #   be backed by (required for provider overrides).\n    # @param [BoxCollection] boxes BoxCollection to look up the\n    #   box Vagrantfile.\n    # @param [Pathname] data_path Machine data path\n    # @return [Hash<Symbol, Object>] Various configuration parameters for a\n    #   machine. See the main documentation body for more info.\n    def machine_config(name, provider, boxes, data_path=nil, validate_provider=true)\n      keys = @keys.dup\n\n      sub_machine = @config.vm.defined_vms[name]\n      if !sub_machine\n        raise Errors::MachineNotFound,\n          name: name, provider: provider\n      end\n\n      provider_plugin  = nil\n      provider_cls     = nil\n      provider_options = {}\n      box_formats      = nil\n      if provider != nil\n        provider_plugin  = Vagrant.plugin(\"2\").manager.providers[provider]\n        if !provider_plugin && validate_provider\n          providers  = Vagrant.plugin(\"2\").manager.providers.to_hash.keys\n          if providers\n            providers_str = providers.join(', ')\n          else\n            providers_str = \"N/A\"\n          end\n\n          if providers.include? provider.downcase\n            raise Errors::ProviderNotFoundSuggestion,\n              machine: name, provider: provider,\n              suggestion: provider.downcase, providers: providers_str\n          end\n\n          raise Errors::ProviderNotFound,\n            machine: name, provider: provider, providers: providers_str\n        end\n\n        if validate_provider\n          provider_cls     = provider_plugin[0]\n          provider_options = provider_plugin[1]\n          box_formats      = provider_options[:box_format] || provider\n\n          # Test if the provider is usable or not\n          begin\n            provider_cls.usable?(true)\n          rescue Errors::VagrantError => e\n            raise Errors::ProviderNotUsable,\n              machine: name.to_s,\n              provider: provider.to_s,\n              message: e.to_s\n          end\n        else\n          box_formats = provider\n        end\n      end\n\n      # Add the sub-machine configuration to the loader and keys\n      vm_config_key = \"#{object_id}_machine_#{name}\"\n      @loader.set(vm_config_key, sub_machine.config_procs)\n      keys << vm_config_key\n\n      # Load once so that we can get the proper box value\n      config, config_warnings, config_errors = @loader.load(keys)\n\n      # Track the original box so we know if we changed\n      box = nil\n      initial_box = original_box = config.vm.box\n      initial_version = original_version = config.vm.box_version\n\n      # Check if this machine has a local box metadata file\n      # describing the existing guest. If so, load it and\n      # set the box name and version to allow the actual\n      # box in use to be discovered.\n      if data_path\n        meta_file = data_path.join(\"box_meta\")\n        if meta_file.file?\n          box_meta = JSON.parse(meta_file.read)\n          config.vm.box = box_meta[\"name\"]\n          config.vm.box_version = box_meta[\"version\"]\n        end\n      end\n\n      # The proc below loads the box and provider overrides. This is\n      # in a proc because it may have to recurse if the provider override\n      # changes the box.\n      load_box_proc = lambda do\n        local_keys = keys.dup\n\n        # Load the box Vagrantfile, if there is one\n        if !config.vm.box.to_s.empty? && boxes\n          box = boxes.find(config.vm.box, box_formats, config.vm.box_version, config.vm.box_architecture)\n          if box\n            box_vagrantfile = find_vagrantfile(box.directory)\n            if box_vagrantfile && !config.vm.ignore_box_vagrantfile\n              box_config_key =\n                \"#{boxes.object_id}_#{box.name}_#{box.provider}\".to_sym\n              @loader.set(box_config_key, box_vagrantfile)\n              local_keys.unshift(box_config_key)\n              config, config_warnings, config_errors = @loader.load(local_keys)\n            elsif box_vagrantfile && config.vm.ignore_box_vagrantfile\n              @logger.warn(\"Ignoring #{box.name} provided Vagrantfile inside box\")\n            end\n          end\n        end\n\n        # Load provider overrides\n        provider_overrides = config.vm.get_provider_overrides(provider)\n        if !provider_overrides.empty?\n          config_key =\n            \"#{object_id}_vm_#{name}_#{config.vm.box}_#{provider}\".to_sym\n          @loader.set(config_key, provider_overrides)\n          local_keys << config_key\n          config, config_warnings, config_errors = @loader.load(local_keys)\n        end\n\n        # If the box changed, then we need to reload\n        if original_box != config.vm.box || original_version != config.vm.box_version\n          # TODO: infinite loop protection?\n\n          original_box = config.vm.box\n          original_version = config.vm.box_version\n          load_box_proc.call\n        end\n      end\n\n      # Load the box and provider overrides\n      load_box_proc.call\n\n      # NOTE: In cases where the box_meta file contains stale information\n      #       and the reference box no longer exists, fall back to initial\n      #       configuration and attempt to load that\n      if box.nil?\n        @logger.warn(\"Failed to locate #{config.vm.box} with version #{config.vm.box_version}\")\n        @logger.warn(\"Performing lookup with initial values #{initial_box} with version #{initial_version}\")\n        config.vm.box = original_box = initial_box\n        config.vm.box_version = original_box = initial_version\n        load_box_proc.call\n      end\n\n      # Ensure box attributes are set to original values in\n      # case they were modified by the local box metadata\n      config.vm.box = original_box\n      config.vm.box_version = original_version\n\n      return {\n        box: box,\n        provider_cls: provider_cls,\n        provider_options: provider_options.dup,\n        config: config,\n        config_warnings: config_warnings,\n        config_errors: config_errors,\n      }\n    end\n\n    # Returns a list of the machines that are defined within this\n    # Vagrantfile.\n    #\n    # @return [Array<Symbol>]\n    def machine_names\n      @config.vm.defined_vm_keys.dup\n    end\n\n    # Returns a list of the machine names as well as the options that\n    # were specified for that machine.\n    #\n    # @return [Hash<Symbol, Hash>]\n    def machine_names_and_options\n      {}.tap do |r|\n        @config.vm.defined_vms.each do |name, subvm|\n          r[name] = subvm.options || {}\n        end\n      end\n    end\n\n    # Returns the name of the machine that is designated as the\n    # \"primary.\"\n    #\n    # In the case of a single-machine environment, this is just the\n    # single machine name. In the case of a multi-machine environment,\n    # then this is the machine that is marked as primary, or nil if\n    # no primary machine was specified.\n    #\n    # @return [Symbol]\n    def primary_machine_name\n      # If it is a single machine environment, then return the name\n      return machine_names.first if machine_names.length == 1\n\n      # If it is a multi-machine environment, then return the primary\n      @config.vm.defined_vms.each do |name, subvm|\n        return name if subvm.options[:primary]\n      end\n\n      # If no primary was specified, nil it is\n      nil\n    end\n\n    protected\n\n    def find_vagrantfile(search_path)\n      [\"Vagrantfile\", \"vagrantfile\"].each do |vagrantfile|\n        current_path = search_path.join(vagrantfile)\n        return current_path if current_path.file?\n      end\n\n      nil\n    end\n  end\nend\n"
  },
  {
    "path": "lib/vagrant/version.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule Vagrant\n  # This will always be up to date with the current version of Vagrant,\n  # since it is used to generate the gemspec and is also the source of\n  # the version for `vagrant -v`\n  VERSION = File.read(\n    File.expand_path(\"../../../version.txt\", __FILE__)).chomp\nend\n"
  },
  {
    "path": "lib/vagrant.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# Load the shared helpers first to make the custom\n# require helper available.\nrequire \"vagrant/shared_helpers\"\n\nrequire \"log4r\"\n\n# Add patches to log4r to support trace level\nrequire \"vagrant/patches/log4r\"\nrequire \"vagrant/patches/net-ssh\"\nrequire \"vagrant/patches/rubygems\"\nrequire \"vagrant/patches/timeout_error\"\n\n# Set our log levels and include trace\nrequire 'log4r/configurator'\nLog4r::Configurator.custom_levels(*([\"TRACE\"] + Log4r::Log4rConfig::LogLevels))\n\n# Update the default formatter within the log4r library to ensure\n# sensitive values are being properly scrubbed from logger data\nclass Log4r::BasicFormatter\n  alias_method :vagrant_format_object, :format_object\n  def format_object(obj)\n    Vagrant::Util::CredentialScrubber.desensitize(vagrant_format_object(obj))\n  end\nend\n\nrequire \"optparse\"\n\nmodule Vagrant\n  # This is a customized OptionParser for Vagrant plugins. It\n  # will automatically add any default CLI options defined\n  # outside of command implementations to the local option\n  # parser instances in use\n  class OptionParser < ::OptionParser\n    def initialize(*_)\n      super\n      Vagrant.default_cli_options.each do |opt_proc|\n        opt_proc.call(self)\n      end\n    end\n  end\nend\n\n# Inject the option parser into the vagrant plugins\n# module so it is automatically used when defining\n# command options\nmodule VagrantPlugins\n  OptionParser = Vagrant::OptionParser\nend\n\n# Load in our helpers and utilities\nrequire \"rubygems\"\nrequire \"vagrant/util\"\nrequire \"vagrant/plugin/manager\"\n\n# Enable logging if it is requested. We do this before\n# anything else so that we can setup the output before\n# any logging occurs.\n\n# NOTE: We must do this little hack to allow\n# rest-client to write using the `<<` operator.\n# See https://github.com/rest-client/rest-client/issues/34#issuecomment-290858\n# for more information\nclass VagrantLogger < Log4r::Logger\n  def << msg\n    debug(msg.strip)\n  end\nend\n\nif ENV[\"VAGRANT_LOG\"] && ENV[\"VAGRANT_LOG\"] != \"\"\n  level = Log4r::LNAMES.index(ENV[\"VAGRANT_LOG\"].upcase)\n  if level.nil?\n    level = Log4r::LNAMES.index(\"FATAL\")\n  end\n\n  if !level\n    # We directly write to stderr here because the VagrantError system\n    # is not setup yet.\n    $stderr.puts \"Invalid VAGRANT_LOG level is set: #{ENV[\"VAGRANT_LOG\"]}\"\n    $stderr.puts \"\"\n    $stderr.puts \"Please use one of the standard log levels: debug, info, warn, or error\"\n    exit 1\n  end\n\n  # Set the logging level on all \"vagrant\" namespaced\n  # logs as long as we have a valid level.\n  if level\n    [\"vagrant\", \"vagrantplugins\"].each do |lname|\n      logger = VagrantLogger.new(lname)\n      if ENV[\"VAGRANT_LOG_FILE\"] && ENV[\"VAGRANT_LOG_FILE\"] != \"\"\n        logger.outputters = Log4r::FileOutputter.new(\"vagrant\", filename: ENV[\"VAGRANT_LOG_FILE\"])\n      else\n        logger.outputters = Log4r::Outputter.stderr\n      end\n      logger.level = level\n    end\n    Log4r::RootLogger.instance.level = level\n\n    base_formatter = Log4r::BasicFormatter.new\n    if ENV[\"VAGRANT_LOG_TIMESTAMP\"]\n      base_formatter = Log4r::PatternFormatter.new(\n        pattern: \"%d [%5l] %m\",\n        date_pattern: \"%F %T\"\n      )\n    end\n\n    Log4r::Outputter.stderr.formatter = Vagrant::Util::LoggingFormatter.new(base_formatter)\n  end\nend\n\nrequire 'json'\nrequire 'pathname'\nrequire 'stringio'\n\nrequire 'childprocess'\nrequire 'i18n'\n\n# OpenSSL must be loaded here since when it is loaded via `autoload`\n# there are issues with ciphers not being properly loaded.\nrequire 'openssl'\n\n# Always make the version available\nrequire 'vagrant/version'\nglobal_logger = Log4r::Logger.new(\"vagrant::global\")\nVagrant.global_logger = global_logger\nglobal_logger.info(\"Vagrant version: #{Vagrant::VERSION}\")\nglobal_logger.info(\"Ruby version: #{RUBY_VERSION}\")\nglobal_logger.info(\"RubyGems version: #{Gem::VERSION}\")\nENV.each do |k, v|\n  next if k.start_with?(\"VAGRANT_OLD\")\n  global_logger.info(\"#{k}=#{v.inspect}\") if k.start_with?(\"VAGRANT_\")\nend\n\n# If the vagrant_ssl library exists, a recent version\n# of openssl is in use and its needed to load all the\n# providers needed\nvagrant_ssl_locations = [\n  File.expand_path(\"vagrant/vagrant_ssl.so\", __dir__),\n  File.expand_path(\"vagrant/vagrant_ssl.bundle\", __dir__)\n]\nif vagrant_ssl_locations.any? { |f| File.exist?(f) }\n  global_logger.debug(\"vagrant ssl helper found for loading ssl providers\")\n  begin\n    require \"vagrant/vagrant_ssl\"\n    Vagrant.vagrant_ssl_load\n    global_logger.debug(\"ssl providers successfully loaded\")\n  rescue LoadError => err\n    global_logger.warn(\"failed to load ssl providers, attempting to continue (#{err})\")\n  rescue => err\n    global_logger.warn(\"unexpected failure loading ssl providers, attempting to continue (#{err})\")\n  end\nelse\n  global_logger.warn(\"vagrant ssl helper was not found, continuing...\")\nend\n\n# We need these components always so instead of an autoload we\n# just require them explicitly here.\nrequire \"vagrant/plugin\"\nrequire \"vagrant/registry\"\n\nmodule Vagrant\n  autoload :Action,         'vagrant/action'\n  autoload :Alias,          'vagrant/alias'\n  autoload :BatchAction,    'vagrant/batch_action'\n  autoload :Box,            'vagrant/box'\n  autoload :BoxCollection,  'vagrant/box_collection'\n  autoload :BoxMetadata,    'vagrant/box_metadata'\n  autoload :Bundler,        'vagrant/bundler'\n  autoload :CLI,            'vagrant/cli'\n  autoload :CapabilityHost, 'vagrant/capability_host'\n  autoload :Config,         'vagrant/config'\n  autoload :Environment,    'vagrant/environment'\n  autoload :Errors,         'vagrant/errors'\n  autoload :Guest,          'vagrant/guest'\n  autoload :Host,           'vagrant/host'\n  autoload :Machine,        'vagrant/machine'\n  autoload :MachineIndex,   'vagrant/machine_index'\n  autoload :MachineState,   'vagrant/machine_state'\n  autoload :Plugin,         'vagrant/plugin'\n  autoload :Registry,       'vagrant/registry'\n  autoload :UI,             'vagrant/ui'\n  autoload :Util,           'vagrant/util'\n  autoload :Vagrantfile,    'vagrant/vagrantfile'\n  autoload :VERSION,        'vagrant/version'\n\n  # These are the various plugin versions and their components in\n  # a lazy loaded Hash-like structure.\n  PLUGIN_COMPONENTS = Registry.new.tap do |c|\n    c.register(:\"1\")                  { Plugin::V1::Plugin }\n    c.register([:\"1\", :command])      { Plugin::V1::Command }\n    c.register([:\"1\", :communicator]) { Plugin::V1::Communicator }\n    c.register([:\"1\", :config])       { Plugin::V1::Config }\n    c.register([:\"1\", :guest])        { Plugin::V1::Guest }\n    c.register([:\"1\", :host])         { Plugin::V1::Host }\n    c.register([:\"1\", :provider])     { Plugin::V1::Provider }\n    c.register([:\"1\", :provisioner])  { Plugin::V1::Provisioner }\n\n    c.register(:\"2\")                  { Plugin::V2::Plugin }\n    c.register([:\"2\", :command])      { Plugin::V2::Command }\n    c.register([:\"2\", :communicator]) { Plugin::V2::Communicator }\n    c.register([:\"2\", :config])       { Plugin::V2::Config }\n    c.register([:\"2\", :guest])        { Plugin::V2::Guest }\n    c.register([:\"2\", :host])         { Plugin::V2::Host }\n    c.register([:\"2\", :provider])     { Plugin::V2::Provider }\n    c.register([:\"2\", :provisioner])  { Plugin::V2::Provisioner }\n    c.register([:\"2\", :push])         { Plugin::V2::Push }\n    c.register([:\"2\", :synced_folder]) { Plugin::V2::SyncedFolder }\n\n    c.register(:remote)               { Plugin::Remote::Plugin }\n  end\n\n  # Configure a Vagrant environment. The version specifies the version\n  # of the configuration that is expected by the block. The block, based\n  # on that version, configures the environment.\n  #\n  # Note that the block isn't run immediately. Instead, the configuration\n  # block is stored until later, and is run when an environment is loaded.\n  #\n  # @param [String] version Version of the configuration\n  def self.configure(version, &block)\n    Config.run(version, &block)\n  end\n\n  # This checks if a plugin with the given name is available (installed\n  # and enabled). This can be used from the Vagrantfile to easily branch\n  # based on plugin availability.\n  def self.has_plugin?(name, version=nil)\n    return false unless Vagrant.plugins_enabled?\n\n    if !version\n      # We check the plugin names first because those are cheaper to check\n      return true if plugin(\"2\").manager.registered.any? { |p| p.name == name }\n    end\n\n    # Now check the plugin gem names\n    require \"vagrant/plugin/manager\"\n    Plugin::Manager.instance.plugin_installed?(name, version)\n  end\n\n  # Returns a superclass to use when creating a plugin for Vagrant.\n  # Given a specific version, this returns a proper superclass to use\n  # to register plugins for that version.\n  #\n  # Optionally, if you give a specific component, then it will return\n  # the proper superclass for that component as well.\n  #\n  # Plugins and plugin components should subclass the classes returned by\n  # this method. This method lets Vagrant core control these superclasses\n  # and change them over time without affecting plugins. For example, if\n  # the V1 superclass happens to be \"Vagrant::V1,\" future versions of\n  # Vagrant may move it to \"Vagrant::Plugins::V1\" and plugins will not be\n  # affected.\n  #\n  # @param [String] version\n  # @param [String] component\n  # @return [Class]\n  def self.plugin(version, component=nil)\n    # Build up the key and return a result\n    key    = version.to_s.to_sym\n    key    = [key, component.to_s.to_sym] if component\n    result = PLUGIN_COMPONENTS.get(key)\n\n    # If we found our component then we return that\n    return result if result\n\n    # If we didn't find a result, then raise an exception, depending\n    # on if we got a component or not.\n    raise ArgumentError, \"Plugin superclass not found for version/component: \" +\n      \"#{version} #{component}\"\n  end\n\n  # @deprecated\n  def self.require_plugin(name)\n    puts \"require_plugin is deprecated and has no effect any longer.\"\n    puts \"Use `vagrant plugin` commands to manage plugins. This warning will\"\n    puts \"be removed in the next version of Vagrant.\"\n  end\n\n  # This checks if Vagrant is installed in a specific version.\n  #\n  # Example:\n  #\n  #    Vagrant.version?(\">= 2.1.0\")\n  #\n  def self.version?(*requirements)\n    req = Gem::Requirement.new(*requirements)\n    req.satisfied_by?(Gem::Version.new(VERSION))\n  end\n\n  # This allows a Vagrantfile to specify the version of Vagrant that is\n  # required. You can specify a list of requirements which will all be checked\n  # against the running Vagrant version.\n  #\n  # This should be specified at the _top_ of any Vagrantfile.\n  #\n  # Examples are shown below:\n  #\n  #   require_version(\">= 1.3.5\")\n  #   require_version(\">= 1.3.5\", \"< 1.4.0\")\n  #   require_version(\"~> 1.3.5\")\n  #\n  def self.require_version(*requirements)\n    logger = Log4r::Logger.new(\"vagrant::root\")\n    logger.info(\"Version requirements from Vagrantfile: #{requirements.inspect}\")\n\n    if version?(*requirements)\n      logger.info(\"  - Version requirements satisfied!\")\n      return\n    end\n\n    raise Errors::VagrantVersionBad,\n      requirements: requirements.join(\", \"),\n      version: VERSION\n  end\n\n  # This allows plugin developers to access the original environment before\n  # Vagrant even ran. This is useful when shelling out, especially to other\n  # Ruby processes.\n  #\n  # @return [Hash]\n  def self.original_env\n    {}.tap do |h|\n      ENV.each do |k,v|\n        if k.start_with?(\"VAGRANT_OLD_ENV\")\n          key = k.sub(/^VAGRANT_OLD_ENV_/, \"\")\n          if !key.empty?\n            h[key] = v\n          end\n        end\n      end\n    end\n  end\nend\n\n# Default I18n to load the en locale\nI18n.load_path << File.expand_path(\"templates/locales/en.yml\", Vagrant.source_root)\n\nif I18n.config.respond_to?(:enforce_available_locales=)\n  # Make sure only available locales are used. This will be the default in the\n  # future but we need this to silence a deprecation warning from 0.6.9\n  I18n.config.enforce_available_locales = true\nend\n\nif Vagrant.enable_resolv_replace\n  global_logger.info(\"resolv replacement has been enabled!\")\nelse\n  global_logger.warn(\"resolv replacement has not been enabled!\")\nend\n\n# A lambda that knows how to load plugins from a single directory.\nplugin_load_proc = lambda do |directory|\n  # We only care about directories\n  next false if !directory.directory?\n\n  # If there is a plugin file in the top-level directory, then load\n  # that up.\n  plugin_file = directory.join(\"plugin.rb\")\n  if plugin_file.file?\n    global_logger.debug(\"Loading core plugin: #{plugin_file}\")\n    load(plugin_file)\n    next true\n  end\nend\n\n# Go through the `plugins` directory and attempt to load any plugins. The\n# plugins are allowed to be in a directory in `plugins` or at most one\n# directory deep within the plugins directory. So a plugin can be at\n# `plugins/foo` or also at `plugins/foo/bar`, but no deeper.\nVagrant.source_root.join(\"plugins\").children(true).each do |directory|\n  # Ignore non-directories\n  next if !directory.directory?\n\n  # Load from this directory, and exit if we successfully loaded a plugin\n  next if plugin_load_proc.call(directory)\n\n  # Otherwise, attempt to load from sub-directories\n  directory.children(true).each(&plugin_load_proc)\nend\n"
  },
  {
    "path": "plugins/README.md",
    "content": "# Vagrant Core Plugins\n\nThese are plugins that ship with Vagrant. Vagrant core uses its own\nplugin system to power a lot of the core pieces that ship with Vagrant.\nEach plugin will have its own README which explains its specific role.\n\n## Generate proto\n\n```\ngrpc_tools_ruby_protoc -I . --ruby_out=gen/plugin --grpc_out=gen/plugin ./plugin_server.proto\n```\n"
  },
  {
    "path": "plugins/commands/autocomplete/command/install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire 'vagrant/util/install_cli_autocomplete'\n\nmodule VagrantPlugins\n  module CommandAutocomplete\n    module Command\n      class Install < Vagrant.plugin(\"2\", :command)\n        def execute\n          options = {\n            shells: []\n          }\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant autocomplete install [-h] [shell name]\"\n            o.separator \"\"\n            o.separator \"Available shells: #{Vagrant::Util::InstallCLIAutocomplete::SUPPORTED_SHELLS.keys.join(' ')}\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"-b\", \"--bash\", \"Install bash autocomplete\") do |c|\n              options[:shells].append(\"bash\")\n            end\n\n            o.on(\"-z\", \"--zsh\", \"Install zsh autocomplete\") do |c|\n              options[:shells].append(\"zsh\")\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.length > 0\n\n          written_paths = Vagrant::Util::InstallCLIAutocomplete.install(options[:shells])\n          if written_paths && written_paths.length > 0\n            @env.ui.info(I18n.t(\"vagrant.autocomplete.installed\", paths: written_paths.join(\"\\n- \")))\n          else\n            @env.ui.info(I18n.t(\"vagrant.autocomplete.not_installed\"))\n          end\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/autocomplete/command/root.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"optparse\"\nrequire 'vagrant/util/install_cli_autocomplete'\n\nmodule VagrantPlugins\n  module CommandAutocomplete\n    module Command\n      class Root < Vagrant.plugin(\"2\", :command)\n        def self.synopsis\n          \"manages autocomplete installation on host\"\n        end\n\n        def initialize(argv, env)\n          super\n\n          @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)\n\n          @subcommands = Vagrant::Registry.new\n          @subcommands.register(:install) do\n            require File.expand_path(\"../install\", __FILE__)\n            Install\n          end\n        end\n\n        def execute\n          if @main_args.include?(\"-h\") || @main_args.include?(\"--help\")\n            # Print the help for all the box commands.\n            return help\n          end\n\n          # If we reached this far then we must have a subcommand. If not,\n          # then we also just print the help and exit.\n          command_class = @subcommands.get(@sub_command.to_sym) if @sub_command\n          return help if !command_class || !@sub_command\n          @logger.debug(\"Invoking command class: #{command_class} #{@sub_args.inspect}\")\n\n          # Initialize and execute the command class\n          command_class.new(@sub_args, @env).execute\n        end\n\n        def help\n          opts = OptionParser.new do |opts|\n            opts.banner = \"Usage: vagrant autocomplete <subcommand>\"\n            opts.separator \"\"\n            opts.separator \"Available subcommands:\"\n\n            # Add the available subcommands as separators in order to print them\n            # out as well.\n            keys = []\n            keys = @subcommands.keys.map(&:to_s)\n\n            keys.sort.each do |key|\n              opts.separator \"     #{key}\"\n            end\n\n            opts.separator \"\"\n            opts.separator \"For help on any individual subcommand run `vagrant autocomplete <subcommand> -h`\"\n          end\n\n          @env.ui.info(opts.help, prefix: false)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/autocomplete/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\n\nmodule VagrantPlugins\n  module CommandAutocomplete\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"autocomplete command\"\n      description <<-DESC\n      The `autocomplete` manipulates Vagrant the autocomplete feature.\n      DESC\n\n      command(\"autocomplete\") do\n        require File.expand_path(\"../command/root\", __FILE__)\n        Command::Root\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/box/command/add.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire_relative 'download_mixins'\n\nmodule VagrantPlugins\n  module CommandBox\n    module Command\n      class Add < Vagrant.plugin(\"2\", :command)\n        include DownloadMixins\n\n        def execute\n          options = {\n            architecture: :auto,\n          }\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant box add [options] <name, url, or path>\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"-c\", \"--clean\", \"Clean any temporary download files\") do |c|\n              options[:clean] = c\n            end\n\n            o.on(\"-f\", \"--force\", \"Overwrite an existing box if it exists\") do |f|\n              options[:force] = f\n            end\n\n            build_download_options(o, options)\n\n            o.on(\"--location-trusted\", \"Trust 'Location' header from HTTP redirects and use the same credentials for subsequent urls as for the initial one\") do |l|\n                options[:location_trusted] = l\n            end\n\n            o.on(\"-a\", \"--architecture ARCH\", String, \"Architecture the box should satisfy\") do |a|\n              options[:architecture] = a\n            end\n\n            o.on(\"--provider PROVIDER\", String, \"Provider the box should satisfy\") do |p|\n              options[:provider] = p\n            end\n\n            o.on(\"--box-version VERSION\", String, \"Constrain version of the added box\") do |v|\n              options[:version] = v\n            end\n\n            o.separator \"\"\n            o.separator \"The box descriptor can be the name of a box on HashiCorp's Vagrant Cloud,\"\n            o.separator \"or a URL, or a local .box file, or a local .json file containing\"\n            o.separator \"the catalog metadata.\"\n            o.separator \"\"\n            o.separator \"The options below only apply if you're adding a box file directly,\"\n            o.separator \"and not using a Vagrant server or a box structured like 'user/box':\"\n            o.separator \"\"\n\n            o.on(\"--checksum CHECKSUM\", String, \"Checksum for the box\") do |c|\n              options[:checksum] = c\n            end\n\n            o.on(\"--checksum-type TYPE\", String, \"Checksum type (md5, sha1, sha256)\") do |c|\n              options[:checksum_type] = c.to_sym\n            end\n\n            o.on(\"--name BOX\", String, \"Name of the box\") do |n|\n              options[:name] = n\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          if argv.empty? || argv.length > 2\n            raise Vagrant::Errors::CLIInvalidUsage,\n              help: opts.help.chomp\n          end\n\n          url = argv[0]\n          if argv.length == 2\n            options[:name] = argv[0]\n            url = argv[1]\n          end\n\n          @env.action_runner.run(Vagrant::Action.action_box_add, {\n            box_url: url,\n            box_name: options[:name],\n            box_provider: options[:provider],\n            box_architecture: options[:architecture],\n            box_version: options[:version],\n            box_checksum_type: options[:checksum_type],\n            box_checksum: options[:checksum],\n            box_clean: options[:clean],\n            box_force: options[:force],\n            box_download_ca_cert: options[:ca_cert],\n            box_download_ca_path: options[:ca_path],\n            box_download_client_cert: options[:client_cert],\n            box_download_insecure: options[:insecure],\n            box_download_location_trusted: options[:location_trusted],\n            ui: Vagrant::UI::Prefixed.new(@env.ui, \"box\"),\n          })\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/box/command/download_mixins.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommandBox\n    module DownloadMixins\n      # This adds common download command line flags to the given\n      # OptionParser, storing the result in the `options` dictionary.\n      #\n      # @param [OptionParser] parser\n      # @param [Hash] options\n      def build_download_options(parser, options)\n        # Add the options\n        parser.on(\"--insecure\", \"Do not validate SSL certificates\") do |i|\n          options[:insecure] = i\n        end\n\n        parser.on(\"--cacert FILE\", String, \"CA certificate for SSL download\") do |c|\n          options[:ca_cert] = c\n        end\n\n        parser.on(\"--capath DIR\", String, \"CA certificate directory for SSL download\") do |c|\n          options[:ca_path] = c\n        end\n\n        parser.on(\"--cert FILE\", String, \"A client SSL cert, if needed\") do |c|\n          options[:client_cert] = c\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/box/command/list.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandBox\n    module Command\n      class List < Vagrant.plugin(\"2\", :command)\n        def execute\n          options = {}\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant box list [options]\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"-i\", \"--box-info\", \"Displays additional information about the boxes\") do |i|\n              options[:info] = i\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n\n          boxes = @env.boxes.all\n          if boxes.empty?\n            @env.ui.warn(I18n.t(\"vagrant.commands.box.no_installed_boxes\"), prefix: false)\n            return 0\n          end\n\n          list_boxes(boxes, options[:info])\n\n          # Success, exit status 0\n          0\n        end\n\n        private\n\n        def list_boxes(boxes, extra_info)\n          # Find the longest box name\n          longest_box = boxes.max_by { |x| x[0].length }\n          longest_box_length = longest_box[0].length\n\n          # Group boxes by name and version and start iterating\n          boxes.group_by { |b| [b[0], b[1]] }.each do |box_info, box_data|\n            name, version = box_info\n            # Now group by provider so we can collect common architectures\n            box_data.group_by { |b| b[2] }.each do |provider, data|\n              architectures = data.map { |d| d.last }.compact.sort.uniq\n              meta_info = [provider, version]\n              if !architectures.empty?\n                meta_info << \"(#{architectures.join(\", \")})\"\n              end\n              @env.ui.info(\"#{name.ljust(longest_box_length)} (#{meta_info.join(\", \")})\")\n              data.each do |arch_info|\n                @env.ui.machine(\"box-name\", name)\n                @env.ui.machine(\"box-provider\", provider)\n                @env.ui.machine(\"box-version\", version)\n                @env.ui.machine(\"box-architecture\", arch_info.last || \"n/a\")\n                info_file = @env.boxes.find(name, provider, version, arch_info.last).\n                              directory.join(\"info.json\")\n                if info_file.file?\n                  info = JSON.parse(info_file.read)\n                  info.each do |k, v|\n                    @env.ui.machine(\"box-info\", k, v)\n\n                    if extra_info\n                      @env.ui.info(\"  - #{k}: #{v}\", prefix: false)\n                    end\n                  end\n                end\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/box/command/outdated.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire_relative 'download_mixins'\n\nmodule VagrantPlugins\n  module CommandBox\n    module Command\n      class Outdated < Vagrant.plugin(\"2\", :command)\n        include DownloadMixins\n\n        def execute\n          options = {}\n          download_options = {}\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant box outdated [options]\"\n            o.separator \"\"\n            o.separator \"Checks if there is a new version available for the box\"\n            o.separator \"that you are using. If you pass in the --global flag,\"\n            o.separator \"all boxes will be checked for updates.\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"--global\", \"Check all boxes installed\") do |g|\n              options[:global] = g\n            end\n\n            o.on(\"-f\", \"--force\", \"Force checks for latest box updates\") do |f|\n              options[:force] = f\n            end\n\n            build_download_options(o, download_options)\n          end\n\n          argv = parse_options(opts)\n          return if !argv\n\n          # If we're checking the boxes globally, then do that.\n          if options[:global]\n            outdated_global(download_options)\n            return 0\n          end\n\n          with_target_vms(argv) do |machine|\n            @env.action_runner.run(Vagrant::Action.action_box_outdated, {\n              box_outdated_force: options[:force],\n              box_outdated_refresh: true,\n              box_outdated_success_ui: true,\n              machine: machine,\n            }.merge(download_options))\n          end\n          return 0\n        end\n\n        def outdated_global(download_options)\n          @env.boxes.all.reverse.each do |name, version, provider|\n            box = @env.boxes.find(name, provider, version)\n            if !box&.metadata_url\n              @env.ui.output(I18n.t(\n                \"vagrant.box_outdated_no_metadata\",\n                name: name,\n                provider: provider))\n              next\n            end\n\n            md = nil\n            begin\n              md = box.load_metadata(download_options)\n            rescue Vagrant::Errors::BoxMetadataDownloadError => e\n              @env.ui.error(I18n.t(\n                \"vagrant.box_outdated_metadata_error\",\n                name: box.name,\n                provider: box.provider,\n                message: e.extra_data[:message]))\n              next\n            end\n\n            box_versions = md.versions(provider: box.provider, architecture: box.architecture)\n\n            if box_versions.empty?\n              latest_box_version = box_versions.last.to_i\n            else\n              latest_box_version = box_versions.last\n            end\n\n            if !md.compatible_version_update?(box.version, latest_box_version, provider: box.provider, architecture: box.architecture)\n              @env.ui.success(I18n.t(\n                \"vagrant.box_up_to_date\",\n                name: box.name,\n                provider: box.provider,\n                version: box.version))\n            else\n              @env.ui.warn(I18n.t(\n                \"vagrant.box_outdated\",\n                name: box.name,\n                provider: box.provider,\n                current: box.version,\n                latest: latest_box_version.to_s,))\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/box/command/prune.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandBox\n    module Command\n      class Prune < Vagrant.plugin(\"2\", :command)\n        def execute\n          options = {}\n          options[:force] = false\n          options[:dry_run] = false\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant box prune [options]\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"-p PROVIDER\", \"--provider PROVIDER\", String, \"The specific provider type for the boxes to destroy.\") do |p|\n              options[:provider] = p\n            end\n\n            o.on(\"-n\", \"--dry-run\", \"Only print the boxes that would be removed.\") do |f|\n              options[:dry_run] = f\n            end\n\n            o.on(\"--name NAME\", String, \"The specific box name to check for outdated versions.\") do |name|\n              options[:name] = name\n            end\n\n            o.on(\"-f\", \"--force\", \"Destroy without confirmation even when box is in use.\") do |f|\n              options[:force] = f\n            end\n\n            o.on(\"-k\", \"--keep-active-boxes\", \"When combined with `--force`, will keep boxes still actively in use.\") do |k|\n              options[:keep] = k\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n\n          boxes = @env.boxes.all.sort\n          if boxes.empty?\n            return @env.ui.warn(I18n.t(\"vagrant.commands.box.no_installed_boxes\"), prefix: false)\n          end\n\n          delete_oldest_boxes(boxes, options[:provider], options[:force], options[:name], options[:dry_run], options[:keep])\n\n          # Success, exit status 0\n          0\n        end\n\n        private\n\n        def delete_oldest_boxes(boxes, only_provider, skip_confirm, only_name, dry_run, keep_used_boxes)\n          # Find the longest box name\n          longest_box = boxes.max_by { |x| x[0].length }\n          longest_box_length = longest_box[0].length\n\n          # Hash map to keep track of newest versions\n          newest_boxes = Hash.new\n\n          # First find the newest version for every installed box\n          boxes.each do |name, version, provider|\n            next if only_provider and only_provider != provider.to_s\n            next if only_name and only_name != name\n\n            # Nested to make sure it works for boxes with different providers\n            if newest_boxes.has_key?(name)\n              if newest_boxes[name].has_key?(provider)\n                saved = Gem::Version.new(newest_boxes[name][provider])\n                current = Gem::Version.new(version)\n                if current > saved\n                  newest_boxes[name][provider] = version\n                end\n              else\n                newest_boxes[name][provider] = version\n              end\n            else\n              newest_boxes[name] = Hash.new\n              newest_boxes[name][provider] = version\n            end\n          end\n\n          @env.ui.info(\"The following boxes will be kept...\");\n          newest_boxes.each do |name, providers|\n            providers.each do |provider, version|\n              @env.ui.info(\"#{name.ljust(longest_box_length)} (#{provider}, #{version})\")\n\n              @env.ui.machine(\"box-name\", name)\n              @env.ui.machine(\"box-provider\", provider)\n              @env.ui.machine(\"box-version\", version)\n            end\n          end\n\n          @env.ui.info(\"\", prefix: false)\n          @env.ui.info(\"Checking for older boxes...\");\n\n          # Track if we removed anything so the user can be informed\n          removed_any_box = false\n          boxes.each do |name, version, provider|\n            next if !newest_boxes.has_key?(name) or !newest_boxes[name].has_key?(provider)\n\n            current = Gem::Version.new(version)\n            saved = Gem::Version.new(newest_boxes[name][provider])\n            if current < saved\n              removed_any_box = true\n\n              # Use the remove box action\n              if dry_run\n                @env.ui.info(\"Would remove #{name} #{provider} #{version}\")\n              else\n                @env.action_runner.run(Vagrant::Action.action_box_remove, {\n                    box_name: name,\n                    box_provider: provider,\n                    box_version: version,\n                    force_confirm_box_remove: skip_confirm,\n                    keep_used_boxes: keep_used_boxes,\n                    box_remove_all_versions: false,\n                })\n              end\n            end\n          end\n\n          if !removed_any_box\n            @env.ui.info(\"No old versions of boxes to remove...\");\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/box/command/remove.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandBox\n    module Command\n      class Remove < Vagrant.plugin(\"2\", :command)\n        def execute\n          options = {}\n          options[:force] = false\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant box remove <name>\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"-f\", \"--force\", \"Remove without confirmation.\") do |f|\n              options[:force] = f\n            end\n\n            o.on(\"-a\", \"--architecture ARCH\", String, \"The specific architecture for the box to remove\") do |a|\n              options[:architecture] = a\n            end\n            o.on(\"--provider PROVIDER\", String,\n                 \"The specific provider type for the box to remove\") do |p|\n              options[:provider] = p\n            end\n\n            o.on(\"--box-version VERSION\", String,\n                 \"The specific version of the box to remove\") do |v|\n              options[:version] = v\n            end\n\n            o.on(\"--all\", \"Remove all available versions of the box\") do |a|\n              options[:all] = a\n            end\n\n            o.on(\"--all-providers\", \"Remove all providers within a version of the box\") do |a|\n              options[:all_providers] = a\n            end\n\n            o.on(\"--all-architectures\", \"Remove all architectures within a provider a version of the box\") do |a|\n              options[:all_architectures] = a\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          if argv.empty? || argv.length > 2\n            raise Vagrant::Errors::CLIInvalidUsage,\n              help: opts.help.chomp\n          end\n\n          if argv.length == 2\n            # @deprecated\n            @env.ui.warn(\"WARNING: The second argument to `vagrant box remove`\")\n            @env.ui.warn(\"is deprecated. Please use the --provider flag. This\")\n            @env.ui.warn(\"feature will stop working in the next version.\")\n            options[:provider] = argv[1]\n          end\n\n          @env.action_runner.run(Vagrant::Action.action_box_remove, {\n            box_name:     argv[0],\n            box_architecture: options[:architecture],\n            box_provider: options[:provider],\n            box_version:  options[:version],\n            force_confirm_box_remove: options[:force],\n            box_remove_all_versions: options[:all],\n            box_remove_all_providers: options[:all_providers],\n            box_remove_all_architectures: options[:all_architectures]\n          })\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/box/command/repackage.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\nrequire 'optparse'\nrequire \"pathname\"\n\nmodule VagrantPlugins\n  module CommandBox\n    module Command\n      class Repackage < Vagrant.plugin(\"2\", :command)\n        def execute\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant box repackage <name> <provider> <version>\"\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.length != 3\n\n          box_name     = argv[0]\n          box_provider = argv[1].to_sym\n          box_version  = argv[2]\n\n          # Verify the box exists that we want to repackage\n          box = @env.boxes.find(box_name, box_provider, \"= #{box_version}\")\n          if !box\n            raise Vagrant::Errors::BoxNotFoundWithProviderAndVersion,\n              name: box_name,\n              provider: box_provider.to_s,\n              version: box_version\n          end\n\n          # Repackage the box\n          output_name = @env.vagrantfile.config.package.name || \"package.box\"\n          output_path = Pathname.new(File.expand_path(output_name, FileUtils.pwd))\n          box.repackage(output_path)\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/box/command/root.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandBox\n    module Command\n      class Root < Vagrant.plugin(\"2\", :command)\n        def self.synopsis\n          \"manages boxes: installation, removal, etc.\"\n        end\n\n        def initialize(argv, env)\n          super\n\n          @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)\n\n          @subcommands = Vagrant::Registry.new\n          @subcommands.register(:add) do\n            require File.expand_path(\"../add\", __FILE__)\n            Add\n          end\n\n          @subcommands.register(:list) do\n            require File.expand_path(\"../list\", __FILE__)\n            List\n          end\n\n          @subcommands.register(:outdated) do\n            require_relative \"outdated\"\n            Outdated\n          end\n\n          @subcommands.register(:remove) do\n            require File.expand_path(\"../remove\", __FILE__)\n            Remove\n          end\n\n          @subcommands.register(:prune) do\n            require_relative \"prune\"\n            Prune\n          end\n\n          @subcommands.register(:repackage) do\n            require File.expand_path(\"../repackage\", __FILE__)\n            Repackage\n          end\n\n          @subcommands.register(:update) do\n            require_relative \"update\"\n            Update\n          end\n        end\n\n        def execute\n          if @main_args.include?(\"-h\") || @main_args.include?(\"--help\")\n            # Print the help for all the box commands.\n            return help\n          end\n\n          # If we reached this far then we must have a subcommand. If not,\n          # then we also just print the help and exit.\n          command_class = @subcommands.get(@sub_command.to_sym) if @sub_command\n          return help if !command_class || !@sub_command\n          @logger.debug(\"Invoking command class: #{command_class} #{@sub_args.inspect}\")\n\n          # Initialize and execute the command class\n          command_class.new(@sub_args, @env).execute\n        end\n\n        # Prints the help out for this command\n        def help\n          opts = OptionParser.new do |opts|\n            opts.banner = \"Usage: vagrant box <subcommand> [<args>]\"\n            opts.separator \"\"\n            opts.separator \"Available subcommands:\"\n\n            # Add the available subcommands as separators in order to print them\n            # out as well.\n            keys = []\n            @subcommands.each { |key, value| keys << key.to_s }\n\n            keys.sort.each do |key|\n              opts.separator \"     #{key}\"\n            end\n            opts.separator \"\"\n            opts.separator \"For help on any individual subcommand run `vagrant box <subcommand> -h`\"\n          end\n\n          @env.ui.info(opts.help, prefix: false)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/box/command/update.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire_relative 'download_mixins'\n\nmodule VagrantPlugins\n  module CommandBox\n    module Command\n      class Update < Vagrant.plugin(\"2\", :command)\n        include DownloadMixins\n\n        def execute\n          options = {}\n          download_options = {}\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant box update [options]\"\n            o.separator \"\"\n            o.separator \"Updates the box that is in use in the current Vagrant environment,\"\n            o.separator \"if there any updates available. This does not destroy/recreate the\"\n            o.separator \"machine, so you'll have to do that to see changes.\"\n            o.separator \"\"\n            o.separator \"To update a specific box (not tied to a Vagrant environment), use the\"\n            o.separator \"--box flag.\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"--box BOX\", String, \"Update a specific box\") do |b|\n              options[:box] = b\n            end\n\n            o.on(\"--architecture ARCHITECTURE\", String, \"Update box with specific architecture\") do |a|\n              options[:architecture] = a\n            end\n\n            o.on(\"--provider PROVIDER\", String, \"Update box with specific provider\") do |p|\n              options[:provider] = p.to_sym\n            end\n\n            o.on(\"-f\", \"--force\", \"Overwrite an existing box if it exists\") do |f|\n              options[:force] = f\n            end\n\n            build_download_options(o, download_options)\n          end\n\n          argv = parse_options(opts)\n          return if !argv\n\n          if options[:box]\n            update_specific(options[:box], options[:provider], options[:architecture], download_options, options[:force])\n          else\n            update_vms(argv, options[:provider], download_options, options[:force])\n          end\n\n          0\n        end\n\n        def update_specific(name, provider, architecture, download_options, force)\n          box_info = Vagrant::Util::HashWithIndifferentAccess.new\n          @env.boxes.all.each do |box_name, box_version, box_provider, box_architecture|\n            next if name != box_name\n            box_info[box_provider] ||= Vagrant::Util::HashWithIndifferentAccess.new\n            box_info[box_provider][box_version] ||= []\n            box_info[box_provider][box_version].push(box_architecture.to_s).uniq!\n          end\n\n          if box_info.empty?\n            raise Vagrant::Errors::BoxNotFound, name: name.to_s\n          end\n\n          if !provider\n            if box_info.size > 1\n              raise Vagrant::Errors::BoxUpdateMultiProvider,\n                name: name.to_s,\n                providers: box_info.keys.map(&:to_s).sort.join(\", \")\n            end\n\n            provider = box_info.keys.first\n          elsif !box_info[provider]\n            raise Vagrant::Errors::BoxNotFoundWithProvider,\n              name: name.to_s,\n              provider: provider.to_s,\n              providers: box_info.keys.map(&:to_s).sort.join(\", \")\n          end\n\n          version = box_info[provider].keys.sort_by{ |v| Gem::Version.new(v) }.last\n          architecture_list = box_info[provider][version]\n\n          if !architecture\n            if architecture_list.size > 1\n              raise Vagrant::Errors::BoxUpdateMultiArchitecture,\n                name: name.to_s,\n                provider: provider.to_s,\n                version: version.to_s,\n                architectures: architecture_list.sort.join(\", \")\n            end\n\n            architecture = architecture_list.first\n          elsif !architecture_list.include?(architecture)\n            raise Vagrant::Errors::BoxNotFoundWithProviderArchitecture,\n              name: name.to_s,\n              provider: provider.to_s,\n              version: version.to_s,\n              architecture: architecture,\n              architectures: architecture_list.sort.join(\", \")\n          end\n\n          # Architecture gets cast to a string when collecting information\n          # above. Convert it back to a nil if it's empty\n          architecture = nil if architecture.to_s.empty?\n\n          box = @env.boxes.find(name, provider, version, architecture)\n          box_update(box, \"> #{version}\", @env.ui, download_options, force)\n        end\n\n        def update_vms(argv, provider, download_options, force)\n          machines = {}\n\n          with_target_vms(argv, provider: provider) do |machine|\n            if !machine.config.vm.box\n              machine.ui.output(I18n.t(\n                \"vagrant.errors.box_update_no_name\"))\n              next\n            end\n\n            if !machine.box\n              collection = Vagrant::BoxCollection.new(@env.boxes_path)\n              machine.box = collection.find(machine.config.vm.box, provider || machine.provider_name || @env.default_provider, \"> 0\")\n              if !machine.box\n                machine.ui.output(I18n.t(\n                  \"vagrant.errors.box_update_no_box\",\n                  name: machine.config.vm.box))\n                next\n              end\n            end\n\n            name     = machine.box.name\n            provider = machine.box.provider\n            version  = machine.config.vm.box_version || machine.box.version\n\n            machines[\"#{name}_#{provider}_#{version}\"] = machine\n          end\n\n          machines.each do |_, machine|\n            box = machine.box\n            version = machine.config.vm.box_version\n            # Get download options from machine configuration if not specified\n            # on the command line.\n            download_options[:ca_cert] ||= machine.config.vm.box_download_ca_cert\n            download_options[:ca_path] ||= machine.config.vm.box_download_ca_path\n            download_options[:client_cert] ||= machine.config.vm.box_download_client_cert\n            if download_options[:insecure].nil?\n              download_options[:insecure] = machine.config.vm.box_download_insecure\n            end\n\n            begin\n              box_update(box, version, machine.ui, download_options, force)\n            rescue Vagrant::Errors::BoxUpdateNoMetadata => e\n              machine.ui.warn(e)\n              next\n            end\n          end\n        end\n\n        def box_update(box, version, ui, download_options, force)\n          ui.output(I18n.t(\"vagrant.box_update_checking\", name: box.name))\n          ui.detail(\"Latest installed version: #{box.version}\")\n          ui.detail(\"Version constraints: #{version}\")\n          ui.detail(\"Provider: #{box.provider}\")\n          ui.detail(\"Architecture: #{box.architecture.inspect}\") if box.architecture\n\n          update = box.has_update?(version, download_options: download_options)\n          if !update\n            ui.success(I18n.t(\n              \"vagrant.box_up_to_date_single\",\n              name: box.name, version: box.version))\n            return\n          end\n\n          ui.output(I18n.t(\n            \"vagrant.box_updating\",\n            name: update[0].name,\n            provider: update[2].name,\n            old: box.version,\n            new: update[1].version))\n          @env.action_runner.run(Vagrant::Action.action_box_add, {\n            box_url: box.metadata_url,\n            box_provider: update[2].name,\n            box_version: update[1].version,\n            box_architecture: update[2].architecture,\n            ui: ui,\n            box_force: force,\n            box_download_client_cert: download_options[:client_cert],\n            box_download_ca_cert: download_options[:ca_cert],\n            box_download_ca_path: download_options[:ca_path],\n            box_download_insecure: download_options[:insecure]\n          })\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/box/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandBox\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"box command\"\n      description \"The `box` command gives you a way to manage boxes.\"\n\n      command(\"box\") do\n        require File.expand_path(\"../command/root\", __FILE__)\n        Command::Root\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cap/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandCap\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"checks and executes capability\"\n      end\n\n      def execute\n        options = {}\n        options[:check] = false\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant cap [options] TYPE NAME [args]\"\n          o.separator \"\"\n          o.separator \"This is an advanced command. If you don't know what this\"\n          o.separator \"does and you aren't explicitly trying to use it, you probably\"\n          o.separator \"don't want to use this.\"\n          o.separator \"\"\n          o.separator \"This command checks or executes arbitrary capabilities that\"\n          o.separator \"Vagrant has for hosts, guests, and providers.\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"--check\", \"Only checks for a capability, does not execute\") do |f|\n            options[:check] = f\n          end\n\n          # TODO: Rename this back to `target` to maintain api\n          o.on(\"-t\", \"--target-guest=TARGET\", \"Target guest to run against (if applicable)\") do |t|\n            options[:target] = t\n          end\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n        if argv.length < 2\n          raise Vagrant::Errors::CLIInvalidUsage,\n            help: opts.help.chomp\n        end\n\n        type = argv.shift.to_sym\n        name = argv.shift.to_sym\n\n        # Get the proper capability host to check\n        cap_host = nil\n        if type == :host\n          cap_host = @env.host\n        else\n          with_target_vms(options[:target] || []) do |vm|\n            cap_host = case type\n                       when :provider\n                         vm.provider\n                       when :guest\n                         vm.guest\n                       else\n                         raise Vagrant::Errors::CLIInvalidUsage,\n                           help: opts.help.chomp\n                       end\n          end\n        end\n\n        # If we're just checking, then just return exit codes\n        if options[:check]\n          return 0 if cap_host.capability?(name)\n          return 1\n        end\n\n        # Otherwise, call it\n        cap_host.capability(name, *argv)\n\n        # Success, exit status 0\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cap/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandCap\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"cap command\"\n      description <<-DESC\n      The `cap` command checks and executes arbitrary capabilities.\n      DESC\n\n      command(\"cap\", primary: false) do\n        require_relative \"command\"\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/auth/login.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module AuthCommand\n      module Command\n        class Login < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud auth login [options]\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n              o.on(\"-c\", \"--check\", \"Checks if currently logged in\") do |c|\n                options[:check] = c\n              end\n              o.on(\"-d\", \"--description DESCRIPTION\", String, \"Set description for the Vagrant Cloud token\") do |d|\n                options[:description] = d\n              end\n\n              o.on(\"-t\", \"--token TOKEN\", String, \"Set the Vagrant Cloud token\") do |t|\n                options[:token] = t\n              end\n\n              o.on(\"-u\", \"--username USERNAME_OR_EMAIL\", String, \"Vagrant Cloud username or email address\") do |l|\n                options[:login] = l\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if !argv.empty?\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            client = Client.new(@env)\n            client.username_or_email = options[:login]\n\n            # Determine what task we're actually taking based on flags\n            if options[:check]\n              return execute_check(client)\n            elsif options[:token]\n              return execute_token(client, options[:token])\n            else\n              if client.logged_in?\n                @env.ui.success(I18n.t(\"cloud_command.check_logged_in\"))\n              else\n                client_login(@env, options.slice(:login, :description))\n              end\n            end\n\n            0\n          end\n\n          def execute_check(client)\n            if client.logged_in?\n              @env.ui.success(I18n.t(\"cloud_command.check_logged_in\"))\n              return 0\n            else\n              @env.ui.error(I18n.t(\"cloud_command.check_not_logged_in\"))\n              return 1\n            end\n          end\n\n          def execute_token(client, token)\n            client.store_token(token)\n            @env.ui.success(I18n.t(\"cloud_command.token_saved\"))\n\n            if client.logged_in?\n              @env.ui.success(I18n.t(\"cloud_command.check_logged_in\"))\n              return 0\n            else\n              @env.ui.error(I18n.t(\"cloud_command.invalid_token\"))\n              return 1\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/auth/logout.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module AuthCommand\n      module Command\n        class Logout < Vagrant.plugin(\"2\", :command)\n          def execute\n            options = {}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud auth logout\"\n              o.separator \"\"\n              o.separator \"Log out of Vagrant Cloud\"\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if !argv.empty?\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            @client = Client.new(@env)\n            @client.clear_token\n            @env.ui.success(I18n.t(\"cloud_command.logged_out\"))\n            return 0\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/auth/middleware/add_authentication.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"cgi\"\nrequire \"uri\"\nrequire \"log4r\"\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/client/client\")\n\nmodule VagrantPlugins\n  module CloudCommand\n    class AddAuthentication\n      REPLACEMENT_HOSTS = [\n        \"app.vagrantup.com\".freeze,\n        \"atlas.hashicorp.com\".freeze\n      ].freeze\n      TARGET_HOST = \"vagrantcloud.com\".freeze\n      CUSTOM_HOST_NOTIFY_WAIT = 5\n\n      def self.custom_host_notified!\n        @_host_notify = true\n      end\n\n      def self.custom_host_notified?\n        if defined?(@_host_notify)\n          @_host_notify\n        else\n          false\n        end\n      end\n\n      def initialize(app, env)\n        @app = app\n        @logger = Log4r::Logger.new(\"vagrant::cloud::auth::authenticate-box-url\")\n        CloudCommand::Plugin.init!\n      end\n\n      def call(env)\n        if ENV[\"VAGRANT_SERVER_ACCESS_TOKEN_BY_URL\"]\n          @logger.warn(\"Adding access token as GET parameter by user request\")\n          client = Client.new(env[:env])\n          token  = client.token\n\n          env[:box_urls].map! do |url|\n            begin\n              u = URI.parse(url)\n              if u.host != TARGET_HOST && REPLACEMENT_HOSTS.include?(u.host)\n                u.host = TARGET_HOST\n                u.to_s\n              else\n                url\n              end\n            rescue URI::Error\n              url\n            end\n          end\n\n          server_uri = URI.parse(Vagrant.server_url.to_s)\n\n          if token && !server_uri.host.to_s.empty?\n            env[:box_urls].map! do |url|\n              begin\n                u = URI.parse(url)\n\n                if u.host == server_uri.host\n                  if server_uri.host != TARGET_HOST && !self.class.custom_host_notified?\n                    env[:ui].warn(I18n.t(\"cloud_command.middleware.authentication.different_target\",\n                      custom_host: server_uri.host, known_host: TARGET_HOST) + \"\\n\")\n                    sleep CUSTOM_HOST_NOTIFY_WAIT\n                    self.class.custom_host_notified!\n                  end\n\n                  q = CGI.parse(u.query || \"\")\n\n                  current = q[\"access_token\"]\n                  if current && current.empty?\n                    q[\"access_token\"] = token\n                  end\n\n                  u.query = URI.encode_www_form(q)\n                end\n\n                u.to_s\n              rescue URI::Error\n                url\n              end\n            end\n          end\n        else\n          env[:box_urls].map! do |url|\n            begin\n              u = URI.parse(url)\n              q = CGI.parse(u.query || \"\")\n              if !q[\"access_token\"].empty?\n                @logger.warn(\"Removing access token from URL parameter.\")\n                q.delete(\"access_token\")\n                if q.empty?\n                  u.query = nil\n                else\n                  u.query = URI.encode_www_form(q)\n                end\n                u.to_s\n              else\n                @logger.warn(\"Authentication token not found as GET parameter.\")\n                url\n              end\n            rescue URI::Error\n              url\n            end\n          end\n        end\n        @app.call(env)\n      end.freeze\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/auth/middleware/add_downloader_authentication.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"cgi\"\nrequire \"uri\"\nrequire \"vagrant/util/credential_scrubber\"\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/client/client\")\nrequire_relative \"./add_authentication\"\n\n# Similar to AddAuthentication this middleware will add authentication for interacting\n# with Vagrant cloud. It does this by adding Authentication headers to a\n# Vagrant::Util::Downloader object.\nmodule VagrantPlugins\n  module CloudCommand\n    class AddDownloaderAuthentication <  AddAuthentication\n\n      def initialize(app, env)\n        super\n        @logger = Log4r::Logger.new(\"vagrant::cloud::auth::add-download-authentication\")\n      end\n\n      def call(env)\n        if ENV[\"VAGRANT_SERVER_ACCESS_TOKEN_BY_URL\"]\n          @logger.warn(\"Authentication header not added due to user requested access token URL parameter\")\n        else\n          client = Client.new(env[:env])\n          token  = client.token\n          Vagrant::Util::CredentialScrubber.sensitive(token)\n\n          begin\n            target_url = URI.parse(env[:downloader].source)\n            if target_url.host != TARGET_HOST && REPLACEMENT_HOSTS.include?(target_url.host)\n              target_url.host = TARGET_HOST\n              env[:downloader].source = target_url.to_s\n            end\n          rescue URI::Error\n            # if there is an error, use current target_url\n          end\n\n          server_uri = URI.parse(Vagrant.server_url.to_s)\n          if token && !server_uri.host.to_s.empty?\n            if target_url.host == server_uri.host\n              if server_uri.host != TARGET_HOST && !self.class.custom_host_notified?\n                env[:ui].warn(I18n.t(\"cloud_command.middleware.authentication.different_target\",\n                  custom_host: server_uri.host, known_host: TARGET_HOST) + \"\\n\")\n                sleep CUSTOM_HOST_NOTIFY_WAIT\n                self.class.custom_host_notified!\n              end\n\n              if Array(env[:downloader].headers).any? { |h| h.include?(\"Authorization\") }\n                @logger.info(\"Not adding an authentication header, one already found\")\n              else\n                env[:downloader].headers << \"Authorization: Bearer #{token}\"\n              end\n            else\n              @logger.debug(\"Not adding authentication header, host mismatch #{target_url.host} != #{server_uri.host}\")\n            end\n\n            env[:downloader]\n          end\n        end\n\n        @app.call(env)\n      end.freeze\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/auth/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CloudCommand\n    module AuthCommand\n      class Plugin < Vagrant.plugin(\"2\")\n        name \"vagrant cloud auth\"\n        description <<-DESC\n        Authorization commands for Vagrant Cloud\n        DESC\n\n        command(:auth) do\n          require_relative \"root\"\n          Command::Root\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/auth/root.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CloudCommand\n    module AuthCommand\n      module Command\n        class Root < Vagrant.plugin(\"2\", :command)\n          def self.synopsis\n            \"Manages Vagrant Cloud authorization related to Vagrant Cloud\"\n          end\n\n          def initialize(argv, env)\n            super\n\n            @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)\n            @subcommands = Vagrant::Registry.new\n            @subcommands.register(:login) do\n              require File.expand_path(\"../login\", __FILE__)\n              Command::Login\n            end\n            @subcommands.register(:logout) do\n              require File.expand_path(\"../logout\", __FILE__)\n              Command::Logout\n            end\n            @subcommands.register(:whoami) do\n              require File.expand_path(\"../whoami\", __FILE__)\n              Command::Whoami\n            end\n          end\n\n          def execute\n            if @main_args.include?(\"-h\") || @main_args.include?(\"--help\")\n              # Print the help for all the box commands.\n              return help\n            end\n\n            # If we reached this far then we must have a subcommand. If not,\n            # then we also just print the help and exit.\n            command_class = @subcommands.get(@sub_command.to_sym) if @sub_command\n            return help if !command_class || !@sub_command\n            @logger.debug(\"Invoking command class: #{command_class} #{@sub_args.inspect}\")\n\n            # Initialize and execute the command class\n            command_class.new(@sub_args, @env).execute\n          end\n\n          # Prints the help out for this command\n          def help\n            opts = OptionParser.new do |opts|\n              opts.banner = \"Usage: vagrant cloud auth <subcommand> [<args>]\"\n              opts.separator \"\"\n              opts.separator \"Authorization with Vagrant Cloud\"\n              opts.separator \"\"\n              opts.separator \"Available subcommands:\"\n\n              # Add the available subcommands as separators in order to print them\n              # out as well.\n              keys = []\n              @subcommands.each { |key, value| keys << key.to_s }\n\n              keys.sort.each do |key|\n                opts.separator \"     #{key}\"\n              end\n              opts.separator \"\"\n              opts.separator \"For help on any individual subcommand run `vagrant cloud auth <subcommand> -h`\"\n            end\n\n            @env.ui.info(opts.help, prefix: false)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/auth/whoami.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module AuthCommand\n      module Command\n        class Whoami < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud auth whoami [token]\"\n              o.separator \"\"\n              o.separator \"Display currently logged in user\"\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.size > 1\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            if argv.first\n              token = argv.first\n            else\n              client = Client.new(@env)\n              token = client.token\n            end\n\n            whoami(token)\n          end\n\n          def whoami(access_token)\n            if access_token.to_s.empty?\n              @env.ui.error(I18n.t(\"cloud_command.check_not_logged_in\"))\n              return 1\n            end\n            begin\n              account = VagrantCloud::Account.new(\n                custom_server: api_server_url,\n                access_token: access_token\n              )\n              @env.ui.success(\"Currently logged in as #{account.username}\")\n              return 0\n            rescue VagrantCloud::Error::ClientError => e\n              @env.ui.error(I18n.t(\"cloud_command.errors.whoami.read_error\"))\n              @env.ui.error(e)\n              return 1\n            end\n            return 1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/box/create.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module BoxCommand\n      module Command\n        class Create < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud box create [options] organization/box-name\"\n              o.separator \"\"\n              o.separator \"Creates an empty box entry on Vagrant Cloud\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n\n              o.on(\"-d\", \"--description DESCRIPTION\", String, \"Full description of the box\") do |d|\n                options[:description] = d\n              end\n              o.on(\"-s\", \"--short-description DESCRIPTION\", String, \"Short description of the box\") do |s|\n                options[:short] = s\n              end\n              o.on(\"-p\", \"--[no-]private\", \"Makes box private\") do |p|\n                options[:private] = p\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.empty? || argv.length > 1\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            @client = client_login(@env)\n\n            org, box_name = argv.first.split('/', 2)\n            create_box(org, box_name, @client.token, options.slice(:description, :short, :private))\n          end\n\n          # Create a new box\n          #\n          # @param [String] org Organization name of box\n          # @param [String] box_name Name of box\n          # @param [String] access_token User access token\n          # @param [Hash] options Options for box filtering\n          # @option options [String] :short Short description of box\n          # @option options [String] :description Full description of box\n          # @option options [Boolean] :private Set box visibility as private\n          # @return [Integer]\n          def create_box(org, box_name, access_token, options={})\n            account = VagrantCloud::Account.new(\n              custom_server: api_server_url,\n              access_token: access_token\n            )\n            box = account.organization(name: org).add_box(box_name)\n            box.short_description = options[:short] if options.key?(:short)\n            box.description = options[:description] if options.key?(:description)\n            box.private = options[:private] if options.key?(:private)\n            box.save\n\n            @env.ui.success(I18n.t(\"cloud_command.box.create_success\", org: org, box_name: box_name))\n            format_box_results(box, @env)\n            0\n          rescue VagrantCloud::Error => e\n            @env.ui.error(I18n.t(\"cloud_command.errors.box.create_fail\", org: org, box_name: box_name))\n            @env.ui.error(e.message)\n            1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/box/delete.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module BoxCommand\n      module Command\n        class Delete < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud box delete [options] organization/box-name\"\n              o.separator \"\"\n              o.separator \"Deletes box entry on Vagrant Cloud\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n\n              o.on(\"-f\", \"--[no-]force\", \"Do not prompt for deletion confirmation\") do |f|\n                options[:force] = f\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.empty? || argv.length > 1\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            if !options[:force]\n              @env.ui.warn(I18n.t(\"cloud_command.box.delete_warn\", box: argv.first))\n              cont = @env.ui.ask(I18n.t(\"cloud_command.continue\"))\n              return 1 if cont.strip.downcase != \"y\"\n            end\n\n            @client = client_login(@env)\n\n            org, box_name = argv.first.split('/', 2)\n            delete_box(org, box_name, @client.token)\n          end\n\n          # Delete the requested box\n          #\n          # @param [String] org Organization name of box\n          # @param [String] box_name Name of box\n          # @param [String] access_token User access token\n          # @return [Integer]\n          def delete_box(org, box_name, access_token)\n            account = VagrantCloud::Account.new(\n              custom_server: api_server_url,\n              access_token: access_token\n            )\n            with_box(account: account, org: org, box: box_name) do |box|\n              box.delete\n              @env.ui.success(I18n.t(\"cloud_command.box.delete_success\", org: org, box_name: box_name))\n              0\n            end\n          rescue VagrantCloud::Error => e\n            @env.ui.error(I18n.t(\"cloud_command.errors.box.delete_fail\", org: org, box_name: box_name))\n            @env.ui.error(e.message)\n            1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/box/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CloudCommand\n    module BoxCommand\n      class Plugin < Vagrant.plugin(\"2\")\n        name \"vagrant cloud box\"\n        description <<-DESC\n        Box life cycle commands for Vagrant Cloud\n        DESC\n\n        command(:box) do\n          require_relative \"root\"\n          Command::Root\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/box/root.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CloudCommand\n    module BoxCommand\n      module Command\n        class Root < Vagrant.plugin(\"2\", :command)\n          def self.synopsis\n            \"Commands to manage boxes on Vagrant Cloud\"\n          end\n\n          def initialize(argv, env)\n            super\n\n            @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)\n            @subcommands = Vagrant::Registry.new\n            @subcommands.register(:create) do\n              require File.expand_path(\"../create\", __FILE__)\n              Command::Create\n            end\n            @subcommands.register(:delete) do\n              require File.expand_path(\"../delete\", __FILE__)\n              Command::Delete\n            end\n            @subcommands.register(:show) do\n              require File.expand_path(\"../show\", __FILE__)\n              Command::Show\n            end\n            @subcommands.register(:update) do\n              require File.expand_path(\"../update\", __FILE__)\n              Command::Update\n            end\n          end\n\n          def execute\n            if @main_args.include?(\"-h\") || @main_args.include?(\"--help\")\n              # Print the help for all the box commands.\n              return help\n            end\n\n            # If we reached this far then we must have a subcommand. If not,\n            # then we also just print the help and exit.\n            command_class = @subcommands.get(@sub_command.to_sym) if @sub_command\n            return help if !command_class || !@sub_command\n            @logger.debug(\"Invoking command class: #{command_class} #{@sub_args.inspect}\")\n\n            # Initialize and execute the command class\n            command_class.new(@sub_args, @env).execute\n          end\n\n          # Prints the help out for this command\n          def help\n            opts = OptionParser.new do |opts|\n              opts.banner = \"Usage: vagrant cloud box <subcommand> [<args>]\"\n              opts.separator \"\"\n              opts.separator \"Commands to manage boxes on Vagrant Cloud\"\n              opts.separator \"\"\n              opts.separator \"Available subcommands:\"\n\n              # Add the available subcommands as separators in order to print them\n              # out as well.\n              keys = []\n              @subcommands.each { |key, value| keys << key.to_s }\n\n              keys.sort.each do |key|\n                opts.separator \"     #{key}\"\n              end\n              opts.separator \"\"\n              opts.separator \"For help on any individual subcommand run `vagrant cloud box <subcommand> -h`\"\n            end\n\n            @env.ui.info(opts.help, prefix: false)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/box/show.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module BoxCommand\n      module Command\n        class Show < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {\n              architectures: [],\n              providers: [],\n              quiet: true,\n              versions: [],\n            }\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud box show [options] organization/box-name\"\n              o.separator \"\"\n              o.separator \"Displays a boxes attributes on Vagrant Cloud\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n\n              o.on(\"--architectures ARCH\", String, \"Filter results by architecture support (can be defined multiple times)\") do |a|\n                options[:architectures].push(a).uniq!\n              end\n              o.on(\"--versions VERSION\", String, \"Display box information for a specific version (can be defined multiple times)\") do |v|\n                options[:versions].push(v).uniq!\n              end\n              o.on(\"--providers PROVIDER\", String, \"Filter results by provider support (can be defined multiple times)\") do |pv|\n                options[:providers].push(pv).uniq!\n              end\n              o.on(\"--[no-]auth\", \"Authenticate with Vagrant Cloud if required before searching\") do |l|\n                options[:quiet] = !l\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.empty? || argv.length > 1\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            @client = client_login(@env, options.slice(:quiet))\n            org, box_name = argv.first.split('/', 2)\n\n            show_box(org, box_name, @client&.token, options.slice(:architectures, :providers, :versions))\n          end\n\n          # Display the requested box to the user\n          #\n          # @param [String] org Organization name of box\n          # @param [String] box_name Name of box\n          # @param [String] access_token User access token\n          # @param [Hash] options Options for box filtering\n          # @option options [String] :versions Specific verisons of box\n          # @return [Integer]\n          def show_box(org, box_name, access_token, options={})\n            account = VagrantCloud::Account.new(\n              custom_server: api_server_url,\n              access_token: access_token\n            )\n            with_box(account: account, org: org, box: box_name) do |box|\n              list = [box]\n\n              # If specific version(s) provided, filter out the version\n              list = list.first.versions.find_all { |v|\n                options[:versions].include?(v.version)\n              } if !Array(options[:versions]).empty?\n\n              # If specific provider(s) provided, filter out the provider(s)\n              list = list.find_all { |item|\n                if item.is_a?(VagrantCloud::Box)\n                  item.versions.any? { |v|\n                    v.providers.any? { |p|\n                      options[:providers].include?(p.name)\n                    }\n                  }\n                else\n                  item.providers.any? { |p|\n                    options[:providers].include?(p.name)\n                  }\n                end\n              } if !Array(options[:providers]).empty?\n\n              list = list.find_all { |item|\n                if item.is_a?(VagrantCloud::Box)\n                  item.versions.any? { |v|\n                    v.providers.any? { |p|\n                      options[:architectures].include?(p.architecture)\n                    }\n                  }\n                else\n                  item.providers.any? { |p|\n                    options[:architectures].include?(p.architecture)\n                  }\n                end\n              } if !Array(options[:architectures]).empty?\n\n              if !list.empty?\n                list.each do |b|\n                  format_box_results(b, @env, options.slice(:providers, :architectures))\n                  @env.ui.output(\"\")\n                end\n                0\n              else\n                @env.ui.warn(I18n.t(\"cloud_command.box.show_filter_empty\",\n                  org: org,\n                  box_name: box_name,\n                  architectures: Array(options[:architectures]).empty? ? \"N/A\" : Array(options[:architectures]).join(\", \"),\n                  providers: Array(options[:providers]).empty? ? \"N/A\" : Array(options[:providers]).join(\", \"),\n                  versions: Array(options[:versions]).empty? ? \"N/A\" : Array(options[:versions]).join(\", \")\n                ))\n                1\n              end\n            end\n          rescue VagrantCloud::Error => e\n            @env.ui.error(I18n.t(\"cloud_command.errors.box.show_fail\", org: org, box_name:box_name))\n            @env.ui.error(e.message)\n            1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/box/update.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module BoxCommand\n      module Command\n        class Update < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud box update [options] organization/box-name\"\n              o.separator \"\"\n              o.separator \"Updates a box entry on Vagrant Cloud\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n\n              o.on(\"-d\", \"--description DESCRIPTION\", \"Full description of the box\") do |d|\n                options[:description] = d\n              end\n              o.on(\"-s\", \"--short-description DESCRIPTION\", \"Short description of the box\") do |s|\n                options[:short] = s\n              end\n              o.on(\"-p\", \"--[no-]private\", \"Makes box private\") do |p|\n                options[:private] = p\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.empty? || argv.length > 1 || options.slice(:description, :short, :private).length == 0\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            @client = client_login(@env)\n            org, box_name = argv.first.split('/', 2)\n\n            update_box(org, box_name, @client.token, options.slice(:short, :description, :private))\n          end\n\n          # Update an existing box\n          #\n          # @param [String] org Organization name of box\n          # @param [String] box_name Name of box\n          # @param [String] access_token User access token\n          # @param [Hash] options Options for box filtering\n          # @option options [String] :short Short description of box\n          # @option options [String] :description Full description of box\n          # @option options [Boolean] :private Set box visibility as private\n          # @return [Integer]\n          def update_box(org, box_name, access_token, options={})\n            account = VagrantCloud::Account.new(\n              custom_server: api_server_url,\n              access_token: access_token\n            )\n            with_box(account: account, org: org, box: box_name) do |box|\n              box.short_description = options[:short] if options.key?(:short)\n              box.description = options[:description] if options.key?(:description)\n              box.private = options[:private] if options.key?(:private)\n              box.save\n              @env.ui.success(I18n.t(\"cloud_command.box.update_success\", org: org, box_name: box_name))\n              format_box_results(box, @env)\n              0\n            end\n          rescue VagrantCloud::Error => e\n            @env.ui.error(I18n.t(\"cloud_command.errors.box.update_fail\", org: org, box_name: box_name))\n            @env.ui.error(e.message)\n            1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/client/client.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant_cloud\"\nrequire \"vagrant/util/downloader\"\nrequire \"vagrant/util/presence\"\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/errors\")\n\nmodule VagrantPlugins\n  module CloudCommand\n    class Client\n      # @private\n      # Reset the cached values for scrubber. This is not considered a public\n      # API and should only be used for testing.\n      def self.reset!\n        class_variables.each(&method(:remove_class_variable))\n      end\n\n      ######################################################################\n      # Class that deals with managing users 'local' token for Vagrant Cloud\n      ######################################################################\n      APP = \"app\".freeze\n\n      include Util\n      include Vagrant::Util::Presence\n\n      attr_accessor :client\n      attr_accessor :username_or_email\n      attr_accessor :password\n      attr_reader :two_factor_default_delivery_method\n      attr_reader :two_factor_delivery_methods\n\n      # Initializes a login client with the given Vagrant::Environment.\n      #\n      # @param [Vagrant::Environment] env\n      def initialize(env)\n        @logger = Log4r::Logger.new(\"vagrant::cloud::client\")\n        @env    = env\n        if !defined?(@@client)\n          @@client = VagrantCloud::Client.new(\n            access_token: token,\n            url_base: api_server_url\n          )\n        end\n        @client = @@client\n      end\n\n      # Removes the token, effectively logging the user out.\n      def clear_token\n        @logger.info(\"Clearing token\")\n        token_path.delete if token_path.file?\n      end\n\n      # Checks if the user is logged in by verifying their authentication\n      # token.\n      #\n      # @return [Boolean]\n      def logged_in?\n        return false if !client&.access_token\n\n        Vagrant::Util::CredentialScrubber.sensitive(client.access_token)\n\n        with_error_handling do\n          client.authentication_token_validate\n          true\n        end\n      rescue Errors::Unauthorized\n        false\n      end\n\n      # Login logs a user in and returns the token for that user. The token\n      # is _not_ stored unless {#store_token} is called.\n      #\n      # @param [String] description\n      # @param [String] code\n      # @return [String] token The access token, or nil if auth failed.\n      def login(description: nil, code: nil)\n        @logger.info(\"Logging in '#{username_or_email}'\")\n\n        Vagrant::Util::CredentialScrubber.sensitive(password)\n        with_error_handling do\n          r = client.authentication_token_create(username: username_or_email,\n            password: password, description: description, code: code)\n\n          Vagrant::Util::CredentialScrubber.sensitive(r[:token])\n          @client = VagrantCloud::Client.new(\n            access_token: r[:token],\n            url_base: api_server_url\n          )\n          r[:token]\n        end\n      end\n\n      # Requests a 2FA code\n      # @param [String] delivery_method\n      def request_code(delivery_method)\n        @env.ui.warn(\"Requesting 2FA code via #{delivery_method.upcase}...\")\n\n        Vagrant::Util::CredentialScrubber.sensitive(password)\n        with_error_handling do\n          r = client.authentication_request_2fa_code(\n            username: username_or_email, password: password, delivery_method: delivery_method)\n\n          two_factor = r[:two_factor]\n          obfuscated_destination = two_factor[:obfuscated_destination]\n\n          @env.ui.success(\"2FA code sent to #{obfuscated_destination}.\")\n        end\n      end\n\n      # Stores the given token locally, removing any previous tokens.\n      #\n      # @param [String] token\n      def store_token(token)\n        Vagrant::Util::CredentialScrubber.sensitive(token)\n        @logger.info(\"Storing token in #{token_path}\")\n\n        token_path.open(\"w\") do |f|\n          f.write(token)\n        end\n\n        # Reset after we store the token since this is now _our_ token\n        @client = VagrantCloud::Client.new(access_token: token, url_base: api_server_url)\n\n        nil\n      end\n\n      # Reads the access token if there is one. This will first read the\n      # `VAGRANT_CLOUD_TOKEN` environment variable and then fallback to the stored\n      # access token on disk.\n      #\n      # @return [String]\n      def token\n        # If the client is defined, use the client for the access token\n        # to allow proper token generation if required\n        return client.access_token if client && !client.access_token.nil?\n\n        if present?(ENV[\"VAGRANT_CLOUD_TOKEN\"]) && token_path.exist?\n          # Only show warning if it has not been previously shown\n          if !defined?(@@double_token_warning)\n            @env.ui.warn <<-EOH.strip\nVagrant detected both the VAGRANT_CLOUD_TOKEN environment variable and a Vagrant login\ntoken are present on this system. The VAGRANT_CLOUD_TOKEN environment variable takes\nprecedence over the locally stored token. To remove this error, either unset\nthe VAGRANT_CLOUD_TOKEN environment variable or remove the login token stored on disk:\n\n    ~/.vagrant.d/data/vagrant_login_token\n\nEOH\n            @@double_token_warning = true\n          end\n        end\n\n        if present?(ENV[\"VAGRANT_CLOUD_TOKEN\"])\n          @logger.debug(\"Using authentication token from environment variable\")\n          t = ENV[\"VAGRANT_CLOUD_TOKEN\"]\n        elsif token_path.exist?\n          @logger.debug(\"Using authentication token from disk at #{token_path}\")\n          t = token_path.read.strip\n        elsif present?(ENV[\"ATLAS_TOKEN\"])\n          @logger.warn(\"ATLAS_TOKEN detected within environment. Using ATLAS_TOKEN in place of VAGRANT_CLOUD_TOKEN.\")\n          t = ENV[\"ATLAS_TOKEN\"]\n        end\n\n        if !t.nil?\n          Vagrant::Util::CredentialScrubber.sensitive(t)\n          return t\n        end\n\n        @logger.debug(\"No authentication token in environment or #{token_path}\")\n\n        nil\n      end\n\n      protected\n\n      def with_error_handling(&block)\n        yield\n      rescue VagrantCloud::Error::ClientError => e\n        @logger.debug(\"vagrantcloud request error:\")\n        @logger.debug(e.message)\n        @logger.debug(e.backtrace.join(\"\\n\"))\n        raise Errors::Unexpected, error: e.message\n      rescue Excon::Error::Unauthorized\n        @logger.debug(\"Unauthorized!\")\n        raise Errors::Unauthorized\n      rescue Excon::Error::BadRequest => e\n        @logger.debug(\"Bad request:\")\n        @logger.debug(e.message)\n        @logger.debug(e.backtrace.join(\"\\n\"))\n        parsed_response = JSON.parse(e.response.body)\n        errors = parsed_response[\"errors\"].join(\"\\n\")\n        raise Errors::ServerError, errors: errors\n      rescue Excon::Error::NotAcceptable => e\n        @logger.debug(\"Got unacceptable response:\")\n        @logger.debug(e.message)\n        @logger.debug(e.backtrace.join(\"\\n\"))\n\n        parsed_response = JSON.parse(e.response.body)\n\n        if two_factor = parsed_response['two_factor']\n          store_two_factor_information two_factor\n\n          if two_factor_default_delivery_method != APP\n            request_code two_factor_default_delivery_method\n          end\n\n          raise Errors::TwoFactorRequired\n        end\n\n        begin\n          errors = parsed_response[\"errors\"].join(\"\\n\")\n          raise Errors::ServerError, errors: errors\n        rescue JSON::ParserError; end\n\n        @logger.debug(\"Got an unexpected error:\")\n        @logger.debug(e.inspect)\n        raise Errors::Unexpected, error: e.inspect\n      rescue SocketError\n        @logger.info(\"Socket error\")\n        raise Errors::ServerUnreachable, url: Vagrant.server_url.to_s\n      end\n\n      def token_path\n        @env.data_dir.join(\"vagrant_login_token\")\n      end\n\n      def store_two_factor_information(two_factor)\n        @two_factor_default_delivery_method =\n          two_factor['default_delivery_method']\n\n        @two_factor_delivery_methods =\n          two_factor['delivery_methods']\n\n        @env.ui.warn \"2FA is enabled for your account.\"\n        if two_factor_default_delivery_method == APP\n          @env.ui.info \"Enter the code from your authenticator.\"\n        else\n          @env.ui.info \"Default method is \" \\\n            \"'#{two_factor_default_delivery_method}'.\"\n        end\n\n        other_delivery_methods =\n          two_factor_delivery_methods - [APP]\n\n        if other_delivery_methods.any?\n          other_delivery_methods_sentence = other_delivery_methods\n            .map { |word| \"'#{word}'\" }\n            .join(' or ')\n          @env.ui.info \"You can also type #{other_delivery_methods_sentence} \" \\\n            \"to request a new code.\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CloudCommand\n    module Errors\n      class Error < Vagrant::Errors::VagrantError\n        error_namespace(\"cloud_command.errors\")\n      end\n\n      class ServerError < Error\n        error_key(:server_error)\n      end\n\n      class ServerUnreachable < Error\n        error_key(:server_unreachable)\n      end\n\n      class Unauthorized < Error\n        error_key(:unauthorized)\n      end\n\n      class Unexpected < Error\n        error_key(:unexpected_error)\n      end\n\n      class TwoFactorRequired < Error\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/list.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module Command\n      class List < Vagrant.plugin(\"2\", :command)\n        include Util\n\n        def execute\n          options = {}\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant cloud list [options] organization\"\n            o.separator \"\"\n            o.separator \"Search for boxes managed by a specific user/organization\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"-j\", \"--json\", \"Formats results in JSON\") do |j|\n              options[:check] = j\n            end\n            o.on(\"-l\", \"--limit\", Integer, \"Max number of search results (default is 25)\") do |l|\n              options[:check] = l\n            end\n            o.on(\"-p\", \"--provider\", \"Comma separated list of providers to filter search. Defaults to all.\") do |p|\n              options[:check] = p\n            end\n            o.on(\"-s\", \"--sort-by\", \"Column to sort list (created, downloads, updated)\") do |s|\n              options[:check] = s\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          if argv.length > 1\n            raise Vagrant::Errors::CLIInvalidUsage,\n              help: opts.help.chomp\n          end\n\n          @client = client_login(@env)\n\n          # TODO: This endpoint is not implemented yet\n\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/locales/en.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  cloud_command:\n    middleware:\n      authentication:\n        different_target: |-\n          Vagrant has detected a custom Vagrant server in use for downloading\n          box files. An authentication token is currently set which will be\n          added to the box request. If the custom Vagrant server should not\n          be receiving the authentication token, please unset it.\n\n            Known Vagrant server:  %{known_host}\n            Custom Vagrant server: %{custom_host}\n\n          Press ctrl-c to cancel...\n    publish:\n      box_save:\n        Saving box information...\n      upload_provider:\n        Uploading provider with file %{file}\n      release:\n        Releasing box...\n      complete:\n        Complete! Published %{org}/%{box_name}\n      confirm:\n        warn: |-\n          You are about to publish a box on Vagrant Cloud with the following options:\n        box: |-\n          %{org}/%{box_name}:   (v%{version}) for provider '%{provider_name}'\n        private: |-\n            Private:               true\n        release: |-\n            Automatic Release:     true\n        box_url: |-\n            Remote Box file:       %{url}\n        box_description: |-\n            Box Description:       %{description}\n        box_short_desc: |-\n            Box Short Description: %{short_description}\n        checksum_type: |-\n            Checksum Type:         %{checksum_type}\n        checksum_value: |-\n            Checksum Value:        %{checksum_value}\n        architecture: |-\n            Box Architecture:      %{architecture}\n        default_architecture: |-\n            Default Architecture:  true\n        version_desc: |-\n            Version Description:   %{version_description}\n    continue: |-\n      Do you wish to continue? [y/N]\n    box:\n      show_filter_empty: |-\n        No matches found for %{org}/%{box_name}\n        Filters applied:\n          Architectures: %{architectures}\n          Providers:     %{providers}\n          Versions:      %{versions}\n      create_success: |-\n        Created box %{org}/%{box_name}\n      delete_success: |-\n        Deleted box %{org}/%{box_name}\n      delete_warn: |-\n        This will completely remove %{box} from Vagrant Cloud. This cannot be undone.\n      update_success: |-\n        Updated box %{org}/%{box_name}\n      not_found: |-\n        Failed to locate requested box: %{org}/%{box_name}\n    search:\n      no_results: |-\n        No results found for `%{query}`\n    upload:\n      no_url: |-\n        No URL was provided to upload the provider\n        You will need to run the `vagrant cloud provider upload` command to provide a box\n    provider:\n      upload: |-\n        Uploading box file for '%{org}/%{box_name}' (v%{version}) for provider: '%{provider}'\n      upload_success: |-\n        Uploaded provider %{provider} on %{org}/%{box_name} for version %{version}\n      delete_multiple_architectures: |-\n        Multiple architectures detected for %{provider} on %{org}/%{box_name}:\n\n      delete_architectures_prompt: |-\n        Please enter the architecture name to delete:\n      delete_warn: |-\n        This will completely remove provider %{provider} with architecture %{architecture}\n        on version %{version} from %{box} on Vagrant Cloud. This cannot be undone.\n      create_success: |-\n        Created provider %{provider} with %{architecture} architecture on %{org}/%{box_name} for version %{version}\n      delete_success: |-\n        Deleted provider %{provider} with %{architecture} architecture on %{org}/%{box_name}\n        for version %{version}\n      update_success: |-\n        Updated provider %{provider} on %{org}/%{box_name} for version %{version}\n      not_found: |-\n        Failed to locate %{provider_name} provider for %{org}/%{box_name} on version %{version}\n      direct_disable: |-\n        Vagrant is automatically disabling direct upload to backend storage.\n        Uploads directly to backend storage are currently only supported for\n        files 5G in size or smaller. Box file to upload is: %{size}\n    version:\n      create_success: |-\n        Created version %{version} on %{org}/%{box_name} for version %{version}\n      delete_success: |-\n        Deleted version %{version} on %{org}/%{box_name}\n      release_success: |-\n        Released version %{version} on %{org}/%{box_name}\n      revoke_success: |-\n        Revoked version %{version} on %{org}/%{box_name}\n      update_success: |-\n        Updated version %{version} on %{org}/%{box_name}\n      revoke_warn: |-\n        This will revoke version %{version} from %{box} from Vagrant Cloud. This cannot be undone.\n      release_warn: |-\n        This will release version %{version} from %{box} to Vagrant Cloud and be available to download.\n      delete_warn: |-\n        This will completely remove version %{version} from %{box} from Vagrant Cloud. This cannot be undone.\n      not_found: |-\n        Failed to locate version %{version} for %{org}/%{box_name}\n    errors:\n      search:\n        fail: |-\n          Could not complete search request\n      publish:\n        fail: |-\n          Failed to create box %{org}/%{box_name}\n      box:\n        create_fail: |-\n          Failed to create box %{org}/%{box_name}\n        delete_fail: |-\n          Failed to delete box %{org}/%{box_name}\n        show_fail: |-\n          Could not get information about box %{org}/%{box_name}\n        update_fail: |-\n          Failed to update box %{org}/%{box_name}\n      whoami:\n        read_error: |-\n          Failed to locate account information\n      provider:\n        create_fail: |-\n          Failed to create '%{architecture}' variant of %{provider} provider for version %{version} of the %{org}/%{box_name} box\n        update_fail: |-\n          Failed to update '%{architecture}' variant of %{provider} provider for version %{version} of the %{org}/%{box_name} box\n        delete_fail: |-\n          Failed to delete '%{architecture}' variant of %{provider} provider for version %{version} of the %{org}/%{box_name} box\n        upload_fail: |-\n          Failed to upload '%{architecture}' variant of %{provider} provider for version %{version} of the %{org}/%{box_name} box\n      version:\n        create_fail: |-\n          Failed to create version %{version} on box %{org}/%{box_name}\n        delete_fail: |-\n          Failed to delete version %{version} on box %{org}/%{box_name}\n        release_fail: |-\n          Failed to release version %{version} on box %{org}/%{box_name}\n        revoke_fail: |-\n          Failed to revoke version %{version} on box %{org}/%{box_name}\n        update_fail: |-\n          Failed to update version %{version} on box %{org}/%{box_name}\n      server_error: |-\n        The Vagrant Cloud server responded with a not-OK response:\n\n        %{errors}\n      server_unreachable: |-\n        The Vagrant Cloud server is not currently accepting connections. Please check\n        your network connection and try again later.\n\n      unauthorized: |-\n        Invalid username or password. Please try again.\n      unexpected_error: |-\n        An unexpected error occurred: %{error}\n\n    check_logged_in: |-\n      You are already logged in.\n    check_not_logged_in: |-\n      You are not currently logged in. Please run `vagrant login` and provide\n      your login information to authenticate.\n    command_header: |-\n      In a moment we will ask for your username and password to HashiCorp's\n      Vagrant Cloud. After authenticating, we will store an access token locally on\n      disk. Your login details will be transmitted over a secure connection, and\n      are never stored on disk locally.\n\n      If you do not have an Vagrant Cloud account, sign up at\n      https://www.vagrantcloud.com\n    invalid_login: |-\n      Invalid username or password. Please try again.\n    invalid_token: |-\n      Invalid token. Please try again.\n    logged_in: |-\n      You are now logged in.\n    logged_out: |-\n      You are logged out.\n    token_saved: |-\n      The token was successfully saved.\n"
  },
  {
    "path": "plugins/commands/cloud/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant_cloud'\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/util\")\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/client/client\")\n\nmodule VagrantPlugins\n  module CloudCommand\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"vagrant-cloud\"\n      description <<-DESC\n      Provides the cloud command and internal API access to Vagrant Cloud.\n      DESC\n\n      command(:cloud) do\n        require_relative \"root\"\n        init!\n        Command::Root\n      end\n\n      action_hook(:cloud_authenticated_boxes, :authenticate_box_url) do |hook|\n        require_relative \"auth/middleware/add_authentication\"\n        hook.prepend(AddAuthentication)\n      end\n\n      action_hook(:cloud_authenticated_boxes, :authenticate_box_downloader) do |hook|\n        require_relative \"auth/middleware/add_downloader_authentication\"\n        hook.prepend(AddDownloaderAuthentication)\n      end\n\n      protected\n\n      def self.init!\n        # Set this to match Vagant logging level so we get\n        # desired request/response information within the\n        # logger output\n        ENV[\"VAGRANT_CLOUD_LOG\"] = Vagrant.log_level\n        \n        return if defined?(@_init)\n        I18n.load_path << File.expand_path(\"../locales/en.yml\", __FILE__)\n        I18n.reload!\n        @_init = true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/provider/create.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module ProviderCommand\n      module Command\n        class Create < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {\n              architecture: Vagrant::Util::Platform.architecture,\n            }\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud provider create [options] organization/box-name provider-name version [url]\"\n              o.separator \"\"\n              o.separator \"Creates a provider entry on Vagrant Cloud\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n\n              o.on(\"-a\", \"--architecture ARCH\", String, \"Architecture of guest box (defaults to current host architecture)\") do |a|\n                options[:architecture] = a\n              end\n              o.on(\"-c\", \"--checksum CHECKSUM_VALUE\", String, \"Checksum of the box for this provider. --checksum-type option is required.\") do |c|\n                options[:checksum] = c\n              end\n              o.on(\"-C\", \"--checksum-type TYPE\", String, \"Type of checksum used (md5, sha1, sha256, sha384, sha512). --checksum option is required.\") do |c|\n                options[:checksum_type] = c\n              end\n              o.on(\"--[no-]default-architecture\", \"Mark as default architecture for specific provider\") do |d|\n                options[:default_architecture] = d\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.count < 3 || argv.count > 4\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            @client = client_login(@env)\n\n            org, box_name = argv.first.split('/', 2)\n            provider_name = argv[1]\n            version = argv[2]\n            url = argv[3]\n\n            create_provider(org, box_name, version, provider_name, url, @client.token, options)\n          end\n\n          # Create a provider for the box version\n          #\n          # @param [String] org Organization name\n          # @param [String] box Box name\n          # @param [String] version Box version\n          # @param [String] provider Provider name\n          # @param [String] url Provider asset URL\n          # @param [String] access_token User Vagrant Cloud access token\n          # @param [Hash] options\n          # @option options [String] :architecture Architecture of guest box\n          # @option options [String] :checksum Checksum of the box asset\n          # @option options [String] :checksum_type Type of the checksum\n          # @option options [Boolean] :default_architecture Default architecture for named provider\n          # @return [Integer]\n          def create_provider(org, box, version, provider, url, access_token, options={})\n            if !url\n              @env.ui.warn(I18n.t(\"cloud_command.upload.no_url\"))\n            end\n            account = VagrantCloud::Account.new(\n              custom_server: api_server_url,\n              access_token: access_token\n            )\n            with_version(account: account, org: org, box: box, version: version) do |version|\n              provider = version.add_provider(provider, options[:architecture])\n              provider.checksum = options[:checksum] if options.key?(:checksum)\n              provider.checksum_type = options[:checksum_type] if options.key?(:checksum_type)\n              provider.architecture = options[:architecture] if options.key?(:architecture)\n              provider.default_architecture = options[:default_architecture] if options.key?(:default_architecture)\n              provider.url = url if url\n\n              provider.save\n\n              @env.ui.success(I18n.t(\"cloud_command.provider.create_success\",\n                architecture: options[:architecture], provider: provider.name, org: org, box_name: box, version: version.version))\n              format_box_results(provider, @env)\n              0\n            end\n          rescue VagrantCloud::Error => e\n            @env.ui.error(I18n.t(\"cloud_command.errors.provider.create_fail\",\n              architecture: options[:architecture], provider: provider.name, org: org, box_name: box, version: version))\n            @env.ui.error(e.message)\n            1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/provider/delete.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module ProviderCommand\n      module Command\n        class Delete < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud provider delete [options] organization/box-name provider-name version [architecture]\"\n              o.separator \"\"\n              o.separator \"Deletes a provider entry on Vagrant Cloud\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n              o.on(\"-f\", \"--[no-]force\", \"Force deletion of box version provider without confirmation\") do |f|\n                options[:force] = f\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.count < 3 || argv.count > 4\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            org, box_name = argv.first.split('/', 2)\n            provider_name = argv[1]\n            version = argv[2]\n            architecture = argv[3]\n\n            @client = client_login(@env)\n            account = VagrantCloud::Account.new(\n              custom_server: api_server_url,\n              access_token: @client.token\n            )\n\n            if architecture.nil?\n              architecture = select_provider_architecture(account, org, box_name, version, provider_name)\n            end\n\n            @env.ui.warn(I18n.t(\"cloud_command.provider.delete_warn\",\n              architecture: architecture, provider: provider_name, version: version, box: argv.first))\n\n            if !options[:force]\n              cont = @env.ui.ask(I18n.t(\"cloud_command.continue\"))\n              return 1 if cont.strip.downcase != \"y\"\n            end\n\n            delete_provider(org, box_name, version, provider_name, architecture, account, options)\n          end\n\n          def select_provider_architecture(account, org, box, version, provider)\n            with_version(account: account, org: org, box: box, version: version) do |box_version|\n              list = box_version.providers.map(&:architecture)\n              return list.first if list.size == 1\n\n              @env.ui.info(I18n.t(\"cloud_command.provider.delete_multiple_architectures\",\n                org: org, box_name: box, provider: provider))\n              list.each do |provider_name|\n                @env.ui.info(\" * #{provider_name}\")\n              end\n              selected = nil\n              while selected.nil?\n                user_input = @env.ui.ask(I18n.t(\"cloud_command.provider.delete_architectures_prompt\") + \" \")\n                selected = user_input if list.include?(user_input)\n              end\n\n              return selected\n            end\n          end\n\n          # Delete a provider for the box version\n          #\n          # @param [String] org Organization name\n          # @param [String] box Box name\n          # @param [String] version Box version\n          # @param [String] provider Provider name\n          # @param [String] architecture Architecture of guest\n          # @param [VagrantCloud::Account] account VagrantCloud account\n          # @param [Hash] options Currently unused\n          # @return [Integer]\n          def delete_provider(org, box, version, provider, architecture, account, options={})\n            with_provider(account: account, org: org, box: box, version: version, provider: provider, architecture: architecture) do |p|\n              p.delete\n              @env.ui.error(I18n.t(\"cloud_command.provider.delete_success\",\n                architecture: architecture, provider: provider, org: org, box_name: box, version: version))\n              0\n            end\n          rescue VagrantCloud::Error => e\n            @env.ui.error(I18n.t(\"cloud_command.errors.provider.delete_fail\",\n              architecture: architecture, provider: provider, org: org, box_name: box, version: version))\n            @env.ui.error(e)\n            1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/provider/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CloudCommand\n    module ProviderCommand\n      class Plugin < Vagrant.plugin(\"2\")\n        name \"vagrant cloud box\"\n        description <<-DESC\n        Provider life cycle commands for Vagrant Cloud\n        DESC\n\n        command(:provider) do\n          require_relative \"root\"\n          Command::Root\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/provider/root.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CloudCommand\n    module ProviderCommand\n      module Command\n        class Root < Vagrant.plugin(\"2\", :command)\n          def self.synopsis\n            \"Provider commands\"\n          end\n\n          def initialize(argv, env)\n            super\n\n            @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)\n            @subcommands = Vagrant::Registry.new\n            @subcommands.register(:create) do\n              require File.expand_path(\"../create\", __FILE__)\n              Command::Create\n            end\n            @subcommands.register(:delete) do\n              require File.expand_path(\"../delete\", __FILE__)\n              Command::Delete\n            end\n            @subcommands.register(:update) do\n              require File.expand_path(\"../update\", __FILE__)\n              Command::Update\n            end\n            @subcommands.register(:upload) do\n              require File.expand_path(\"../upload\", __FILE__)\n              Command::Upload\n            end\n          end\n\n          def execute\n            if @main_args.include?(\"-h\") || @main_args.include?(\"--help\")\n              # Print the help for all the provider commands.\n              return help\n            end\n\n            # If we reached this far then we must have a subcommand. If not,\n            # then we also just print the help and exit.\n            command_class = @subcommands.get(@sub_command.to_sym) if @sub_command\n            return help if !command_class || !@sub_command\n            @logger.debug(\"Invoking command class: #{command_class} #{@sub_args.inspect}\")\n\n            # Initialize and execute the command class\n            command_class.new(@sub_args, @env).execute\n          end\n\n          # Prints the help out for this command\n          def help\n            opts = OptionParser.new do |opts|\n              opts.banner = \"Usage: vagrant cloud provider <subcommand> [<args>]\"\n              opts.separator \"\"\n              opts.separator \"For various provider actions with Vagrant Cloud\"\n              opts.separator \"\"\n              opts.separator \"Available subcommands:\"\n\n              # Add the available subcommands as separators in order to print them\n              # out as well.\n              keys = []\n              @subcommands.each { |key, value| keys << key.to_s }\n\n              keys.sort.each do |key|\n                opts.separator \"     #{key}\"\n              end\n              opts.separator \"\"\n              opts.separator \"For help on any individual subcommand run `vagrant cloud provider <subcommand> -h`\"\n            end\n\n            @env.ui.info(opts.help, prefix: false)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/provider/update.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module ProviderCommand\n      module Command\n        class Update < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud provider update [options] organization/box-name provider-name version architecture [url]\"\n              o.separator \"\"\n              o.separator \"Updates a provider entry on Vagrant Cloud\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n\n              o.on(\"-a\", \"--architecture ARCH\", String, \"Update architecture value of guest box\") do |a|\n                options[:architecture] = a\n              end\n              o.on(\"-c\", \"--checksum CHECKSUM_VALUE\", String, \"Checksum of the box for this provider. --checksum-type option is required.\") do |c|\n                options[:checksum] = c\n              end\n              o.on(\"-C\", \"--checksum-type TYPE\", String, \"Type of checksum used (md5, sha1, sha256, sha384, sha512). --checksum option is required.\") do |c|\n                options[:checksum_type] = c\n              end\n              o.on(\"--[no-]default-architecture\", \"Mark as default architecture for specific provider\") do |d|\n                options[:default_architecture] = d\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.count < 4 || argv.count > 5\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            @client = client_login(@env)\n\n            org, box_name = argv.first.split('/', 2)\n            provider_name = argv[1]\n            version = argv[2]\n            architecture = argv[3]\n            url = argv[4]\n\n            update_provider(org, box_name, version, provider_name, architecture, url, @client.token, options)\n          end\n\n          # Update a provider for the box version\n          #\n          # @param [String] org Organization name\n          # @param [String] box Box name\n          # @param [String] version Box version\n          # @param [String] provider Provider name\n          # @param [String] architecture Architecture of guest\n          # @param [String] access_token User Vagrant Cloud access token\n          # @param [Hash] options\n          # @option options [String] :checksum Checksum of the box asset\n          # @option options [String] :checksum_type Type of the checksum\n          # @return [Integer]\n          def update_provider(org, box, version, provider, architecture, url, access_token, options)\n            if !url\n              @env.ui.warn(I18n.t(\"cloud_command.upload.no_url\"))\n            end\n            account = VagrantCloud::Account.new(\n              custom_server: api_server_url,\n              access_token: access_token\n            )\n\n            with_provider(account: account, org: org, box: box, version: version, provider: provider, architecture: architecture) do |p|\n              p.checksum = options[:checksum] if options.key?(:checksum)\n              p.checksum_type = options[:checksum_type] if options.key?(:checksum_type)\n              p.architecture = options[:architecture] if options.key?(:architecture)\n              p.default_architecture = options[:default_architecture] if options.key?(:default_architecture)\n              p.url = url if !url.nil?\n              p.save\n\n              @env.ui.success(I18n.t(\"cloud_command.provider.update_success\",\n                architecture: architecture, provider: provider, org: org, box_name: box, version: version))\n\n              format_box_results(p, @env)\n              0\n            end\n          rescue VagrantCloud::Error => e\n            @env.ui.error(I18n.t(\"cloud_command.errors.provider.update_fail\",\n              architecture: architecture, provider: provider, org: org, box_name: box, version: version))\n            @env.ui.error(e.message)\n            1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/provider/upload.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\nrequire \"vagrant/util/uploader\"\n\nmodule VagrantPlugins\n  module CloudCommand\n    module ProviderCommand\n      module Command\n        class Upload < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {direct: true}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud provider upload [options] organization/box-name provider-name version architecture box-file\"\n              o.separator \"\"\n              o.separator \"Uploads a box file to Vagrant Cloud for a specific provider\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n              o.on(\"-D\", \"--[no-]direct\", \"Upload asset directly to backend storage\") do |d|\n                options[:direct] = d\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.count != 5\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            @client = client_login(@env)\n\n            org, box_name = argv.first.split('/', 2)\n            provider_name = argv[1]\n            version = argv[2]\n            architecture = argv[3]\n            file = File.expand_path(argv[4])\n\n            upload_provider(org, box_name, version, provider_name, architecture, file, @client.token, options)\n          end\n\n          # Upload an asset for a box version provider\n          #\n          # @param [String] org Organization name\n          # @param [String] box Box name\n          # @param [String] version Box version\n          # @param [String] provider Provider name\n          # @param [String] architecture Architecture name\n          # @param [String] file Path to asset\n          # @param [String] access_token User Vagrant Cloud access token\n          # @param [Hash] options\n          # @option options [Boolean] :direct Upload directly to backend storage\n          # @return [Integer]\n          def upload_provider(org, box, version, provider, architecture, file, access_token, options)\n            account = VagrantCloud::Account.new(\n              custom_server: api_server_url,\n              access_token: access_token\n            )\n\n            # Include size check on file and disable direct if over 5G\n            if options[:direct]\n              fsize = File.stat(file).size\n              if fsize > (5 * Vagrant::Util::Numeric::GIGABYTE)\n                box_size = Vagrant::Util::Numeric.bytes_to_string(fsize)\n                @env.ui.warn(I18n.t(\"cloud_command.provider.direct_disable\", size: box_size))\n                options[:direct] = false\n              end\n            end\n\n            with_provider(account: account, org: org, box: box, version: version, provider: provider, architecture: architecture) do |p|\n              p.upload(direct: options[:direct]) do |upload_url|\n                m = options[:direct] ? :put : :put\n                uploader = Vagrant::Util::Uploader.new(upload_url, file, ui: @env.ui, method: m)\n                ui = Vagrant::UI::Prefixed.new(@env.ui, \"cloud\")\n                ui.output(I18n.t(\"cloud_command.provider.upload\",\n                  org: org, box_name: box, version: version, provider: provider))\n                ui.info(\"Upload File: #{file}\")\n                uploader.upload!\n                ui.success(I18n.t(\"cloud_command.provider.upload_success\",\n                  org: org, box_name: box, version: version, provider: provider))\n              end\n              0\n            end\n          rescue Vagrant::Errors::UploaderError, VagrantCloud::Error => e\n            @env.ui.error(I18n.t(\"cloud_command.errors.provider.upload_fail\",\n              provider: provider, org: org, box_name: box, version: version))\n            @env.ui.error(e.message)\n            1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/publish.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\nrequire \"vagrant/util/uploader\"\n\nmodule VagrantPlugins\n  module CloudCommand\n    module Command\n      class Publish < Vagrant.plugin(\"2\", :command)\n        include Util\n\n        def execute\n          options = {\n            architecture: Vagrant::Util::Platform.architecture,\n            direct_upload: true,\n          }\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant cloud publish [options] organization/box-name version provider-name [provider-file]\"\n            o.separator \"\"\n            o.separator \"Create and release a new Vagrant Box on Vagrant Cloud\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"-a\", \"--architecture ARCH\", String, \"Architecture of guest box (defaults to current host architecture)\") do |a|\n              options[:architecture] = a\n            end\n            o.on(\"--url URL\", String, \"Remote URL to download this provider (cannot be used with provider-file)\") do |u|\n              options[:url] = u\n            end\n            o.on(\"-d\", \"--description DESCRIPTION\", String, \"Full description of box\") do |d|\n              options[:description] = d\n            end\n            o.on(\"--version-description DESCRIPTION\", String, \"Description of the version to create\") do |v|\n              options[:version_description] = v\n            end\n            o.on(\"-f\", \"--[no-]force\", \"Disables confirmation to create or update box\") do |f|\n              options[:force] = f\n            end\n            o.on(\"-p\", \"--[no-]private\", \"Makes box private\") do |p|\n              options[:private] = p\n            end\n            o.on(\"-r\", \"--[no-]release\", \"Releases box\") do |p|\n              options[:release] = p\n            end\n            o.on(\"-s\", \"--short-description DESCRIPTION\", String, \"Short description of the box\") do |s|\n              options[:short_description] = s\n            end\n            o.on(\"-c\", \"--checksum CHECKSUM_VALUE\", String, \"Checksum of the box for this provider. --checksum-type option is required.\") do |c|\n              options[:checksum] = c\n            end\n            o.on(\"-C\", \"--checksum-type TYPE\", String, \"Type of checksum used (md5, sha1, sha256, sha384, sha512). --checksum option is required.\") do |c|\n              options[:checksum_type] = c\n            end\n            o.on(\"--[no-]direct-upload\", \"Upload asset directly to backend storage\") do |d|\n              options[:direct_upload] = d\n            end\n            o.on(\"--[no-]default-architecture\", \"Mark as default architecture for specific provider\") do |d|\n              options[:default_architecture] = d\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n\n          if argv.length < 3 || # missing required arguments\n              argv.length > 4 || # too many arguments\n              (argv.length < 4 && !options.key?(:url)) || # file argument required if url is not provided\n              (argv.length > 3 && options.key?(:url)) # cannot provide url and file argument\n            raise Vagrant::Errors::CLIInvalidUsage,\n              help: opts.help.chomp\n          end\n\n          org, box_name = argv.first.split('/', 2)\n          _, version, provider_name, box_file = argv\n\n          if box_file && !File.file?(box_file)\n            raise Vagrant::Errors::BoxFileNotExist,\n              file: box_file\n          end\n\n          @client = client_login(@env)\n          params = options.slice(:private, :release, :url, :short_description,\n            :description, :version_description, :checksum, :checksum_type,\n            :architecture, :default_architecture)\n\n          # Display output to user describing action to be taken\n          display_preamble(org, box_name, version, provider_name, params)\n\n          if !options[:force]\n            cont = @env.ui.ask(I18n.t(\"cloud_command.continue\"))\n            return 1 if cont.strip.downcase != \"y\"\n          end\n\n          # Load up all the models we'll need to publish the asset\n          box = load_box(org, box_name, @client.token)\n          box_v = load_box_version(box, version)\n          box_p = load_version_provider(box_v, provider_name, params[:architecture])\n\n          # Update all the data\n          set_box_info(box, params.slice(:private, :short_description, :description))\n          set_version_info(box_v, params.slice(:version_description))\n          set_provider_info(box_p, params.slice(\n            :architecture,\n            :checksum,\n            :checksum_type,\n            :default_architecture,\n            :url))\n\n          # Save any updated state\n          @env.ui.warn(I18n.t(\"cloud_command.publish.box_save\"))\n          box.save\n\n          # If we have a box file asset, upload it\n          if box_file\n            upload_box_file(box_p, box_file, options.slice(:direct_upload))\n          end\n\n          # If configured to release the box, release it\n          if options[:release] && !box_v.released?\n            release_version(box_v)\n          end\n\n          # And we're done!\n          @env.ui.success(I18n.t(\"cloud_command.publish.complete\", org: org, box_name: box_name))\n          format_box_results(box_p, @env)\n          0\n        rescue VagrantCloud::Error => err\n          @env.ui.error(I18n.t(\"cloud_command.errors.publish.fail\", org: org, box_name: box_name))\n          @env.ui.error(err.message)\n          1\n        end\n\n        # Upload the file for the given box provider\n        #\n        # @param [VagrantCloud::Box::Provider] provider Vagrant Cloud box version provider\n        # @param [String] box_file Path to local asset for upload\n        # @param [Hash] options\n        # @option options [Boolean] :direct_upload Upload directly to backend storage\n        # @return [nil]\n        def upload_box_file(provider, box_file, options={})\n          box_file = File.absolute_path(box_file)\n          @env.ui.info(I18n.t(\"cloud_command.publish.upload_provider\", file: box_file))\n          # Include size check on file and disable direct if over 5G\n          if options[:direct_upload]\n            fsize = File.stat(box_file).size\n            if fsize > (5 * Vagrant::Util::Numeric::GIGABYTE)\n              box_size = Vagrant::Util::Numeric.bytes_to_string(fsize)\n              @env.ui.warn(I18n.t(\"cloud_command.provider.direct_disable\", size: box_size))\n              options[:direct_upload] = false\n            end\n          end\n\n          provider.upload(direct: options[:direct_upload]) do |upload_url|\n            Vagrant::Util::Uploader.new(upload_url, box_file, ui: @env.ui, method: :put).upload!\n          end\n          nil\n        end\n\n        # Release the box version\n        #\n        # @param [VagrantCloud::Box::Version] version Vagrant Cloud box version\n        # @return [nil]\n        def release_version(version)\n          @env.ui.info(I18n.t(\"cloud_command.publish.release\"))\n          version.release\n          nil\n        end\n\n        # Set any box related attributes that were provided\n        #\n        # @param [VagrantCloud::Box] box Vagrant Cloud box\n        # @param [Hash] options\n        # @option options [Boolean] :private Box access is private\n        # @option options [String] :short_description Short description of box\n        # @option options [String] :description Full description of box\n        # @return [VagrantCloud::Box]\n        def set_box_info(box, options={})\n          box.private = options[:private] if options.key?(:private)\n          box.short_description = options[:short_description] if options.key?(:short_description)\n          box.description = options[:description] if options.key?(:description)\n          box\n        end\n\n        # Set any version related attributes that were provided\n        #\n        # @param [VagrantCloud::Box::Version] version Vagrant Cloud box version\n        # @param [Hash] options\n        # @option options [String] :version_description Description for this version\n        # @return [VagrantCloud::Box::Version]\n        def set_version_info(version, options={})\n          version.description = options[:version_description] if options.key?(:version_description)\n          version\n        end\n\n        # Set any provider related attributes that were provided\n        #\n        # @param [VagrantCloud::Box::Provider] provider Vagrant Cloud box version provider\n        # @param [Hash] options\n        # @option options [String] architecture Guest architecture of box\n        # @option options [String] :url Remote URL for self hosted\n        # @option options [String] :checksum_type Type of checksum value provided\n        # @option options [String] :checksum Checksum of the box asset\n        # @option options [Boolean] :default_architecture Default architecture for named provider\n        # @return [VagrantCloud::Box::Provider]\n        def set_provider_info(provider, options={})\n          provider.url = options[:url] if options.key?(:url)\n          provider.checksum_type = options[:checksum_type] if options.key?(:checksum_type)\n          provider.checksum = options[:checksum] if options.key?(:checksum)\n          provider.architecture = options[:architecture] if options.key?(:architecture)\n          provider.default_architecture = options[:default_architecture] if options.key?(:default_architecture)\n          provider\n        end\n\n        # Load the requested version provider\n        #\n        # @param [VagrantCloud::Box::Version] version The version of the Vagrant Cloud box\n        # @param [String] provider_name Name of the provider\n        # @return [VagrantCloud::Box::Provider]\n        def load_version_provider(version, provider_name, architecture)\n          provider = version.providers.detect { |pv|\n            pv.name == provider_name &&\n              pv.architecture == architecture\n          }\n          return provider if provider\n          version.add_provider(provider_name, architecture)\n        end\n\n        # Load the requested box version\n        #\n        # @param [VagrantCloud::Box] box The Vagrant Cloud box\n        # @param [String] version Version of the box\n        # @return [VagrantCloud::Box::Version]\n        def load_box_version(box, version)\n          v = box.versions.detect { |v| v.version == version }\n          return v if v\n          box.add_version(version)\n        end\n\n        # Load the requested box\n        #\n        # @param [String] org Organization name for box\n        # @param [String] box_name Name of the box\n        # @param [String] access_token User access token\n        # @return [VagrantCloud::Box]\n        def load_box(org, box_name, access_token)\n          account = VagrantCloud::Account.new(\n            custom_server: api_server_url,\n            access_token: access_token\n          )\n          box = account.organization(name: org).boxes.detect { |b| b.name == box_name }\n          return box if box\n          account.organization(name: org).add_box(box_name)\n        end\n\n        # Display publishing information to user before starting process\n        #\n        # @param [String] org Organization name\n        # @param [String] box_name Name of the box to publish\n        # @param [String] version Version of the box to publish\n        # @param [String] provider_name Name of the provider being published\n        # @param [Hash] options\n        # @option options [String] :architecture Name of architecture of provider being published#\n        # @option options [Boolean] :private Box is private\n        # @option options [Boolean] :release Box should be released\n        # @option options [String] :url Remote URL for self-hosted boxes\n        # @option options [Boolean] :default_architecture Architecture is default for provider name\n        # @option options [String] :description Description of the box\n        # @option options [String] :short_description Short description of the box\n        # @option options [String] :version_description Description of the box version\n        # @return [nil]\n        def display_preamble(org, box_name, version, provider_name, options={})\n          @env.ui.warn(I18n.t(\"cloud_command.publish.confirm.warn\"))\n          @env.ui.info(I18n.t(\"cloud_command.publish.confirm.box\", org: org,\n            box_name: box_name, version: version, provider_name: provider_name))\n          @env.ui.info(I18n.t(\"cloud_command.publish.confirm.private\")) if options[:private]\n          @env.ui.info(I18n.t(\"cloud_command.publish.confirm.release\")) if options[:release]\n          @env.ui.info(I18n.t(\"cloud_command.publish.confirm.architecture\",\n            architecture: options[:architecture]))\n          @env.ui.info(I18n.t(\"cloud_command.publish.confirm.default_architecture\")) if options[:default_architecture]\n          @env.ui.info(I18n.t(\"cloud_command.publish.confirm.box_url\",\n            url: options[:url])) if options[:url]\n          @env.ui.info(I18n.t(\"cloud_command.publish.confirm.box_description\",\n            description: options[:description])) if options[:description]\n          @env.ui.info(I18n.t(\"cloud_command.publish.confirm.box_short_desc\",\n            short_description: options[:short_description])) if options[:short_description]\n          @env.ui.info(I18n.t(\"cloud_command.publish.confirm.version_desc\",\n            version_description: options[:version_description])) if options[:version_description]\n          nil\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/root.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CloudCommand\n    module Command\n      class Root < Vagrant.plugin(\"2\", :command)\n        def self.synopsis\n          \"manages everything related to Vagrant Cloud\"\n        end\n\n        def initialize(argv, env)\n          super\n\n          @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)\n          @subcommands = Vagrant::Registry.new\n          @subcommand_helptext = {}\n\n          @subcommands.register(:auth) do\n            require File.expand_path(\"../auth/root\", __FILE__)\n            AuthCommand::Command::Root\n          end\n          @subcommand_helptext[:auth] = \"For various authorization operations on Vagrant Cloud\"\n\n          @subcommands.register(:box) do\n            require File.expand_path(\"../box/root\", __FILE__)\n            BoxCommand::Command::Root\n          end\n          @subcommand_helptext[:box] = \"For managing a Vagrant box entry on Vagrant Cloud\"\n\n          # TODO: Uncomment this when API endpoint exists\n          #@subcommands.register(:list) do\n          #  require File.expand_path(\"../list\", __FILE__)\n          #  List\n          #end\n          #@subcommand_helptext[:list] = \"Displays a list of Vagrant boxes that the current user manages\"\n\n          @subcommands.register(:search) do\n            require File.expand_path(\"../search\", __FILE__)\n            Search\n          end\n          @subcommand_helptext[:search] = \"Search Vagrant Cloud for available boxes\"\n\n          @subcommands.register(:provider) do\n            require File.expand_path(\"../provider/root\", __FILE__)\n            ProviderCommand::Command::Root\n          end\n          @subcommand_helptext[:provider] = \"For managing a Vagrant box's provider options\"\n\n          @subcommands.register(:publish) do\n            require File.expand_path(\"../publish\", __FILE__)\n            Publish\n          end\n          @subcommand_helptext[:publish] = \"A complete solution for creating or updating a new box on Vagrant Cloud\"\n\n          @subcommands.register(:version) do\n            require File.expand_path(\"../version/root\", __FILE__)\n            VersionCommand::Command::Root\n          end\n          @subcommand_helptext[:version] = \"For managing a Vagrant box's versions\"\n        end\n\n        def execute\n          if @main_args.include?(\"-h\") || @main_args.include?(\"--help\")\n            # Print the help for all the box commands.\n            return help\n          end\n\n          # If we reached this far then we must have a subcommand. If not,\n          # then we also just print the help and exit.\n          command_class = @subcommands.get(@sub_command.to_sym) if @sub_command\n          return help if !command_class || !@sub_command\n          @logger.debug(\"Invoking command class: #{command_class} #{@sub_args.inspect}\")\n\n          # Initialize and execute the command class\n          command_class.new(@sub_args, @env).execute\n        end\n\n        # Prints the help out for this command\n        def help\n          opts = OptionParser.new do |opts|\n            opts.banner = \"Usage: vagrant cloud <subcommand> [<args>]\"\n            opts.separator \"\"\n            opts.separator \"The cloud command can be used for taking actions against\"\n            opts.separator \"Vagrant Cloud like searching or uploading a Vagrant Box\"\n            opts.separator \"\"\n            opts.separator \"Available subcommands:\"\n\n            # Add the available subcommands as separators in order to print them\n            # out as well.\n            keys = []\n            @subcommands.each { |key, value| keys << key.to_s }\n\n            keys.sort.each do |key|\n              opts.separator \"     #{key.ljust(15)} #{@subcommand_helptext[key.to_sym]}\"\n            end\n            opts.separator \"\"\n            opts.separator \"For help on any individual subcommand run `vagrant cloud <subcommand> -h`\"\n          end\n\n          @env.ui.info(opts.help, prefix: false)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/search.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module Command\n      class Search < Vagrant.plugin(\"2\", :command)\n        include Util\n\n        def execute\n          options = {quiet: true}\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant cloud search [options] query\"\n            o.separator \"\"\n            o.separator \"Search for boxes managed by a specific\"\n            o.separator \"user/organization on Vagrant Cloud\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"-a\", \"--architecture ARCH\", \"Filter search results to a single architecture. Defaults to all.\") do |a|\n              options[:architecture] = a\n            end\n            o.on(\"-j\", \"--json\", \"Formats results in JSON\") do |j|\n              options[:json] = j\n            end\n            o.on(\"-p\", \"--page PAGE\", Integer, \"The page to display Default: 1\") do |j|\n              options[:page] = j\n            end\n            o.on(\"-s\", \"--short\", \"Shows a simple list of box names\") do |s|\n              options[:short] = s\n            end\n            o.on(\"-o\", \"--order ORDER\", String, \"Order to display results ('desc' or 'asc') Default: 'desc'\") do |o|\n              options[:order] = o\n            end\n            o.on(\"-l\", \"--limit LIMIT\", Integer, \"Max number of search results Default: 25\") do |l|\n              options[:limit] = l\n            end\n            o.on(\"-p\", \"--provider PROVIDER\", String, \"Filter search results to a single provider. Defaults to all.\") do |p|\n              options[:provider] = p\n            end\n            o.on(\"--sort-by SORT\", \"Field to sort results on (created, downloads, updated) Default: downloads\") do |s|\n              options[:sort] = s\n            end\n            o.on(\"--[no-]auth\", \"Authenticate with Vagrant Cloud if required before searching\") do |l|\n              options[:quiet] = !l\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          if argv.length != 1\n            raise Vagrant::Errors::CLIInvalidUsage,\n              help: opts.help.chomp\n          end\n\n          @client = client_login(@env, options.slice(:quiet))\n          query = argv.first\n\n          options[:limit] = 25 if !(options[:limit].to_i < 1) && !options[:limit]\n\n          search(query, @client&.token, options)\n        end\n\n        # Perform requested search and display results to user\n        #\n        # @param [String] query Search query string\n        # @param [Hash] options\n        # @option options [String] :provider Filter by provider\n        # @option options [String] :sort Field to sort results\n        # @option options [Integer] :limit Number of results to display\n        # @option options [Integer] :page Page of results to display\n        # @param [String] access_token User access token\n        # @return [Integer]\n        def search(query, access_token, options={})\n          account = VagrantCloud::Account.new(\n            custom_server: api_server_url,\n            access_token: access_token\n          )\n          params = {query: query}.merge(options.slice(:architecture, :provider, :sort, :order, :limit, :page))\n          result = account.searcher.search(**params)\n\n          if result.boxes.empty?\n            @env.ui.warn(I18n.t(\"cloud_command.search.no_results\", query: query))\n            return 0\n          end\n\n          format_search_results(result.boxes, options[:short], options[:json], @env)\n          0\n        rescue VagrantCloud::Error => e\n          @env.ui.error(I18n.t(\"cloud_command.errors.search.fail\"))\n          @env.ui.error(e.message)\n          1\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/util.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CloudCommand\n    module Util\n      # @return [String] Vagrant Cloud server URL\n      def api_server_url\n        if Vagrant.server_url == Vagrant::DEFAULT_SERVER_URL\n          return \"#{Vagrant.server_url}/api/v1\"\n        end\n        begin\n          addr = URI.parse(Vagrant.server_url)\n          if addr.path.empty? || addr.path.to_s == \"/\"\n            addr.path = \"/api/v1\"\n          end\n\n          addr.to_s\n        rescue URI::Error\n          Vagrant.server_url\n        end\n      end\n\n      # @param [Vagrant::Environment] env\n      # @param [Hash] options\n      # @option options [String] :login Username or email\n      # @option options [String] :description Description of login usage for token\n      # @option options [String] :code 2FA code for login\n      # @option options [Boolean] :quiet Do not prompt user\n      # @returns [VagrantPlugins::CloudCommand::Client, nil]\n      def client_login(env, options={})\n        return @_client if defined?(@_client)\n        @_client = Client.new(env)\n        return @_client if @_client.logged_in?\n\n        # If directed to be quiet, do not continue and\n        # just return nil\n        return if options[:quiet]\n\n        # Let the user know what is going on.\n        env.ui.output(I18n.t(\"cloud_command.command_header\") + \"\\n\")\n\n        # If it is a private cloud installation, show that\n        if Vagrant.server_url != Vagrant::DEFAULT_SERVER_URL\n          env.ui.output(\"Vagrant Cloud URL: #{Vagrant.server_url}\")\n        end\n\n        options = {} if !options\n        # Ask for the username\n        if options[:login]\n          @_client.username_or_email = options[:login]\n          env.ui.output(\"Vagrant Cloud username or email: #{@_client.username_or_email}\")\n        else\n          @_client.username_or_email = env.ui.ask(\"Vagrant Cloud username or email: \")\n        end\n\n        @_client.password = env.ui.ask(\"Password (will be hidden): \", echo: false)\n\n        description_default = \"Vagrant login from #{Socket.gethostname}\"\n        if !options[:description]\n          description = env.ui.ask(\"Token description (Defaults to #{description_default.inspect}): \")\n        else\n          description = options[:description]\n          env.ui.output(\"Token description: #{description}\")\n        end\n\n        description = description_default if description.empty?\n\n        code = nil\n\n        begin\n          token = @_client.login(description: description, code: code)\n        rescue Errors::TwoFactorRequired\n          until code\n            code = env.ui.ask(\"2FA code: \")\n\n            if @_client.two_factor_delivery_methods.include?(code.downcase)\n              delivery_method, code = code, nil\n              @_client.request_code delivery_method\n            end\n          end\n\n          retry\n        end\n\n        @_client.store_token(token)\n        Vagrant::Util::CredentialScrubber.sensitive(token)\n        env.ui.success(I18n.t(\"cloud_command.logged_in\"))\n        @_client\n      end\n\n      # Print search results from Vagrant Cloud to the console\n      #\n      # @param [Array<VagrantCloud::Box>] search_results Box search results from Vagrant Cloud\n      # @param [Boolean] short Print short summary\n      # @param [Boolean] json Print output in JSON format\n      # @param [Vagrant::Environment] env Current Vagrant environment\n      # @return [nil]\n      def format_search_results(search_results, short, json, env)\n        result = search_results.map do |b|\n          {\n            name: b.tag,\n            version: b.current_version.version,\n            downloads: format_downloads(b.downloads.to_s),\n            providers: b.current_version.providers.map(&:name).uniq.join(\", \"),\n            architectures: b.current_version.providers.map(&:architecture).join(\", \")\n          }\n        end\n\n        if short\n          result.map { |b| env.ui.info(b[:name]) }\n        elsif json\n          env.ui.info(result.to_json)\n        else\n          column_labels = {}\n          columns = result.first.keys\n          columns.each do |c|\n            column_labels[c] = c.to_s.upcase\n          end\n          print_search_table(env, column_labels, result, [:downloads])\n        end\n        nil\n      end\n\n      # Output box details result from Vagrant Cloud\n      #\n      # @param [VagrantCloud::Box, VagrantCloud::Box::Version] box Box or box version to display\n      # @param [Vagrant::Environment] env Current Vagrant environment\n      # @return [nil]\n      def format_box_results(box, env, options={})\n        if box.is_a?(VagrantCloud::Box)\n          info = box_info(box, options)\n        elsif box.is_a?(VagrantCloud::Box::Version)\n          info = version_info(box)\n        else\n          info = provider_info(box)\n        end\n\n        width = info.keys.map(&:size).max\n        info.each do |k, v|\n          v.to_s.split(\"\\n\").each_with_index do |line, idx|\n            whitespace = width - k.size + line.to_s.size\n            if idx == 0\n              env.ui.info \"#{k}: #{line.rjust(whitespace)}\"\n            else\n              whitespace += k.size + 2\n              env.ui.info line.rjust(whitespace)\n            end\n          end\n        end\n        nil\n      end\n\n      # Load box and yield\n      #\n      # @param [VagrantCloud::Account] account Vagrant Cloud account\n      # @param [String] org Organization name\n      # @param [String] box Box name\n      # @yieldparam [VagrantCloud::Box] box Requested Vagrant Cloud box\n      # @yieldreturn [Integer]\n      # @return [Integer]\n      def with_box(account:, org:, box:)\n        org = account.organization(name: org)\n        b = org.boxes.detect { |b| b.name == box }\n        if !b\n          @env.ui.error(I18n.t(\"cloud_command.box.not_found\",\n            org: org.username, box_name: box))\n          return 1\n        end\n        yield b\n      end\n\n      # Load box version and yield\n      #\n      # @param [VagrantCloud::Account] account Vagrant Cloud account\n      # @param [String] org Organization name\n      # @param [String] box Box name\n      # @param [String] version Box version\n      # @yieldparam [VagrantCloud::Box::Version] version Requested Vagrant Cloud box version\n      # @yieldreturn [Integer]\n      # @return [Integer]\n      def with_version(account:, org:, box:, version:)\n        with_box(account: account, org: org, box: box) do |b|\n          v = b.versions.detect { |v| v.version == version }\n          if !v\n            @env.ui.error(I18n.t(\"cloud_command.version.not_found\",\n              box_name: box, org: org, version: version))\n            return 1\n          end\n          yield v\n        end\n      end\n\n      # Load box version and yield\n      #\n      # @param [VagrantCloud::Account] account Vagrant Cloud account\n      # @param [String] org Organization name\n      # @param [String] box Box name\n      # @param [String] version Box version\n      # @param [String] provider Box version provider name\n      # @yieldparam [VagrantCloud::Box::Provider] provider Requested Vagrant Cloud box version provider\n      # @yieldreturn [Integer]\n      # @return [Integer]\n      def with_provider(account:, org:, box:, version:, provider:, architecture:)\n        with_version(account: account, org: org, box: box, version: version) do |v|\n          p = v.providers.detect { |p|\n            p.name == provider &&\n              p.architecture == architecture\n          }\n          if !p\n            @env.ui.error(I18n.t(\"cloud_command.provider.not_found\",\n              org: org, box_name: box, version: version, provider_name: provider))\n            return 1\n          end\n          yield p\n        end\n      end\n\n      protected\n\n      # Extract box information for display\n      #\n      # @param [VagrantCloud::Box] box Box for extracting information\n      # @return [Hash<String,String>]\n      def box_info(box, options={})\n        current_version = box.current_version\n        if current_version\n          current_version = nil if !Array(options[:providers]).empty? &&\n                                   current_version.providers.none? { |p| options[:providers].include?(p.name) }\n          current_version = nil if !Array(options[:architectures]).empty? &&\n                                   current_version.providers.none? { |p| options[:architectures].include?(p.architecture) }\n        end\n        versions = box.versions\n        # Apply provider filter if defined\n        versions = versions.find_all { |v|\n          v.providers.any? { |p|\n            options[:providers].include?(p.name)\n          }\n        } if !Array(options[:providers]).empty?\n        # Apply architecture filter if defined\n        versions = versions.find_all { |v|\n          v.providers.any? { |p|\n            options[:architectures].include?(p.architecture)\n          }\n        } if !Array(options[:architectures]).empty?\n\n        Hash.new.tap do |i|\n          i[\"Box\"] = box.tag\n          i[\"Description\"] = box.description\n          i[\"Private\"] = box.private ? \"yes\" : \"no\"\n          i[\"Created\"] = box.created_at\n          i[\"Updated\"] = box.updated_at\n          if !current_version.nil?\n            i[\"Current Version\"] = box.current_version.version\n          else\n            i[\"Current Version\"] = \"N/A\"\n          end\n          i[\"Versions\"] = versions.slice(0, 5).map(&:version).join(\", \")\n          if box.versions.size > 5\n            i[\"Versions\"] += \" ...\"\n          end\n          i[\"Downloads\"] = format_downloads(box.downloads)\n        end\n      end\n\n      # Extract version information for display\n      #\n      # @param [VagrantCloud::Box::Version] version Box version for extracting information\n      # @return [Hash<String,String>]\n      def version_info(version)\n        provider_arches = version.providers.group_by(&:name).map { |provider_name, info|\n          \"#{provider_name} (#{info.map(&:architecture).sort.join(\", \")})\"\n        }.sort.join(\"\\n\")\n        Hash.new.tap do |i|\n          i[\"Box\"] = version.box.tag\n          i[\"Version\"] = version.version\n          i[\"Description\"] = version.description\n          i[\"Status\"] = version.status\n          i[\"Providers\"] = provider_arches\n          i[\"Created\"] = version.created_at\n          i[\"Updated\"] = version.updated_at\n        end\n      end\n\n      # Extract provider information for display\n      #\n      # @param [VagrantCloud::Box::Provider] provider Box provider for extracting information\n      # @return [Hash<String,String>]\n      def provider_info(provider)\n        {\n          \"Box\" => provider.version.box.tag,\n          \"Private\" => provider.version.box.private ? \"yes\" : \"no\",\n          \"Version\" => provider.version.version,\n          \"Provider\" => provider.name,\n          \"Architecture\" => provider.architecture,\n          \"Default Architecture\" => provider.default_architecture ? \"yes\" : \"no\",\n        }\n      end\n\n      # Print table results from search request\n      #\n      # @param [Vagrant::Environment] env Current Vagrant environment\n      # @param [Hash] column_labels A hash of key/value pairs for table labels (i.e. {col1: \"COL1\"})\n      # @param [Array] results An array of hashes representing search resuls\n      # @param [Array] to_jrust_keys - List of columns keys to right justify (left justify is defualt)\n      # @return [nil]\n      # @note Modified from https://stackoverflow.com/a/28685559\n      def print_search_table(env, column_labels, results, to_rjust_keys)\n        columns = column_labels.each_with_object({}) do |(col,label),h|\n          h[col] = {\n            label: label,\n            width: [results.map { |g| g[col].size }.max, label.size].max\n          }\n        end\n\n        write_header(env, columns)\n        write_divider(env, columns)\n        results.each { |h| write_line(env, columns, h, to_rjust_keys) }\n        write_divider(env, columns)\n      end\n\n      # Write the header for a table\n      #\n      # @param [Vagrant::Environment] env Current Vagrant environment\n      # @param [Array<Hash>] columns List of columns in Hash format with `:label` and `:width` keys\n      # @return [nil]\n      def write_header(env, columns)\n        env.ui.info \"| #{ columns.map { |_,g| g[:label].ljust(g[:width]) }.join(' | ') } |\"\n        nil\n      end\n\n      # Write a row divider for a table\n      #\n      # @param [Vagrant::Environment] env Current Vagrant environment\n      # @param [Array<Hash>] columns List of columns in Hash format with `:label` and `:width` keys\n      # @return [nil]\n      def write_divider(env, columns)\n        env.ui.info \"+-#{ columns.map { |_,g| \"-\"*g[:width] }.join(\"-+-\") }-+\"\n        nil\n      end\n\n      # Write a line of content for a table\n      #\n      # @param [Vagrant::Environment] env Current Vagrant environment\n      # @param [Array<Hash>] columns List of columns in Hash format with `:label` and `:width` keys\n      # @param [Hash] h Values to print in row\n      # @param [Array<String>] to_rjust_keys List of columns to right justify\n      # @return [nil]\n      def write_line(env, columns, h, to_rjust_keys)\n        str = h.keys.map { |k|\n          if to_rjust_keys.include?(k)\n            h[k].rjust(columns[k][:width])\n          else\n            h[k].ljust(columns[k][:width])\n          end\n        }.join(\" | \")\n        env.ui.info \"| #{str} |\"\n        nil\n      end\n\n      # Converts a string of numbers into a formatted number\n      #\n      # 1234 -> 1,234\n      #\n      # @param [String] number Numer to format\n      def format_downloads(number)\n        number.to_s.chars.reverse.each_slice(3).map(&:join).join(\",\").reverse\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/version/create.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module VersionCommand\n      module Command\n        class Create < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud version create [options] organization/box-name version\"\n              o.separator \"\"\n              o.separator \"Creates a version entry on Vagrant Cloud\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n\n              o.on(\"-d\", \"--description DESCRIPTION\", String, \"A description for this version\") do |d|\n                options[:description] = d\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.empty? || argv.length != 2\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            @client = client_login(@env)\n            org, box_name = argv.first.split('/', 2)\n            version = argv[1]\n\n            create_version(org, box_name, version, @client.token, options.slice(:description))\n          end\n\n          # Create a new version of the box\n          #\n          # @param [String] org Organization box is within\n          # @param [String] box_name Name of box\n          # @param [String] box_version Version of box to create\n          # @param [String] access_token User Vagrant Cloud access token\n          # @param [Hash] options\n          # @option options [String] :description Description of box version\n          # @return [Integer]\n          def create_version(org, box_name, box_version, access_token, options={})\n            account = VagrantCloud::Account.new(\n              custom_server: api_server_url,\n              access_token: access_token\n            )\n            with_box(account: account, org: org, box: box_name) do |box|\n              version = box.add_version(box_version)\n              version.description = options[:description] if options.key?(:description)\n              version.save\n              @env.ui.success(I18n.t(\"cloud_command.version.create_success\",\n                version: box_version, org: org, box_name: box_name))\n              format_box_results(version, @env)\n              0\n            end\n          rescue VagrantCloud::Error => e\n            @env.ui.error(I18n.t(\"cloud_command.errors.version.create_fail\",\n              version: box_version, org: org, box_name: box_name))\n            @env.ui.error(e.message)\n            1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/version/delete.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module VersionCommand\n      module Command\n        class Delete < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud version delete [options] organization/box-name version\"\n              o.separator \"\"\n              o.separator \"Deletes a version entry on Vagrant Cloud\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n              o.on(\"-f\", \"--[no-]force\", \"Force deletion without confirmation\") do |f|\n                options[:force] = f\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.size != 2\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            org, box_name = argv.first.split('/', 2)\n            version = argv[1]\n\n            if !options[:force]\n              @env.ui.warn(I18n.t(\"cloud_command.version.delete_warn\", version: version, box: argv.first))\n              cont = @env.ui.ask(I18n.t(\"cloud_command.continue\"))\n              return 1 if cont.strip.downcase != \"y\"\n            end\n\n            @client = client_login(@env)\n\n            delete_version(org, box_name, version, @client.token, options.slice)\n          end\n\n          # Delete the requested box version\n          #\n          # @param [String] org Box organization name\n          # @param [String] box_name Name of the box\n          # @param [String] box_version Version of the box\n          # @param [String] access_token User Vagrant Cloud access token\n          # @param [Hash] options Current unsued\n          def delete_version(org, box_name, box_version, access_token, options={})\n            account = VagrantCloud::Account.new(\n              custom_server: api_server_url,\n              access_token: access_token\n            )\n            with_version(account: account, org: org, box: box_name, version: box_version) do |version|\n              version.delete\n              @env.ui.success(I18n.t(\"cloud_command.version.delete_success\",\n                version: box_version, org: org, box_name: box_name))\n              0\n            end\n          rescue VagrantCloud::Error => e\n            @env.ui.error(I18n.t(\"cloud_command.errors.version.delete_fail\",\n              version: box_version, org: org, box_name: box_name))\n            @env.ui.error(e.message)\n            1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/version/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CloudCommand\n    module VersionCommand\n      class Plugin < Vagrant.plugin(\"2\")\n        name \"vagrant cloud version\"\n        description <<-DESC\n        Version life cycle commands for Vagrant Cloud\n        DESC\n\n        command(:version) do\n          require_relative \"root\"\n          Command::Root\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/version/release.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module VersionCommand\n      module Command\n        class Release < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud version release [options] organization/box-name version\"\n              o.separator \"\"\n              o.separator \"Releases a version entry on Vagrant Cloud\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n              o.on(\"-f\", \"--[no-]force\", \"Release without confirmation\") do |f|\n                options[:force] = f\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.size != 2\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            if !options[:force]\n              @env.ui.warn(I18n.t(\"cloud_command.version.release_warn\", version: argv[1], box: argv.first))\n              cont = @env.ui.ask(I18n.t(\"cloud_command.continue\"))\n              return 1 if cont.strip.downcase != \"y\"\n            end\n\n            @client = client_login(@env)\n            org, box_name = argv.first.split('/', 2)\n            version = argv[1]\n\n            release_version(org, box_name, version, @client.token, options)\n          end\n\n          # Release the box version\n          #\n          # @param [String] org Organization name\n          # @param [String] box_name Box name\n          # @param [String] version Version of the box\n          # @param [String] access_token User Vagrant Cloud access token\n          # @param [Hash] options Currently unused\n          # @return [Integer]\n          def release_version(org, box_name, version, access_token, options={})\n            account = VagrantCloud::Account.new(\n              custom_server: api_server_url,\n              access_token: access_token\n            )\n            with_version(account: account, org: org, box: box_name, version: version) do |v|\n              v.release\n              @env.ui.success(I18n.t(\"cloud_command.version.release_success\",\n                version: version, org: org, box_name: box_name))\n              0\n            end\n          rescue VagrantCloud::Error => e\n            @env.ui.error(I18n.t(\"cloud_command.errors.version.release_fail\",\n              version: version, org: org, box_name: box_name))\n            @env.ui.error(e.message)\n            1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/version/revoke.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module VersionCommand\n      module Command\n        class Revoke < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud version revoke [options] organization/box-name version\"\n              o.separator \"\"\n              o.separator \"Revokes a version entry on Vagrant Cloud\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n              o.on(\"-f\", \"--[no-]force\", \"Force revocation without confirmation\") do |f|\n                options[:force] = f\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.size != 2\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            if !options[:force]\n              @env.ui.warn(I18n.t(\"cloud_command.version.revoke_warn\", version: argv[1], box: argv.first))\n              cont = @env.ui.ask(I18n.t(\"cloud_command.continue\"))\n              return 1 if cont.strip.downcase != \"y\"\n            end\n\n            @client = client_login(@env)\n            org, box_name = argv.first.split('/', 2)\n            version = argv[1]\n\n            revoke_version(org, box_name, version, @client.token, options)\n          end\n\n          # Revoke release of box version\n          #\n          # @param [String] org Organization name\n          # @param [String] box_name Box name\n          # @param [String] version Version of the box\n          # @param [String] access_token User Vagrant Cloud access token\n          # @param [Hash] options Currently unused\n          # @return [Integer]\n          def revoke_version(org, box_name, box_version, access_token, options={})\n            account = VagrantCloud::Account.new(\n              custom_server: api_server_url,\n              access_token: access_token\n            )\n            with_version(account: account, org: org, box: box_name, version: box_version) do |version|\n              version.revoke\n              @env.ui.success(I18n.t(\"cloud_command.version.revoke_success\",\n                version: box_version, org: org, box_name: box_name))\n              format_box_results(version, @env)\n              0\n            end\n          rescue VagrantCloud::Error => e\n            @env.ui.error(I18n.t(\"cloud_command.errors.version.revoke_fail\",\n              version: box_version, org: org, box_name: box_name))\n            @env.ui.error(e.message)\n            1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/version/root.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CloudCommand\n    module VersionCommand\n      module Command\n        class Root < Vagrant.plugin(\"2\", :command)\n          def self.synopsis\n            \"Version commands\"\n          end\n\n          def initialize(argv, env)\n            super\n\n            @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)\n            @subcommands = Vagrant::Registry.new\n            @subcommands.register(:create) do\n              require File.expand_path(\"../create\", __FILE__)\n              Command::Create\n            end\n            @subcommands.register(:delete) do\n              require File.expand_path(\"../delete\", __FILE__)\n              Command::Delete\n            end\n            @subcommands.register(:revoke) do\n              require File.expand_path(\"../revoke\", __FILE__)\n              Command::Revoke\n            end\n            @subcommands.register(:release) do\n              require File.expand_path(\"../release\", __FILE__)\n              Command::Release\n            end\n            @subcommands.register(:update) do\n              require File.expand_path(\"../update\", __FILE__)\n              Command::Update\n            end\n          end\n\n          def execute\n            if @main_args.include?(\"-h\") || @main_args.include?(\"--help\")\n              # Print the help for all the version commands.\n              return help\n            end\n\n            # If we reached this far then we must have a subcommand. If not,\n            # then we also just print the help and exit.\n            command_class = @subcommands.get(@sub_command.to_sym) if @sub_command\n            return help if !command_class || !@sub_command\n            @logger.debug(\"Invoking command class: #{command_class} #{@sub_args.inspect}\")\n\n            # Initialize and execute the command class\n            command_class.new(@sub_args, @env).execute\n          end\n\n          # Prints the help out for this command\n          def help\n            opts = OptionParser.new do |opts|\n              opts.banner = \"Usage: vagrant cloud version <subcommand> [<args>]\"\n              opts.separator \"\"\n              opts.separator \"For taking various actions against a Vagrant box's version attribute on Vagrant Cloud\"\n              opts.separator \"\"\n              opts.separator \"Available subcommands:\"\n\n              # Add the available subcommands as separators in order to print them\n              # out as well.\n              keys = []\n              @subcommands.each { |key, value| keys << key.to_s }\n\n              keys.sort.each do |key|\n                opts.separator \"     #{key}\"\n              end\n              opts.separator \"\"\n              opts.separator \"For help on any individual subcommand run `vagrant cloud version <subcommand> -h`\"\n            end\n\n            @env.ui.info(opts.help, prefix: false)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/cloud/version/update.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CloudCommand\n    module VersionCommand\n      module Command\n        class Update < Vagrant.plugin(\"2\", :command)\n          include Util\n\n          def execute\n            options = {}\n\n            opts = OptionParser.new do |o|\n              o.banner = \"Usage: vagrant cloud version update [options] organization/box-name version\"\n              o.separator \"\"\n              o.separator \"Updates a version entry on Vagrant Cloud\"\n              o.separator \"\"\n              o.separator \"Options:\"\n              o.separator \"\"\n\n              o.on(\"-d\", \"--description DESCRIPTION\", \"A description for this version\") do |d|\n                options[:description] = d\n              end\n            end\n\n            # Parse the options\n            argv = parse_options(opts)\n            return if !argv\n            if argv.size != 2\n              raise Vagrant::Errors::CLIInvalidUsage,\n                help: opts.help.chomp\n            end\n\n            @client = client_login(@env)\n            org, box_name = argv.first.split('/', 2)\n            version = argv[1]\n\n            update_version(org, box_name, version, @client.token, options)\n          end\n\n          # Update the version of the box\n          # @param [String] org Organization name\n          # @param [String] box_name Box name\n          # @param [String] version Version of the box\n          # @param [String] access_token User Vagrant Cloud access token\n          # @param [Hash] options\n          # @options options [String] :description Description of box version\n          # @return [Integer]\n          def update_version(org, box_name, box_version, access_token, options)\n            account = VagrantCloud::Account.new(\n              custom_server: api_server_url,\n              access_token: access_token\n            )\n            with_version(account: account, org: org, box: box_name, version: box_version) do |version|\n              version.description = options[:description] if options.key?(:description)\n              version.save\n\n              @env.ui.success(I18n.t(\"cloud_command.version.update_success\",\n                version: box_version, org: org, box_name: box_name))\n              format_box_results(version, @env)\n              0\n            end\n          rescue VagrantCloud::Error => e\n            @env.ui.error(I18n.t(\"cloud_command.errors.version.update_fail\",\n              version: box_version, org: org, box_name: box_name))\n            @env.ui.error(e.message)\n            1\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/destroy/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommandDestroy\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"stops and deletes all traces of the vagrant machine\"\n      end\n\n      def execute\n        options = {}\n        options[:force] = false\n        options[:force_halt] = true\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant destroy [options] [name|id]\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"-f\", \"--force\", \"Destroy without confirmation.\") do |f|\n            options[:force] = f\n          end\n\n          o.on(\"--[no-]parallel\",\n               \"Enable or disable parallelism if provider supports it (automatically enables force)\") do |p|\n            options[:parallel] = p\n          end\n\n          o.on(\"-g\", \"--graceful\", \"Gracefully poweroff of VM\") do |f|\n            options[:force_halt] = false\n          end\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        if options[:parallel] && !options[:force]\n          @env.ui.warn(I18n.t(\"vagrant.commands.destroy.warning\"))\n          sleep(5)\n          options[:force] = true\n        end\n\n        @logger.debug(\"'Destroy' each target VM...\")\n\n        machines = []\n        init_states = {}\n        declined = 0\n\n        @env.batch(options[:parallel]) do |batch|\n          with_target_vms(argv, reverse: true) do |vm|\n            # gather states to be checked after destroy\n            init_states[vm.name] = vm.state.id\n            machines << vm\n            batch.action(vm, :destroy, force_confirm_destroy: options[:force], force_halt: options[:force_halt])\n          end\n        end\n\n        machines.each do |m|\n          if init_states[m.name] != :not_created && m.state.id == init_states[m.name]\n            @logger.debug(\"state was not changed for '#{m.name}', marking as failed (state: #{m.state.id})\")\n            declined += 1\n          end\n        end\n\n        # Nothing was declined\n        return 0 if declined == 0\n\n        # Everything was declined, state was not changed\n        return 1 if declined == machines.length\n\n        # Some was declined\n        return 2\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/destroy/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandDestroy\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"destroy command\"\n      description <<-DESC\n      The `destroy` command deletes and removes the files and record of your virtual machines.\n      All data is lost and a new VM will have to be created using `up`\n      DESC\n\n      command(\"destroy\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/global-status/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandGlobalStatus\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"outputs status Vagrant environments for this user\"\n      end\n\n      def execute\n        options = {}\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant global-status\"\n          o.separator \"\"\n          o.on(\"--prune\", \"Prune invalid entries.\") do |p|\n            options[:prune] = true\n          end\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        columns = [\n          [\"id\", :id],\n          [\"name\", :name],\n          [\"provider\", :provider],\n          [\"state\", :state],\n          [\"directory\", :vagrantfile_path],\n        ]\n\n        widths = {}\n        widths[:id] = 8\n        widths[:name] = 6\n        widths[:provider] = 6\n        widths[:state] = 6\n        widths[:vagrantfile_path] = 35\n\n        entries = []\n        prune   = []\n        @env.machine_index.each do |entry|\n          # If we're pruning and this entry is invalid, skip it\n          # and prune it later.\n          if options[:prune] && !entry.valid?(@env.home_path)\n            prune << entry\n            next\n          end\n\n          entries << entry\n\n          columns.each do |_, method|\n            # Skip the id\n            next if method == :id\n\n            widths[method] ||= 0\n            cur = entry.send(method).to_s.length\n            widths[method] = cur if cur > widths[method]\n          end\n        end\n\n        # Prune all the entries to prune\n        prune.each do |entry|\n          deletable = @env.machine_index.get(entry.id)\n          @env.machine_index.delete(deletable) if deletable\n        end\n\n        # Machine-readable (non-formatted) output\n        @env.ui.machine(\"metadata\", \"machine-count\", entries.length.to_s);\n        entries.each do |entry|\n          opts = { \"target\" => entry.name.to_s }\n          @env.ui.machine(\"machine-id\", entry.id.to_s[0...7], opts)\n          @env.ui.machine(\"provider-name\", entry.provider.to_s, opts)\n          @env.ui.machine(\"machine-home\", entry.vagrantfile_path.to_s, opts)\n          @env.ui.machine(\"state\", entry.state.to_s, opts)\n        end\n\n        # Human-readable (table formatted) output\n        total_width = 0\n        columns.each do |header, method|\n          header = header.ljust(widths[method]) if widths[method]\n          @env.ui.info(\"#{header} \", new_line: false)\n          total_width += header.length + 1\n        end\n        @env.ui.info(\"\")\n        @env.ui.info(\"-\" * total_width)\n\n        if entries.empty?\n          @env.ui.info(I18n.t(\"vagrant.global_status_none\"))\n          return 0\n        end\n\n        entries.each do |entry|\n          columns.each do |_, method|\n            v = entry.send(method).to_s\n            v = v[0...7] if method == :id\n            v = v.ljust(widths[method]) if widths[method]\n            @env.ui.info(\"#{v} \", new_line: false)\n          end\n\n          @env.ui.info(\"\")\n        end\n\n        @env.ui.info(\" \\n\" + I18n.t(\"vagrant.global_status_footer\"))\n\n        # Success, exit status 0\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/global-status/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandGlobalStatus\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"global-status command\"\n      description <<-DESC\n      The `global-status` command shows what the running state (running/saved/..)\n      is of all the Vagrant environments known to the system.\n      DESC\n\n      command(\"global-status\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/halt/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandHalt\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"stops the vagrant machine\"\n      end\n\n      def execute\n        options = {}\n        options[:force] = false\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant halt [options] [name|id]\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"-f\", \"--force\", \"Force shut down (equivalent of pulling power)\") do |f|\n            options[:force] = f\n          end\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        @logger.debug(\"Halt command: #{argv.inspect} #{options.inspect}\")\n        with_target_vms(argv, reverse: true) do |vm|\n          vm.action(:halt, force_halt: options[:force])\n        end\n\n        # Success, exit status 0\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/halt/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandHalt\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"halt command\"\n      description <<-DESC\n      The `halt` command shuts your virtual machine down forcefully.\n      The command `up` recreates it.\n      DESC\n\n      command(\"halt\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/help/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandHelp\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"shows the help for a subcommand\"\n      end\n\n      def execute\n        return @env.cli([]) if @argv.empty?\n        @env.cli([@argv[0], \"-h\"])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/help/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandHelp\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"help command\"\n      description <<-DESC\n      The `help` command shows help for the given command.\n      DESC\n\n      command(\"help\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/init/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire 'vagrant/util/template_renderer'\n\nmodule VagrantPlugins\n  module CommandInit\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"initializes a new Vagrant environment by creating a Vagrantfile\"\n      end\n\n      def execute\n        options = {\n          force: false,\n          minimal: false,\n          output: \"Vagrantfile\",\n          template: ENV[\"VAGRANT_DEFAULT_TEMPLATE\"]\n        }\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant init [options] [name [url]]\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"--box-version VERSION\", \"Version of the box to add\") do |f|\n            options[:box_version] = f\n          end\n\n          o.on(\"-f\", \"--force\", \"Overwrite existing Vagrantfile\") do |f|\n            options[:force] = f\n          end\n\n          o.on(\"-m\", \"--minimal\", \"Use minimal Vagrantfile template (no help comments). Ignored with --template\") do |m|\n            options[:minimal] = m\n          end\n\n          o.on(\"--output FILE\", String,\n               \"Output path for the box. '-' for stdout\") do |output|\n            options[:output] = output\n          end\n\n          o.on(\"--template FILE\", String, \"Path to custom Vagrantfile template\") do |template|\n            options[:template] = template\n          end\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        save_path = nil\n        if options[:output] != \"-\"\n          save_path = Pathname.new(options[:output]).expand_path(@env.cwd)\n          save_path.delete if save_path.exist? && options[:force]\n          raise Vagrant::Errors::VagrantfileExistsError if save_path.exist?\n        end\n\n        # Determine the template and template root to use\n        template_root = \"\"\n        if options[:template].nil?\n          options[:template] = \"Vagrantfile\"\n\n          if options[:minimal]\n            options[:template] = \"Vagrantfile.min\"\n          end\n\n          template_root = ::Vagrant.source_root.join(\"templates/commands/init\")\n        end\n\n        # Strip the .erb extension off the template if the user passes it in\n        options[:template] = options[:template].chomp(\".erb\")\n\n        # Make sure the template actually exists\n        full_template_path = Vagrant::Util::TemplateRenderer.new(options[:template], template_root: template_root).full_template_path\n        if !File.file?(full_template_path)\n          raise Vagrant::Errors::VagrantfileTemplateNotFoundError, path: full_template_path\n        end\n\n        contents = Vagrant::Util::TemplateRenderer.render(options[:template],\n          box_name: argv[0] || \"base\",\n          box_url: argv[1],\n          box_version: options[:box_version],\n          template_root: template_root\n        )\n\n        if save_path\n          # Write out the contents\n          begin\n            save_path.open(\"w+\") do |f|\n              f.write(contents)\n            end\n          rescue Errno::EACCES\n            raise Vagrant::Errors::VagrantfileWriteError\n          end\n\n          @env.ui.info(I18n.t(\"vagrant.commands.init.success\"), prefix: false)\n        else\n          @env.ui.info(contents, prefix: false)\n        end\n\n        # Success, exit status 0\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/init/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandInit\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"init command\"\n      description <<-DESC\n      The `init` command sets up your working directory to be a\n      Vagrant-managed environment.\n      DESC\n\n      command(\"init\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/list-commands/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"optparse\"\n\nmodule VagrantPlugins\n  module CommandListCommands\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"outputs all available Vagrant subcommands, even non-primary ones\"\n      end\n\n      def execute\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant list-commands\"\n        end\n\n        argv = parse_options(opts)\n        return if !argv\n\n        # Add the available subcommands as separators in order to print them\n        # out as well.\n        commands = {}\n        longest = 0\n        Vagrant.plugin(\"2\").manager.commands.each do |key, data|\n          key           = key.to_s\n          klass         = data[0].call\n          commands[key] = klass.synopsis\n          longest       = key.length if key.length > longest\n        end\n\n        command_output = []\n        commands.keys.sort.each do |key|\n          command_output << \"#{key.ljust(longest+2)} #{commands[key]}\"\n          @env.ui.machine(\"cli-command\", key.dup)\n        end\n\n        @env.ui.info(\n          I18n.t(\"vagrant.list_commands\", list: command_output.join(\"\\n\")))\n\n        # Success, exit status 0\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/list-commands/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandListCommands\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"list-commands command\"\n      description <<-DESC\n      The `list-commands` command will list all commands that Vagrant\n      understands, even hidden ones.\n      DESC\n\n      command(\"list-commands\", primary: false) do\n        require_relative \"command\"\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/login/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module LoginCommand\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"vagrant-login\"\n      description <<-DESC\n      Provides the login command and internal API access to Vagrant Cloud.\n      DESC\n\n      command(:login) do\n        require File.expand_path(\"../../cloud/auth/login\", __FILE__)\n        init!\n        VagrantPlugins::CloudCommand::AuthCommand::Command::Login\n      end\n\n      def self.init!\n        return if defined?(@_init)\n        I18n.load_path << File.expand_path(\"../../cloud/locales/en.yml\", __FILE__)\n        I18n.reload!\n        @_init = true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/package/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\nrequire 'securerandom'\n\nmodule VagrantPlugins\n  module CommandPackage\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"packages a running vagrant environment into a box\"\n      end\n\n      def execute\n        options = {}\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant package [options] [name|id]\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"--base NAME\", \"Name of a VM in VirtualBox to package as a base box (VirtualBox Only)\") do |b|\n            options[:base] = b\n          end\n\n          o.on(\"--output NAME\", \"Name of the file to output\") do |output|\n            options[:output] = output\n          end\n\n          o.on(\"--include FILE,FILE..\", Array, \"Comma separated additional files to package with the box\") do |i|\n            options[:include] = i\n          end\n\n          o.on(\"--info FILE\", \"Path to a custom info.json file containing additional box information\") do |info|\n            options[:info] = info\n          end\n\n          o.on(\"--vagrantfile FILE\", \"Vagrantfile to package with the box\") do |v|\n            options[:vagrantfile] = v\n          end\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        @logger.debug(\"package options: #{options.inspect}\")\n        if options[:base]\n          package_base(options)\n        else\n          package_target(argv[0], options)\n        end\n\n        # Success, exit status 0\n        0\n      end\n\n      protected\n\n      def package_base(options)\n        # XXX: This whole thing is hardcoded and very temporary. The whole\n        # `vagrant package --base` process is deprecated for something much\n        # better in the future. We just hardcode this to keep VirtualBox working\n        # for now.\n        provider = Vagrant.plugin(\"2\").manager.providers[:virtualbox]\n        tmp_data_directory = File.join(@env.tmp_path, SecureRandom.uuid)\n        FileUtils.mkdir_p(tmp_data_directory)\n        begin\n          vm = Vagrant::Machine.new(\n            options[:base],\n            :virtualbox, provider[0], nil, provider[1],\n            @env.vagrantfile.config,\n            Pathname.new(tmp_data_directory), nil,\n            @env, @env.vagrantfile, true)\n          @logger.debug(\"Packaging base VM: #{vm.name}\")\n          package_vm(vm, options)\n        ensure\n          FileUtils.rm_rf(tmp_data_directory)\n        end\n      end\n\n      def package_target(name, options)\n        with_target_vms(name, single_target: true) do |vm|\n          @logger.debug(\"Packaging VM: #{vm.name}\")\n          package_vm(vm, options)\n        end\n      end\n\n      def package_vm(vm, options)\n        opts = options.inject({}) do |acc, data|\n          k,v = data\n          acc[\"package.#{k}\"] = v\n          acc\n        end\n\n        env = vm.action(:package, opts)\n        temp_dir = env[\"export.temp_dir\"]\n      ensure\n        FileUtils.rm_rf(temp_dir) if temp_dir\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/package/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandPackage\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"package command\"\n      description <<-DESC\n      The `package` command will take a previously existing Vagrant\n      environment and package it into a box file.\n      DESC\n\n      command(\"package\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/action/expunge_plugins.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/plugin/manager\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Action\n      # This middleware removes user installed plugins by\n      # removing:\n      #   * ~/.vagrant.d/plugins.json\n      #   * ~/.vagrant.d/gems\n      # Usage should be restricted to when a repair is\n      # unsuccessful and the only reasonable option remaining\n      # is to re-install all plugins\n      class ExpungePlugins\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          if !env[:force]\n\n            result = nil\n            attempts = 0\n            while attempts < 5 && result.nil?\n              attempts += 1\n              result = env[:ui].ask(\n                I18n.t(\"vagrant.commands.plugin.expunge_confirm\") +\n                  \" [N]: \"\n              )\n              result = result.to_s.downcase.strip\n              result = \"n\" if result.empty?\n              if ![\"y\", \"yes\", \"n\", \"no\"].include?(result)\n                result = nil\n                env[:ui].error(\"Please answer Y or N\")\n              else\n                result = result[0,1]\n              end\n            end\n\n            if result != 'y'\n              abort_action = true\n            end\n          end\n\n          if !abort_action\n            files = []\n            dirs = []\n\n            # Do not include global paths if local only\n            if !env[:env_local_only] || env[:global_only]\n              files << Vagrant::Plugin::Manager.instance.user_file.path\n              dirs << Vagrant::Bundler.instance.plugin_gem_path\n            end\n\n            # Add local paths if they exist\n            if Vagrant::Plugin::Manager.instance.local_file && (env[:env_local_only] || !env[:global_only])\n              files << Vagrant::Plugin::Manager.instance.local_file.path\n              dirs << Vagrant::Bundler.instance.env_plugin_gem_path\n            end\n\n            # Expunge files and directories\n            files.find_all(&:exist?).map(&:delete)\n            dirs.find_all(&:exist?).map(&:rmtree)\n\n            env[:ui].info(I18n.t(\"vagrant.commands.plugin.expunge_complete\"))\n\n            @app.call(env)\n          else\n            env[:ui].info(I18n.t(\"vagrant.commands.plugin.expunge_aborted\"))\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/action/install_gem.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"vagrant/plugin/manager\"\nrequire \"vagrant/util/platform\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Action\n      # This action takes the `:plugin_name` variable in the environment\n      # and installs it using the RubyGems API.\n      class InstallGem\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::plugins::plugincommand::installgem\")\n        end\n\n        def call(env)\n          entrypoint  = env[:plugin_entry_point]\n          plugin_name = env[:plugin_name]\n          sources     = env[:plugin_sources]\n          version     = env[:plugin_version]\n          env_local   = env[:plugin_env_local]\n\n          # Install the gem\n          plugin_name_label = plugin_name\n          plugin_name_label += \" --version '#{version}'\" if version\n          env[:ui].info(I18n.t(\"vagrant.commands.plugin.installing\",\n                               name: plugin_name_label))\n\n          manager = Vagrant::Plugin::Manager.instance\n          plugin_spec = manager.install_plugin(\n            plugin_name,\n            version:   version,\n            require:   entrypoint,\n            sources:   sources,\n            verbose:   !!env[:plugin_verbose],\n            env_local: env_local\n          )\n\n          # Record it so we can uninstall if something goes wrong\n          @installed_plugin_name = plugin_spec.name\n\n          # Tell the user\n          env[:ui].success(I18n.t(\"vagrant.commands.plugin.installed\",\n                                  name: plugin_spec.name,\n                                  version: plugin_spec.version.to_s))\n\n          # If the plugin's spec includes a post-install message display it\n          post_install_message = plugin_spec.post_install_message\n          if post_install_message\n            if post_install_message.is_a?(Array)\n              post_install_message = post_install_message.join(\" \")\n            end\n\n            env[:ui].info(I18n.t(\"vagrant.commands.plugin.post_install\",\n                                 name: plugin_spec.name,\n                                 message: post_install_message.to_s))\n          end\n\n          # Continue\n          @app.call(env)\n        end\n\n        def recover(env)\n          # If any error happens, we uninstall it and remove it from\n          # the state file. We can only do this if we successfully installed\n          # the gem in the first place.\n          if @installed_plugin_name\n            new_env = env.dup\n            new_env.delete(:interrupted)\n            new_env[:plugin_name] = @installed_plugin_name\n            new_env[:action_runner].run(Action.action_uninstall, new_env)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/action/license_plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\nrequire \"pathname\"\nrequire \"rubygems\"\nrequire \"set\"\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Action\n      # This middleware licenses a plugin by copying the license file to\n      # the proper place.\n      class LicensePlugin\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::plugins::plugincommand::license\")\n        end\n\n        def call(env)\n          # Verify the license file exists\n          license_file = Pathname.new(env[:plugin_license_path])\n          if !license_file.file?\n            raise Vagrant::Errors::PluginInstallLicenseNotFound,\n              name: env[:plugin_name],\n              path: license_file.to_s\n          end\n\n          # Copy it in.\n          final_path = env[:home_path].join(\"license-#{env[:plugin_name]}.lic\")\n          @logger.info(\"Copying license from: #{license_file}\")\n          @logger.info(\"Copying license to: #{final_path}\")\n          env[:ui].info(I18n.t(\"vagrant.commands.plugin.installing_license\",\n                               name: env[:plugin_name]))\n          FileUtils.cp(license_file, final_path)\n\n          # Installed!\n          env[:ui].success(I18n.t(\"vagrant.commands.plugin.installed_license\",\n                                 name: env[:plugin_name]))\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/action/list_plugins.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/plugin/manager\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Action\n      # This middleware lists all the installed plugins.\n      #\n      # This is a bit more complicated than simply listing installed\n      # gems or what is in the state file as installed. Instead, this\n      # actually compares installed gems with what the state file claims\n      # is installed, and outputs the appropriate truly installed\n      # plugins.\n      class ListPlugins\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          manager = Vagrant::Plugin::Manager.instance\n          plugins = manager.installed_plugins\n          specs   = Hash[\n            manager.installed_specs.map do |spec|\n              [spec.name, spec]\n            end\n          ]\n\n          # Output!\n          if specs.empty?\n            env[:ui].info(I18n.t(\"vagrant.commands.plugin.no_plugins\"))\n            return @app.call(env)\n          end\n\n          plugins.each do |plugin_name, plugin|\n\n            spec = specs[plugin_name]\n            next if spec.nil?\n\n            meta = \", global\"\n            if plugin\n              meta = \", system\" if plugin[\"system\"]\n              meta = \", local\" if plugin[\"env_local\"]\n            end\n            env[:ui].info \"#{spec.name} (#{spec.version}#{meta})\"\n            env[:ui].machine(\"plugin-name\", spec.name)\n            env[:ui].machine(\n              \"plugin-version\",\n              \"#{spec.version}#{meta}\",\n              target: spec.name)\n\n            if plugin[\"gem_version\"] && plugin[\"gem_version\"] != \"\"\n              env[:ui].info(I18n.t(\n                \"vagrant.commands.plugin.plugin_version\",\n                version: plugin[\"gem_version\"]))\n              env[:ui].machine(\n                \"plugin-version-constraint\",\n                plugin[\"gem_version\"],\n                target: spec.name)\n            end\n\n            if plugin[\"require\"] && plugin[\"require\"] != \"\"\n              env[:ui].info(I18n.t(\n                \"vagrant.commands.plugin.plugin_require\",\n                require: plugin[\"require\"]))\n              env[:ui].machine(\n                \"plugin-custom-entrypoint\",\n                plugin[\"require\"],\n                target: spec.name)\n            end\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/action/plugin_exists_check.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/plugin/manager\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Action\n      # This class checks to see if the plugin is installed already, and\n      # if so, raises an exception/error to output to the user.\n      class PluginExistsCheck\n        def initialize(app, env)\n          @app    = app\n        end\n\n        def call(env)\n          installed = Vagrant::Plugin::Manager.instance.installed_plugins\n          if !installed.key?(env[:plugin_name])\n            raise Vagrant::Errors::PluginNotInstalled,\n              name: env[:plugin_name]\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/action/repair_plugins.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/plugin/manager\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Action\n      # This middleware attempts to repair installed plugins.\n      #\n      # In general, if plugins are failing to properly load the\n      # core issue will likely be one of two issues:\n      #   1. manual modifications within ~/.vagrant.d/\n      #   2. vagrant upgrade\n      # Running an install on configured plugin set will most\n      # likely fix these issues, which is all this action does.\n      class RepairPlugins\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::plugins::plugincommand::repair\")\n        end\n\n        def call(env)\n          env[:ui].info(I18n.t(\"vagrant.commands.plugin.repairing\"))\n          plugins = Vagrant::Plugin::Manager.instance.globalize!\n          begin\n            ENV[\"VAGRANT_DISABLE_PLUGIN_INIT\"] = nil\n            Vagrant::Bundler.instance.init!(plugins, :repair)\n            ENV[\"VAGRANT_DISABLE_PLUGIN_INIT\"] = \"1\"\n            env[:ui].info(I18n.t(\"vagrant.commands.plugin.repair_complete\"))\n          rescue => e\n            @logger.error(\"Failed to repair user installed plugins: #{e.class} - #{e}\")\n            e.backtrace.each do |backtrace_line|\n              @logger.debug(backtrace_line)\n            end\n            env[:ui].error(I18n.t(\"vagrant.commands.plugin.repair_failed\", message: e.message))\n          end\n          # Continue\n          @app.call(env)\n        end\n      end\n\n      class RepairPluginsLocal\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::plugins::plugincommand::repair_local\")\n        end\n\n        def call(env)\n          env[:ui].info(I18n.t(\"vagrant.commands.plugin.repairing_local\"))\n          Vagrant::Plugin::Manager.instance.localize!(env[:env]).each_pair do |pname, pinfo|\n            env[:env].action_runner.run(Action.action_install,\n              plugin_name: pname,\n              plugin_entry_point: pinfo[\"require\"],\n              plugin_sources: pinfo[\"sources\"],\n              plugin_version: pinfo[\"gem_version\"],\n              plugin_env_local: true\n            )\n          end\n          env[:ui].info(I18n.t(\"vagrant.commands.plugin.repair_local_complete\"))\n          # Continue\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/action/uninstall_plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Action\n      # This middleware uninstalls a plugin by simply removing it from\n      # the state file. Running a {PruneGems} after should properly remove\n      # it from the gem index.\n      class UninstallPlugin\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          # Remove it!\n          env[:ui].info(I18n.t(\"vagrant.commands.plugin.uninstalling\",\n                               name: env[:plugin_name]))\n\n          manager = Vagrant::Plugin::Manager.instance\n          manager.uninstall_plugin(env[:plugin_name], env_local: env[:env_local])\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/action/update_gems.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/plugin/manager\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Action\n      class UpdateGems\n        def initialize(app, env)\n          @app    = app\n        end\n\n        def call(env)\n          names = env[:plugin_name] || []\n\n          if names.empty?\n            env[:ui].info(I18n.t(\"vagrant.commands.plugin.updating\"))\n          else\n            env[:ui].info(I18n.t(\"vagrant.commands.plugin.updating_specific\",\n                                 names: names.join(\", \")))\n          end\n\n          manager = Vagrant::Plugin::Manager.instance\n          installed_plugins = manager.installed_plugins\n          new_specs       = manager.update_plugins(names)\n          updated_plugins = manager.installed_plugins\n\n          updated = {}\n          installed_plugins.each do |name, info|\n            update = updated_plugins[name]\n            if update && update[\"installed_gem_version\"] != info[\"installed_gem_version\"]\n              updated[name] = update[\"installed_gem_version\"]\n            end\n          end\n\n          if updated.empty?\n            env[:ui].success(I18n.t(\"vagrant.commands.plugin.up_to_date\"))\n          end\n\n          updated.each do |name, version|\n            env[:ui].success(I18n.t(\"vagrant.commands.plugin.updated\",\n                                    name: name, version: version.to_s))\n          end\n\n          # Continue\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/action.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nrequire \"vagrant/action/builder\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Action\n      # This middleware sequence will remove all plugins.\n      def self.action_expunge\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ExpungePlugins\n        end\n      end\n\n      # This middleware sequence will install a plugin.\n      def self.action_install\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use InstallGem\n        end\n      end\n\n      # This middleware sequence licenses paid addons.\n      def self.action_license\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use PluginExistsCheck\n          b.use LicensePlugin\n        end\n      end\n\n      # This middleware sequence will list all installed plugins.\n      def self.action_list\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ListPlugins\n        end\n      end\n\n      # This middleware sequence will repair installed plugins.\n      def self.action_repair\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use RepairPlugins\n        end\n      end\n\n      # This middleware sequence will repair installed local plugins.\n      def self.action_repair_local\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use RepairPluginsLocal\n        end\n      end\n\n      # This middleware sequence will uninstall a plugin.\n      def self.action_uninstall\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use PluginExistsCheck\n          b.use UninstallPlugin\n        end\n      end\n\n      # This middleware sequence will update a plugin.\n      def self.action_update\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use UpdateGems\n        end\n      end\n\n      # The autoload farm\n      action_root = Pathname.new(File.expand_path(\"../action\", __FILE__))\n      autoload :ExpungePlugins, action_root.join(\"expunge_plugins\")\n      autoload :InstallGem, action_root.join(\"install_gem\")\n      autoload :LicensePlugin, action_root.join(\"license_plugin\")\n      autoload :ListPlugins, action_root.join(\"list_plugins\")\n      autoload :PluginExistsCheck, action_root.join(\"plugin_exists_check\")\n      autoload :RepairPlugins, action_root.join(\"repair_plugins\")\n      autoload :RepairPluginsLocal, action_root.join(\"repair_plugins\")\n      autoload :UninstallPlugin, action_root.join(\"uninstall_plugin\")\n      autoload :UpdateGems, action_root.join(\"update_gems\")\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/command/base.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/plugin/state_file\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Command\n      class Base < Vagrant.plugin(\"2\", :command)\n        # This is a helper for executing an action sequence with the proper\n        # environment hash setup so that the plugin specific helpers are\n        # in.\n        #\n        # @param [Object] callable the Middleware callable\n        # @param [Hash] env Extra environment hash that is merged in.\n        def action(callable, env=nil)\n          @env.action_runner.run(callable, env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/command/expunge.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire_relative \"base\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Command\n      class Expunge < Base\n        def execute\n          options = {}\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant plugin expunge [-h]\"\n\n            o.on(\"--force\", \"Do not prompt for confirmation\") do |force|\n              options[:force] = force\n            end\n\n            o.on(\"--local\", \"Include plugins from local project for expunge\") do |l|\n              options[:env_local] = l\n            end\n\n            o.on(\"--local-only\", \"Only expunge local project plugins\") do |l|\n              options[:env_local_only] = l\n            end\n\n            o.on(\"--global-only\", \"Only expunge global plugins\") do |l|\n              options[:global_only] = l\n            end\n\n            o.on(\"--reinstall\", \"Reinstall current plugins after expunge\") do |reinstall|\n              options[:reinstall] = reinstall\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.length > 0\n\n          plugins = Vagrant::Plugin::Manager.instance.installed_plugins\n\n          if !options[:reinstall] && !options[:force] && !plugins.empty?\n            result = nil\n            attempts = 0\n            while attempts < 5 && result.nil?\n              attempts += 1\n              result = @env.ui.ask(\n                I18n.t(\"vagrant.commands.plugin.expunge_request_reinstall\") +\n                  \" [N]: \"\n              )\n              result = result.to_s.downcase.strip\n              result = \"n\" if result.empty?\n              if ![\"y\", \"yes\", \"n\", \"no\"].include?(result)\n                result = nil\n                @env.ui.error(\"Please answer Y or N\")\n              else\n                result = result[0,1]\n              end\n            end\n            options[:reinstall] = result == \"y\"\n          end\n\n          # Remove all installed user plugins\n          action(Action.action_expunge, options)\n\n          if options[:reinstall]\n            @env.ui.info(I18n.t(\"vagrant.commands.plugin.expunge_reinstall\"))\n            plugins.each do |plugin_name, plugin_info|\n              next if plugin_info[\"system\"] # system plugins do not require re-install\n              # Rebuild information hash to use symbols\n              plugin_info = Hash[\n                plugin_info.map do |key, value|\n                  [\"plugin_#{key}\".to_sym, value]\n                end\n              ]\n              action(\n                Action.action_install,\n                plugin_info.merge(\n                  plugin_name: plugin_name\n                )\n              )\n            end\n          end\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/command/install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire_relative \"base\"\nrequire_relative \"mixin_install_opts\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Command\n      class Install < Base\n        include MixinInstallOpts\n\n        LOCAL_INSTALL_PAUSE = 3\n\n        def execute\n          options = { verbose: false }\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant plugin install <name>... [-h]\"\n            o.separator \"\"\n            build_install_opts(o, options)\n\n            o.on(\"--local\", \"Install plugin for local project only\") do |l|\n              options[:env_local] = l\n            end\n\n            o.on(\"--verbose\", \"Enable verbose output for plugin installation\") do |v|\n              options[:verbose] = v\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n\n          if argv.length < 1\n            raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if !options[:env_local]\n\n            errors = @env.vagrantfile.config.vagrant.validate(nil)\n            if !errors[\"vagrant\"].empty?\n              raise Errors::ConfigInvalid,\n                errors: Util::TemplateRenderer.render(\n                \"config/validation_failed\",\n                errors: errors)\n            end\n\n            local_plugins = @env.vagrantfile.config.vagrant.plugins\n            plugin_list = local_plugins.map do |name, info|\n              \"#{name} (#{info.fetch(:version, \"> 0\")})\"\n            end.join(\"\\n\")\n\n\n            @env.ui.info(I18n.t(\"vagrant.plugins.local.install_all\",\n              plugins: plugin_list) + \"\\n\")\n\n            # Pause to allow user to cancel\n            sleep(LOCAL_INSTALL_PAUSE)\n\n            local_plugins.each do |name, info|\n              action(Action.action_install,\n                plugin_entry_point: info[:entry_point],\n                plugin_version:     info[:version],\n                plugin_sources:     info[:sources] || Vagrant::Bundler::DEFAULT_GEM_SOURCES.dup,\n                plugin_name:        name,\n                plugin_env_local:   true\n              )\n            end\n          else\n            # Install the gem\n            argv.each do |name|\n              action(Action.action_install,\n                plugin_entry_point: options[:entry_point],\n                plugin_version:     options[:plugin_version],\n                plugin_sources:     options[:plugin_sources],\n                plugin_name:        name,\n                plugin_verbose:     options[:verbose],\n                plugin_env_local:   options[:env_local]\n              )\n            end\n          end\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/command/license.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire_relative \"base\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Command\n      class License < Base\n        def execute\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant plugin license <name> <license-file> [-h]\"\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.length < 2\n\n          # License the plugin\n          action(Action.action_license, {\n            plugin_license_path: argv[1],\n            plugin_name:         argv[0]\n          })\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/command/list.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire_relative \"base\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Command\n      class List < Base\n        def execute\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant plugin list [-h]\"\n\n            # Stub option to allow Vagrantfile loading\n            o.on(\"--local\", \"Include local project plugins\"){|_|}\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.length > 0\n\n          # List the installed plugins\n          action(Action.action_list)\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/command/mixin_install_opts.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Command\n      module MixinInstallOpts\n        def build_install_opts(o, options)\n          options[:plugin_sources] = Vagrant::Bundler::DEFAULT_GEM_SOURCES.dup\n\n          o.on(\"--entry-point NAME\", String,\n               \"The name of the entry point file for loading the plugin.\") do |entry_point|\n            options[:entry_point] = entry_point\n          end\n\n          o.on(\"--plugin-clean-sources\",\n            \"Remove all plugin sources defined so far (including defaults)\") do |clean|\n            options[:plugin_sources] = [] if clean\n          end\n\n          o.on(\"--plugin-source PLUGIN_SOURCE\", String,\n               \"Add a RubyGems repository source\") do |plugin_source|\n            options[:plugin_sources] << plugin_source\n          end\n\n          o.on(\"--plugin-version PLUGIN_VERSION\", String,\n               \"Install a specific version of the plugin\") do |plugin_version|\n            options[:plugin_version] = plugin_version\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/command/repair.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire_relative \"base\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Command\n      class Repair < Base\n        def execute\n          options = {}\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant plugin repair [-h]\"\n\n            o.on(\"--local\", \"Repair plugins in local project\") do |l|\n              options[:env_local] = l\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.length > 0\n\n          if Vagrant::Plugin::Manager.instance.local_file\n            action(Action.action_repair_local, env: @env)\n          end\n\n          # Attempt to repair installed plugins\n          action(Action.action_repair, options)\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/command/root.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Command\n      class Root < Vagrant.plugin(\"2\", :command)\n        def self.synopsis\n          \"manages plugins: install, uninstall, update, etc.\"\n        end\n\n        def initialize(argv, env)\n          super\n\n          @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)\n\n          @subcommands = Vagrant::Registry.new\n          @subcommands.register(:expunge) do\n            require_relative \"expunge\"\n            Expunge\n          end\n\n          @subcommands.register(:install) do\n            require_relative \"install\"\n            Install\n          end\n\n          @subcommands.register(:license) do\n            require_relative \"license\"\n            License\n          end\n\n          @subcommands.register(:list) do\n            require_relative \"list\"\n            List\n          end\n\n          @subcommands.register(:repair) do\n            require_relative \"repair\"\n            Repair\n          end\n\n          @subcommands.register(:update) do\n            require_relative \"update\"\n            Update\n          end\n\n          @subcommands.register(:uninstall) do\n            require_relative \"uninstall\"\n            Uninstall\n          end\n        end\n\n        def execute\n          if @main_args.include?(\"-h\") || @main_args.include?(\"--help\")\n            # Print the help for all the sub-commands.\n            return help\n          end\n\n          # If we reached this far then we must have a subcommand. If not,\n          # then we also just print the help and exit.\n          command_class = @subcommands.get(@sub_command.to_sym) if @sub_command\n          return help if !command_class || !@sub_command\n          @logger.debug(\"Invoking command class: #{command_class} #{@sub_args.inspect}\")\n\n          # Initialize and execute the command class\n          command_class.new(@sub_args, @env).execute\n        end\n\n        # Prints the help out for this command\n        def help\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant plugin <command> [<args>]\"\n            o.separator \"\"\n            o.separator \"Available subcommands:\"\n\n            # Add the available subcommands as separators in order to print them\n            # out as well.\n            keys = []\n            @subcommands.each { |key, value| keys << key.to_s }\n\n            keys.sort.each do |key|\n              o.separator \"     #{key}\"\n            end\n            o.separator \"\"\n            o.separator \"For help on any individual command run `vagrant plugin COMMAND -h`\"\n          end\n\n          @env.ui.info(opts.help, prefix: false)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/command/uninstall.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire_relative \"base\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Command\n      class Uninstall < Base\n        def execute\n          options = {}\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant plugin uninstall <name> [<name2> <name3> ...] [-h]\"\n\n            o.on(\"--local\", \"Remove plugin from local project\") do |l|\n              options[:env_local] = l\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.length < 1\n\n          # Uninstall the gems\n          argv.each do |gem|\n            action(Action.action_uninstall, plugin_name: gem, env_local: options[:env_local])\n          end\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/command/update.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire_relative \"base\"\nrequire_relative \"mixin_install_opts\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    module Command\n      class Update < Base\n        include MixinInstallOpts\n\n        def execute\n          options = {}\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant plugin update [names...] [-h]\"\n            o.separator \"\"\n\n            o.on(\"--local\", \"Update plugin in local project\") do |l|\n              options[:env_local] = l\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n\n          # Update the gem\n          action(Action.action_update, {\n            plugin_name:        argv,\n            env_local:          options[:env_local]\n          })\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/gem_helper.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"rubygems\"\nrequire \"rubygems/config_file\"\nrequire \"rubygems/gem_runner\"\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    # This class provides methods to help with calling out to the\n    # `gem` command but using the RubyGems API.\n    class GemHelper\n      def initialize(gem_home)\n        @gem_home = gem_home.to_s\n        @logger   = Log4r::Logger.new(\"vagrant::plugins::plugincommand::gemhelper\")\n      end\n\n      # This will yield the given block with the proper ENV setup so\n      # that RubyGems only sees the gems in the Vagrant-managed gem\n      # path.\n      def with_environment\n        old_gem_home = ENV[\"GEM_HOME\"]\n        old_gem_path = ENV[\"GEM_PATH\"]\n        ENV[\"GEM_HOME\"] = @gem_home\n        ENV[\"GEM_PATH\"] = @gem_home\n        @logger.debug(\"Set GEM_* to: #{ENV[\"GEM_HOME\"]}\")\n\n        # Clear paths so that it reads the new GEM_HOME setting\n        Gem.paths = ENV\n\n        # Set a custom configuration to avoid loading ~/.gemrc loads and\n        # /etc/gemrc and so on.\n        old_config = nil\n        begin\n          old_config = Gem.configuration\n        rescue Psych::SyntaxError\n          # Just ignore this. This means that the \".gemrc\" file has\n          # an invalid syntax and can't be loaded. We don't care, because\n          # when we set Gem.configuration to nil later, it'll force a reload\n          # if it is needed.\n        end\n        Gem.configuration = NilGemConfig.new\n\n        # Clear the sources so that installation uses custom sources\n        old_sources = Gem.sources\n        Gem.sources = Gem.default_sources\n        Vagrant::Bundler::DEFAULT_GEM_SOURCES.each do |source|\n          if !Gem.sources.include?(source)\n            Gem.sources << source\n          end\n        end\n\n        # Use a silent UI so that we have no output\n        Gem::DefaultUserInteraction.use_ui(Gem::SilentUI.new) do\n          return yield\n        end\n      ensure\n        # Restore the old GEM_* settings\n        ENV[\"GEM_HOME\"] = old_gem_home\n        ENV[\"GEM_PATH\"] = old_gem_path\n\n        # Reset everything\n        Gem.configuration = old_config\n        Gem.paths   = ENV\n        Gem.sources = old_sources.to_a\n      end\n\n      # This is pretty hacky but it is a custom implementation of\n      # Gem::ConfigFile so that we don't load any gemrc files.\n      class NilGemConfig < Gem::ConfigFile\n        def initialize\n          # We _can not_ `super` here because that can really mess up\n          # some other configuration state. We need to just set everything\n          # directly.\n\n          @api_keys       = {}\n          @args           = []\n          @backtrace      = false\n          @bulk_threshold = 1000\n          @hash           = {}\n          @update_sources = true\n          @verbose        = true\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/plugin/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandPlugin\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"plugin command\"\n      description <<-DESC\n      This command helps manage and install plugins within the\n      Vagrant environment.\nDESC\n\n      command(\"plugin\") do\n        require File.expand_path(\"../command/root\", __FILE__)\n        Command::Root\n      end\n    end\n\n    autoload :Action, File.expand_path(\"../action\", __FILE__)\n  end\nend\n"
  },
  {
    "path": "plugins/commands/port/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/presence\"\n\nrequire \"optparse\"\n\nmodule VagrantPlugins\n  module CommandPort\n    class Command < Vagrant.plugin(\"2\", :command)\n      include Vagrant::Util::Presence\n\n      def self.synopsis\n        \"displays information about guest port mappings\"\n      end\n\n      def execute\n        options = {}\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant port [options] [name|id]\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"--guest PORT\", \"Output the host port that maps to the given guest port\") do |port|\n            options[:guest] = port\n          end\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        with_target_vms(argv, single_target: true) do |vm|\n          vm.action_raw(:config_validate,\n            Vagrant::Action::Builtin::ConfigValidate)\n\n          if !vm.provider.capability?(:forwarded_ports)\n            @env.ui.error(I18n.t(\"port_command.missing_capability\",\n              provider: vm.provider_name,\n            ))\n            return 1\n          end\n\n          ports = vm.provider.capability(:forwarded_ports)\n\n          if !present?(ports)\n            @env.ui.info(I18n.t(\"port_command.empty_ports\"))\n            return 0\n          end\n\n          if present?(options[:guest])\n            return print_single(vm, ports, options[:guest])\n          else\n            return print_all(vm, ports)\n          end\n        end\n      end\n\n      private\n\n      # Print all the guest <=> host port mappings.\n      # @return [0] the exit code\n      def print_all(vm, ports)\n        @env.ui.info(I18n.t(\"port_command.details\"))\n        @env.ui.info(\"\")\n        ports.each do |host, guest|\n          @env.ui.info(\"#{guest.to_s.rjust(6)} (guest) => #{host} (host)\")\n          @env.ui.machine(\"forwarded_port\", guest, host, target: vm.name.to_s)\n        end\n        return 0\n      end\n\n      # Print the host mapping that matches the given guest target.\n      # @return [0,1] the exit code\n      def print_single(vm, ports, target)\n        map = ports.find { |_, guest| \"#{guest}\" == \"#{target}\" }\n        if !present?(map)\n          @env.ui.error(I18n.t(\"port_command.no_matching_port\",\n            port: target,\n          ))\n          return 1\n        end\n\n        @env.ui.info(\"#{map[0]}\")\n        return 0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/port/locales/en.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  port_command:\n    details: |-\n      The forwarded ports for the machine are listed below. Please note that\n      these values may differ from values configured in the Vagrantfile if the\n      provider supports automatic port collision detection and resolution.\n    empty_ports: |-\n      The provider reported there are no forwarded ports for this virtual\n      machine. This can be caused if there are no ports specified in the\n      Vagrantfile or if the virtual machine is not currently running. Please\n      check that the virtual machine is running and try again.\n    missing_capability: |-\n      The %{provider} provider does not support listing forwarded ports. This is\n      most likely a limitation of the provider and not a bug in Vagrant. If you\n      believe this is a bug in Vagrant, please search existing issues before\n      opening a new one.\n    no_matching_port: |-\n      The guest is not currently mapping port %{port} to the host machine. Is\n      the port configured in the Vagrantfile? You may need to run `vagrant reload`\n      if changes were made to the port configuration in the Vagrantfile.\n"
  },
  {
    "path": "plugins/commands/port/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandPort\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"port command\"\n      description <<-DESC\n      The `port` command displays guest port mappings.\n      DESC\n\n      command(\"port\") do\n        require_relative \"command\"\n        self.init!\n        Command\n      end\n\n      protected\n\n      def self.init!\n        return if defined?(@_init)\n        I18n.load_path << File.expand_path(\"../locales/en.yml\", __FILE__)\n        I18n.reload!\n        @_init = true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/powershell/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"optparse\"\n\nrequire \"vagrant/util/powershell\"\nrequire_relative \"../../communicators/winrm/helper\"\n\nmodule VagrantPlugins\n  module CommandPS\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"connects to machine via powershell remoting\"\n      end\n\n      def execute\n        options = {}\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant powershell [-- extra powershell args]\"\n\n          o.separator \"\"\n          o.separator \"Opens a PowerShell session on the host to the guest\"\n          o.separator \"machine if both support powershell remoting.\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"-c\", \"--command COMMAND\", \"Execute a powershell command directly\") do |c|\n            options[:command] = c\n          end\n\n          o.on(\"-e\", \"--elevated\", \"Execute a powershell command with elevated permissions\") do |c|\n            options[:elevated] = true\n          end\n        end\n\n        # Parse out the extra args to send to the ps session, which\n        # is everything after the \"--\"\n        split_index = @argv.index(\"--\")\n        if split_index\n          options[:extra_args] = @argv.drop(split_index + 1)\n          @argv                = @argv.take(split_index)\n        end\n\n        # Parse the options and return if we don't have any target.\n        argv = parse_options(opts)\n        return if !argv\n\n        # Elevated option enabled means we can only execute commands\n        raise Errors::ElevatedNoCommand if !options[:command] && options[:elevated]\n\n        # Execute ps session if we can\n        with_target_vms(argv, single_target: true) do |machine|\n          if !machine.communicate.ready?\n            raise Vagrant::Errors::VMNotCreatedError\n          end\n\n          if options[:command]\n            if machine.config.vm.communicator != :winrm\n              raise VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady\n            end\n\n            out_code = machine.communicate.execute(options[:command].dup, elevated: options[:elevated]) do |type,data|\n              machine.ui.detail(data) if type == :stdout\n            end\n            if out_code == 0\n              machine.ui.success(\"Command: #{options[:command]} executed successfully with output code #{out_code}.\")\n            end\n            next\n          end\n\n          # Check if the host even supports ps remoting\n          raise Errors::HostUnsupported if !@env.host.capability?(:ps_client)\n\n          ps_info = VagrantPlugins::CommunicatorWinRM::Helper.winrm_info(machine)\n          ps_info[:username] = machine.config.winrm.username\n          ps_info[:password] = machine.config.winrm.password\n          # Extra arguments if we have any\n          ps_info[:extra_args] = options[:extra_args]\n\n          result = ready_ps_remoting_for(machine, ps_info)\n\n          machine.ui.detail(\n            \"Creating powershell session to #{ps_info[:host]}:#{ps_info[:port]}\")\n          machine.ui.detail(\"Username: #{ps_info[:username]}\")\n\n          begin\n            @env.host.capability(:ps_client, ps_info)\n          ensure\n            if result[\"PreviousTrustedHosts\"]\n              reset_ps_remoting_for(machine, ps_info)\n            end\n          end\n        end\n      end\n\n      def ready_ps_remoting_for(machine, ps_info)\n        machine.ui.output(I18n.t(\"vagrant_ps.detecting\"))\n        script_path = File.expand_path(\"../scripts/enable_psremoting.ps1\", __FILE__)\n        args = []\n        args << \"-hostname\" << ps_info[:host]\n        args << \"-port\" << ps_info[:port].to_s\n        args << \"-username\" << ps_info[:username]\n        args << \"-password\" << ps_info[:password]\n        result = Vagrant::Util::PowerShell.execute(script_path, *args)\n        if result.exit_code != 0\n          raise Errors::PowerShellError,\n            script: script_path,\n            stderr: result.stderr\n        end\n\n        result_output = JSON.parse(result.stdout)\n        raise Errors::PSRemotingUndetected if !result_output[\"Success\"]\n        result_output\n      end\n\n      def reset_ps_remoting_for(machine, ps_info)\n        machine.ui.output(I18n.t(\"vagrant_ps.resetting\"))\n        script_path = File.expand_path(\"../scripts/reset_trustedhosts.ps1\", __FILE__)\n        args = []\n        args << \"-hostname\" << ps_info[:host]\n        result = Vagrant::Util::PowerShell.execute(script_path, *args)\n        if result.exit_code != 0\n          raise Errors::PowerShellError,\n            script: script_path,\n            stderr: result.stderr\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/powershell/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommandPS\n    module Errors\n      # A convenient superclass for all our errors.\n      class PSCommandError < Vagrant::Errors::VagrantError\n        error_namespace(\"vagrant_ps.errors\")\n      end\n\n      class HostUnsupported < PSCommandError\n        error_key(:host_unsupported)\n      end\n\n      class PSRemotingUndetected < PSCommandError\n        error_key(:ps_remoting_undetected)\n      end\n\n      class PowerShellError < PSCommandError\n        error_key(:powershell_error)\n      end\n\n      class ElevatedNoCommand < PSCommandError\n        error_key(:elevated_no_command)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/powershell/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandPS\n    autoload :Errors, File.expand_path(\"../errors\", __FILE__)\n\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"powershell command\"\n      description <<-DESC\n      The powershell command opens a remote PowerShell session to the\n      machine if it supports powershell remoting.\n      DESC\n\n      command(\"powershell\") do\n        require_relative \"command\"\n        init!\n        Command\n      end\n\n      protected\n\n      def self.init!\n        return if defined?(@_init)\n        I18n.load_path << File.expand_path(\"templates/locales/command_ps.yml\", Vagrant.source_root)\n        I18n.load_path << File.expand_path(\"templates/locales/comm_winrm.yml\", Vagrant.source_root)\n        I18n.reload!\n        @_init = true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/powershell/scripts/enable_psremoting.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nParam(\n    [string]$hostname,\n    [string]$port,\n    [string]$username,\n    [string]$password\n)\n# If we are in this script, we know basic winrm is working\n# If the user is not using a domain account and chances are\n# they are not, PS Remoting will not work if the guest is not\n# listed in the trusted hosts.\n\n$encrypted_password = ConvertTo-SecureString $password -asplaintext -force\n$creds = New-Object System.Management.Automation.PSCredential (\n    \"$hostname\\\\$username\", $encrypted_password)\n\n$result = @{\n    Success = $false\n    PreviousTrustedHosts = $null\n}\ntry {\n    invoke-command -computername $hostname `\n                   -Credential $creds `\n                   -Port $port `\n                   -ScriptBlock {} `\n                   -ErrorAction Stop\n    $result.Success = $true\n} catch{}\n\nif(!$result.Success) { \n    $newHosts = @()\n    $result.PreviousTrustedHosts=(\n        Get-Item \"wsman:\\localhost\\client\\trustedhosts\").Value\n    $hostArray=$result.PreviousTrustedHosts.Split(\",\").Trim()\n    if($hostArray -contains \"*\") {\n        $result.PreviousTrustedHosts = $null\n    }\n    elseif(!($hostArray -contains $hostname)) {\n        $strNewHosts = $hostname\n        if($result.PreviousTrustedHosts.Length -gt 0){\n            $strNewHosts = $result.PreviousTrustedHosts + \",\" + $strNewHosts\n        }\n        Set-Item -Path \"wsman:\\localhost\\client\\trustedhosts\" `\n          -Value $strNewHosts -Force\n\n        try {\n            invoke-command -computername $hostname `\n                           -Credential $creds `\n                           -Port $port `\n                           -ScriptBlock {} `\n                           -ErrorAction Stop\n            $result.Success = $true\n        } catch{\n            Set-Item -Path \"wsman:\\localhost\\client\\trustedhosts\" `\n              -Value $result.PreviousTrustedHosts -Force\n            $result.PreviousTrustedHosts = $null\n        }\n    }\n}\n\nWrite-Output $(ConvertTo-Json $result)\n"
  },
  {
    "path": "plugins/commands/powershell/scripts/reset_trustedhosts.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nParam(\n    [string]$hostname\n)\n\n$trustedHosts = (\n    Get-Item \"wsman:\\localhost\\client\\trustedhosts\").Value.Replace(\n    $hostname, '')\n$trustedHosts = $trustedHosts.Replace(\",,\",\"\")\nif($trustedHosts.EndsWith(\",\")){\n    $trustedHosts = $trustedHosts.Substring(0,$trustedHosts.length-1)\n}\nSet-Item \"wsman:\\localhost\\client\\trustedhosts\" -Value $trustedHosts -Force"
  },
  {
    "path": "plugins/commands/provider/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandProvider\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"show provider for this environment\"\n      end\n\n      def execute\n        options = {}\n        options[:install] = false\n        options[:usable] = false\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant provider [options] [args]\"\n          o.separator \"\"\n          o.separator \"This command interacts with the provider for this environment.\"\n          o.separator \"With no arguments, it'll output the default provider for this\"\n          o.separator \"environment.\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"--install\", \"Installs the provider if possible\") do |f|\n            options[:install] = f\n          end\n\n          o.on(\"--usable\", \"Checks if the named provider is usable\") do |f|\n            options[:usable] = f\n          end\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        # Get the machine\n        machine = nil\n        with_target_vms(argv, single_target: true) do |m|\n          machine = m\n        end\n\n        # Output some machine readable stuff\n        @env.ui.machine(\"provider-name\", machine.provider_name, target: machine.name.to_s)\n\n        # Check if we're just doing a usability check\n        if options[:usable]\n          @env.ui.output(machine.provider_name.to_s)\n          return 0 if machine.provider.class.usable?(false)\n          return 1\n        end\n\n        # Check if we're requesting installation\n        if options[:install]\n          key = \"provider_install_#{machine.provider_name}\".to_sym\n          if !@env.host.capability?(key)\n            raise Vagrant::Errors::ProviderCantInstall,\n              provider: machine.provider_name.to_s\n          end\n\n          @env.host.capability(key)\n          return\n        end\n\n        # No subtask, just output the provider name\n        @env.ui.output(machine.provider_name.to_s)\n\n        # Success, exit status 0\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/provider/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandProvider\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"provider command\"\n      description <<-DESC\n      The `provider` command is used to interact with the various providers\n      that are installed with Vagrant.\n      DESC\n\n      command(\"provider\", primary: false) do\n        require_relative \"command\"\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/provision/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandProvision\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"provisions the vagrant machine\"\n      end\n\n      def execute\n        options = {}\n        options[:provision_types] = nil\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant provision [vm-name] [--provision-with x,y,z]\"\n\n          o.on(\"--provision-with x,y,z\", Array,\n                    \"Enable only certain provisioners, by type or by name.\") do |list|\n            options[:provision_types] = list.map { |type| type.to_sym }\n          end\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        # Go over each VM and provision!\n        @logger.debug(\"'provision' each target VM...\")\n        with_target_vms(argv) do |machine|\n          machine.action(:provision, options)\n        end\n\n        # Success, exit status 0\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/provision/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandProvision\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"provision command\"\n      description <<-DESC\n      The `provision` command provisions your virtual machine based on the\n      configuration of the Vagrantfile.\n      DESC\n\n      command(\"provision\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/push/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandPush\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"deploys code in this environment to a configured destination\"\n      end\n\n      # @todo support multiple strategies if requested by the community\n      def execute\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant push [strategy] [options]\"\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        name = validate_pushes!(@env.pushes, argv[0])\n\n        # Validate the configuration\n        @env.machine(@env.machine_names.first, @env.default_provider).action_raw(\n          :config_validate,\n          Vagrant::Action::Builtin::ConfigValidate)\n\n        @logger.debug(\"'push' environment with strategy: `#{name}'\")\n        @env.push(name)\n\n        0\n      end\n\n      # Validate that the given list of names corresponds to valid pushes.\n      #\n      # @raise Vagrant::Errors::PushesNotDefined\n      #   if there are no pushes defined\n      # @raise Vagrant::Errors::PushStrategyNotProvided\n      #   if there are multiple push strategies defined and none were specified\n      # @raise Vagrant::Errors::PushStrategyNotDefined\n      #   if the given push name do not correspond to a push strategy\n      #\n      # @param [Array<Symbol>] pushes\n      #   the list of pushes defined by the environment\n      # @param [String] name\n      #   the name provided by the user on the command line\n      #\n      # @return [Symbol]\n      #   the compiled list of pushes\n      #\n      def validate_pushes!(pushes, name = nil)\n        if pushes.nil? || pushes.empty?\n          raise Vagrant::Errors::PushesNotDefined\n        end\n\n        if name.nil?\n          if pushes.length == 1\n            return pushes.first.to_sym\n          else\n            raise Vagrant::Errors::PushStrategyNotProvided,\n              pushes: pushes.map(&:to_s)\n          end\n        end\n\n        name = name.to_sym\n        if !pushes.include?(name)\n          raise Vagrant::Errors::PushStrategyNotDefined,\n            name: name.to_s,\n            pushes: pushes.map(&:to_s)\n        end\n\n        return name\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/push/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandPush\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"push command\"\n      description <<-DESC\n      The `push` command deploys code in this environment.\n      DESC\n\n      command(\"push\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/rdp/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"optparse\"\n\nmodule VagrantPlugins\n  module CommandRDP\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"connects to machine via RDP\"\n      end\n\n      def execute\n        options = {}\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant rdp [options] [name|id] [-- extra args]\"\n        end\n\n        # Parse out the extra args to send to the RDP client, which\n        # is everything after the \"--\"\n        split_index = @argv.index(\"--\")\n        if split_index\n          options[:extra_args] = @argv.drop(split_index + 1)\n          @argv                = @argv.take(split_index)\n        end\n\n        # Parse the options and return if we don't have any target.\n        argv = parse_options(opts)\n        return if !argv\n\n        # Check if the host even supports RDP\n        raise Errors::HostUnsupported if !@env.host.capability?(:rdp_client)\n\n        # Execute RDP if we can\n        with_target_vms(argv, single_target: true) do |machine|\n          if !machine.communicate.ready?\n            raise Vagrant::Errors::VMNotCreatedError\n          end\n\n          machine.ui.output(I18n.t(\"vagrant_rdp.detecting\"))\n          rdp_info = get_rdp_info(machine)\n          raise Errors::RDPUndetected if !rdp_info\n\n          # Extra arguments if we have any\n          rdp_info[:extra_args] = options[:extra_args]\n\n          machine.ui.detail(\n            \"Address: #{rdp_info[:host]}:#{rdp_info[:port]}\")\n          machine.ui.detail(\"Username: #{rdp_info[:username]}\")\n\n          machine.ui.success(I18n.t(\"vagrant_rdp.connecting\"))\n          @env.host.capability(:rdp_client, rdp_info)\n        end\n      end\n\n      protected\n\n      def get_rdp_info(machine)\n        rdp_info = {}\n        if machine.provider.capability?(:rdp_info)\n          rdp_info = machine.provider.capability(:rdp_info)\n          rdp_info ||= {}\n        end\n\n        ssh_info = machine.ssh_info\n\n        if !rdp_info[:username]\n          username = ssh_info[:username]\n          if machine.config.vm.communicator == :winrm\n            username = machine.config.winrm.username\n          end\n          rdp_info[:username] = username\n        end\n\n        if !rdp_info[:password]\n          password = ssh_info[:password]\n          if machine.config.vm.communicator == :winrm\n            password = machine.config.winrm.password\n          end\n          rdp_info[:password] = password\n        end\n\n        rdp_info[:host] ||= ssh_info[:host]\n        rdp_info[:port] ||= machine.config.rdp.port\n        rdp_info[:username] ||= machine.config.rdp.username\n\n        if rdp_info[:host] == \"127.0.0.1\"\n          # We need to find a forwarded port...\n          search_port = machine.config.rdp.search_port\n          ports       = nil\n          if machine.provider.capability?(:forwarded_ports)\n            ports = machine.provider.capability(:forwarded_ports)\n          else\n            ports = {}.tap do |result|\n              machine.config.vm.networks.each do |type, netopts|\n                next if type != :forwarded_port\n                next if !netopts[:host]\n                result[netopts[:host]] = netopts[:guest]\n              end\n            end\n          end\n\n          ports = ports.invert\n          port  = ports[search_port]\n          rdp_info[:port] = port\n          return nil if !port\n        end\n\n        return rdp_info\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/rdp/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommandRDP\n    class Config < Vagrant.plugin(\"2\", :config)\n      attr_accessor :port\n      attr_accessor :search_port\n      attr_accessor :username\n\n      def initialize\n        @port        = UNSET_VALUE\n        @search_port = UNSET_VALUE\n        @username = UNSET_VALUE\n      end\n\n      def finalize!\n        @port        = 3389 if @port == UNSET_VALUE\n        @search_port = 3389 if @search_port == UNSET_VALUE\n        @username = nil if @username == UNSET_VALUE\n      end\n\n      def validate(machine)\n        errors = _detected_errors\n        { \"RDP\" => errors }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/rdp/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommandRDP\n    module Errors\n      # A convenient superclass for all our errors.\n      class RDPError < Vagrant::Errors::VagrantError\n        error_namespace(\"vagrant_rdp.errors\")\n      end\n\n      class HostUnsupported < RDPError\n        error_key(:host_unsupported)\n      end\n\n      class RDPUndetected < RDPError\n        error_key(:rdp_undetected)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/rdp/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandRDP\n    autoload :Errors, File.expand_path(\"../errors\", __FILE__)\n\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"rdp command\"\n      description <<-DESC\n      The rdp command opens a remote desktop Window to the\n      machine if it supports RDP.\n      DESC\n\n      command(\"rdp\") do\n        require File.expand_path(\"../command\", __FILE__)\n        init!\n        Command\n      end\n\n      config(\"rdp\") do\n        require_relative \"config\"\n        Config\n      end\n\n      protected\n\n      def self.init!\n        return if defined?(@_init)\n        I18n.load_path << File.expand_path(\n          \"templates/locales/command_rdp.yml\", Vagrant.source_root)\n        I18n.reload!\n        @_init = true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/reload/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire \"vagrant\"\n\nrequire Vagrant.source_root.join(\"plugins/commands/up/start_mixins\")\n\nmodule VagrantPlugins\n  module CommandReload\n    class Command < Vagrant.plugin(\"2\", :command)\n      # We assume that the `up` plugin exists and that we'll have access\n      # to this.\n      include VagrantPlugins::CommandUp::StartMixins\n\n      def self.synopsis\n        \"restarts vagrant machine, loads new Vagrantfile configuration\"\n      end\n\n      def execute\n        options = {}\n        options[:provision_ignore_sentinel] = false\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant reload [vm-name]\"\n          o.separator \"\"\n          build_start_options(o, options)\n          o.on(\"-f\", \"--force\", \"Force shut down (equivalent of pulling power)\") do |f|\n            options[:force_halt] = f\n          end\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        # Validate the provisioners\n        validate_provisioner_flags!(options, argv)\n\n        @logger.debug(\"'reload' each target VM...\")\n        machines = []\n        with_target_vms(argv) do |machine|\n          machines << machine\n          machine.action(:reload, options)\n        end\n\n        # Output the post-up messages that we have, if any\n        machines.each do |m|\n          next if !m.config.vm.post_up_message\n          next if m.config.vm.post_up_message == \"\"\n\n          # Add a newline to separate things.\n          @env.ui.info(\"\", prefix: false)\n\n          m.ui.success(I18n.t(\n            \"vagrant.post_up_message\",\n            name: m.name.to_s,\n            message: m.config.vm.post_up_message))\n        end\n\n        # Success, exit status 0\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/reload/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandReload\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"reload command\"\n      description <<-DESC\n      The `reload` command will halt, reconfigure your machine based on\n      the Vagrantfile, and bring it back up.\n      DESC\n\n      command(\"reload\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/resume/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire Vagrant.source_root.join(\"plugins/commands/up/start_mixins\")\n\nmodule VagrantPlugins\n  module CommandResume\n    class Command < Vagrant.plugin(\"2\", :command)\n      # We assume that the `up` plugin exists and that we'll have access\n      # to this.\n      include VagrantPlugins::CommandUp::StartMixins\n\n      def self.synopsis\n        \"resume a suspended vagrant machine\"\n      end\n\n      def execute\n        options = {}\n        options[:provision_ignore_sentinel] = false\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant resume [vm-name]\"\n          o.separator \"\"\n          build_start_options(o, options)\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        # Validate the provisioners\n        validate_provisioner_flags!(options, argv)\n\n        @logger.debug(\"'resume' each target VM...\")\n        with_target_vms(argv) do |machine|\n          machine.action(:resume, options)\n        end\n\n        # Success, exit status 0\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/resume/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandResume\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"resume command\"\n      description <<-DESC\n      The `resume` command resumes a suspend virtual machine.\n      DESC\n\n      command(\"resume\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/snapshot/command/delete.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandSnapshot\n    module Command\n      class Delete < Vagrant.plugin(\"2\", :command)\n        def execute\n          options = {}\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant snapshot delete [options] [vm-name] <name>\"\n            o.separator \"\"\n            o.separator \"Delete a snapshot taken previously with snapshot save.\"\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          if argv.empty? || argv.length > 2\n            raise Vagrant::Errors::CLIInvalidUsage,\n              help: opts.help.chomp\n          end\n\n          name = argv.pop\n          with_target_vms(argv) do |vm|\n            if !vm.provider.capability?(:snapshot_list)\n              raise Vagrant::Errors::SnapshotNotSupported\n            end\n\n            snapshot_list = vm.provider.capability(:snapshot_list)\n\n            if snapshot_list.include? name\n              vm.action(:snapshot_delete, snapshot_name: name)\n            else\n              raise Vagrant::Errors::SnapshotNotFound,\n                snapshot_name: name,\n                machine: vm.name.to_s\n            end\n          end\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/snapshot/command/list.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandSnapshot\n    module Command\n      class List < Vagrant.plugin(\"2\", :command)\n        def execute\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant snapshot list [options] [vm-name]\"\n            o.separator \"\"\n            o.separator \"List all snapshots taken for a machine.\"\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n\n          with_target_vms(argv) do |vm|\n            if !vm.id\n              vm.ui.info(I18n.t(\"vagrant.commands.common.vm_not_created\"))\n              next\n            end\n\n            if !vm.provider.capability?(:snapshot_list)\n              raise Vagrant::Errors::SnapshotNotSupported\n            end\n\n            snapshots = vm.provider.capability(:snapshot_list)\n            if snapshots.empty?\n              vm.ui.output(I18n.t(\"vagrant.actions.vm.snapshot.list_none\"))\n              vm.ui.detail(I18n.t(\"vagrant.actions.vm.snapshot.list_none_detail\"))\n              next\n            end\n\n            vm.ui.output(\"\", prefix: true)\n            snapshots.each do |snapshot|\n              vm.ui.detail(snapshot, prefix: false)\n            end\n          end\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/snapshot/command/pop.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'json'\nrequire 'optparse'\n\nrequire Vagrant.source_root.join(\"plugins/commands/up/start_mixins\")\n\nrequire_relative \"push_shared\"\n\nmodule VagrantPlugins\n  module CommandSnapshot\n    module Command\n      class Pop < Vagrant.plugin(\"2\", :command)\n        include PushShared\n        include VagrantPlugins::CommandUp::StartMixins\n\n        def execute\n          options = {}\n          options[:snapshot_delete] = true\n          options[:provision_ignore_sentinel] = false\n          options[:snapshot_start] = true\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant snapshot pop [options] [vm-name]\"\n            o.separator \"\"\n            o.separator \"Restore state that was pushed onto the snapshot stack\"\n            o.separator \"with `vagrant snapshot push`.\"\n            o.separator \"\"\n            build_start_options(o, options)\n\n            o.on(\"--no-delete\", \"Don't delete the snapshot after the restore\") do\n                options[:snapshot_delete] = false\n            end\n            o.on(\"--no-start\", \"Don't start the snapshot after the restore\") do\n              options[:snapshot_start] = false\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n\n          # Validate the provisioners\n          validate_provisioner_flags!(options, argv)\n\n          return shared_exec(argv, method(:pop), options)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/snapshot/command/push.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'json'\nrequire 'optparse'\n\nrequire_relative \"push_shared\"\n\nmodule VagrantPlugins\n  module CommandSnapshot\n    module Command\n      class Push < Vagrant.plugin(\"2\", :command)\n        include PushShared\n\n        def execute\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant snapshot push [options] [vm-name]\"\n            o.separator \"\"\n            o.separator \"Take a snapshot of the current state of the machine and 'push'\"\n            o.separator \"it onto the stack of states. You can use `vagrant snapshot pop`\"\n            o.separator \"to restore back to this state at any time.\"\n            o.separator \"\"\n            o.separator \"If you use `vagrant snapshot save` or restore at any point after\"\n            o.separator \"a push, pop will still bring you back to this pushed state.\"\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n\n          return shared_exec(argv, method(:push))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/snapshot/command/push_shared.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'json'\n\nmodule VagrantPlugins\n  module CommandSnapshot\n    module Command\n      module PushShared\n        def shared_exec(argv, m, opts={})\n          with_target_vms(argv) do |vm|\n            if !vm.id\n              vm.ui.info(\"Not created. Cannot push snapshot state.\")\n              next\n            end\n\n            vm.env.lock(\"machine-snapshot-stack\") do\n              m.call(vm, opts)\n            end\n          end\n\n          # Success, exit with 0\n          0\n        end\n\n        def push(machine, opts={})\n          snapshot_name = \"push_#{Time.now.to_i}_#{rand(10000)}\"\n\n          # Save the snapshot. This will raise an exception if it fails.\n          machine.action(:snapshot_save, snapshot_name: snapshot_name)\n        end\n\n        def pop(machine, opts={})\n          # By reverse sorting, we should be able to find the first\n          # pushed snapshot.\n          name = nil\n          snapshots = machine.provider.capability(:snapshot_list)\n          snapshots.sort.reverse.each do |snapshot|\n            if snapshot =~ /^push_\\d+_\\d+$/\n              name = snapshot\n              break\n            end\n          end\n\n          # If no snapshot was found, we never pushed\n          if !name\n            machine.ui.info(I18n.t(\"vagrant.commands.snapshot.no_push_snapshot\"))\n            return\n          end\n\n          # Restore the snapshot and tell the provider to delete it, if required\n          opts[:snapshot_name] = name\n          machine.action(:snapshot_restore, opts)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/snapshot/command/restore.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire Vagrant.source_root.join(\"plugins/commands/up/start_mixins\")\n\nmodule VagrantPlugins\n  module CommandSnapshot\n    module Command\n      class Restore < Vagrant.plugin(\"2\", :command)\n\n        include VagrantPlugins::CommandUp::StartMixins\n\n        def execute\n          options = {}\n          options[:provision_ignore_sentinel] = false\n          options[:snapshot_start] = true\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant snapshot restore [options] [vm-name] <name>\"\n            o.separator \"\"\n            build_start_options(o, options)\n            o.separator \"Restore a snapshot taken previously with snapshot save.\"\n\n            o.on(\"--no-start\", \"Don't start the snapshot after the restore\") do\n              options[:snapshot_start] = false\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          if argv.empty? || argv.length > 2\n            raise Vagrant::Errors::CLIInvalidUsage,\n              help: opts.help.chomp\n          end\n\n          # Validate the provisioners\n          validate_provisioner_flags!(options, argv)\n\n          name = argv.pop\n          options[:snapshot_name] = name\n\n          with_target_vms(argv) do |vm|\n            if !vm.provider.capability?(:snapshot_list)\n              raise Vagrant::Errors::SnapshotNotSupported\n            end\n\n            snapshot_list = vm.provider.capability(:snapshot_list)\n\n            if snapshot_list.include? name\n              vm.action(:snapshot_restore, options)\n            else\n              raise Vagrant::Errors::SnapshotNotFound,\n                snapshot_name: name,\n                machine: vm.name.to_s\n            end\n          end\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/snapshot/command/root.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandSnapshot\n    module Command\n      class Root < Vagrant.plugin(\"2\", :command)\n        def self.synopsis\n          \"manages snapshots: saving, restoring, etc.\"\n        end\n\n        def initialize(argv, env)\n          super\n\n          @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)\n\n          @subcommands = Vagrant::Registry.new\n          @subcommands.register(:save) do\n            require_relative \"save\"\n            Save\n          end\n\n          @subcommands.register(:restore) do\n            require_relative \"restore\"\n            Restore\n          end\n\n          @subcommands.register(:delete) do\n            require_relative \"delete\"\n            Delete\n          end\n\n          @subcommands.register(:list) do\n            require_relative \"list\"\n            List\n          end\n\n          @subcommands.register(:push) do\n            require_relative \"push\"\n            Push\n          end\n\n          @subcommands.register(:pop) do\n            require_relative \"pop\"\n            Pop\n          end\n        end\n\n        def execute\n          if @main_args.include?(\"-h\") || @main_args.include?(\"--help\")\n            # Print the help for all the commands.\n            @env.ui.info(help, prefix: false)\n            return 0\n          end\n\n          # If we reached this far then we must have a subcommand. If not,\n          # then we also just print the help and exit.\n          command_class = @subcommands.get(@sub_command.to_sym) if @sub_command\n          if !command_class || !@sub_command\n            raise Vagrant::Errors::CLIInvalidUsage,\n              help: help()\n          end\n          @logger.debug(\"Invoking command class: #{command_class} #{@sub_args.inspect}\")\n\n          # Initialize and execute the command class\n          command_class.new(@sub_args, @env).execute\n        end\n\n        # Prints the help out for this command\n        def help\n          opts = OptionParser.new do |opts|\n            opts.banner = \"Usage: vagrant snapshot <subcommand> [<args>]\"\n            opts.separator \"\"\n            opts.separator \"Available subcommands:\"\n\n            # Add the available subcommands as separators in order to print them\n            # out as well.\n            keys = []\n            @subcommands.each { |key, value| keys << key.to_s }\n\n            keys.sort.each do |key|\n              opts.separator \"     #{key}\"\n            end\n            opts.separator \"\"\n            opts.separator \"For help on any individual subcommand run `vagrant snapshot <subcommand> -h`\"\n          end\n\n          opts.help\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/snapshot/command/save.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandSnapshot\n    module Command\n      class Save < Vagrant.plugin(\"2\", :command)\n        def execute\n          options = {}\n          options[:force] = false\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant snapshot save [options] [vm-name] <name>\"\n            o.separator \"\"\n            o.separator \"Take a snapshot of the current state of the machine. The snapshot\"\n            o.separator \"can be restored via `vagrant snapshot restore` at any point in the\"\n            o.separator \"future to get back to this exact machine state.\"\n            o.separator \"\"\n            o.separator \"If no vm-name is given, Vagrant will take a snapshot of\"\n            o.separator \"the entire environment with the same snapshot name.\"\n            o.separator \"\"\n            o.separator \"Snapshots are useful for experimenting in a machine and being able\"\n            o.separator \"to rollback quickly.\"\n\n            o.on(\"-f\", \"--force\", \"Replace snapshot without confirmation\") do |f|\n              options[:force] = f\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n          if argv.empty? || argv.length > 2\n            raise Vagrant::Errors::CLIInvalidUsage,\n              help: opts.help.chomp\n          end\n\n          name = argv.pop\n\n          with_target_vms(argv) do |vm|\n            if !vm.provider.capability?(:snapshot_list)\n              raise Vagrant::Errors::SnapshotNotSupported\n            end\n\n            # In this case, no vm name was given, and we are iterating over the\n            # entire environment. If a vm hasn't been created yet, we can't list\n            # its snapshots\n            if vm.id.nil?\n              @env.ui.warn(I18n.t(\"vagrant.commands.snapshot.save.vm_not_created\",\n                                  name: vm.name))\n              next\n            end\n\n            snapshot_list = vm.provider.capability(:snapshot_list)\n\n            if !snapshot_list.include? name\n              vm.action(:snapshot_save, snapshot_name: name)\n            elsif options[:force]\n              # not a unique snapshot name\n              vm.action(:snapshot_delete, snapshot_name: name)\n              vm.action(:snapshot_save, snapshot_name: name)\n            else\n              raise Vagrant::Errors::SnapshotConflictFailed\n            end\n          end\n\n          # Success, exit status 0\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/snapshot/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandSnapshot\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"snapshot command\"\n      description \"The `snapshot` command gives you a way to manage snapshots.\"\n\n      command(\"snapshot\") do\n        require_relative \"command/root\"\n        Command::Root\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/ssh/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandSSH\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"connects to machine via SSH\"\n      end\n\n      def execute\n        options = {}\n        options[:tty] = true\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant ssh [options] [name|id] [-- extra ssh args]\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"-c\", \"--command COMMAND\", \"Execute an SSH command directly\") do |c|\n            options[:command] = c\n          end\n\n          o.on(\"-p\", \"--plain\", \"Plain mode, leaves authentication up to user\") do |p|\n            options[:plain_mode] = p\n          end\n\n          o.on(\"-t\", \"--[no-]tty\", \"Enables tty when executing an ssh command (defaults to true)\") do |t|\n            options[:tty] = t\n          end\n        end\n\n        # Parse out the extra args to send to SSH, which is everything\n        # after the \"--\"\n        split_index = @argv.index(\"--\")\n        if split_index\n          options[:ssh_args] = @argv.drop(split_index + 1)\n          @argv              = @argv.take(split_index)\n        end\n\n        # Parse the options and return if we don't have any target.\n        argv = parse_options(opts)\n        return if !argv\n\n        # Execute the actual SSH\n        with_target_vms(argv, single_target: true) do |vm|\n          ssh_opts = {\n            plain_mode: options[:plain_mode],\n            extra_args: options[:ssh_args]\n          }\n\n          if options[:command]\n            @logger.debug(\"Executing single command on remote machine: #{options[:command]}\")\n            env = vm.action(:ssh_run,\n                            ssh_opts: ssh_opts,\n                            ssh_run_command: options[:command],\n                            tty: options[:tty],)\n\n            # Exit with the exit status of the command or a 0 if we didn't\n            # get one.\n            exit_status = env[:ssh_run_exit_status] || 0\n            return exit_status\n          else\n            Vagrant::Bundler.instance.deinit\n            @logger.debug(\"Invoking `ssh` action on machine\")\n            vm.action(:ssh, ssh_opts: ssh_opts)\n\n            # We should never reach this point, since the point of `ssh`\n            # is to exec into the proper SSH shell, but we'll just return\n            # an exit status of 0 anyways.\n            return 0\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/ssh/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandSSH\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"ssh command\"\n      description <<-DESC\n      The `ssh` command allows you to SSH in to your running virtual machine.\n      DESC\n\n      command(\"ssh\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/ssh_config/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire \"vagrant/util/safe_puts\"\nrequire \"vagrant/util/platform\"\n\nmodule VagrantPlugins\n  module CommandSSHConfig\n    class Command < Vagrant.plugin(\"2\", :command)\n      include Vagrant::Util::SafePuts\n\n      def self.synopsis\n        \"outputs OpenSSH valid configuration to connect to the machine\"\n      end\n\n      def convert_win_paths(paths)\n        paths.map! { |path| Vagrant::Util::Platform.format_windows_path(path, :disable_unc) }\n      end\n\n      def execute\n        options = {}\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant ssh-config [options] [name|id]\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"--host NAME\", \"Name the host for the config\") do |h|\n            options[:host] = h\n          end\n        end\n\n        argv = parse_options(opts)\n        return if !argv\n\n        with_target_vms(argv) do |machine|\n          ssh_info = machine.ssh_info\n          raise Vagrant::Errors::SSHNotReady if ssh_info.nil?\n\n          if Vagrant::Util::Platform.windows?\n            ssh_info[:private_key_path] = convert_win_paths(ssh_info[:private_key_path])\n          end\n\n          variables = {\n            host_key: options[:host] || machine.name || \"vagrant\",\n            ssh_host: ssh_info[:host],\n            ssh_port: ssh_info[:port],\n            ssh_user: ssh_info[:username],\n            keys_only: ssh_info[:keys_only],\n            verify_host_key: ssh_info[:verify_host_key],\n            private_key_path: ssh_info[:private_key_path],\n            log_level: ssh_info[:log_level],\n            forward_agent: ssh_info[:forward_agent],\n            forward_x11:   ssh_info[:forward_x11],\n            proxy_command: ssh_info[:proxy_command],\n            ssh_command:   ssh_info[:ssh_command],\n            forward_env:   ssh_info[:forward_env],\n            config:        ssh_info[:config],\n            disable_deprecated_algorithms: ssh_info[:disable_deprecated_algorithms]\n          }\n\n          # Render the template and output directly to STDOUT\n          template = \"commands/ssh_config/config\"\n          config   = Vagrant::Util::TemplateRenderer.render(template, variables)\n          machine.ui.machine(\"ssh-config\", config)\n          safe_puts(config)\n          safe_puts\n        end\n\n        # Success, exit status 0\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/ssh_config/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandSSHConfig\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"ssh-config command\"\n      description <<-DESC\n      The `ssh-config` command dumps an OpenSSH compatible configuration\n      that can be used to quickly SSH into your virtual machine.\n      DESC\n\n      command(\"ssh-config\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/status/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandStatus\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"outputs status of the vagrant machine\"\n      end\n\n      def execute\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant status [name|id]\"\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        max_name_length = 25\n        with_target_vms(argv) do |machine|\n          max_name_length = machine.name.length if machine.name.length > max_name_length\n        end\n\n        state = nil\n        results = []\n        with_target_vms(argv) do |machine|\n          state = machine.state if !state\n          current_state = machine.state\n          results << \"#{machine.name.to_s.ljust(max_name_length)} \" +\n            \"#{current_state.short_description} (#{machine.provider_name})\"\n\n          opts = { target: machine.name.to_s }\n          @env.ui.machine(\"provider-name\", machine.provider_name, opts)\n          @env.ui.machine(\"state\", current_state.id, opts)\n          @env.ui.machine(\"state-human-short\", current_state.short_description, opts)\n          @env.ui.machine(\"state-human-long\", current_state.long_description, opts)\n        end\n\n        message = nil\n        if results.length == 1\n          message = state.long_description\n        else\n          message = I18n.t(\"vagrant.commands.status.listing\")\n        end\n\n        @env.ui.info(I18n.t(\"vagrant.commands.status.output\",\n                            states: results.join(\"\\n\"),\n                            message: message),\n                     prefix: false)\n\n        # Success, exit status 0\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/status/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandStatus\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"status command\"\n      description <<-DESC\n      The `status` command shows what the running state (running/saved/..)\n      is of all your virtual machines in this environment.\n      DESC\n\n      command(\"status\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/suspend/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandSuspend\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"suspends the machine\"\n      end\n\n      def execute\n        options = {}\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant suspend [options] [name|id]\"\n          o.separator \"\"\n          o.on(\"-a\", \"--all-global\", \"Suspend all running vms globally.\") do |p|\n            options[:all] = true\n          end\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        @logger.debug(\"'suspend' each target VM...\")\n        target = []\n        if options[:all]\n          if argv.size > 0\n            raise Vagrant::Errors::CommandSuspendAllArgs\n          end\n\n          m = @env.machine_index.each { |m| m }\n          target = m.keys\n        else\n          target = argv\n        end\n\n        with_target_vms(target) do |vm|\n          vm.action(:suspend)\n        end\n\n        # Success, exit status 0\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/suspend/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandSuspend\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"suspend command\"\n      description <<-DESC\n      The `suspend` command suspends execution and puts it to sleep.\n      The command `resume` returns it to running status.\n      DESC\n\n      command(\"suspend\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/up/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\nrequire 'set'\n\nrequire File.expand_path(\"../start_mixins\", __FILE__)\n\nmodule VagrantPlugins\n  module CommandUp\n    class Command < Vagrant.plugin(\"2\", :command)\n      include StartMixins\n\n      def self.synopsis\n        \"starts and provisions the vagrant environment\"\n      end\n\n      def execute\n        options = {}\n        options[:destroy_on_error] = true\n        options[:install_provider] = true\n        options[:parallel] = true\n        options[:provision_ignore_sentinel] = false\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant up [options] [name|id]\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          build_start_options(o, options)\n\n          o.on(\"--[no-]destroy-on-error\",\n               \"Destroy machine if any fatal error happens (default to true)\") do |destroy|\n            options[:destroy_on_error] = destroy\n          end\n\n          o.on(\"--[no-]parallel\",\n               \"Enable or disable parallelism if provider supports it\") do |parallel|\n            options[:parallel] = parallel\n          end\n\n          o.on(\"--provider PROVIDER\", String,\n               \"Back the machine with a specific provider\") do |provider|\n            options[:provider] = provider\n          end\n\n          o.on(\"--[no-]install-provider\",\n               \"If possible, install the provider if it isn't installed\") do |p|\n            options[:install_provider] = p\n          end\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        # Validate the provisioners\n        validate_provisioner_flags!(options, argv)\n\n        # Go over each VM and bring it up\n        @logger.debug(\"'Up' each target VM...\")\n\n        # Get the names of the machines we want to bring up\n        names = argv\n        if names.empty?\n          autostart = false\n          @env.vagrantfile.machine_names_and_options.each do |n, o|\n            autostart = true if o.key?(:autostart)\n            o[:autostart] = true if !o.key?(:autostart)\n            names << n.to_s if o[:autostart]\n          end\n\n          # If we have an autostart key but no names, it means that\n          # all machines are autostart: false and we don't start anything.\n          names = nil if autostart && names.empty?\n        end\n\n        # Build up the batch job of what we'll do\n        machines = []\n        if names\n          # To prevent vagrant from attempting to validate a global vms config\n          # (which doesn't exist within the local dir) when attempting to\n          # install a machines provider, this check below will disable the\n          # install_providers function if a user gives us a machine id instead\n          # of the machines name.\n          machine_names = []\n          with_target_vms(names, provider: options[:provider]){|m| machine_names << m.name }\n          options[:install_provider] = false if !(machine_names - names).empty?\n\n          # If we're installing providers, then do that. We don't\n          # parallelize this step because it is likely the same provider\n          # anyways.\n          if options[:install_provider]\n            install_providers(names, provider: options[:provider])\n          end\n\n          @env.batch(options[:parallel]) do |batch|\n            with_target_vms(names, provider: options[:provider]) do |machine|\n              @env.ui.info(I18n.t(\n                \"vagrant.commands.up.upping\",\n                name: machine.name,\n                provider: machine.provider_name))\n\n              machines << machine\n\n              batch.action(machine, :up, options)\n            end\n          end\n        end\n\n        if machines.empty?\n          @env.ui.info(I18n.t(\"vagrant.up_no_machines\"))\n          return 0\n        end\n\n        # Output the post-up messages that we have, if any\n        machines.each do |m|\n          next if !m.config.vm.post_up_message\n          next if m.config.vm.post_up_message == \"\"\n\n          # Add a newline to separate things.\n          @env.ui.info(\"\", prefix: false)\n\n          m.ui.success(I18n.t(\n            \"vagrant.post_up_message\",\n            name: m.name.to_s,\n            message: m.config.vm.post_up_message))\n        end\n\n        # Success, exit status 0\n        0\n      end\n\n      protected\n\n      def install_providers(names, provider: nil)\n        # First create a set of all the providers we need to check for.\n        # Most likely this will be a set of one.\n        providers = Set.new\n        with_target_vms(names, provider: provider) do |machine|\n          # Check if we have this machine in the index\n          entry    = @env.machine_index.get(machine.name.to_s)\n\n          # Get the provider for this machine. This logic isn't completely\n          # straightforward. If we have a forced provider, we always use\n          # that no matter what. If we have an entry in the index (meaning\n          # the machine may be created), we use that provider no matter\n          # what since that will be used by the core. If we have none, then\n          # we ask the Vagrant env what the default provider would be and use\n          # that.\n          #\n          # Note that this logic is a bit redundant if we have \"provider\"\n          # set but I think its probably cleaner to put this logic in one\n          # place.\n          p = provider\n          p = entry.provider.to_sym if !p && entry\n          p = @env.default_provider(\n            machine: machine.name.to_sym, check_usable: false) if !p\n\n          # Add it to the set\n          providers.add(p)\n        end\n\n        # Go through and determine if we can install the providers\n        providers.delete_if do |name|\n          !@env.can_install_provider?(name)\n        end\n\n        # Install the providers if we have to\n        providers.each do |name|\n          # Find the provider. Ignore if we can't find it, this error\n          # will pop up later in the process.\n          parts = Vagrant.plugin(\"2\").manager.providers[name]\n          next if !parts\n\n          # If the provider is already installed, then our work here is done\n          cls = parts[0]\n          next if cls.installed?\n\n          # Some human-friendly output\n          ui = Vagrant::UI::Prefixed.new(@env.ui, \"\")\n          ui.output(I18n.t(\n            \"vagrant.installing_provider\",\n            provider: name.to_s))\n          ui.detail(I18n.t(\"vagrant.installing_provider_detail\"))\n\n          # Install the provider\n          @env.install_provider(name)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/up/middleware/store_box_metadata.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\n\nmodule VagrantPlugins\n  module CommandUp\n    # Stores metadata information about the box used\n    # for the current guest. This allows Vagrant to\n    # determine the box currently in use when the\n    # Vagrantfile is modified with a new box name or\n    # version while the guest still exists.\n    class StoreBoxMetadata\n      def initialize(app, env)\n        @app = app\n        @logger = Log4r::Logger.new(\"vagrant::up::storeboxmetadata\")\n      end\n\n      def call(env)\n        if env[:machine].box\n          box = env[:machine].box\n          box_meta = {\n            name: box.name,\n            version: box.version,\n            provider: box.provider,\n            directory: box.directory.sub(Vagrant.user_data_path.to_s + \"/\", \"\")\n          }\n          meta_file = env[:machine].data_dir.join(\"box_meta\")\n          @logger.debug(\"Writing box metadata file to #{meta_file}\")\n          File.open(meta_file.to_s, \"w+\") do |file|\n            file.write(JSON.dump(box_meta))\n          end\n        else\n          @logger.debug(\"No box data found for #{env[:machine].name} with provider #{env[:machine].provider_name}\")\n        end\n        @app.call(env)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/up/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandUp\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"up command\"\n      description <<-DESC\n      The `up` command brings the virtual environment up and running.\n      DESC\n\n      command(\"up\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n\n      action_hook(:store_box_metadata, :machine_action_up) do |hook|\n        require_relative \"middleware/store_box_metadata\"\n        hook.append(StoreBoxMetadata)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/up/start_mixins.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"set\"\n\nmodule VagrantPlugins\n  module CommandUp\n    module StartMixins\n      # This adds the standard `start` command line flags to the given\n      # OptionParser, storing the result in the `options` dictionary.\n      #\n      # @param [OptionParser] parser\n      # @param [Hash] options\n      def build_start_options(parser, options)\n        # Setup the defaults\n        options[:provision_types] = nil\n\n        # Add the options\n        parser.on(\"--[no-]provision\", \"Enable or disable provisioning\") do |p|\n          options[:provision_enabled] = p\n          options[:provision_ignore_sentinel] = true\n        end\n\n        parser.on(\"--provision-with x,y,z\", Array,\n                \"Enable only certain provisioners, by type or by name.\") do |list|\n          options[:provision_types] = list.map { |type| type.to_sym }\n          options[:provision_enabled] = true\n          options[:provision_ignore_sentinel] = true\n        end\n      end\n\n      # This validates the provisioner flags and raises an exception\n      # if there are invalid ones.\n      def validate_provisioner_flags!(options, argv)\n        if options[:provision_types].nil?\n          return\n        end\n\n        provisioner_names = Set.new\n        with_target_vms(argv) do |machine|\n          machine.config.vm.provisioners.map(&:name).each do |name|\n            provisioner_names.add(name)\n          end\n        end\n\n        if (provisioner_names & options[:provision_types]).empty?\n          (options[:provision_types] || []).each do |type|\n              klass = Vagrant.plugin(\"2\").manager.provisioners[type]\n              if !klass\n                raise Vagrant::Errors::ProvisionerFlagInvalid,\n                  name: type.to_s\n              end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/upload/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\nrequire \"rubygems/package\"\n\nmodule VagrantPlugins\n  module CommandUpload\n    class Command < Vagrant.plugin(\"2\", :command)\n\n      VALID_COMPRESS_TYPES = [:tgz, :zip].freeze\n\n      def self.synopsis\n        \"upload to machine via communicator\"\n      end\n\n      def execute\n        options = {}\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant upload [options] <source> [destination] [name|id]\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"-t\", \"--temporary\", \"Upload source to temporary directory\") do |t|\n            options[:temporary] = t\n          end\n\n          o.on(\"-c\", \"--compress\", \"Use gzip compression for upload\") do |c|\n            options[:compress] = c\n          end\n\n          o.on(\"-C\", \"--compression-type=TYPE\", \"Type of compression to use (#{VALID_COMPRESS_TYPES.join(\", \")})\") do |c|\n            options[:compression_type] = c.to_sym\n            options[:compress] = true\n          end\n        end\n\n        argv = parse_options(opts)\n        return if !argv\n\n        case argv.size\n        when 3\n          source, destination, guest = argv\n        when 2, 1\n          source = argv[0]\n          if @env.active_machines.map(&:first).map(&:to_s).include?(argv[1])\n            guest = argv[1]\n          else\n            destination = argv[1]\n          end\n        else\n          raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp\n        end\n\n        # NOTE: We do this to handle paths on Windows like: \"..\\space dir\\\"\n        # because the final separator acts to escape the quote and ends up\n        # in the source value.\n        source = source.sub(/[\"']$/, \"\")\n        destination ||= File.basename(source)\n\n        if File.file?(source)\n          type = :file\n        elsif File.directory?(source)\n          type = :directory\n        else\n          raise Vagrant::Errors::UploadSourceMissing,\n            source: source\n        end\n\n        with_target_vms(guest, single_target: true) do |machine|\n          if options[:temporary]\n            if !machine.guest.capability?(:create_tmp_path)\n              raise Vagrant::Errors::UploadMissingTempCapability\n            end\n            extension = File.extname(source) if type == :file\n            destination = machine.guest.capability(:create_tmp_path, type: type, extension: extension)\n          end\n\n          if options[:compress]\n            compression_setup!(machine, options)\n            @env.ui.info(I18n.t(\"vagrant.commands.upload.compress\",\n              source: source,\n              type: options[:compression_type]\n            ))\n            destination_decompressed = destination\n            destination = machine.guest.capability(:create_tmp_path, type: :file, extension: \".#{options[:compression_type]}\")\n            source_display = source\n            source = options[:compression_type] == :zip ? compress_source_zip(source) : compress_source_tgz(source)\n          end\n\n          @env.ui.info(I18n.t(\"vagrant.commands.upload.start\",\n            source: source,\n            destination: destination\n          ))\n\n          # If the source is a directory, attach a `/.` to the end so we\n          # upload the contents to the destination instead of within a\n          # folder at the destination\n          if File.directory?(source) && !source.end_with?(\".\")\n            upload_source = File.join(source, \".\")\n          else\n            upload_source = source\n          end\n\n          machine.communicate.upload(upload_source, destination)\n\n          if options[:compress]\n            @env.ui.info(I18n.t(\"vagrant.commands.upload.decompress\",\n              destination: destination_decompressed,\n              type: options[:compression_type]\n            ))\n            machine.guest.capability(options[:decompression_method], destination, destination_decompressed, type: type)\n            destination = destination_decompressed\n            FileUtils.rm(source)\n            source = source_display\n          end\n        end\n\n        @env.ui.info(I18n.t(\"vagrant.commands.upload.complete\",\n          source: source,\n          destination: destination\n        ))\n\n        # Success, exit status 0\n        0\n      end\n\n      # Setup compression options and validate host and guest have capability\n      # to handle compression\n      #\n      # @param [Vagrant::Machine] machine Vagrant guest machine\n      # @param [Hash] options Command options\n      def compression_setup!(machine, options)\n        if !options[:compression_type]\n          if machine.guest.capability_host_chain.first[0] == :windows\n            options[:compression_type] = :zip\n          else\n            options[:compression_type] = :tgz\n          end\n        end\n        if !VALID_COMPRESS_TYPES.include?(options[:compression_type])\n          raise Vagrant::Errors::UploadInvalidCompressionType,\n            type: options[:compression_type],\n            valid_types: VALID_COMPRESS_TYPES.join(\", \")\n        end\n        options[:decompression_method] = \"decompress_#{options[:compression_type]}\".to_sym\n        if !machine.guest.capability?(options[:decompression_method])\n          raise Vagrant::Errors::UploadMissingExtractCapability,\n            type: options[:compression_type]\n        end\n      end\n\n      # Compress path using zip into temporary file\n      #\n      # @param [String] path Path to compress\n      # @return [String] path to compressed file\n      def compress_source_zip(path)\n        require \"zip\"\n        zipfile = Tempfile.create([\"vagrant\", \".zip\"])\n        zipfile.close\n        if File.file?(path)\n          source_items = [path]\n        else\n          source_items = Dir.glob(File.join(path, \"**\", \"**\", \"*\"))\n        end\n        c_dir = nil\n        Zip::File.open(zipfile.path, Zip::File::CREATE) do |zip|\n          source_items.each do |source_item|\n            next if File.directory?(source_item)\n            trim_item = source_item.sub(path, \"\").sub(%r{^[/\\\\]}, \"\")\n            dirname = File.dirname(trim_item)\n            zip.mkdir dirname if c_dir != dirname\n            c_dir = dirname\n            zip.get_output_stream(trim_item) do |f|\n              source_file = File.open(source_item, \"rb\")\n              while data = source_file.read(2048)\n                f.write(data)\n              end\n            end\n          end\n        end\n        zipfile.path\n      end\n\n      # Compress path using tar and gzip into temporary file\n      #\n      # @param [String] path Path to compress\n      # @return [String] path to compressed file\n      def compress_source_tgz(path)\n        tarfile = Tempfile.create([\"vagrant\", \".tar\"])\n        tarfile.close\n        tarfile = File.open(tarfile.path, \"wb+\")\n        tgzfile = Tempfile.create([\"vagrant\", \".tgz\"])\n        tgzfile.close\n        tgzfile = File.open(tgzfile.path, \"wb\")\n        tar = Gem::Package::TarWriter.new(tarfile)\n        tgz = Zlib::GzipWriter.new(tgzfile)\n        if File.file?(path)\n          tar.add_file(File.basename(path), File.stat(path).mode) do |io|\n            File.open(path, \"rb\") do |file|\n              while bytes = file.read(4096)\n                io.write(bytes)\n              end\n            end\n          end\n        else\n          Dir.glob(File.join(path, \"**/**/*\")).each do |item|\n            rel_path = item.sub(path, \"\")\n            item_mode = File.stat(item).mode\n\n            if File.directory?(item)\n              tar.mkdir(rel_path, item_mode)\n            else\n              tar.add_file(rel_path, item_mode) do |io|\n                File.open(item, \"rb\") do |file|\n                  while bytes = file.read(4096)\n                    io.write(bytes)\n                  end\n                end\n              end\n            end\n          end\n        end\n        tar.close\n        tarfile.rewind\n        while bytes = tarfile.read(4096)\n          tgz.write bytes\n        end\n        tgz.close\n        tgzfile.close\n        tarfile.close\n        File.delete(tarfile.path)\n        tgzfile.path\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/upload/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandUpload\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"upload command\"\n      description <<-DESC\n      The `upload` command uploads files to guest via communicator\n      DESC\n\n      command(\"upload\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/validate/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nmodule VagrantPlugins\n  module CommandValidate\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"validates the Vagrantfile\"\n      end\n\n      def execute\n        options = {}\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant validate [options]\"\n          o.separator \"\"\n          o.separator \"Validates a Vagrantfile config\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"-p\", \"--ignore-provider\", \"Ignores provider config options\") do |p|\n            options[:ignore_provider] = p\n          end\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        action_env = {}\n        if options[:ignore_provider]\n          action_env[:ignore_provider] = true\n          tmp_data_dir = mockup_providers!\n        end\n\n        # Validate the configuration of all machines\n        with_target_vms() do |machine|\n          machine.action_raw(:config_validate, Vagrant::Action::Builtin::ConfigValidate, action_env)\n        end\n\n        @env.ui.info(I18n.t(\"vagrant.commands.validate.success\"))\n\n        # Success, exit status 0\n        0\n      ensure\n        FileUtils.remove_entry tmp_data_dir if tmp_data_dir\n      end\n\n      protected\n\n      # This method is required to bypass some of the provider checks that would\n      # otherwise raise exceptions before Vagrant could load and validate a config.\n      # It essentially ignores that there are no installed or usable prodivers so\n      # that Vagrant can go along and validate the rest of the Vagrantfile and ignore\n      # any provider blocks.\n      #\n      # return [String] tmp_data_dir - Temporary dir used to store guest metadata during validation\n      def mockup_providers!\n        require 'log4r'\n        logger = Log4r::Logger.new(\"vagrant::validate\")\n        logger.debug(\"Overriding all registered provider classes for validate\")\n\n        # Without setting up a tmp Environment, Vagrant will completely\n        # erase the local data dotfile and you can lose state after the\n        # validate command completes.\n        tmp_data_dir = Dir.mktmpdir(\"vagrant-validate-\")\n        @env = Vagrant::Environment.new(\n          cwd: @env.cwd,\n          home_path: @env.home_path,\n          ui_class: @env.ui_class,\n          vagrantfile_name: @env.vagrantfile_name,\n          local_data_path: tmp_data_dir,\n          data_dir: tmp_data_dir\n        )\n\n        Vagrant.plugin(\"2\").manager.providers.each do |key, data|\n          data[0].class_eval do\n            def initialize(machine)\n            end\n\n            def machine_id_changed\n            end\n\n            def self.installed?\n              true\n            end\n\n            def self.usable?(raise_error=false)\n              true\n            end\n\n            def state\n              state_id = Vagrant::MachineState::NOT_CREATED_ID\n              short = :not_created\n              long = :not_created\n              Vagrant::MachineState.new(state_id, short, long)\n            end\n          end\n        end\n        tmp_data_dir\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/validate/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandValidate\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"validate command\"\n      description <<-DESC\n      The `validate` command validates the Vagrantfile.\n      DESC\n\n      command(\"validate\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/version/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"optparse\"\n\nmodule VagrantPlugins\n  module CommandVersion\n    class Command < Vagrant.plugin(\"2\", :command)\n      def self.synopsis\n        \"prints current and latest Vagrant version\"\n      end\n\n      def execute\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant version\"\n        end\n\n        # Parse the options\n        argv = parse_options(opts)\n        return if !argv\n\n        # Output the currently installed version instantly.\n        @env.ui.output(I18n.t(\n          \"vagrant.version_current\", version: Vagrant::VERSION))\n        @env.ui.machine(\"version-installed\", Vagrant::VERSION)\n\n        # Load the latest information\n        cp = Vagrant::Util::CheckpointClient.instance.result\n        if !cp\n          @env.ui.output(\"\\n\"+I18n.t(\n            \"vagrant.version_no_checkpoint\"))\n          return 0\n        end\n\n        latest = cp[\"current_version\"]\n\n        # Output latest version\n        @env.ui.output(I18n.t(\n          \"vagrant.version_latest\", version: latest))\n        @env.ui.machine(\"version-latest\", latest)\n\n        # Determine if it's a new version, and if so, output some more\n        # information.\n        current = Gem::Version.new(Vagrant::VERSION)\n        latest  = Gem::Version.new(latest)\n        if current >= latest\n          @env.ui.success(\" \\n\" + I18n.t(\n            \"vagrant.version_latest_installed\"))\n          return 0\n        end\n\n        # Out of date! Let the user know how to upgrade.\n        @env.ui.output(\" \\n\" + I18n.t(\n          \"vagrant.version_upgrade_howto\", version: latest.to_s))\n\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/version/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandVersion\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"version command\"\n      description <<-DESC\n      The `version` command prints the currently installed version\n      as well as the latest available version.\n      DESC\n\n      command(\"version\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/winrm/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\nrequire \"vagrant/util/safe_puts\"\n\nmodule VagrantPlugins\n  module CommandWinRM\n    class Command < Vagrant.plugin(\"2\", :command)\n      include Vagrant::Util::SafePuts\n\n      def self.synopsis\n        \"executes commands on a machine via WinRM\"\n      end\n\n      def execute\n        options = {\n          command: [],\n          shell: :powershell\n        }\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant winrm [options] [name|id]\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"-c\", \"--command COMMAND\", \"Execute a WinRM command directly\") do |c|\n            options[:command] << c\n          end\n\n          o.on(\"-e\", \"--elevated\", \"Run with elevated credentials\") do |e|\n            options[:elevated] = true\n          end\n\n          o.on(\"-s\", \"--shell SHELL\", [:powershell, :cmd], \"Use specified shell (powershell, cmd)\") do |s|\n            options[:shell] = s\n          end\n        end\n\n        argv = parse_options(opts)\n        return if !argv\n\n        with_target_vms(argv, single_target: true) do |machine|\n          if machine.config.vm.communicator != :winrm\n            raise Vagrant::Errors::WinRMInvalidCommunicator\n          end\n\n          opts = {\n            shell: options[:shell],\n            elevated: !!options[:elevated]\n          }\n\n          options[:command].each do |cmd|\n            begin\n              machine.communicate.execute(cmd, opts) do |type, data|\n                io = type == :stderr ? $stderr : $stdout\n                safe_puts(data, io: io, printer: :print)\n              end\n            rescue VagrantPlugins::CommunicatorWinRM::Errors::WinRMBadExitStatus\n              return 1\n            end\n          end\n        end\n\n        # Success, exit status 0\n        0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/winrm/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandWinRM\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"winrm command\"\n      description <<-DESC\n      The `winrm` command executes commands on a machine via WinRM\n      DESC\n\n      command(\"winrm\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/winrm_config/command.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire \"vagrant/util/safe_puts\"\nrequire_relative \"../../communicators/winrm/helper\"\n\nmodule VagrantPlugins\n  module CommandWinRMConfig\n    class Command < Vagrant.plugin(\"2\", :command)\n      include Vagrant::Util::SafePuts\n\n      def self.synopsis\n        \"outputs WinRM configuration to connect to the machine\"\n      end\n\n      def convert_win_paths(paths)\n        paths.map! { |path| Vagrant::Util::Platform.format_windows_path(path, :disable_unc) }\n      end\n\n      def execute\n        options = {}\n\n        opts = OptionParser.new do |o|\n          o.banner = \"Usage: vagrant winrm-config [options] [name|id]\"\n          o.separator \"\"\n          o.separator \"Options:\"\n          o.separator \"\"\n\n          o.on(\"--host NAME\", \"Name the host for the config\") do |h|\n            options[:host] = h\n          end\n        end\n\n        argv = parse_options(opts)\n        return if !argv\n\n        with_target_vms(argv) do |machine|\n          winrm_info = CommunicatorWinRM::Helper.winrm_info(machine)\n          raise Vagrant::Errors::WinRMNotRead if winrm_info.nil?\n\n          rdp_info = get_rdp_info(machine) || {}\n\n          variables = {\n            host_key: options[:host] || machine.name || \"vagrant\",\n            rdp_host: rdp_info[:host] || winrm_info[:host],\n            rdp_port: rdp_info[:port],\n            rdp_user: rdp_info[:username],\n            rdp_pass: rdp_info[:password],\n            winrm_host: winrm_info[:host],\n            winrm_port: winrm_info[:port],\n            winrm_user: machine.config.winrm.username,\n            winrm_password: machine.config.winrm.password\n          }\n\n          template = \"commands/winrm_config/config\"\n          config = Vagrant::Util::TemplateRenderer.render(template, variables)\n          machine.ui.machine(\"winrm-config\", config)\n          safe_puts(config)\n          safe_puts\n        end\n\n        # Success, exit status 0\n        0\n      end\n\n      protected\n\n      # Generate RDP information for machine\n      #\n      # @param [Vagrant::Machine] machine Guest machine\n      # @return [Hash, nil]\n      def get_rdp_info(machine)\n        rdp_info = {}\n        if machine.provider.capability?(:rdp_info)\n          rdp_info = machine.provider.capability(:rdp_info)\n          rdp_info ||= {}\n        end\n\n        ssh_info = machine.ssh_info || {}\n\n        if !rdp_info[:username]\n          username = ssh_info[:username]\n          if machine.config.vm.communicator == :winrm\n            username = machine.config.winrm.username\n          end\n          rdp_info[:username] = username\n        end\n\n        if !rdp_info[:password]\n          password = ssh_info[:password]\n          if machine.config.vm.communicator == :winrm\n            password = machine.config.winrm.password\n          end\n          rdp_info[:password] = password\n        end\n\n        rdp_info[:host] ||= ssh_info[:host]\n        rdp_info[:port] ||= machine.config.rdp.port\n        rdp_info[:username] ||= machine.config.rdp.username\n\n        if rdp_info[:host] == \"127.0.0.1\"\n          # We need to find a forwarded port...\n          search_port = machine.config.rdp.search_port\n          ports       = nil\n          if machine.provider.capability?(:forwarded_ports)\n            ports = machine.provider.capability(:forwarded_ports)\n          else\n            ports = {}.tap do |result|\n              machine.config.vm.networks.each do |type, netopts|\n                next if type != :forwarded_port\n                next if !netopts[:host]\n                result[netopts[:host]] = netopts[:guest]\n              end\n            end\n          end\n\n          ports = ports.invert\n          port  = ports[search_port]\n          rdp_info[:port] = port\n          return nil if !port\n        end\n\n        rdp_info\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/commands/winrm_config/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommandWinRMConfig\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"winrm-config command\"\n      description <<-DESC\n      The `winrm-config` command dumps WinRM configuration information\n      DESC\n\n      command(\"winrm-config\") do\n        require File.expand_path(\"../command\", __FILE__)\n        Command\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/none/communicator.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommunicatorNone\n    # This class provides no communication with the VM.\n    # It allows Vagrant to manage a machine lifecycle\n    # while not actually connecting to it. The communicator\n    # stubs out all methods to be successful allowing\n    # Vagrant to proceed \"as normal\" without actually\n    # doing anything.\n    class Communicator < Vagrant.plugin(\"2\", :communicator)\n      def self.match?(_)\n        # Any machine can be not communicated with\n        true\n      end\n\n      def initialize(_)\n        @logger = Log4r::Logger.new(self.class.name.downcase)\n      end\n\n      def ready?\n        @logger.debug(\"#ready? stub called on none\")\n        true\n      end\n\n      def execute(*_)\n        @logger.debug(\"#execute stub called on none\")\n        0\n      end\n\n      def sudo(*_)\n        @logger.debug(\"#sudo stub called on none\")\n        0\n      end\n\n      def test(*_)\n        @logger.debug(\"#test stub called on none\")\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/none/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  Vagrant::Util::Experimental.guard_with(:none_communicator) do\n    module CommunicatorNone\n      class Plugin < Vagrant.plugin(\"2\")\n        name \"none communicator\"\n        description <<-DESC\n        This plugin provides no communication to remote machines.\n        It allows Vagrant to manage remote machines without the\n        ability to connect to them for configuration/provisioning.\n        Any calls to methods provided by this communicator will\n        always be successful.\n        DESC\n\n        communicator(\"none\") do\n          require File.expand_path(\"../communicator\", __FILE__)\n          Communicator\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/ssh/communicator.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'etc'\nrequire 'logger'\nrequire 'pathname'\nrequire 'stringio'\nrequire 'thread'\nrequire 'timeout'\n\nrequire 'log4r'\nrequire 'net/ssh'\nrequire 'net/ssh/proxy/command'\nrequire 'net/scp'\n\nrequire 'vagrant/util/ansi_escape_code_remover'\nrequire 'vagrant/util/file_mode'\nrequire 'vagrant/util/keypair'\nrequire 'vagrant/util/platform'\nrequire 'vagrant/util/retryable'\n\nmodule VagrantPlugins\n  module CommunicatorSSH\n    # This class provides communication with the VM via SSH.\n    class Communicator < Vagrant.plugin(\"2\", :communicator)\n      READY_COMMAND=\"\"\n      # Marker for start of PTY enabled command output\n      PTY_DELIM_START = \"bccbb768c119429488cfd109aacea6b5-pty\"\n      # Marker for end of PTY enabled command output\n      PTY_DELIM_END = \"bccbb768c119429488cfd109aacea6b5-pty\"\n      # Marker for start of regular command output\n      CMD_GARBAGE_MARKER = \"41e57d38-b4f7-4e46-9c38-13873d338b86-vagrant-ssh\"\n      # These are the exceptions that we retry because they represent\n      # errors that are generally fixed from a retry and don't\n      # necessarily represent immediate failure cases.\n      SSH_RETRY_EXCEPTIONS = [\n        Errno::EACCES,\n        Errno::EADDRINUSE,\n        Errno::ECONNABORTED,\n        Errno::ECONNREFUSED,\n        Errno::ECONNRESET,\n        Errno::ENETUNREACH,\n        Errno::EHOSTUNREACH,\n        Errno::EPIPE,\n        Net::SSH::Disconnect,\n        Timeout::Error\n      ]\n\n      include Vagrant::Util::ANSIEscapeCodeRemover\n      include Vagrant::Util::Retryable\n\n      def self.match?(machine)\n        # All machines are currently expected to have SSH.\n        true\n      end\n\n      def initialize(machine)\n        @lock    = Mutex.new\n        @machine = machine\n        @logger  = Log4r::Logger.new(\"vagrant::communication::ssh\")\n        @connection = nil\n        @inserted_key = false\n      end\n\n      def wait_for_ready(timeout)\n        Timeout.timeout(timeout) do\n          # Wait for ssh_info to be ready\n          ssh_info = nil\n          while true\n            ssh_info = @machine.ssh_info\n            break if ssh_info\n            sleep(0.5)\n          end\n\n          # Got it! Let the user know what we're connecting to.\n          if !@ssh_info_notification\n            @machine.ui.detail(\"SSH address: #{ssh_info[:host]}:#{ssh_info[:port]}\")\n            @machine.ui.detail(\"SSH username: #{ssh_info[:username]}\")\n            ssh_auth_type = \"private key\"\n            ssh_auth_type = \"password\" if ssh_info[:password]\n            @machine.ui.detail(\"SSH auth method: #{ssh_auth_type}\")\n            @ssh_info_notification = true\n          end\n\n          previous_messages = {}\n          while true\n            message  = nil\n            begin\n              begin\n                connect(retries: 1)\n                return true if ready?\n              rescue Vagrant::Errors::VagrantError => e\n                @logger.info(\"SSH not ready: #{e.inspect}\")\n                raise\n              end\n            rescue Vagrant::Errors::SSHConnectionTimeout\n              message = \"Connection timeout.\"\n            rescue Vagrant::Errors::SSHAuthenticationFailed\n              message = \"Authentication failure.\"\n            rescue Vagrant::Errors::SSHDisconnected\n              message = \"Remote connection disconnect.\"\n            rescue Vagrant::Errors::SSHConnectionRefused\n              message = \"Connection refused.\"\n            rescue Vagrant::Errors::SSHConnectionReset\n              message = \"Connection reset.\"\n            rescue Vagrant::Errors::SSHConnectionAborted\n              message = \"Connection aborted.\"\n            rescue Vagrant::Errors::SSHHostDown\n              message = \"Host appears down.\"\n            rescue Vagrant::Errors::SSHNoRoute\n              message = \"Host unreachable.\"\n            rescue Vagrant::Errors::SSHInvalidShell\n              raise\n            rescue Vagrant::Errors::SSHKeyTypeNotSupported\n              raise\n            rescue Vagrant::Errors::SSHKeyTypeNotSupportedByServer\n              raise\n            rescue Vagrant::Errors::SSHKeyBadOwner\n              raise\n            rescue Vagrant::Errors::SSHKeyBadPermissions\n              raise\n            rescue Vagrant::Errors::SSHInsertKeyUnsupported\n              raise\n            rescue Vagrant::Errors::VagrantError => e\n              # Ignore it, SSH is not ready, some other error.\n            end\n\n            # If we have a message to show, then show it. We don't show\n            # repeated messages unless they've been repeating longer than\n            # 10 seconds.\n            if message\n              message_at   = Time.now.to_f\n              show_message = true\n              if previous_messages[message]\n                show_message = (message_at - previous_messages[message]) > 10.0\n              end\n\n              if show_message\n                @machine.ui.detail(\"Warning: #{message} Retrying...\")\n                previous_messages[message] = message_at\n              end\n            end\n          end\n        end\n      rescue Timeout::Error\n        return false\n      end\n\n      def ready?\n        @logger.debug(\"Checking whether SSH is ready...\")\n\n        # Attempt to connect. This will raise an exception if it fails.\n        begin\n          connect\n          @logger.info(\"SSH is ready!\")\n        rescue Vagrant::Errors::VagrantError => e\n          # We catch a `VagrantError` which would signal that something went\n          # wrong expectedly in the `connect`, which means we didn't connect.\n          @logger.info(\"SSH not up: #{e.inspect}\")\n          return false\n        end\n\n        # Verify the shell is valid\n        if execute(self.class.const_get(:READY_COMMAND), error_check: false) != 0\n          raise Vagrant::Errors::SSHInvalidShell\n        end\n\n        # If we're already attempting to switch out the SSH key, then\n        # just return that we're ready (for Machine#guest).\n        @lock.synchronize do\n          return true if @inserted_key || !machine_config_ssh.insert_key\n          @inserted_key = true\n        end\n\n        # If we used a password, then insert the insecure key\n        ssh_info = @machine.ssh_info\n        return if ssh_info.nil?\n        insert   = ssh_info[:password] && ssh_info[:private_key_path].empty?\n        ssh_info[:private_key_path].each do |pk|\n          if insecure_key?(pk)\n            insert = true\n            @machine.ui.detail(\"\\n\"+I18n.t(\"vagrant.inserting_insecure_detected\"))\n            break\n          end\n        end\n\n        if insert\n          # If we don't have the power to insert/remove keys, then its an error\n          cap = @machine.guest.capability?(:insert_public_key) &&\n            @machine.guest.capability?(:remove_public_key)\n          raise Vagrant::Errors::SSHInsertKeyUnsupported if !cap\n\n          key_type = machine_config_ssh.key_type\n\n          begin\n            # If the key type is set to `:auto` check for supported type. Otherwise\n            # ensure that the key type is supported by the guest\n            if key_type == :auto\n              key_type = catch(:key_type) do\n                begin\n                  Vagrant::Util::Keypair::PREFER_KEY_TYPES.each do |type_name, type|\n                    throw :key_type, type if supports_key_type?(type_name)\n                  end\n                  nil\n                rescue => err\n                  @logger.warn(\"Failed to check key types server supports: #{err}\")\n                  nil\n                end\n              end\n\n              @logger.debug(\"Detected key type for new private key: #{key_type}\")\n\n              # If no key type was discovered, default to rsa\n              if key_type.nil?\n                @logger.debug(\"Failed to detect supported key type in: #{supported_key_types.join(\", \")}\")\n                available_types = supported_key_types.map { |t|\n                  next if !Vagrant::Util::Keypair::PREFER_KEY_TYPES.key?(t)\n                  \"#{t} (#{Vagrant::Util::Keypair::PREFER_KEY_TYPES[t]})\"\n                }.compact.join(\", \")\n\n                raise Vagrant::Errors::SSHKeyTypeNotSupportedByServer,\n                      requested_key_type: \":auto\",\n                      available_key_types: available_types\n              end\n            else\n              type_name = Vagrant::Util::Keypair::PREFER_KEY_TYPES.key(key_type)\n              if !supports_key_type?(type_name)\n                available_types = supported_key_types.map { |t|\n                  next if !Vagrant::Util::Keypair::PREFER_KEY_TYPES.key?(t)\n                  \"#{t} (#{Vagrant::Util::Keypair::PREFER_KEY_TYPES[t]})\"\n                }.compact.join(\", \")\n                raise Vagrant::Errors::SSHKeyTypeNotSupportedByServer,\n                      requested_key_type: \"#{type_name} (#{key_type})\",\n                      available_key_types: available_types\n              end\n            end\n          rescue ServerDataError\n            @logger.warn(\"failed to load server data for key type check\")\n            if key_type.nil? || key_type == :auto\n              @logger.warn(\"defaulting key type to :rsa due to failed server data loading\")\n              key_type = :rsa\n            end\n          end\n\n          @logger.info(\"Creating new ssh keypair (type: #{key_type.inspect})\")\n          _pub, priv, openssh = Vagrant::Util::Keypair.create(type: key_type)\n\n          @logger.info(\"Inserting key to avoid password: #{openssh}\")\n          @machine.ui.detail(\"\\n\"+I18n.t(\"vagrant.inserting_random_key\"))\n          @machine.guest.capability(:insert_public_key, openssh)\n\n          # Write out the private key in the data dir so that the\n          # machine automatically picks it up.\n          @machine.data_dir.join(\"private_key\").open(\"wb+\") do |f|\n            f.write(priv)\n          end\n\n          # Adjust private key file permissions if host provides capability\n          if @machine.env.host.capability?(:set_ssh_key_permissions)\n            @machine.env.host.capability(:set_ssh_key_permissions, @machine.data_dir.join(\"private_key\"))\n          end\n\n          # Remove the old key if it exists\n          @machine.ui.detail(I18n.t(\"vagrant.inserting_remove_key\"))\n          @machine.guest.capability(\n            :remove_public_key,\n            Vagrant.source_root.join(\"keys\", \"vagrant.pub\").read.chomp)\n\n          # Done, restart.\n          @machine.ui.detail(I18n.t(\"vagrant.inserted_key\"))\n          @connection.close\n          @connection = nil\n\n          return ready?\n        end\n\n        # If we reached this point then we successfully connected\n        true\n      end\n\n      def execute(command, opts=nil, &block)\n        opts = {\n          error_check: true,\n          error_class: Vagrant::Errors::VagrantError,\n          error_key:   :ssh_bad_exit_status,\n          good_exit:   0,\n          command:     command,\n          shell:       nil,\n          sudo:        false,\n          force_raw:   false\n        }.merge(opts || {})\n\n        opts[:good_exit] = Array(opts[:good_exit])\n\n        # Connect via SSH and execute the command in the shell.\n        stdout = \"\"\n        stderr = \"\"\n        exit_status = connect do |connection|\n          shell_opts = {\n            sudo: opts[:sudo],\n            shell: opts[:shell],\n            force_raw: opts[:force_raw]\n          }\n\n          shell_execute(connection, command, **shell_opts) do |type, data|\n            if type == :stdout\n              stdout += data\n            elsif type == :stderr\n              stderr += data\n            end\n\n            block.call(type, data) if block\n          end\n        end\n\n        # Check for any errors\n        if opts[:error_check] && !opts[:good_exit].include?(exit_status)\n          # The error classes expect the translation key to be _key,\n          # but that makes for an ugly configuration parameter, so we\n          # set it here from `error_key`\n          error_opts = opts.merge(\n            _key: opts[:error_key],\n            stdout: stdout,\n            stderr: stderr\n          )\n          raise opts[:error_class], error_opts\n        end\n\n        # Return the exit status\n        exit_status\n      end\n\n      def sudo(command, opts=nil, &block)\n        # Run `execute` but with the `sudo` option.\n        opts = { sudo: true }.merge(opts || {})\n        execute(command, opts, &block)\n      end\n\n      def download(from, to=nil)\n        @logger.debug(\"Downloading: #{from} to #{to}\")\n\n        scp_connect do |scp|\n          scp.download!(from, to)\n        end\n      end\n\n      def test(command, opts=nil)\n        opts = { error_check: false }.merge(opts || {})\n        execute(command, opts) == 0\n      end\n\n      def upload(from, to)\n        @logger.debug(\"Uploading: #{from} to #{to}\")\n\n        if File.directory?(from)\n          if from.end_with?(\".\")\n            @logger.debug(\"Uploading directory contents of: #{from}\")\n            from = from.sub(/\\.$/, \"\")\n          else\n            @logger.debug(\"Uploading full directory container of: #{from}\")\n            to = File.join(to, File.basename(File.expand_path(from)))\n          end\n        end\n\n        scp_connect do |scp|\n          uploader = lambda do |path, remote_dest=nil|\n            if File.directory?(path)\n              dest = File.join(to, path.sub(/^#{Regexp.escape(from)}/, \"\"))\n              create_remote_directory(dest)\n              Dir.new(path).each do |entry|\n                next if entry == \".\" || entry == \"..\"\n                full_path = File.join(path, entry)\n                create_remote_directory(dest)\n                uploader.call(full_path, dest)\n              end\n            else\n              if remote_dest\n                dest = File.join(remote_dest, File.basename(path))\n              else\n                dest = to\n                if to.end_with?(File::SEPARATOR)\n                  dest = File.join(to, File.basename(path))\n                end\n              end\n              @logger.debug(\"Ensuring remote directory exists for destination upload\")\n              create_remote_directory(File.dirname(dest))\n              @logger.debug(\"Uploading file #{path} to remote #{dest}\")\n              upload_file = File.open(path, \"rb\")\n              begin\n                scp.upload!(upload_file, dest)\n              ensure\n                upload_file.close\n              end\n            end\n          end\n          uploader.call(from)\n        end\n      rescue RuntimeError => e\n        # Net::SCP raises a runtime error for this so the only way we have\n        # to really catch this exception is to check the message to see if\n        # it is something we care about. If it isn't, we re-raise.\n        raise if e.message !~ /Permission denied/\n\n        # Otherwise, it is a permission denied, so let's raise a proper\n        # exception\n        raise Vagrant::Errors::SCPPermissionDenied,\n          from: from.to_s,\n          to: to.to_s\n      end\n\n      def reset!\n        if @connection\n          @connection.close\n          @connection = nil\n        end\n        @ssh_info_notification = true # suppress ssh info output\n        wait_for_ready(5)\n      end\n\n      def generate_environment_export(env_key, env_value)\n        template = machine_config_ssh.export_command_template\n        template.sub(\"%ENV_KEY%\", env_key).sub(\"%ENV_VALUE%\", env_value) + \"\\n\"\n      end\n\n      protected\n\n      # Opens an SSH connection and yields it to a block.\n      def connect(**opts)\n        if @connection && !@connection.closed?\n          # There is a chance that the socket is closed despite us checking\n          # 'closed?' above. To test this we need to send data through the\n          # socket.\n          #\n          # We wrap the check itself in a 5 second timeout because there\n          # are some cases where this will just hang.\n          begin\n            Timeout.timeout(5) do\n              @connection.exec!(\"\")\n            end\n          rescue Exception => e\n            @logger.info(\"Connection errored, not re-using. Will reconnect.\")\n            @logger.debug(e.inspect)\n            @connection = nil\n          end\n\n          # If the @connection is still around, then it is valid,\n          # and we use it.\n          if @connection\n            @logger.debug(\"Re-using SSH connection.\")\n            return yield @connection if block_given?\n            return\n          end\n        end\n\n        # Get the SSH info for the machine, raise an exception if the\n        # provider is saying that SSH is not ready.\n        ssh_info = @machine.ssh_info\n        raise Vagrant::Errors::SSHNotReady if ssh_info.nil?\n\n        # Default some options\n        opts[:retries] = ssh_info[:connect_retries] if !opts.key?(:retries)\n        opts[:retry_delay] = ssh_info[:connect_retry_delay] if !opts.key?(:retry_delay)\n\n        # Set some valid auth methods. We disable the auth methods that\n        # we're not using if we don't have the right auth info.\n        auth_methods = [\"none\", \"hostbased\"]\n        auth_methods << \"publickey\" if ssh_info[:private_key_path]\n        auth_methods << \"password\" if ssh_info[:password]\n\n        # Build the options we'll use to initiate the connection via Net::SSH\n        common_connect_opts = {\n          auth_methods:          auth_methods,\n          config:                false,\n          forward_agent:         ssh_info[:forward_agent],\n          send_env:              ssh_info[:forward_env],\n          keys_only:             ssh_info[:keys_only],\n          verify_host_key:       ssh_info[:verify_host_key],\n          password:              ssh_info[:password],\n          port:                  ssh_info[:port],\n          timeout:               ssh_info[:connect_timeout],\n          user_known_hosts_file: [],\n          verbose:               :debug\n        }\n\n        # Connect to SSH, giving it a few tries\n        connection = nil\n        begin\n          timeout = 60\n\n          @logger.info(\"Attempting SSH connection...\")\n          connection = retryable(tries: opts[:retries], on: SSH_RETRY_EXCEPTIONS, sleep: opts[:retry_delay]) do\n            Timeout.timeout(timeout) do\n              begin\n                # This logger will get the Net-SSH log data for us.\n                ssh_logger_io = StringIO.new\n                ssh_logger    = Logger.new(ssh_logger_io)\n\n                # Setup logging for connections\n                connect_opts = common_connect_opts.dup\n                connect_opts[:logger] = ssh_logger\n\n                if ssh_info[:private_key_path]\n                  connect_opts[:keys] = ssh_info[:private_key_path]\n                end\n\n                if ssh_info[:proxy_command]\n                  connect_opts[:proxy] = Net::SSH::Proxy::Command.new(ssh_info[:proxy_command])\n                end\n\n                if ssh_info[:config]\n                  connect_opts[:config] = ssh_info[:config]\n                end\n\n                if ssh_info[:remote_user]\n                  connect_opts[:remote_user] = ssh_info[:remote_user]\n                end\n\n                if @machine.config.ssh.keep_alive\n                  connect_opts[:keepalive] = true\n                  connect_opts[:keepalive_interval] = 5\n                end\n                \n                @logger.info(\"Attempting to connect to SSH...\")\n                @logger.info(\"  - Host: #{ssh_info[:host]}\")\n                @logger.info(\"  - Port: #{ssh_info[:port]}\")\n                @logger.info(\"  - Username: #{ssh_info[:username]}\")\n                @logger.info(\"  - Password? #{!!ssh_info[:password]}\")\n                @logger.info(\"  - Key Path: #{ssh_info[:private_key_path]}\")\n                @logger.debug(\"  - connect_opts: #{connect_opts}\")\n\n                Net::SSH.start(ssh_info[:host], ssh_info[:username], **connect_opts)\n              ensure\n                # Make sure we output the connection log\n                @logger.debug(\"== Net-SSH connection debug-level log START ==\")\n                @logger.debug(ssh_logger_io.string)\n                @logger.debug(\"== Net-SSH connection debug-level log END ==\")\n              end\n            end\n          end\n        rescue Errno::EACCES\n          # This happens on connect() for unknown reasons yet...\n          raise Vagrant::Errors::SSHConnectEACCES\n        rescue Errno::ETIMEDOUT, Timeout::Error, IO::TimeoutError\n          # This happens if we continued to timeout when attempting to connect.\n          raise Vagrant::Errors::SSHConnectionTimeout\n        rescue Net::SSH::AuthenticationFailed\n          # This happens if authentication failed. We wrap the error in our\n          # own exception.\n          raise Vagrant::Errors::SSHAuthenticationFailed\n        rescue Net::SSH::Disconnect\n          # This happens if the remote server unexpectedly closes the\n          # connection. This is usually raised when SSH is running on the\n          # other side but can't properly setup a connection. This is\n          # usually a server-side issue.\n          raise Vagrant::Errors::SSHDisconnected\n        rescue Errno::ECONNREFUSED\n          # This is raised if we failed to connect the max amount of times\n          raise Vagrant::Errors::SSHConnectionRefused\n        rescue Errno::ECONNRESET\n          # This is raised if we failed to connect the max number of times\n          # due to an ECONNRESET.\n          raise Vagrant::Errors::SSHConnectionReset\n        rescue Errno::ECONNABORTED\n          # This is raised if we failed to connect the max number of times\n          # due to an ECONNABORTED\n          raise Vagrant::Errors::SSHConnectionAborted\n        rescue Errno::EHOSTDOWN\n          # This is raised if we get an ICMP DestinationUnknown error.\n          raise Vagrant::Errors::SSHHostDown\n        rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH\n          # This is raised if we can't work out how to route traffic.\n          raise Vagrant::Errors::SSHNoRoute\n        rescue Net::SSH::Exception => e\n          # This is an internal error in Net::SSH\n          raise Vagrant::Errors::NetSSHException, message: e.message\n        rescue NotImplementedError\n          # This is raised if a private key type that Net-SSH doesn't support\n          # is used. Show a nicer error.\n          raise Vagrant::Errors::SSHKeyTypeNotSupported\n        end\n\n        @connection          = connection\n        @connection_ssh_info = ssh_info\n\n        # Yield the connection that is ready to be used and\n        # return the value of the block\n        return yield connection if block_given?\n      end\n\n      # The shell wrapper command used in shell_execute defined by\n      # the sudo and shell options.\n      def shell_cmd(opts)\n        sudo  = opts[:sudo]\n        shell = opts[:shell]\n\n        # Determine the shell to execute. Prefer the explicitly passed in shell\n        # over the default configured shell. If we are using `sudo` then we\n        # need to wrap the shell in a `sudo` call.\n        cmd = machine_config_ssh.shell\n        cmd = shell if shell\n        cmd = machine_config_ssh.sudo_command.gsub(\"%c\", cmd) if sudo\n        cmd\n      end\n\n      # Executes the command on an SSH connection within a login shell.\n      def shell_execute(connection, command, **opts)\n        opts = {\n          sudo: false,\n          shell: nil\n        }.merge(opts)\n\n        sudo  = opts[:sudo]\n\n        @logger.info(\"Execute: #{command} (sudo=#{sudo.inspect})\")\n        exit_status = nil\n\n        # These variables are used to scrub PTY output if we're in a PTY\n        pty = false\n        pty_stdout = \"\"\n\n        # Open the channel so we can execute or command\n        channel = connection.open_channel do |ch|\n          if machine_config_ssh.pty\n            ch.request_pty do |ch2, success|\n              pty = success && command != \"\"\n\n              if success\n                @logger.debug(\"pty obtained for connection\")\n              else\n                @logger.warn(\"failed to obtain pty, will try to continue anyways\")\n              end\n            end\n          end\n\n          marker_found = false\n          data_buffer = ''\n          stderr_marker_found = false\n          stderr_data_buffer = ''\n\n          ch.exec(shell_cmd(opts)) do |ch2, _|\n            # Setup the channel callbacks so we can get data and exit status\n            ch2.on_data do |ch3, data|\n              # Filter out the clear screen command\n              data = remove_ansi_escape_codes(data)\n\n              if pty\n                pty_stdout << data\n              else\n                if !marker_found\n                  data_buffer << data\n                  marker_index = data_buffer.index(CMD_GARBAGE_MARKER)\n                  if marker_index\n                    marker_found = true\n                    data_buffer.slice!(0, marker_index + CMD_GARBAGE_MARKER.size)\n                    data.replace(data_buffer)\n                    data_buffer = nil\n                  end\n                end\n\n                if block_given? && marker_found && !data.empty?\n                  yield :stdout, data\n                end\n              end\n            end\n\n            ch2.on_extended_data do |ch3, type, data|\n              # Filter out the clear screen command\n              data = remove_ansi_escape_codes(data)\n              @logger.debug(\"stderr: #{data}\")\n              if !stderr_marker_found\n                stderr_data_buffer << data\n                marker_index = stderr_data_buffer.index(CMD_GARBAGE_MARKER)\n                if marker_index\n                  stderr_marker_found = true\n                  stderr_data_buffer.slice!(0, marker_index + CMD_GARBAGE_MARKER.size)\n                  data.replace(stderr_data_buffer)\n                  stderr_data_buffer = nil\n                end\n              end\n\n              if block_given? && stderr_marker_found && !data.empty?\n                yield :stderr, data\n              end\n            end\n\n            ch2.on_request(\"exit-status\") do |ch3, data|\n              exit_status = data.read_long\n              @logger.debug(\"Exit status: #{exit_status}\")\n\n              # Close the channel, since after the exit status we're\n              # probably done. This fixes up issues with hanging.\n              ch.close\n            end\n\n            # Set the terminal\n            ch2.send_data(generate_environment_export(\"TERM\", \"vt100\"))\n\n            # Set SSH_AUTH_SOCK if we are in sudo and forwarding agent.\n            # This is to work around often misconfigured boxes where\n            # the SSH_AUTH_SOCK env var is not preserved.\n            if @connection_ssh_info[:forward_agent] && sudo\n              auth_socket = \"\"\n              execute(\"echo; printf $SSH_AUTH_SOCK\") do |type, data|\n                if type == :stdout\n                  auth_socket += data\n                end\n              end\n\n              if auth_socket != \"\"\n                # Make sure we only read the last line which should be\n                # the $SSH_AUTH_SOCK env var we printed.\n                auth_socket = auth_socket.split(\"\\n\").last.to_s.chomp\n              end\n\n              if auth_socket == \"\"\n                @logger.warn(\"No SSH_AUTH_SOCK found despite forward_agent being set.\")\n              else\n                @logger.info(\"Setting SSH_AUTH_SOCK remotely: #{auth_socket}\")\n                ch2.send_data(generate_environment_export(\"SSH_AUTH_SOCK\", auth_socket))\n              end\n            end\n\n            # Output the command. If we're using a pty we have to do\n            # a little dance to make sure we get all the output properly\n            # without the cruft added from pty mode.\n            if pty\n              data = \"stty raw -echo\\n\"\n              data += generate_environment_export(\"PS1\", \"\")\n              data += generate_environment_export(\"PS2\", \"\")\n              data += generate_environment_export(\"PROMPT_COMMAND\", \"\")\n              data += \"printf #{PTY_DELIM_START}\\n\"\n              data += \"#{command}\\n\"\n              data += \"exitcode=$?\\n\"\n              data += \"printf #{PTY_DELIM_END}\\n\"\n              data += \"exit $exitcode\\n\"\n              data = data.force_encoding('ASCII-8BIT')\n              ch2.send_data(data)\n            else\n              ch2.send_data(\"printf '#{CMD_GARBAGE_MARKER}'\\n(>&2 printf '#{CMD_GARBAGE_MARKER}')\\n#{command}\\n\".force_encoding('ASCII-8BIT'))\n              # Remember to exit or this channel will hang open\n              ch2.send_data(\"exit\\n\")\n            end\n\n            # Send eof to let server know we're done\n            ch2.eof!\n          end\n        end\n\n        begin\n          # Wait for the channel to complete\n          begin\n            channel.wait\n          rescue Errno::ECONNRESET, IOError\n            @logger.info(\n              \"SSH connection unexpected closed. Assuming reboot or something.\")\n            exit_status = 0\n            pty = false\n          rescue Net::SSH::ChannelOpenFailed\n            raise Vagrant::Errors::SSHChannelOpenFail\n          rescue Net::SSH::Disconnect\n            raise Vagrant::Errors::SSHDisconnected\n          end\n        end\n\n        # If we're in a PTY, we now finally parse the output\n        if pty\n          @logger.debug(\"PTY stdout: #{pty_stdout}\")\n          if !pty_stdout.include?(PTY_DELIM_START) || !pty_stdout.include?(PTY_DELIM_END)\n            @logger.error(\"PTY stdout doesn't include delims\")\n            raise Vagrant::Errors::SSHInvalidShell.new\n          end\n\n          data = pty_stdout[/.*#{PTY_DELIM_START}(.*?)#{PTY_DELIM_END}/m, 1]\n          data ||= \"\"\n          @logger.debug(\"PTY stdout parsed: #{data}\")\n          yield :stdout, data if block_given?\n        end\n\n        if !exit_status\n          @logger.debug(\"Exit status: #{exit_status.inspect}\")\n          raise Vagrant::Errors::SSHNoExitStatus\n        end\n\n        # Return the final exit status\n        return exit_status\n      end\n\n      # Opens an SCP connection and yields it so that you can download\n      # and upload files.\n      def scp_connect\n        # Connect to SCP and yield the SCP object\n        connect do |connection|\n          scp = Net::SCP.new(connection)\n          return yield scp\n        end\n      rescue Net::SCP::Error => e\n        # If we get the exit code of 127, then this means SCP is unavailable.\n        raise Vagrant::Errors::SCPUnavailable if e.message =~ /\\(127\\)/\n\n        # Otherwise, just raise the error up\n        raise\n      end\n\n      # This will test whether path is the Vagrant insecure private key.\n      #\n      # @param [String] path\n      def insecure_key?(path)\n        return false if !path\n        return false if !File.file?(path)\n        Dir.glob(Vagrant.source_root.join(\"keys\", \"vagrant.key.*\")).any? do |source_path|\n          File.read(path).chomp == File.read(source_path).chomp\n        end\n      end\n\n      def create_remote_directory(dir)\n        execute(\"mkdir -p \\\"#{dir}\\\"\")\n      end\n\n      def machine_config_ssh\n        @machine.config.ssh\n      end\n\n      protected\n\n      class ServerDataError < StandardError; end\n\n      # Check if server supports given key type\n      #\n      # @param [String, Symbol] type Key type\n      # @return [Boolean]\n      # @note This does not use a stable API and may be subject\n      # to unexpected breakage on net-ssh updates\n      def supports_key_type?(type)\n        if @connection.nil?\n          raise Vagrant::Errors::SSHNotReady\n        end\n\n        supported_key_types.include?(type.to_s)\n      end\n\n      def supported_key_types\n        return @supported_key_types if @supported_key_types\n\n        if @connection.nil?\n          raise Vagrant::Errors::SSHNotReady\n        end\n\n        list = \"\"\n        result = sudo(\"sshd -T | grep key\", {error_check: false}) do |type, data|\n          list << data\n        end\n\n        # If the command failed, attempt to extract some supported\n        # key information from within net-ssh\n        if result != 0\n          server_data = @connection.\n            transport&.\n            algorithms&.\n            instance_variable_get(:@server_data)\n          if server_data.nil?\n            @logger.warn(\"No server data available for key type support check\")\n            raise ServerDataError, \"no data available\"\n          end\n          if !server_data.is_a?(Hash)\n            @logger.warn(\"Server data is not expected type (expecting Hash, got #{server_data.class})\")\n            raise ServerDataError, \"unexpected type encountered (expecting Hash, got #{server_data.class})\"\n          end\n\n          @logger.debug(\"server supported key type list (extracted from connection server info using host key): #{server_data[:host_key]}\")\n          return @supported_key_types = server_data[:host_key]\n        end\n\n        # Convert the options into a Hash for easy access\n        opts = Hash[*list.split(\"\\n\").map{|line| line.split(\" \", 2)}.flatten]\n\n        # Define the option names to check for in preferred order\n        # NOTE: pubkeyacceptedkeytypes has been renamed to pubkeyacceptedalgorithms\n        #   ref: https://github.com/openssh/openssh-portable/commit/ee9c0da8035b3168e8e57c1dedc2d1b0daf00eec\n        [\"pubkeyacceptedalgorithms\", \"pubkeyacceptedkeytypes\", \"hostkeyalgorithms\"].each do |opt_name|\n          next if !opts.key?(opt_name)\n\n          @supported_key_types = opts[opt_name].split(\",\")\n          @logger.debug(\"server supported key type list (using #{opt_name}): #{@supported_key_types}\")\n\n          return @supported_key_types\n        end\n\n        # Still here means unable to determine key types\n        # so log what information was returned and toss\n        # and error\n        @logger.warn(\"failed to determine supported key types from remote inspection\")\n        @logger.debug(\"data returned for supported key types remote inspection: #{list.inspect}\")\n\n        raise ServerDataError, \"no data available\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/ssh/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommunicatorSSH\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"ssh communicator\"\n      description <<-DESC\n      This plugin allows Vagrant to communicate with remote machines using\n      SSH as the underlying protocol, powered internally by Ruby's\n      net-ssh library.\n      DESC\n\n      communicator(\"ssh\") do\n        require File.expand_path(\"../communicator\", __FILE__)\n        Communicator\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/command_filter.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    # Handles loading and applying all available WinRM command filters\n    class CommandFilter\n      @@cmd_filters = [\n        \"cat\",\n        \"chmod\",\n        \"chown\",\n        \"grep\",\n        \"rm\",\n        \"test\",\n        \"uname\",\n        \"which\",\n        \"mkdir\",\n      ]\n\n      # Filter the given Vagrant command to ensure compatibility with Windows\n      #\n      # @param [String] The Vagrant shell command\n      # @returns [String] Windows runnable command or empty string\n      def filter(command)\n        command_filters.each { |c| command = c.filter(command) if c.accept?(command) }\n        command\n      end\n\n      # All the available Linux command filters\n      #\n      # @returns [Array] All Linux command filter instances\n      def command_filters\n        @command_filters ||= create_command_filters()\n      end\n\n      private\n\n      def create_command_filters\n        [].tap do |filters|\n          @@cmd_filters.each do |cmd|\n            require_relative \"command_filters/#{cmd}\"\n            class_name = \"VagrantPlugins::CommunicatorWinRM::CommandFilters::#{cmd.capitalize}\"\n            filters << Module.const_get(class_name).new\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/command_filters/cat.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    module CommandFilters\n      # Handles the special case of determining the guest OS using cat\n      class Cat\n        def filter(command)\n          # cat /etc/release | grep -i OmniOS\n          # cat /etc/redhat-release\n          # cat /etc/issue | grep 'Core Linux'\n          # cat /etc/release | grep -i SmartOS\n          ''\n        end\n\n        def accept?(command)\n          # cat works in PowerShell, however we don't want to run Guest\n          # OS detection as this will fail on Windows because the lack of the\n          # grep command\n          command.start_with?('cat /etc/')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/command_filters/chmod.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    module CommandFilters\n      # Converts a *nix 'chmod' command to a PowerShell equivalent (none)\n      class Chmod\n        def filter(command)\n          # Not supported on Windows, the communicator should skip this command\n          ''\n        end\n\n        def accept?(command)\n          command.start_with?('chmod ')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/command_filters/chown.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    module CommandFilters\n      # Converts a *nix 'chown' command to a PowerShell equivalent (none)\n      class Chown\n        def filter(command)\n          # Not supported on Windows, the communicator should skip this command\n          ''\n        end\n\n        def accept?(command)\n          command.start_with?('chown ')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/command_filters/grep.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    module CommandFilters\n      # Converts a *nix 'grep' command to a PowerShell equivalent (none)\n      class Grep\n\n        def filter(command)\n          # grep 'Fedora release [12][67890]' /etc/redhat-release\n          # grep Funtoo /etc/gentoo-release\n          # grep Gentoo /etc/gentoo-release\n\n          # grep is often used to detect the guest type in Vagrant, so don't bother running\n          # to speed up OS detection\n          ''\n        end\n\n        def accept?(command)\n          command.start_with?('grep ')\n        end\n      end\n    end\n  end\nend"
  },
  {
    "path": "plugins/communicators/winrm/command_filters/mkdir.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"shellwords\"\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    module CommandFilters\n      # Converts a *nix 'mkdir' command to a PowerShell equivalent\n      class Mkdir\n        def filter(command)\n          # mkdir -p /some/dir\n          # mkdir /some/dir\n          cmd_parts = Shellwords.split(command.strip)\n          dir = cmd_parts.pop\n          while !dir.nil? && dir.start_with?('-')\n            dir = cmd_parts.pop\n          end\n          # This will ignore any -p switches, which are redundant in PowerShell,\n          # and ambiguous in PowerShell 4+\n          return \"mkdir \\\"#{dir}\\\" -force\"\n        end\n\n        def accept?(command)\n          command.start_with?('mkdir ')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/command_filters/rm.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"shellwords\"\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    module CommandFilters\n      # Converts a *nix 'rm' command to a PowerShell equivalent\n      class Rm\n        def filter(command)\n          # rm -Rf /some/dir\n          # rm -R /some/dir\n          # rm -R -f /some/dir\n          # rm -f /some/dir\n          # rm /some/dir\n          cmd_parts = Shellwords.split(command.strip)\n\n          # Figure out if we need to do this recursively\n          recurse = false\n          cmd_parts.each do |k|\n            argument = k.downcase\n            if argument == '-r' || argument == '-rf' || argument == '-fr'\n              recurse = true\n              break\n            end\n          end\n\n          # Figure out which argument is the path\n          dir = cmd_parts.pop\n          while !dir.nil? && dir.start_with?('-')\n            dir = cmd_parts.pop\n          end\n\n          ret_cmd = ''\n          if recurse\n            ret_cmd = \"if (Test-Path \\\"#{dir}\\\") {Remove-Item \\\"#{dir}\\\" -force -recurse}\"\n          else\n            ret_cmd = \"if (Test-Path \\\"#{dir}\\\") {Remove-Item \\\"#{dir}\\\" -force}\"\n          end\n          return ret_cmd\n        end\n\n        def accept?(command)\n          command.start_with?('rm ')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/command_filters/test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"shellwords\"\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    module CommandFilters\n      # Converts a *nix 'test' command to a PowerShell equivalent\n      class Test\n        def filter(command)\n          # test -d /tmp/dir\n          # test -f /tmp/afile\n          # test -L /somelink\n          # test -x /tmp/some.exe\n\n          cmd_parts = Shellwords.split(command.strip)\n          flag = cmd_parts[1]\n          path = cmd_parts[2]\n\n          if flag == '-d'\n            check_for_directory(path)\n          elsif flag == '-f' || flag == '-x'\n            check_for_file(path)\n          else\n            check_exists(path)\n          end\n        end\n\n        def accept?(command)\n          command.start_with?(\"test \")\n        end\n\n        private\n\n        def check_for_directory(path)\n          <<-EOH\n            $p = \"#{path}\"\n            if ((Test-Path $p) -and (get-item $p).PSIsContainer) {\n              exit 0\n            }\n            exit 1\n          EOH\n        end\n\n        def check_for_file(path)\n          <<-EOH\n            $p = \"#{path}\"\n            if ((Test-Path $p) -and (!(get-item $p).PSIsContainer)) {\n              exit 0\n            }\n            exit 1\n          EOH\n        end\n\n        def check_exists(path)\n          <<-EOH\n            $p = \"#{path}\"\n            if (Test-Path $p) {\n              exit 0\n            }\n            exit 1\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/command_filters/uname.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    module CommandFilters\n      # Converts a *nix 'uname' command to a PowerShell equivalent (none)\n      class Uname\n        def filter(command)\n          # uname -s | grep 'Darwin'\n          # uname -s | grep VMkernel\n          # uname -s | grep 'FreeBSD'\n          # uname -s | grep 'Linux'\n          # uname -s | grep NetBSD\n          # uname -s | grep 'OpenBSD'\n          # uname -sr | grep SunOS | grep -v 5.11\n          # uname -sr | grep 'SunOS 5.11'\n\n          # uname is used to detect the guest type in Vagrant, so don't bother running\n          # to speed up OS detection\n          ''\n        end\n\n        def accept?(command)\n          command.start_with?('uname ')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/command_filters/which.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"shellwords\"\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    module CommandFilters\n      # Converts a *nix 'which' command to a PowerShell equivalent\n      class Which\n        def filter(command)\n          executable = Shellwords.split(command.strip)[1]\n          return <<-EOH\n            $command = [Array](Get-Command \"#{executable}\" -errorAction SilentlyContinue)\n            if ($null -eq $command) { exit 1 }\n            write-host $command[0].Definition\n            exit 0\n          EOH\n        end\n\n        def accept?(command)\n          command.start_with?('which ')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/communicator.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"tempfile\"\nrequire \"timeout\"\n\nrequire_relative \"helper\"\nrequire_relative \"shell\"\nrequire_relative \"command_filter\"\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    # Provides communication channel for Vagrant commands via WinRM.\n    class Communicator < Vagrant.plugin(\"2\", :communicator)\n      include Vagrant::Util\n\n      def self.match?(machine)\n        # This is useless, and will likely be removed in the future (this\n        # whole method).\n        true\n      end\n\n      def initialize(machine)\n        @cmd_filter = CommandFilter.new()\n        @logger     = Log4r::Logger.new(\"vagrant::communication::winrm\")\n        @machine    = machine\n        @shell      = nil\n\n        @logger.info(\"Initializing WinRMCommunicator\")\n      end\n\n      def wait_for_ready(timeout)\n        Timeout.timeout(timeout) do\n          # Wait for winrm_info to be ready\n          winrm_info = nil\n          while true\n            winrm_info = nil\n            begin\n              winrm_info = Helper.winrm_info(@machine)\n            rescue Errors::WinRMNotReady\n              @logger.debug(\"WinRM not ready yet; retrying until boot_timeout is reached.\")\n            end\n            break if winrm_info\n            sleep 0.5\n          end\n\n          # Got it! Let the user know what we're connecting to.\n          @machine.ui.detail(\"WinRM address: #{shell.host}:#{shell.port}\")\n          @machine.ui.detail(\"WinRM username: #{shell.username}\")\n          @machine.ui.detail(\"WinRM execution_time_limit: #{shell.execution_time_limit}\")\n          @machine.ui.detail(\"WinRM transport: #{shell.config.transport}\")\n\n          last_message = nil\n          last_message_repeat_at = 0\n          while true\n            message  = nil\n            begin\n              begin\n                return true if ready?\n              rescue Vagrant::Errors::VagrantError => e\n                @logger.info(\"WinRM not ready: #{e.inspect}\")\n                raise\n              end\n            rescue Errors::ConnectionTimeout\n              message = \"Connection timeout.\"\n            rescue Errors::AuthenticationFailed\n              message = \"Authentication failure.\"\n            rescue Errors::Disconnected\n              message = \"Remote connection disconnect.\"\n            rescue Errors::ConnectionRefused\n              message = \"Connection refused.\"\n            rescue Errors::ConnectionReset\n              message = \"Connection reset.\"\n            rescue Errors::HostDown\n              message = \"Host appears down.\"\n            rescue Errors::NoRoute\n              message = \"Host unreachable.\"\n            rescue Errors::TransientError => e\n              # Any other retryable errors\n              message = e.message\n            end\n\n            # If we have a message to show, then show it. We don't show\n            # repeated messages unless they've been repeating longer than\n            # 10 seconds.\n            if message\n              message_at   = Time.now.to_f\n              show_message = true\n              if last_message == message\n                show_message = (message_at - last_message_repeat_at) > 10.0\n              end\n\n              if show_message\n                @machine.ui.detail(\"Warning: #{message} Retrying...\")\n                last_message = message\n                last_message_repeat_at = message_at\n              end\n            end\n          end\n        end\n      rescue Timeout::Error\n        return false\n      end\n\n      def ready?\n        @logger.info(\"Checking whether WinRM is ready...\")\n\n        result = Timeout.timeout(@machine.config.winrm.timeout) do\n          shell(true).cmd(\"hostname\")\n        end\n\n        @logger.info(\"WinRM is ready!\")\n        return true\n      rescue Errors::TransientError, VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady => e\n        # We catch a `TransientError` which would signal that something went\n        # that might work if we wait and retry.\n        @logger.info(\"WinRM not up: #{e.inspect}\")\n\n        # We reset the shell to trigger calling of winrm_finder again.\n        # This resolves a problem when using vSphere where the winrm_info was not refreshing\n        # thus never getting the correct hostname.\n        @shell = nil\n        return false\n      end\n\n      def reset!\n        shell(true)\n      end\n\n      def shell(reload=false)\n        @shell = nil if reload\n        @shell ||= create_shell\n      end\n\n      def execute(command, opts={}, &block)\n        # If this is a *nix command with no Windows equivalent, don't run it\n        command = @cmd_filter.filter(command)\n        return 0 if command.empty?\n\n        opts = {\n          command:     command,\n          elevated:    false,\n          error_check: true,\n          error_class: Errors::WinRMBadExitStatus,\n          error_key:   nil, # use the error_class message key\n          good_exit:   0,\n          shell:       :powershell,\n          interactive: false,\n        }.merge(opts || {})\n\n        opts[:shell] = :elevated if opts[:elevated]\n        opts[:good_exit] = Array(opts[:good_exit])\n        @logger.debug(\"#{opts[:shell]} executing:\\n#{command}\")\n        output = shell.send(opts[:shell], command, opts, &block)\n        execution_output(output, opts)\n      end\n      alias_method :sudo, :execute\n\n      def test(command, opts=nil)\n        # If this is a *nix command (which we know about) with no Windows\n        # equivalent, assume failure\n        command = @cmd_filter.filter(command)\n        return false if command.empty?\n\n        opts = {\n          command:     command,\n          elevated:    false,\n          error_check: false,\n        }.merge(opts || {})\n\n        # If we're passed a *nix command which PS can't parse we get exit code\n        # 0, but output in stderr. We need to check both exit code and stderr.\n        output = shell.send(:powershell, command)\n        return output.exitcode == 0 && output.stderr.length == 0\n      end\n\n      def upload(from, to)\n        @logger.info(\"Uploading: #{from} to #{to}\")\n        shell.upload(from, to)\n      end\n\n      def download(from, to)\n        @logger.info(\"Downloading: #{from} to #{to}\")\n        shell.download(from, to)\n      end\n\n      protected\n\n      # This creates a new WinRMShell based on the information we know\n      # about this machine.\n      def create_shell\n        winrm_info = Helper.winrm_info(@machine)\n\n        WinRMShell.new(\n          winrm_info[:host],\n          winrm_info[:port],\n          @machine.config.winrm\n        )\n      end\n\n      # Handles the raw WinRM shell result and converts it to a\n      # standard Vagrant communicator result\n      def execution_output(output, opts)\n        if opts[:shell] == :wql\n          return output\n        elsif opts[:error_check] && \\\n          !opts[:good_exit].include?(output.exitcode)\n          raise_execution_error(output, opts)\n        end\n        output.exitcode\n      end\n\n      def raise_execution_error(output, opts)\n        # WinRM can return multiple stderr and stdout entries\n        error_opts = opts.merge(\n          stdout: output.stdout,\n          stderr: output.stderr\n        )\n\n        # Use a different error message key if the caller gave us one,\n        # otherwise use the error's default message\n        error_opts[:_key] = opts[:error_key] if opts[:error_key]\n\n        # Raise the error, use the type the caller gave us or the comm default\n        raise opts[:error_class], error_opts\n      end\n    end #WinRM class\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    class Config < Vagrant.plugin(\"2\", :config)\n      attr_accessor :username\n      attr_accessor :password\n      attr_accessor :host\n      attr_accessor :port\n      attr_accessor :guest_port\n      attr_accessor :max_tries\n      attr_accessor :retry_delay\n      attr_accessor :timeout\n      attr_accessor :transport\n      attr_accessor :ssl_peer_verification\n      attr_accessor :execution_time_limit\n      attr_accessor :basic_auth_only\n      attr_accessor :codepage\n\n      def initialize\n        @username               = UNSET_VALUE\n        @password               = UNSET_VALUE\n        @host                   = UNSET_VALUE\n        @port                   = UNSET_VALUE\n        @guest_port             = UNSET_VALUE\n        @max_tries              = UNSET_VALUE\n        @retry_delay            = UNSET_VALUE\n        @timeout                = UNSET_VALUE\n        @transport              = UNSET_VALUE\n        @ssl_peer_verification  = UNSET_VALUE\n        @execution_time_limit   = UNSET_VALUE\n        @basic_auth_only        = UNSET_VALUE\n        @codepage               = UNSET_VALUE\n      end\n\n      def finalize!\n        @username = \"vagrant\"   if @username == UNSET_VALUE\n        @password = \"vagrant\"   if @password == UNSET_VALUE\n        @transport = :negotiate if @transport == UNSET_VALUE\n        @host = nil           if @host == UNSET_VALUE\n        is_ssl = @transport == :ssl\n        @port = (is_ssl ? 5986 : 5985)       if @port == UNSET_VALUE\n        @guest_port = (is_ssl ? 5986 : 5985) if @guest_port == UNSET_VALUE\n        @max_tries = 20        if @max_tries == UNSET_VALUE\n        @retry_delay = 2       if @retry_delay == UNSET_VALUE\n        @timeout = 1800        if @timeout == UNSET_VALUE\n        @ssl_peer_verification = true if @ssl_peer_verification == UNSET_VALUE\n        @execution_time_limit = \"PT2H\"   if @execution_time_limit == UNSET_VALUE\n        @basic_auth_only = false    if @basic_auth_only == UNSET_VALUE\n        @codepage = nil        if @codepage == UNSET_VALUE\n      end\n\n      def validate(machine)\n        errors = []\n\n        errors << \"winrm.username cannot be nil.\"    if @username.nil?\n        errors << \"winrm.password cannot be nil.\"    if @password.nil?\n        errors << \"winrm.port cannot be nil.\"        if @port.nil?\n        errors << \"winrm.guest_port cannot be nil.\"  if @guest_port.nil?\n        errors << \"winrm.max_tries cannot be nil.\"   if @max_tries.nil?\n        errors << \"winrm.retry_delay cannot be nil.\" if @retry_delay.nil?\n        errors << \"winrm.timeout cannot be nil.\"     if @timeout.nil?\n        errors << \"winrm.execution_time_limit cannot be nil.\"     if @execution_time_limit.nil?\n        unless @ssl_peer_verification == true || @ssl_peer_verification == false\n          errors << \"winrm.ssl_peer_verification must be a boolean.\"\n        end\n        unless @basic_auth_only == true || @basic_auth_only == false\n          errors << \"winrm.basic_auth_only must be a boolean.\"\n        end\n\n        { \"WinRM\" => errors }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    module Errors\n      # A convenient superclass for all our errors.\n      class WinRMError < Vagrant::Errors::VagrantError\n        error_namespace(\"vagrant_winrm.errors\")\n      end\n\n      class TransientError < WinRMError\n      end\n\n      class AuthenticationFailed < WinRMError\n        error_key(:authentication_failed)\n      end\n\n      class ExecutionError < WinRMError\n        error_key(:execution_error)\n      end\n\n      class InvalidShell < WinRMError\n        error_key(:invalid_shell)\n      end\n\n      class WinRMBadExitStatus < WinRMError\n        error_key(:winrm_bad_exit_status)\n      end\n\n      class WinRMNotReady < WinRMError\n        error_key(:winrm_not_ready)\n      end\n\n      class WinRMFileTransferError < WinRMError\n        error_key(:winrm_file_transfer_error)\n      end\n\n      class InvalidTransport < WinRMError\n        error_key(:invalid_transport)\n      end\n\n      class SSLError < WinRMError\n        error_key(:ssl_error)\n      end\n\n      class ConnectionTimeout < TransientError\n        error_key(:connection_timeout)\n      end\n\n      class Disconnected < TransientError\n        error_key(:disconnected)\n      end\n\n      class ConnectionRefused < TransientError\n        error_key(:connection_refused)\n      end\n\n      class ConnectionReset < TransientError\n        error_key(:connection_reset)\n      end\n\n      class HostDown < TransientError\n        error_key(:host_down)\n      end\n\n      class NoRoute < TransientError\n        error_key(:no_route)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/helper.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    # This is a helper module that provides some functions to the\n    # communicator. This is extracted into a module so that we can\n    # easily unit test these methods.\n    module Helper\n      # Returns the host and port to access WinRM.\n      #\n      # This asks the provider via the `winrm_info` capability if it\n      # exists, otherwise defaulting to its own heuristics.\n      #\n      # @param [Vagrant::Machine] machine\n      # @return [Hash]\n      def self.winrm_info(machine)\n        info = {}\n        if machine.provider.capability?(:winrm_info)\n          info = machine.provider.capability(:winrm_info)\n          raise Errors::WinRMNotReady if !info\n        end\n\n        info[:host] ||= winrm_address(machine)\n        info[:port] ||= winrm_port(machine, info[:host] == \"127.0.0.1\")\n        return info\n      end\n\n      # Returns the address to access WinRM. This does not contain\n      # the port.\n      #\n      # @param [Vagrant::Machine] machine\n      # @return [String]\n      def self.winrm_address(machine)\n        addr = machine.config.winrm.host\n        return addr if addr\n\n        ssh_info = machine.ssh_info\n        raise Errors::WinRMNotReady if winrm_info_invalid?(ssh_info)\n        return ssh_info[:host]\n      end\n\n      def self.winrm_info_invalid?(ssh_info)\n        invalid = (!ssh_info || ssh_info[:host].to_s.empty? || ssh_info[:host].start_with?(\"169.254\"))\n        return invalid\n      end\n\n      # Returns the port to access WinRM.\n      #\n      # @param [Vagrant::Machine] machine\n      # @return [Integer]\n      def self.winrm_port(machine, local=true)\n        host_port = machine.config.winrm.port\n        if machine.config.winrm.guest_port\n          # If we're not requesting a local port, return\n          # the guest port directly.\n          return machine.config.winrm.guest_port if !local\n\n          # Search by guest port if we can. We use a provider capability\n          # if we have it. Otherwise, we just scan the Vagrantfile defined\n          # ports.\n          port = nil\n          if machine.provider.capability?(:forwarded_ports)\n            Array(machine.provider.capability(:forwarded_ports)).each do |host, guest|\n              if guest == machine.config.winrm.guest_port\n                port = host\n                break\n              end\n            end\n          end\n\n          if !port\n            machine.config.vm.networks.each do |type, netopts|\n              next if type != :forwarded_port\n              next if !netopts[:host]\n              if netopts[:guest] == machine.config.winrm.guest_port\n                port = netopts[:host]\n                break\n              end\n            end\n          end\n\n          # Set it if we found it\n          host_port = port if port\n        end\n\n        host_port\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    autoload :Errors, File.expand_path(\"../errors\", __FILE__)\n\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"winrm communicator\"\n      description <<-DESC\n      This plugin allows Vagrant to communicate with remote machines using\n      WinRM.\n      DESC\n\n      communicator(\"winrm\") do\n        require File.expand_path(\"../communicator\", __FILE__)\n        init!\n        Communicator\n      end\n\n      config(\"winrm\") do\n        require_relative \"config\"\n        Config\n      end\n\n      protected\n\n      def self.init!\n        return if defined?(@_init)\n        @_init = true\n\n        # Setup the I18n\n        I18n.load_path << File.expand_path(\n          \"templates/locales/comm_winrm.yml\", Vagrant.source_root)\n        I18n.reload!\n\n        # Check if vagrant-winrm plugin is installed and\n        # output warning to user if found\n        if !ENV[\"VAGRANT_IGNORE_WINRM_PLUGIN\"] &&\n            Vagrant::Plugin::Manager.instance.installed_plugins.keys.include?(\"vagrant-winrm\")\n            $stderr.puts <<-EOF\nWARNING: Vagrant has detected the `vagrant-winrm` plugin. Vagrant ships with\nWinRM support builtin and no longer requires the `vagrant-winrm` plugin. To\nprevent unexpected errors please uninstall the `vagrant-winrm` plugin using\nthe command shown below:\n\n  vagrant plugin uninstall vagrant-winrm\n\nTo disable this warning, set the environment variable `VAGRANT_IGNORE_WINRM_PLUGIN`\nEOF\n        end\n        # Load the WinRM gem\n        require \"vagrant/util/silence_warnings\"\n        Vagrant::Util::SilenceWarnings.silence! do\n          require \"winrm\"\n        end\n      end\n\n      # @private\n      # Reset the cached init value. This is not considered a public\n      # API and should only be used for testing.\n      def self.reset!\n        send(:remove_instance_variable, :@_init)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winrm/shell.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"timeout\"\n\nrequire \"log4r\"\n\nrequire \"vagrant/util/retryable\"\nrequire \"vagrant/util/silence_warnings\"\n\nVagrant::Util::SilenceWarnings.silence! do\n  require \"winrm\"\nend\n\nrequire \"winrm-elevated\"\nrequire \"winrm-fs\"\n\nmodule VagrantPlugins\n  module CommunicatorWinRM\n    class WinRMShell\n      include Vagrant::Util::Retryable\n\n      # Exit code generated when user is invalid. Can occur\n      # after a hostname update\n      INVALID_USERID_EXITCODE = -2147024809\n\n      # These are the exceptions that we retry because they represent\n      # errors that are generally fixed from a retry and don't\n      # necessarily represent immediate failure cases.\n      @@exceptions_to_retry_on = [\n        HTTPClient::KeepAliveDisconnected,\n        WinRM::WinRMHTTPTransportError,\n        WinRM::WinRMAuthorizationError,\n        WinRM::WinRMWSManFault,\n        Errno::EACCES,\n        Errno::EADDRINUSE,\n        Errno::ECONNREFUSED,\n        Errno::ECONNRESET,\n        Errno::ENETUNREACH,\n        Errno::EHOSTUNREACH,\n        Timeout::Error\n      ]\n\n      attr_reader :logger\n      attr_reader :host\n      attr_reader :port\n      attr_reader :username\n      attr_reader :password\n      attr_reader :execution_time_limit\n      attr_reader :config\n\n      def initialize(host, port, config)\n        @logger = Log4r::Logger.new(\"vagrant::communication::winrmshell\")\n        @logger.debug(\"initializing WinRMShell\")\n\n        @host                  = host\n        @port                  = port\n        @username              = config.username\n        @password              = config.password\n        @execution_time_limit  = config.execution_time_limit\n        @config                = config\n      end\n\n      def powershell(command, opts = {}, &block)\n        connection.shell(:powershell) do |shell|\n          execute_with_rescue(shell, command, &block)\n        end\n      end\n\n      def cmd(command, opts = {}, &block)\n        shell_opts = {}\n        shell_opts[:codepage] = @config.codepage if @config.codepage\n        connection.shell(:cmd, shell_opts) do |shell|\n          execute_with_rescue(shell, command, &block)\n        end\n      end\n\n      def elevated(command, opts = {}, &block)\n        connection.shell(:elevated) do |shell|\n          shell.interactive_logon = opts[:interactive] || false\n          result = execute_with_rescue(shell, command, &block)\n          if result.exitcode == INVALID_USERID_EXITCODE && result.stderr.include?(\":UserId:\")\n            uname = shell.username\n            ename = elevated_username\n            if uname != ename\n              @logger.warn(\"elevated command failed due to username error\")\n              @logger.warn(\"retrying command using machine prefixed username - #{ename}\")\n              begin\n                shell.username = ename\n                result = execute_with_rescue(shell, command, &block)\n              ensure\n                shell.username = uname\n              end\n            end\n          end\n          result\n        end\n      end\n\n      def wql(query, opts = {}, &block)\n        retryable(tries: @config.max_tries, on: @@exceptions_to_retry_on, sleep: @config.retry_delay) do\n          connection.run_wql(query)\n        end\n      rescue => e\n        raise_winrm_exception(e, \"run_wql\", query)\n      end\n\n      # @param from [Array<String>, String] a single path or folder, or an\n      #        array of paths and folders to upload to the guest\n      # @param to [String] a path or folder on the guest to upload to\n      # @return [FixNum] Total size transfered from host to guest\n      def upload(from, to)\n        file_manager = WinRM::FS::FileManager.new(connection)\n        if from.is_a?(String) && File.directory?(from)\n          if from.end_with?(\".\")\n            from = from[0, from.length - 1]\n          else\n            to = File.join(to, File.basename(File.expand_path(from)))\n          end\n        end\n        if from.is_a?(Array)\n          # Preserve return FixNum of bytes transfered\n          return_bytes = 0\n          from.each do |file|\n            return_bytes += file_manager.upload(file, to)\n          end\n          return return_bytes\n        else\n          file_manager.upload(from, to)\n        end\n      end\n\n      def download(from, to)\n        file_manager = WinRM::FS::FileManager.new(connection)\n        file_manager.download(from, to)\n      end\n\n      protected\n\n      def execute_with_rescue(shell, command, &block)\n        handle_output(shell, command, &block)\n      rescue => e\n        raise_winrm_exception(e, shell.class.name.split(\"::\").last, command)\n      end\n\n      def handle_output(shell, command, &block)\n        output = shell.run(command) do |out, err|\n          block.call(:stdout, out) if block_given? && out\n          block.call(:stderr, err) if block_given? && err\n        end\n\n        @logger.debug(\"Output: #{output.inspect}\")\n\n        # Verify that we didn't get a parser error, and if so we should\n        # set the exit code to 1. Parse errors return exit code 0 so we\n        # need to do this.\n        if output.exitcode == 0\n          if output.stderr.include?(\"ParserError\")\n            @logger.warn(\"Detected ParserError, setting exit code to 1\")\n            output.exitcode = 1\n          end\n        end\n\n        return output\n      end\n\n      def raise_winrm_exception(exception, shell = nil, command = nil)\n        case exception\n        when WinRM::WinRMAuthorizationError\n          raise Errors::AuthenticationFailed,\n              user: @config.username,\n              password: @config.password,\n              endpoint: endpoint,\n              message: exception.message\n        when WinRM::WinRMHTTPTransportError\n          raise Errors::ExecutionError,\n            shell: shell,\n            command: command,\n            message: exception.message\n        when OpenSSL::SSL::SSLError\n          raise Errors::SSLError, message: exception.message\n        when HTTPClient::TimeoutError\n          raise Errors::ConnectionTimeout, message: exception.message\n        when IO::TimeoutError\n          raise Errors::ConnectionTimeout\n        when Errno::ETIMEDOUT\n          raise Errors::ConnectionTimeout\n          # This is raised if the connection timed out\n        when Errno::ECONNREFUSED\n          # This is raised if we failed to connect the max amount of times\n          raise Errors::ConnectionRefused\n        when Errno::ECONNRESET\n          # This is raised if we failed to connect the max number of times\n          # due to an ECONNRESET.\n          raise Errors::ConnectionReset\n        when Errno::EHOSTDOWN\n          # This is raised if we get an ICMP DestinationUnknown error.\n          raise Errors::HostDown\n        when Errno::EHOSTUNREACH\n          # This is raised if we can't work out how to route traffic.\n          raise Errors::NoRoute\n        else\n          raise Errors::ExecutionError,\n            shell: shell,\n            command: command,\n            message: exception.message\n        end\n      end\n\n      def new_connection\n        @logger.info(\"Attempting to connect to WinRM...\")\n        @logger.info(\"  - Host: #{@host}\")\n        @logger.info(\"  - Port: #{@port}\")\n        @logger.info(\"  - Username: #{@config.username}\")\n        @logger.info(\"  - Transport: #{@config.transport}\")\n\n        client = ::WinRM::Connection.new(endpoint_options)\n        client.logger = @logger\n        client\n      end\n\n      def connection\n        @connection ||= new_connection\n      end\n\n      def endpoint\n        case @config.transport.to_sym\n        when :ssl\n          \"https://#{@host}:#{@port}/wsman\"\n        when :plaintext, :negotiate\n          \"http://#{@host}:#{@port}/wsman\"\n        else\n          raise Errors::WinRMInvalidTransport, transport: @config.transport\n        end\n      end\n\n      def endpoint_options\n        { endpoint: endpoint,\n          transport: @config.transport,\n          operation_timeout: @config.timeout,\n          user: @username,\n          password: @password,\n          host: @host,\n          port: @port,\n          basic_auth_only: @config.basic_auth_only,\n          no_ssl_peer_verification: !@config.ssl_peer_verification,\n          retry_delay: @config.retry_delay,\n          retry_limit: @config.max_tries }\n      end\n\n      def elevated_username\n        if username.include?(\"\\\\\")\n          return username\n        end\n        computername = \"\"\n        powershell(\"Write-Output $env:computername\") do |type, data|\n          computername << data if type == :stdout\n        end\n        computername.strip!\n        if computername.empty?\n          return username\n        end\n        \"#{computername}\\\\#{username}\"\n      end\n    end #WinShell class\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winssh/communicator.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../ssh/communicator\", __FILE__)\n\nrequire \"net/sftp\"\n\nmodule VagrantPlugins\n  module CommunicatorWinSSH\n    # This class provides communication with a Windows VM running\n    # the Windows native port of OpenSSH\n    class Communicator < VagrantPlugins::CommunicatorSSH::Communicator\n      # Command to run when checking if connection is ready and working\n      READY_COMMAND=\"dir\"\n\n      def initialize(machine)\n        super\n        @logger = Log4r::Logger.new(\"vagrant::communication::winssh\")\n      end\n\n      # Wrap the shell if required. By default we are using powershell\n      # which requires no modification. If cmd is defined as shell, add\n      # prefix to start within cmd.exe\n      def shell_cmd(opts)\n        case opts[:shell].to_s\n        when \"cmd\"\n          \"cmd.exe /c '#{opts[:command]}'\"\n        else\n          opts[:command]\n        end\n      end\n\n      # Executes the command on an SSH connection within a login shell.\n      def shell_execute(connection, command, **opts)\n        opts[:shell] ||= machine_config_ssh.shell\n\n        command = shell_cmd(opts.merge(command: command))\n\n        @logger.info(\"Execute: #{command} - opts: #{opts}\")\n        exit_status = nil\n\n        # Open the channel so we can execute or command\n        channel = connection.open_channel do |ch|\n          marker_found = false\n          data_buffer = ''\n          stderr_marker_found = false\n          stderr_data_buffer = ''\n\n          @logger.debug(\"Base SSH exec command: #{command}\")\n          command = \"$ProgressPreference = 'SilentlyContinue';Write-Output #{CMD_GARBAGE_MARKER};[Console]::Error.WriteLine('#{CMD_GARBAGE_MARKER}');#{command}\"\n\n          ch.exec(command) do |ch2, _|\n            # Setup the channel callbacks so we can get data and exit status\n            ch2.on_data do |ch3, data|\n              # Filter out the clear screen command\n              data = remove_ansi_escape_codes(data)\n\n              if !marker_found\n                data_buffer << data\n                marker_index = data_buffer.index(CMD_GARBAGE_MARKER)\n                if marker_index\n                  marker_found = true\n                  data_buffer.slice!(0, marker_index + CMD_GARBAGE_MARKER.size)\n                  data.replace(data_buffer)\n                  data_buffer = nil\n                end\n              end\n\n              if block_given? && marker_found\n                yield :stdout, data\n              end\n            end\n\n            ch2.on_extended_data do |ch3, type, data|\n              # Filter out the clear screen command\n              data = remove_ansi_escape_codes(data)\n              @logger.debug(\"stderr: #{data}\")\n              if !stderr_marker_found\n                stderr_data_buffer << data\n                marker_index = stderr_data_buffer.index(CMD_GARBAGE_MARKER)\n                if marker_index\n                  stderr_marker_found = true\n                  stderr_data_buffer.slice!(0, marker_index + CMD_GARBAGE_MARKER.size)\n                  data.replace(stderr_data_buffer.lstrip)\n                  data_buffer = nil\n                end\n              end\n\n              if block_given? && stderr_marker_found && !data.empty?\n                yield :stderr, data\n              end\n            end\n\n            ch2.on_request(\"exit-status\") do |ch3, data|\n              exit_status = data.read_long\n              @logger.debug(\"Exit status: #{exit_status}\")\n\n              # Close the channel, since after the exit status we're\n              # probably done. This fixes up issues with hanging.\n              ch.close\n            end\n\n          end\n        end\n\n        begin\n          keep_alive = nil\n\n          if @machine.config.ssh.keep_alive\n            # Begin sending keep-alive packets while we wait for the script\n            # to complete. This avoids connections closing on long-running\n            # scripts.\n            keep_alive = Thread.new do\n              loop do\n                sleep 5\n                @logger.debug(\"Sending SSH keep-alive...\")\n                connection.send_global_request(\"keep-alive@openssh.com\")\n              end\n            end\n          end\n\n          # Wait for the channel to complete\n          begin\n            channel.wait\n          rescue Errno::ECONNRESET, IOError\n            @logger.info(\n              \"SSH connection unexpected closed. Assuming reboot or something.\")\n            exit_status = 0\n            pty = false\n          rescue Net::SSH::ChannelOpenFailed\n            raise Vagrant::Errors::SSHChannelOpenFail\n          rescue Net::SSH::Disconnect\n            raise Vagrant::Errors::SSHDisconnected\n          end\n        ensure\n          # Kill the keep-alive thread\n          keep_alive.kill if keep_alive\n        end\n\n        # Return the final exit status\n        return exit_status\n      end\n\n      def machine_config_ssh\n        @machine.config.winssh\n      end\n\n      def download(from, to=nil)\n        @logger.debug(\"Downloading: #{from} to #{to}\")\n\n        sftp_connect do |sftp|\n          sftp.download!(from, to)\n        end\n      end\n\n      # Note: I could not get Net::SFTP to throw a permissions denied error,\n      # even when uploading to a directory where I did not have write\n      # privileges. I believe this is because Windows SSH sessions are started\n      # in an elevated process.\n      def upload(from, to)\n        to = Vagrant::Util::Platform.unix_windows_path(to)\n        @logger.debug(\"Uploading: #{from} to #{to}\")\n\n        if File.directory?(from)\n          if from.end_with?(\".\")\n            @logger.debug(\"Uploading directory contents of: #{from}\")\n            from = from.sub(/\\.$/, \"\")\n          else\n            @logger.debug(\"Uploading full directory container of: #{from}\")\n            to = File.join(to, File.basename(File.expand_path(from)))\n          end\n        end\n\n        sftp_connect do |sftp|\n          uploader = lambda do |path, remote_dest=nil|\n            if File.directory?(path)\n              Dir.new(path).each do |entry|\n                next if entry == \".\" || entry == \"..\"\n                full_path = File.join(path, entry)\n                dest = File.join(to, path.sub(/^#{Regexp.escape(from)}/, \"\"))\n                sftp.mkdir(dest)\n                uploader.call(full_path, dest)\n              end\n            else\n              if remote_dest\n                dest = File.join(remote_dest, File.basename(path))\n              else\n                dest = to\n                if to.end_with?(File::SEPARATOR)\n                  dest = File.join(to, File.basename(path))\n                end\n              end\n              @logger.debug(\"Ensuring remote directory exists for destination upload\")\n              sftp.mkdir(File.dirname(dest))\n              @logger.debug(\"Uploading file #{path} to remote #{dest}\")\n              upload_file = File.open(path, \"rb\")\n              begin\n                sftp.upload!(upload_file, dest)\n              ensure\n                upload_file.close\n              end\n            end\n          end\n          uploader.call(from)\n        end\n      end\n\n      # Opens an SFTP connection and yields it so that you can download and\n      # upload files. SFTP works more reliably than SCP on Windows due to\n      # issues with shell quoting and escaping.\n      def sftp_connect\n        # Connect to SFTP and yield the SFTP object\n        connect do |connection|\n          return yield connection.sftp\n        end\n      end\n\n      protected\n\n      # The WinSSH communicator connection provides isolated modification\n      # to the generated connection instances. This modification forces\n      # all provided commands to run within powershell\n      def connect(**opts)\n        connection = nil\n        super { |c| connection = c }\n\n        if !connection.instance_variable_get(:@winssh_patched)\n          open_chan = connection.method(:open_channel)\n          connection.define_singleton_method(:open_channel) do |*args, &chan_block|\n            open_chan.call(*args) do |ch|\n              exec = ch.method(:exec)\n              ch.define_singleton_method(:exec) do |command, &block|\n                command = Base64.strict_encode64(command.encode(\"UTF-16LE\", \"UTF-8\"))\n                command = \"powershell -NoLogo -NonInteractive -ExecutionPolicy Bypass \" \\\n                  \"-NoProfile -EncodedCommand #{command}\"\n                exec.call(command, &block)\n              end\n              chan_block.call(ch)\n            end\n          end\n          connection.instance_variable_set(:@winssh_patched, true)\n        end\n\n        if block_given?\n          yield connection\n        else\n          connection\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winssh/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../kernel_v2/config/ssh\", __FILE__)\n\nmodule VagrantPlugins\n  module CommunicatorWinSSH\n    class Config < VagrantPlugins::Kernel_V2::SSHConfig\n\n      attr_accessor :upload_directory\n\n      def initialize\n        super\n        @upload_directory = UNSET_VALUE\n      end\n\n      def finalize!\n        @shell = \"powershell\" if @shell == UNSET_VALUE\n        @sudo_command = \"%c\" if @sudo_command == UNSET_VALUE\n        @upload_directory = \"C:/Windows/Temp\" if @upload_directory == UNSET_VALUE\n        if @export_command_template == UNSET_VALUE\n          @export_command_template = '$env:%ENV_KEY%=\"%ENV_VALUE%\"'\n        end\n        super\n      end\n\n      def to_s\n        \"WINSSH\"\n      end\n\n      # Remove configuration options from regular SSH that are\n      # not used within this communicator\n      undef :forward_x11\n      undef :pty\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/communicators/winssh/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CommunicatorWinSSH\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"windows ssh communicator\"\n      description <<-DESC\n      DESC\n\n      communicator(\"winssh\") do\n        require File.expand_path(\"../communicator\", __FILE__)\n        Communicator\n      end\n\n      config(\"winssh\") do\n        require_relative \"config\"\n        Config\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alma/cap/flavor.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestAlma\n    module Cap\n      class Flavor\n        def self.flavor(machine)\n          # Read the version file\n          version = \"\"\n          machine.communicate.sudo(\"source /etc/os-release && printf $VERSION_ID\") do |type, data|\n            if type == :stdout\n              version = data.split(\".\").first.to_i\n            end\n          end\n\n          if version.nil? || version < 1\n            :alma\n          else\n            \"alma_#{version}\".to_sym\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alma/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../linux/guest\"\n\nmodule VagrantPlugins\n  module GuestAlma\n    class Guest < VagrantPlugins::GuestLinux::Guest\n      # Name used for guest detection\n      GUEST_DETECTION_NAME = \"almalinux\".freeze\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alma/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestAlma\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Alma guest\"\n      description \"Alma guest support.\"\n\n      guest(:alma, :redhat) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:alma, :flavor) do\n        require_relative \"cap/flavor\"\n        Cap::Flavor\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alpine/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/guest_hosts'\n\nmodule VagrantPlugins\n  module GuestAlpine\n    module Cap\n      class ChangeHostName\n        include Vagrant::Util::GuestHosts::Linux\n\n        def self.change_host_name(machine, name)\n          new(machine, name).change!\n        end\n\n        attr_reader :machine, :new_hostname\n\n        def initialize(machine, new_hostname)\n          @machine = machine\n          @new_hostname = new_hostname\n        end\n\n        def change!\n          update_etc_hosts\n          return unless should_change?\n\n          update_etc_hostname\n          refresh_hostname_service\n          update_mailname\n          renew_dhcp\n        end\n\n        def should_change?\n          new_hostname != current_hostname\n        end\n\n        def current_hostname\n          @current_hostname ||= fetch_current_hostname\n        end\n\n        def fetch_current_hostname\n          hostname = ''\n          machine.communicate.sudo 'hostname -f' do |type, data|\n            hostname = data.chomp if type == :stdout && hostname.empty?\n          end\n\n          hostname\n        end\n\n        def update_etc_hostname\n          machine.communicate.sudo(\"echo '#{short_hostname}' > /etc/hostname\")\n        end\n\n        # /etc/hosts should resemble:\n        # 127.0.0.1   localhost\n        # 127.0.1.1   host.fqdn.com host.fqdn host\n        def update_etc_hosts\n          comm = machine.communicate\n          network_with_hostname = machine.config.vm.networks.map {|_, c| c if c[:hostname] }.compact[0]\n          if network_with_hostname\n            replace_host(comm, new_hostname, network_with_hostname[:ip])\n          else\n            add_hostname_to_loopback_interface(comm, new_hostname)\n          end\n        end\n\n        def refresh_hostname_service\n          machine.communicate.sudo('hostname -F /etc/hostname')\n        end\n\n        def update_mailname\n          machine.communicate.sudo('hostname -f > /etc/mailname')\n        end\n\n        def renew_dhcp\n          machine.communicate.sudo('ifdown -a; ifup -a; ifup eth0')\n        end\n\n        def short_hostname\n          new_hostname.split('.').first\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alpine/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# rubocop:disable Metrics/MethodLength\n# rubocop:disable Metrics/AbcSize\n# rubocop:disable Style/BracesAroundHashParameters\n#\n# FIXME: address disabled warnings\n#\nrequire 'set'\nrequire 'tempfile'\nrequire 'pathname'\nrequire 'vagrant/util/template_renderer'\n\nmodule VagrantPlugins\n  module GuestAlpine\n    module Cap\n      class ConfigureNetworks\n        include Vagrant::Util\n        def self.configure_networks(machine, networks)\n          machine.communicate.tap do |comm|\n            # First, remove any previous network modifications\n            # from the interface file.\n            comm.sudo(\"sed -e '/^#VAGRANT-BEGIN/,$ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces.pre\")\n            comm.sudo(\"sed -ne '/^#VAGRANT-END/,$ p' /etc/network/interfaces | tail -n +2 > /tmp/vagrant-network-interfaces.post\")\n\n            # Accumulate the configurations to add to the interfaces file as\n            # well as what interfaces we're actually configuring since we use that\n            # later.\n            interfaces = Set.new\n            entries = []\n            networks.each do |network|\n              interfaces.add(network[:interface])\n              entry = TemplateRenderer.render(\"guests/alpine/network_#{network[:type]}\", { options: network })\n              entries << entry\n            end\n\n            # Perform the careful dance necessary to reconfigure\n            # the network interfaces\n            temp = Tempfile.new('vagrant')\n            temp.binmode\n            temp.write(entries.join(\"\\n\"))\n            temp.close\n\n            comm.upload(temp.path, '/tmp/vagrant-network-entry')\n\n            # Bring down all the interfaces we're reconfiguring. By bringing down\n            # each specifically, we avoid reconfiguring eth0 (the NAT interface) so\n            # SSH never dies.\n            interfaces.each do |interface|\n              comm.sudo(\"if [[ $(/sbin/ip a show eth#{interface} | grep UP) ]]; then /sbin/ifdown eth#{interface} 2> /dev/null; fi\")\n              comm.sudo(\"/sbin/ip addr flush dev eth#{interface} 2> /dev/null\")\n            end\n\n            comm.sudo('cat /tmp/vagrant-network-interfaces.pre /tmp/vagrant-network-entry /tmp/vagrant-network-interfaces.post > /etc/network/interfaces')\n            comm.sudo('rm -f /tmp/vagrant-network-interfaces.pre /tmp/vagrant-network-entry /tmp/vagrant-network-interfaces.post')\n\n            # Bring back up each network interface, reconfigured\n            interfaces.each do |interface|\n              comm.sudo(\"/sbin/ifup eth#{interface}\")\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alpine/cap/halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# rubocop:disable Style/RedundantBegin\n# rubocop:disable Lint/HandleExceptions\n#\n# FIXME: address disabled warnings\n#\nmodule VagrantPlugins\n  module GuestAlpine\n    module Cap\n      class Halt\n        def self.halt(machine)\n          begin\n            machine.communicate.sudo('poweroff')\n          rescue Net::SSH::Disconnect, IOError\n            # Ignore, this probably means connection closed because it\n            # shut down and SSHd was stopped.\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alpine/cap/nfs_client.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n    module GuestAlpine\n        module Cap\n            class NFSClient\n                def self.nfs_client_install(machine)\n                    machine.communicate.sudo('apk update')\n                    machine.communicate.sudo('apk add --upgrade nfs-utils')\n                    machine.communicate.sudo('rc-update add rpc.statd')\n                    machine.communicate.sudo('rc-service rpc.statd start')\n                end\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "plugins/guests/alpine/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestAlpine\n    module Cap\n      class RSync\n        def self.rsync_installed(machine)\n          machine.communicate.test('test -f /usr/bin/rsync')\n        end\n\n        def self.rsync_install(machine)\n          machine.communicate.tap do |comm|\n            comm.sudo('apk add --update-cache rsync')\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alpine/cap/smb.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestAlpine\n    module Cap\n      class SMB\n        def self.smb_install(machine)\n          machine.communicate.tap do |comm|\n            comm.sudo('apk add cifs-utils')\n          end\n        end\n      end\n    end\n  end\nend"
  },
  {
    "path": "plugins/guests/alpine/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant'\n\nmodule VagrantPlugins\n  module GuestAlpine\n    class Guest < Vagrant.plugin('2', :guest)\n      def detect?(machine)\n        machine.communicate.test('cat /etc/alpine-release')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alpine/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant'\n\nmodule VagrantPlugins\n  module GuestAlpine\n    class Plugin < Vagrant.plugin('2')\n      name 'Alpine guest'\n      description 'Alpine Linux guest support.'\n\n      guest(:alpine, :linux) do\n        require File.expand_path('../guest', __FILE__)\n        Guest\n      end\n\n      guest_capability(:alpine, :configure_networks) do\n        require_relative 'cap/configure_networks'\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:alpine, :halt) do\n        require_relative 'cap/halt'\n        Cap::Halt\n      end\n\n      guest_capability(:alpine, :change_host_name) do\n        require_relative 'cap/change_host_name'\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:alpine, :nfs_client_install) do\n        require_relative 'cap/nfs_client'\n        Cap::NFSClient\n      end\n\n      guest_capability(:alpine, :rsync_installed) do\n        require_relative 'cap/rsync'\n        Cap::RSync\n      end\n\n      guest_capability(:alpine, :rsync_install) do\n        require_relative 'cap/rsync'\n        Cap::RSync\n      end\n\n      guest_capability(:alpine, :smb_install) do\n        require_relative 'cap/smb'\n        Cap::SMB\n      end\n\n      def self.check_community_plugin\n        plugins = Vagrant::Plugin::Manager.instance.installed_plugins\n        if plugins.keys.include?(\"vagrant-alpine\")\n          $stderr.puts <<-EOF\nWARNING: Vagrant has detected the `vagrant-alpine` plugin. This plugin's\nfunctionality has been merged into the main Vagrant project and should be\nconsidered deprecated. To uninstall the plugin, run the command shown below:\n\n  vagrant plugin uninstall vagrant-alpine\n\nEOF\n        end\n      end\n\n      self.check_community_plugin\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alt/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../../linux/cap/change_host_name'\n\nmodule VagrantPlugins\n  module GuestALT\n    module Cap\n      class ChangeHostName\n        extend VagrantPlugins::GuestLinux::Cap::ChangeHostName\n        \n        def self.change_name_command(name)\n          basename = name.split(\".\", 2)[0]\n          return <<-EOH.gsub(/^ {14}/, '')\n          # Save current hostname saved in /etc/hosts\n          CURRENT_HOSTNAME_FULL=\"$(hostname -f)\"\n          CURRENT_HOSTNAME_SHORT=\"$(hostname -s)\"\n\n          # Set the hostname - use hostnamectl if available\n          if command -v hostnamectl; then\n            hostnamectl set-hostname --static '#{name}'\n            hostnamectl set-hostname --transient '#{name}'\n          else\n            hostname '#{name}'\n          fi\n\n          # Persist hostname change across reboots\n          if [ -f /etc/sysconfig/network ]; then\n            sed -i 's/\\\\(HOSTNAME=\\\\).*/\\\\1#{name}/' /etc/sysconfig/network\n          elif [ -f /etc/hostname ]; then\n            sed -i 's/.*/#{name}/' /etc/hostname\n          else\n            echo 'Unrecognized system. Hostname change may not persist across reboots.'\n            exit 0\n          fi\n\n          # Restart the network if we find a recognized SYS V init script\n          if command -v service; then\n            if [ -f /etc/init.d/network ]; then\n              service network restart\n            elif [ -f /etc/init.d/networking ]; then\n              service networking restart\n            elif [ -f /etc/init.d/NetworkManager ]; then\n              service NetworkManager restart\n            else\n              echo 'Unrecognized system. Networking was not restarted following hostname change.'\n              exit 0\n            fi\n          fi\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alt/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestALT\n    module Cap\n      class ConfigureNetworks\n        include Vagrant::Util\n        extend Vagrant::Util::GuestInspection::Linux\n\n        def self.configure_networks(machine, networks)\n          comm = machine.communicate\n\n          network_scripts_dir = machine.guest.capability(:network_scripts_dir)\n\n          commands   = {:start => [], :middle => [], :end => []}\n          interfaces = machine.guest.capability(:network_interfaces)\n\n          # Check if NetworkManager is installed on the system\n          nmcli_installed = nmcli?(comm)\n          net_configs = machine.config.vm.networks.map do |type, opts|\n            opts if type.to_s.end_with?(\"_network\")\n          end.compact\n          networks.each.with_index do |network, i|\n            network[:device] = interfaces[network[:interface]]\n            extra_opts = net_configs[i] ? net_configs[i].dup : {}\n\n            if nmcli_installed\n              # Now check if the device is actively being managed by NetworkManager\n              nm_controlled = nm_controlled?(comm, network[:device])\n            end\n\n            if !extra_opts.key?(:nm_controlled)\n              extra_opts[:nm_controlled] = !!nm_controlled\n            end\n\n            extra_opts[:nm_controlled] = case extra_opts[:nm_controlled]\n                                      when true\n                                        \"yes\"\n                                      when false, nil\n                                        \"no\"\n                                      else\n                                        extra_opts[:nm_controlled].to_s\n                                      end\n\n            if extra_opts[:nm_controlled] == \"yes\" && !nmcli_installed\n              raise Vagrant::Errors::NetworkManagerNotInstalled, device: network[:device]\n            end\n\n            # Render a new configuration\n            template_options = extra_opts.merge(network)\n\n            # ALT expects netmasks to be in the CIDR notation, but users may\n            # specify IPV4 netmasks like \"255.255.255.0\". This magic converts\n            # the netmask to the proper value.\n            if template_options[:netmask] && template_options[:netmask].to_s.include?(\".\")\n              template_options[:netmask] = (32-Math.log2((IPAddr.new(template_options[:netmask], Socket::AF_INET).to_i^0xffffffff)+1)).to_i\n            end\n\n            options_entry = TemplateRenderer.render(\"guests/alt/network_#{network[:type]}\", options: template_options)\n\n            # Upload the new configuration\n            options_remote_path = \"/tmp/vagrant-network-entry-#{network[:device]}-#{Time.now.to_i}-#{i}\"\n            ipv4_address_remote_path = \"/tmp/vagrant-network-ipv4-address-entry-#{network[:device]}-#{Time.now.to_i}-#{i}\"\n            ipv4_route_remote_path = \"/tmp/vagrant-network-ipv4-route-entry-#{network[:device]}-#{Time.now.to_i}-#{i}\"\n\n            Tempfile.open(\"vagrant-alt-configure-networks\") do |f|\n              f.binmode\n              f.write(options_entry)\n              f.fsync\n              f.close\n              machine.communicate.upload(f.path, options_remote_path)\n            end\n\n            # Add the new interface and bring it back up\n            iface_path = \"#{network_scripts_dir}/ifaces/#{network[:device]}\"\n\n            if network[:type].to_sym == :static\n              ipv4_address_entry = TemplateRenderer.render(\"guests/alt/network_ipv4address\", options: template_options)\n\n              # Upload the new ipv4address configuration\n              Tempfile.open(\"vagrant-alt-configure-ipv4-address\") do |f|\n                f.binmode\n                f.write(ipv4_address_entry)\n                f.fsync\n                f.close\n                machine.communicate.upload(f.path, ipv4_address_remote_path)\n              end\n\n              ipv4_route_entry = TemplateRenderer.render(\"guests/alt/network_ipv4route\", options: template_options)\n\n              # Upload the new ipv4route configuration\n              Tempfile.open(\"vagrant-alt-configure-ipv4-route\") do |f|\n                f.binmode\n                f.write(ipv4_route_entry)\n                f.fsync\n                f.close\n                machine.communicate.upload(f.path, ipv4_route_remote_path)\n              end\n            end\n\n            if nm_controlled and extra_opts[:nm_controlled] == \"yes\"\n              commands[:start] << \"nmcli d disconnect iface '#{network[:device]}'\"\n            else\n              commands[:start] << \"/sbin/ifdown '#{network[:device]}'\"\n            end\n            commands[:middle] << \"mkdir -p '#{iface_path}'\"\n            commands[:middle] << \"mv -f '#{options_remote_path}' '#{iface_path}/options'\"\n            if network[:type].to_sym == :static\n              commands[:middle] << \"mv -f '#{ipv4_address_remote_path}' '#{iface_path}/ipv4address'\"\n              commands[:middle] << \"mv -f '#{ipv4_route_remote_path}' '#{iface_path}/ipv4route'\"\n            end\n            if extra_opts[:nm_controlled] == \"no\"\n              commands[:end] << \"/sbin/ifup '#{network[:device]}'\"\n            end\n          end\n          if nmcli_installed\n            commands[:middle] << \"((systemctl | grep NetworkManager.service) && systemctl restart NetworkManager) || \" \\\n              \"(test -f /etc/init.d/NetworkManager && /etc/init.d/NetworkManager restart)\"\n          end\n          commands = commands[:start] + commands[:middle] + commands[:end]\n          comm.sudo(commands.join(\"\\n\"))\n          comm.wait_for_ready(5)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alt/cap/flavor.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestALT\n    module Cap\n      class Flavor\n        def self.flavor(machine)\n          comm = machine.communicate\n\n          # Read the version file\n          if comm.test(\"test -f /etc/os-release\")\n            name = nil\n            comm.sudo(\"grep NAME /etc/os-release\") do |type, data|\n              if type == :stdout\n                name = data.split(\"=\")[1].gsub!(/\\A\"|\"\\Z/, '')\n              end\n            end\n\n            if !name.nil? and name == \"Sisyphus\"\n              return :alt\n            end\n\n            version = nil\n            comm.sudo(\"grep VERSION_ID /etc/os-release\") do |type, data|\n              if type == :stdout\n                verstr = data.split(\"=\")[1]\n                if verstr == \"p8\"\n                  version = 8\n                elsif verstr =~ /^[[\\d]]/\n                  version = verstr.chomp.to_i\n                  subversion = verstr.chomp.split(\".\")[1].to_i\n                  if subversion > 90\n                    version += 1\n                  end\n                end\n              end\n            end\n\n            if version.nil? or version == 0\n              return :alt\n            else\n              return :\"alt_#{version}\"\n            end\n          else\n            output = \"\"\n            comm.sudo(\"cat /etc/altlinux-release\") do |_, data|\n              output = data\n            end\n\n            # Detect various flavors we care about\n            if output =~ /(ALT SP|ALT Education|ALT Workstation|ALT Workstation K|ALT Linux starter kit)\\s*8(\\.[1-9])?( .+)?/i\n              return :alt_8\n            elsif output =~ /ALT\\s+8(\\.[1-9])?( .+)?\\s.+/i\n              return :alt_8\n            elsif output =~ /ALT Linux p8( .+)?/i\n              return :alt_8\n            else\n              return :alt\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alt/cap/network_scripts_dir.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestALT\n    module Cap\n      class NetworkScriptsDir\n        def self.network_scripts_dir(machine)\n          \"/etc/net\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alt/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestALT\n    module Cap\n      class RSync\n        def self.rsync_install(machine)\n          machine.communicate.sudo <<-EOH.gsub(/^ {12}/, '')\n            apt-get install -y -qq install rsync\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alt/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestALT\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"cat /etc/altlinux-release\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/alt/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestALT\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"ALT Platform guest\"\n      description \"ALT Platform guest support.\"\n\n      guest(:alt, :redhat) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:alt, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:alt, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:alt, :flavor) do\n        require_relative \"cap/flavor\"\n        Cap::Flavor\n      end\n\n      guest_capability(:alt, :network_scripts_dir) do\n        require_relative \"cap/network_scripts_dir\"\n        Cap::NetworkScriptsDir\n      end\n\n      guest_capability(:alt, :rsync_install) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/amazon/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../debian/cap/configure_networks\"\nrequire_relative \"../../redhat/cap/configure_networks\"\n\nmodule VagrantPlugins\n  module GuestAmazon\n    module Cap\n      class ConfigureNetworks\n        extend Vagrant::Util::GuestInspection::Linux\n\n        def self.configure_networks(machine, networks)\n          # If the guest is using networkd, call the debian capability\n          # as it will handle networkd. Otherwise, fallback to using\n          # the RHEL capability.\n          if systemd_networkd?(machine.communicate)\n            VagrantPlugins::GuestDebian::Cap::ConfigureNetworks.configure_networks(machine, networks)\n          else\n            VagrantPlugins::GuestRedHat::Cap::ConfigureNetworks.configure_networks(machine, networks)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/amazon/cap/flavor.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestAmazon\n    module Cap\n      class Flavor\n        def self.flavor(machine)\n          # Amazon AMI is a frankenstien RHEL, mainly based on 6\n          # Maybe in the future if they incorporate RHEL 7 elements\n          # this should be extended to read /etc/os-release or similar\n          return :rhel\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/amazon/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestAmazon\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"grep 'Amazon Linux' /etc/os-release\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/amazon/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestAmazon\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Amazon Linux guest\"\n      description \"Amazon linux guest support.\"\n\n      guest(:amazon, :redhat) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:amazon, :flavor) do\n        require_relative \"cap/flavor\"\n        Cap::Flavor\n      end\n\n      guest_capability(:amazon, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/arch/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../../linux/cap/change_host_name'\n\nmodule VagrantPlugins\n  module GuestArch\n    module Cap\n      class ChangeHostName\n        extend VagrantPlugins::GuestLinux::Cap::ChangeHostName\n\n        def self.change_name_command(name)\n          \"hostnamectl set-hostname '#{name.split(\".\", 2).first}'\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/arch/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"ipaddr\"\nrequire \"socket\"\nrequire \"tempfile\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestArch\n    module Cap\n      class ConfigureNetworks\n        include Vagrant::Util\n        extend Vagrant::Util::GuestInspection::Linux\n        extend Vagrant::Util::GuestNetworks::Linux\n\n        def self.configure_networks(machine, networks)\n          comm = machine.communicate\n\n          return configure_network_manager(machine, networks) if systemd_network_manager?(comm)\n\n          commands = []\n          uses_systemd_networkd = systemd_networkd?(comm)\n\n          interfaces = machine.guest.capability(:network_interfaces)\n          networks.each.with_index do |network, i|\n            network[:device] = interfaces[network[:interface]]\n\n            # Arch expects netmasks to be in the \"24\" or \"64\", but users may\n            # specify IPV4 netmasks like \"255.255.255.0\". This magic converts\n            # the netmask to the proper value.\n            if network[:netmask] && network[:netmask].to_s.include?(\".\")\n              network[:netmask] = (32-Math.log2((IPAddr.new(network[:netmask], Socket::AF_INET).to_i^0xffffffff)+1)).to_i\n            end\n\n            if uses_systemd_networkd\n              entry = TemplateRenderer.render(\"guests/arch/systemd_networkd/network_#{network[:type]}\",\n                options: network,\n              )\n            else\n              entry = TemplateRenderer.render(\"guests/arch/default_network/network_#{network[:type]}\",\n                options: network,\n              )\n            end\n\n            remote_path = \"/tmp/vagrant-network-#{network[:device]}-#{Time.now.to_i}-#{i}\"\n\n            Tempfile.open(\"vagrant-arch-configure-networks\") do |f|\n              f.binmode\n              f.write(entry)\n              f.fsync\n              f.close\n              comm.upload(f.path, remote_path)\n            end\n\n            if uses_systemd_networkd\n              commands << <<-EOH.gsub(/^ {16}/, '').rstrip\n                # Configure #{network[:device]}\n                chmod 0644 '#{remote_path}' &&\n                mv '#{remote_path}' '/etc/systemd/network/#{network[:device]}.network' &&\n                networkctl reload\n              EOH\n            else\n              commands << <<-EOH.gsub(/^ {16}/, '').rstrip\n                # Configure #{network[:device]}\n                mv '#{remote_path}' '/etc/netctl/#{network[:device]}' &&\n                ip link set '#{network[:device]}' down &&\n                netctl restart '#{network[:device]}' &&\n                netctl enable '#{network[:device]}'\n              EOH\n            end\n          end\n\n          # Run all the network modification commands in one communicator call.\n          comm.sudo(commands.join(\" && \\n\"))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/arch/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestArch\n    module Cap\n      class NFS\n        def self.nfs_client_installed(machine)\n          machine.communicate.test(\"pacman -Q nfs-utils\")\n        end\n\n        def self.nfs_pre(machine)\n          comm = machine.communicate\n\n          # There is a bug in NFS where the rpcbind functionality is not started\n          # and it's not a dependency of nfs-utils. Read more here:\n          #\n          #   https://bbs.archlinux.org/viewtopic.php?id=193410\n          #\n          comm.sudo <<-EOH.gsub(/^ {12}/, \"\")\n            systemctl enable rpcbind &&\n            systemctl start rpcbind\n          EOH\n        end\n\n        def self.nfs_client_install(machine)\n          comm = machine.communicate\n          comm.sudo <<-EOH.gsub(/^ {12}/, \"\")\n            pacman --noconfirm -Syy &&\n            pacman --noconfirm -S nfs-utils ntp\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/arch/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestArch\n    module Cap\n      class RSync\n        def self.rsync_install(machine)\n          comm = machine.communicate\n          comm.sudo <<-EOH.gsub(/^ {12}/, '')\n            pacman -Sy --noconfirm\n            pacman -S --noconfirm rsync\n            exit $?\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/arch/cap/smb.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestArch\n    module Cap\n      class SMB\n        def self.smb_install(machine)\n          comm = machine.communicate\n          if !comm.test(\"test -f /usr/bin/mount.cifs\")\n            comm.sudo <<-EOH.gsub(/^ {14}/, '')\n              pacman -Sy --noconfirm\n              pacman -S --noconfirm smbclient cifs-utils\n            EOH\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/arch/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestArch\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"cat /etc/arch-release\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/arch/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestArch\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Arch guest\"\n      description \"Arch guest support.\"\n\n      guest(:arch, :linux) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:arch, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:arch, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:arch, :nfs_client_install) do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      guest_capability(:arch, :nfs_client_installed) do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      guest_capability(:arch, :nfs_pre) do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      guest_capability(:arch, :rsync_install) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:arch, :smb_install) do\n        require_relative \"cap/smb\"\n        Cap::SMB\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/atomic/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../../linux/cap/change_host_name'\n\nmodule VagrantPlugins\n  module GuestAtomic\n    module Cap\n      class ChangeHostName\n        extend VagrantPlugins::GuestLinux::Cap::ChangeHostName\n\n        def self.change_name_command(name)\n          \"hostnamectl set-hostname '#{name.split(\".\", 2).first}'\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/atomic/cap/docker.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestAtomic\n    module Cap\n      module Docker\n        def self.docker_daemon_running(machine)\n          machine.communicate.test(\"test -S /run/docker.sock\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/atomic/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestAtomic\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"grep 'ostree=.*atomic' /proc/cmdline\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/atomic/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestAtomic\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Atomic Host guest\"\n      description \"Atomic Host guest support.\"\n\n      guest(:atomic, :fedora) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:atomic, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:atomic, :docker_daemon_running) do\n        require_relative \"cap/docker\"\n        Cap::Docker\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/bsd/cap/file_system.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestBSD\n    module Cap\n      class FileSystem\n        # Create a temporary file or directory on the guest\n        #\n        # @param [Vagrant::Machine] machine Vagrant guest machine\n        # @param [Hash] opts Path options\n        # @return [String] path to temporary file or directory\n        def self.create_tmp_path(machine, opts)\n          template = \"vagrant\"\n          cmd = [\"mktemp\"]\n          if opts[:type] == :directory\n            cmd << \"-d\"\n          end\n          cmd << \"-t\"\n          cmd << template\n          tmp_path = \"\"\n          machine.communicate.execute(cmd.join(\" \")) do |type, data|\n            if type == :stdout\n              tmp_path << data\n            end\n          end\n          tmp_path.strip\n        end\n\n        # Decompress tgz file on guest to given location\n        #\n        # @param [Vagrant::Machine] machine Vagrant guest machine\n        # @param [String] compressed_file Path to compressed file on guest\n        # @param [String] destination Path for decompressed files on guest\n        def self.decompress_tgz(machine, compressed_file, destination, opts={})\n          comm = machine.communicate\n          extract_dir = create_tmp_path(machine, type: :directory)\n          cmds = []\n          if opts[:type] == :directory\n            cmds << \"mkdir -p '#{destination}'\"\n          else\n            cmds << \"mkdir -p '#{File.dirname(destination)}'\"\n          end\n          cmds += [\n            \"tar -C '#{extract_dir}' -xzf '#{compressed_file}'\",\n            \"mv '#{extract_dir}'/* '#{destination}'\",\n            \"rm -f '#{compressed_file}'\",\n            \"rm -rf '#{extract_dir}'\"\n          ]\n          cmds.each{ |cmd| comm.execute(cmd) }\n          true\n        end\n\n        # Decompress zip file on guest to given location\n        #\n        # @param [Vagrant::Machine] machine Vagrant guest machine\n        # @param [String] compressed_file Path to compressed file on guest\n        # @param [String] destination Path for decompressed files on guest\n        def self.decompress_zip(machine, compressed_file, destination, opts={})\n          comm = machine.communicate\n          extract_dir = create_tmp_path(machine, type: :directory)\n          cmds = []\n          if opts[:type] == :directory\n            cmds << \"mkdir -p '#{destination}'\"\n          else\n            cmds << \"mkdir -p '#{File.dirname(destination)}'\"\n          end\n          cmds += [\n            \"unzip '#{compressed_file}' -d '#{extract_dir}'\",\n            \"mv '#{extract_dir}'/* '#{destination}'\",\n            \"rm -f '#{compressed_file}'\",\n            \"rm -rf '#{extract_dir}'\"\n          ]\n          cmds.each{ |cmd| comm.execute(cmd) }\n          true\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/bsd/cap/halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestBSD\n    module Cap\n      class Halt\n        def self.halt(machine)\n          begin\n            machine.communicate.sudo(\"/sbin/shutdown -p now\", shell: \"sh\")\n          rescue IOError, Vagrant::Errors::SSHDisconnected\n            # Do nothing, because it probably means the machine shut down\n            # and SSH connection was lost.\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/bsd/cap/mount_virtualbox_shared_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestBSD\n    module Cap\n      class MountVirtualBoxSharedFolder\n        # BSD-based guests do not currently support VirtualBox synced folders.\n        # Instead of raising an error about a missing capability, this defines\n        # the capability and then provides a more detailed error message,\n        # linking to sources on the Internet where the problem is\n        # better-described.\n        def self.mount_virtualbox_shared_folder(machine, name, guestpath, options)\n          raise Vagrant::Errors::VirtualBoxMountNotSupportedBSD\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/bsd/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"shellwords\"\nrequire \"vagrant/util/retryable\"\n\nmodule VagrantPlugins\n  module GuestBSD\n    module Cap\n      class NFS\n        extend Vagrant::Util::Retryable\n\n        # Mount the given NFS folder.\n        def self.mount_nfs_folder(machine, ip, folders)\n          comm = machine.communicate\n\n          # Mount each folder separately so we can retry.\n          folders.each do |name, opts|\n            # Shellescape the paths in case they do not have special characters.\n            guest_path = Shellwords.escape(opts[:guestpath])\n            host_path  = Shellwords.escape(opts[:hostpath])\n\n            # Build the list of mount options.\n            mount_opts =  []\n            mount_opts << \"nfsv#{opts[:nfs_version]}\" if opts[:nfs_version]\n            mount_opts << \"mntudp\" if opts[:nfs_udp]\n            if opts[:mount_options]\n              mount_opts = mount_opts + opts[:mount_options].dup\n            end\n            mount_opts = mount_opts.join(\",\")\n\n            # Make the directory on the guest.\n            machine.communicate.sudo(\"mkdir -p #{guest_path}\")\n\n            # Perform the mount operation.\n            command = \"/sbin/mount -t nfs -o '#{mount_opts}' #{ip}:#{host_path} #{guest_path}\"\n\n            # Run the command, raising a specific error.\n            retryable(on: Vagrant::Errors::NFSMountFailed, tries: 3, sleep: 5) do\n              machine.communicate.sudo(command,\n                error_class: Vagrant::Errors::NFSMountFailed,\n                shell: \"sh\",\n              )\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/bsd/cap/public_key.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire \"vagrant/util/shell_quote\"\n\nmodule VagrantPlugins\n  module GuestBSD\n    module Cap\n      class PublicKey\n        def self.insert_public_key(machine, contents)\n          comm = machine.communicate\n          contents = contents.strip << \"\\n\"\n\n          remote_path = \"/tmp/vagrant-insert-pubkey-#{Time.now.to_i}\"\n          Tempfile.open(\"vagrant-bsd-insert-public-key\") do |f|\n            f.binmode\n            f.write(contents)\n            f.fsync\n            f.close\n            comm.upload(f.path, remote_path)\n          end\n\n          # Use execute (not sudo) because we want to execute this as the SSH\n          # user (which is \"vagrant\" by default).\n          comm.execute <<-EOH.gsub(/^ {12}/, \"\")\n            mkdir -p ~/.ssh\n            chmod 0700 ~/.ssh &&\n              cat '#{remote_path}' >> ~/.ssh/authorized_keys &&\n              chmod 0600 ~/.ssh/authorized_keys\n            result=$?\n            rm -f '#{remote_path}'\n            exit $result\n          EOH\n        end\n\n        def self.remove_public_key(machine, contents)\n          comm = machine.communicate\n          contents = contents.strip << \"\\n\"\n\n          remote_path = \"/tmp/vagrant-remove-pubkey-#{Time.now.to_i}\"\n          Tempfile.open(\"vagrant-bsd-remove-public-key\") do |f|\n            f.binmode\n            f.write(contents)\n            f.fsync\n            f.close\n            comm.upload(f.path, remote_path)\n          end\n\n          # Use execute (not sudo) because we want to execute this as the SSH\n          # user (which is \"vagrant\" by default).\n          comm.execute <<-EOH.sub(/^ {12}/, \"\")\n            result=0\n            if test -f ~/.ssh/authorized_keys; then\n              grep -v -x -f '#{remote_path}' ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.tmp &&\n                mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys &&\n                chmod 0600 ~/.ssh/authorized_keys\n              result=$?\n            fi\n\n            rm -f '#{remote_path}'\n            exit $result\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/bsd/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestBSD\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"uname -s | grep -i 'BSD'\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/bsd/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestBSD\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"BSD-based guest\"\n      description \"BSD-based guest support.\"\n\n      guest(:bsd) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:bsd, :create_tmp_path) do\n        require_relative \"cap/file_system\"\n        Cap::FileSystem\n      end\n\n      guest_capability(:bsd, :decompress_tgz) do\n        require_relative \"cap/file_system\"\n        Cap::FileSystem\n      end\n\n      guest_capability(:bsd, :decompress_zip) do\n        require_relative \"cap/file_system\"\n        Cap::FileSystem\n      end\n\n      guest_capability(:bsd, :halt) do\n        require_relative \"cap/halt\"\n        Cap::Halt\n      end\n\n      guest_capability(:bsd, :insert_public_key) do\n        require_relative \"cap/public_key\"\n        Cap::PublicKey\n      end\n\n      guest_capability(:bsd, :mount_nfs_folder) do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      guest_capability(:bsd, :mount_virtualbox_shared_folder) do\n        require_relative \"cap/mount_virtualbox_shared_folder\"\n        Cap::MountVirtualBoxSharedFolder\n      end\n\n      guest_capability(:bsd, :remove_public_key) do\n        require_relative \"cap/public_key\"\n        Cap::PublicKey\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/centos/cap/flavor.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestCentos\n    module Cap\n      class Flavor\n        def self.flavor(machine)\n          # Pick up version info from `/etc/os-release`. This file started to exist\n          # in CentOS 7. For versions before that (i.e. CentOS 6) just plain `:centos`\n          # should do.\n          version = nil\n          if machine.communicate.test(\"test -f /etc/os-release\")\n            begin\n              machine.communicate.execute(\"source /etc/os-release && printf $VERSION_ID\") do |type, data|\n                if type == :stdout\n                  version = data.split(\".\").first.to_i\n                end\n              end\n            rescue\n            end\n          end\n          if version.nil? || version < 1\n            return :centos\n          else\n            return \"centos_#{version}\".to_sym\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/centos/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\nrequire_relative '../linux/guest'\n\nmodule VagrantPlugins\n  module GuestCentos\n    class Guest < VagrantPlugins::GuestLinux::Guest\n      # Name used for guest detection\n      GUEST_DETECTION_NAME = \"centos\".freeze\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/centos/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestCentos\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"CentOS guest\"\n      description \"CentOS guest support.\"\n\n      guest(:centos, :redhat) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:centos, :flavor) do\n        require_relative \"cap/flavor\"\n        Cap::Flavor\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/coreos/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\nrequire \"yaml\"\n\nmodule VagrantPlugins\n  module GuestCoreOS\n    module Cap\n      class ChangeHostName\n        extend Vagrant::Util::GuestInspection::Linux\n\n        def self.change_host_name(machine, name)\n          comm = machine.communicate\n\n          if systemd_unit_file?(comm, \"system-cloudinit*\")\n            file = Tempfile.new(\"vagrant-coreos-hostname\")\n            file.puts(\"#cloud-config\\n\")\n            file.puts({\"hostname\" => name}.to_yaml)\n            file.close\n\n            dst = \"/var/tmp/hostname.yml\"\n            svc_path = dst.tr(\"/\", \"-\")[1..-1]\n            comm.upload(file.path, dst)\n            comm.sudo(\"systemctl start system-cloudinit@#{svc_path}.service\")\n          else\n            if !comm.test(\"hostname -f | grep '^#{name}$'\", sudo: false)\n              basename = name.split(\".\", 2)[0]\n              comm.sudo(\"hostname '#{basename}'\")\n\n              # Note that when working with CoreOS, we explicitly do not add the\n              # entry to /etc/hosts because this file does not exist on CoreOS.\n              # We could create it, but the recommended approach on CoreOS is to\n              # use Fleet to manage /etc/hosts files.\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/coreos/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\nrequire \"yaml\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestCoreOS\n    module Cap\n      class ConfigureNetworks\n        extend Vagrant::Util::GuestInspection::Linux\n\n        NETWORK_MANAGER_CONN_DIR = \"/etc/NetworkManager/system-connections\".freeze\n        DEFAULT_ENVIRONMENT_IP = \"127.0.0.1\".freeze\n\n        def self.configure_networks(machine, networks)\n          comm = machine.communicate\n          return configure_networks_cloud_init(machine, networks) if comm.test(\"command -v cloud-init\")\n\n          interfaces = machine.guest.capability(:network_interfaces)\n          nm_dev = {}\n          comm.execute(\"nmcli -t c show\") do |type, data|\n            if type == :stdout\n              _, id, _, dev = data.strip.split(\":\")\n              nm_dev[dev] = id\n            end\n          end\n          comm.sudo(\"rm #{File.join(NETWORK_MANAGER_CONN_DIR, 'vagrant-*.conf')}\",\n            error_check: false)\n\n          networks.each_with_index do |network, i|\n            network[:device] = interfaces[network[:interface]]\n            addr = IPAddr.new(network[:ip])\n            mask = addr.mask(network[:netmask])\n            if !network[:mac_address]\n              comm.execute(\"cat /sys/class/net/#{network[:device]}/address\") do |type, data|\n                if type == :stdout\n                  network[:mac_address] = data\n                end\n              end\n            end\n\n            f = Tempfile.new(\"vagrant-coreos-network\")\n            {\n              connection: {\n                type: \"ethernet\",\n                id: network[:device],\n                \"interface-name\": network[:device]\n              },\n              ethernet: {\n                \"mac-address\": network[:mac_address]\n              },\n              ipv4: {\n                method: \"manual\",\n                addresses: \"#{network[:ip]}/#{mask.prefix}\",\n                gateway: network.fetch(:gateway, mask.to_range.first.succ),\n              },\n            }.each_pair do |section, content|\n              f.puts \"[#{section}]\"\n              content.each_pair do |key, value|\n                f.puts \"#{key}=#{value}\"\n              end\n            end\n            f.close\n            comm.sudo(\"nmcli d disconnect '#{network[:device]}'\", error_check: false)\n            comm.sudo(\"nmcli c delete '#{nm_dev[network[:device]]}'\", error_check: false)\n            dst = File.join(\"/var/tmp\", \"vagrant-#{network[:device]}.conf\")\n            final = File.join(NETWORK_MANAGER_CONN_DIR, \"vagrant-#{network[:device]}.conf\")\n            comm.upload(f.path, dst)\n            comm.sudo(\"chown root:root '#{dst}'\")\n            comm.sudo(\"chmod 0600 '#{dst}'\")\n            comm.sudo(\"mv '#{dst}' '#{final}'\")\n            comm.sudo(\"nmcli c load '#{final}'\")\n            comm.sudo(\"nmcli d connect '#{network[:device]}'\")\n            f.delete\n          end\n        end\n\n        def self.configure_networks_cloud_init(machine, networks)\n          cloud_config = {}\n          # Locate configured IP addresses to drop in /etc/environment\n          # for export. If no addresses found, fall back to default\n          public_ip = catch(:public_ip) do\n            machine.config.vm.networks.each do |type, opts|\n              next if type != :public_network\n              throw(:public_ip, opts[:ip]) if opts[:ip]\n            end\n            DEFAULT_ENVIRONMENT_IP\n          end\n          private_ip = catch(:private_ip) do\n            machine.config.vm.networks.each do |type, opts|\n              next if type != :private_network\n              throw(:private_ip, opts[:ip]) if opts[:ip]\n            end\n            public_ip\n          end\n          cloud_config[\"write_files\"] = [\n            {\"path\" => \"/etc/environment\",\n              \"content\" => \"COREOS_PUBLIC_IPV4=#{public_ip}\\nCOREOS_PRIVATE_IPV4=#{private_ip}\"}\n          ]\n\n          # Generate configuration for any static network interfaces\n          # which have been defined\n          interfaces = machine.guest.capability(:network_interfaces)\n          units = networks.map do |network|\n            iface = network[:interface].to_i\n            unit_name = \"50-vagrant#{iface}.network\"\n            device = interfaces[iface]\n            if network[:type].to_s == \"dhcp\"\n              network_content = \"DHCP=yes\"\n            else\n              prefix = IPAddr.new(\"255.255.255.255/#{network[:netmask]}\").to_i.to_s(2).count(\"1\")\n              address = \"#{network[:ip]}/#{prefix}\"\n              network_content = \"Address=#{address}\"\n            end\n            {\"name\" => unit_name,\n              \"runtime\" => \"no\",\n              \"content\" => \"[Match]\\nName=#{device}\\n[Network]\\n#{network_content}\"}\n          end\n          cloud_config[\"coreos\"] = {\"units\" => units.compact}\n\n          # Upload configuration and apply\n          file = Tempfile.new(\"vagrant-coreos-networks\")\n          file.puts(\"#cloud-config\\n\")\n          file.puts(cloud_config.to_yaml)\n          file.close\n\n          dst = \"/var/tmp/networks.yml\"\n          svc_path = dst.tr(\"/\", \"-\")[1..-1]\n          machine.communicate.upload(file.path, dst)\n          machine.communicate.sudo(\"systemctl start system-cloudinit@#{svc_path}.service\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/coreos/cap/docker.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestCoreOS\n    module Cap\n      module Docker\n        def self.docker_daemon_running(machine)\n          machine.communicate.test(\"test -S /run/docker.sock\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/coreos/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestCoreOS\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"(cat /etc/os-release | grep ID=coreos) || (cat /etc/os-release | grep -E 'ID_LIKE=.*coreos.*')\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/coreos/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestCoreOS\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"CoreOS guest\"\n      description \"CoreOS guest support.\"\n\n      guest(:coreos, :linux) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:coreos, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:coreos, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:coreos, :docker_daemon_running) do\n        require_relative \"cap/docker\"\n        Cap::Docker\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/darwin/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/guest_hosts'\n\nmodule VagrantPlugins\n  module GuestDarwin\n    module Cap\n      class ChangeHostName\n        extend Vagrant::Util::GuestHosts::BSD\n\n        def self.change_host_name(machine, name)\n          comm = machine.communicate\n\n          if !comm.test(\"hostname -f | grep '^#{name}$'\", sudo: false)\n            basename = name.split(\".\", 2)[0]\n\n            # LocalHostName should not contain dots - it is used by Bonjour and\n            # visible through file sharing services.\n            comm.sudo <<-EOH.gsub(/^ */, '')\n              # Set hostname\n              scutil --set ComputerName '#{name}' &&\n                scutil --set HostName '#{name}' &&\n                scutil --set LocalHostName '#{basename}'\n              result=$?\n              if [ $result -ne 0 ]; then\n                exit $result\n              fi\n\n              hostname '#{name}'\n            EOH\n          end\n          network_with_hostname = machine.config.vm.networks.map {|_, c| c if c[:hostname] }.compact[0]\n          if network_with_hostname\n            replace_host(comm, name, network_with_hostname[:ip])\n          else\n            add_hostname_to_loopback_interface(comm, name)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/darwin/cap/choose_addressable_ip_addr.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestDarwin\n    module Cap\n      module ChooseAddressableIPAddr\n        def self.choose_addressable_ip_addr(machine, possible)\n          comm = machine.communicate\n\n          possible.each do |ip|\n            if comm.test(\"ping -c1 -t1 #{ip}\")\n              return ip\n            end\n          end\n\n          # If we got this far, there are no addressable IPs\n          return nil\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/darwin/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire \"vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestDarwin\n    module Cap\n      class ConfigureNetworks\n        @@logger = Log4r::Logger.new(\"vagrant::guest::darwin::configure_networks\")\n\n        include Vagrant::Util\n\n        def self.configure_networks(machine, networks)\n          if !machine.provider.capability?(:nic_mac_addresses)\n            raise Vagrant::Errors::CantReadMACAddresses,\n                  provider: machine.provider_name.to_s\n          end\n\n          nic_mac_addresses = machine.provider.capability(:nic_mac_addresses)\n          @@logger.debug(\"mac addresses: #{nic_mac_addresses.inspect}\")\n\n          mac_service_map = create_mac_service_map(machine)\n\n          networks.each do |network|\n            mac_address = nic_mac_addresses[network[:interface]+1]\n            if mac_address.nil?\n              @@logger.warn(\"Could not find mac address for network #{network.inspect}\")\n              next\n            end\n\n            service_name = mac_service_map[mac_address]\n            if service_name.nil?\n              @@logger.warn(\"Could not find network service for mac address #{mac_address}\")\n              next\n            end\n\n            network_type = network[:type].to_sym\n            case network_type.to_sym\n            when :static\n              command = \"networksetup -setmanual \\\"#{service_name}\\\" #{network[:ip]} #{network[:netmask]} #{network[:router]}\"\n            when :static6\n              command = \"networksetup -setv6manual \\\"#{service_name}\\\" #{network[:ip]} #{network[:netmask]} #{network[:router]}\"\n            when :dhcp\n              command = \"networksetup -setdhcp \\\"#{service_name}\\\"\"\n            when :dhcp6\n              # This is not actually possible yet in Vagrant, but when we do\n              # enable IPv6 across the board, Darwin will already have support.\n              command = \"networksetup -setv6automatic \\\"#{service_name}\\\"\"\n            else\n              raise Vagrant::Errors::NetworkTypeNotSupported, type: network_type\n            end\n\n            machine.communicate.sudo(command)\n          end\n        end\n\n        # Creates a hash mapping MAC addresses to network service name\n        # Example: { \"00C100A1B2C3\" => \"Thunderbolt Ethernet\" }\n        def self.create_mac_service_map(machine)\n          tmp_ints = File.join(Dir.tmpdir, File.basename(\"#{machine.id}.interfaces\"))\n          tmp_hw = File.join(Dir.tmpdir, File.basename(\"#{machine.id}.hardware\"))\n\n          machine.communicate.tap do |comm|\n            comm.sudo(\"networksetup -detectnewhardware\")\n            comm.sudo(\"networksetup -listnetworkserviceorder > /tmp/vagrant.interfaces\")\n            comm.sudo(\"networksetup -listallhardwareports > /tmp/vagrant.hardware\")\n            comm.download(\"/tmp/vagrant.interfaces\", tmp_ints)\n            comm.download(\"/tmp/vagrant.hardware\", tmp_hw)\n          end\n\n          interface_map = {}\n          ints = ::IO.read(tmp_ints)\n          ints.split(/\\n\\n/m).each do |i|\n            if i.match(/Hardware/) && i.match(/Ethernet/)\n              # Ethernet, should be 2 lines,\n              # (3) Thunderbolt Ethernet\n              # (Hardware Port: Thunderbolt Ethernet, Device: en1)\n\n              # multiline, should match \"Thunderbolt Ethernet\", \"en1\"\n              devicearry = i.match(/\\([0-9]+\\) (.+)\\n.*Device: (.+)\\)/m)\n              service = devicearry[1]\n              interface = devicearry[2]\n\n              # Should map interface to service { \"en1\" => \"Thunderbolt Ethernet\" }\n              interface_map[interface] = service\n            end\n          end\n          File.delete(tmp_ints)\n\n          mac_service_map = {}\n          macs = ::IO.read(tmp_hw)\n          macs.split(/\\n\\n/m).each do |i|\n            if i.match(/Hardware/) && i.match(/Ethernet/)\n              # Ethernet, should be 3 lines,\n              # Hardware Port: Thunderbolt 1\n              # Device: en1\n              # Ethernet Address: a1:b2:c3:d4:e5:f6\n\n              # multiline, should match \"en1\", \"00:c1:00:a1:b2:c3\"\n              devicearry = i.match(/Device: (.+)\\nEthernet Address: (.+)/m)\n              interface = devicearry[1]\n              naked_mac = devicearry[2].gsub(':','').upcase\n\n              # Skip hardware ports without MAC (bridges, bluetooth, etc.)\n              next if naked_mac == \"N/A\"\n\n              if !interface_map[interface]\n                @@logger.warn(\"Could not find network service for interface #{interface}\")\n                next\n              end\n\n              # Should map MAC to service, { \"00C100A1B2C3\" => \"Thunderbolt Ethernet\" }\n              mac_service_map[naked_mac] = interface_map[interface]\n            end\n          end\n          File.delete(tmp_hw)\n\n          mac_service_map\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/darwin/cap/darwin_version.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestDarwin\n    module Cap\n      class DarwinVersion\n        \n        VERSION_REGEX = /\\d+.\\d+.?\\d*/.freeze\n\n        # Get the darwin version\n        #\n        # @param [Machine]\n        # @return [String] version of drawin\n        def self.darwin_version(machine)\n          output = \"\"\n          machine.communicate.sudo(\"sysctl kern.osrelease\") do |_, data|\n            output = data\n          end\n          output.scan(VERSION_REGEX).first\n        end\n\n        # Get the darwin major version\n        #\n        # @param [Machine]\n        # @return [int] major version of drawin (nil if version is not detected)\n        def self.darwin_major_version(machine)\n          output = \"\"\n          machine.communicate.sudo(\"sysctl kern.osrelease\") do |_, data|\n            output = data\n          end\n          version_string = output.scan(VERSION_REGEX).first\n          if version_string\n            major_version = version_string.split(\".\").first.to_i\n          else\n            major_version = nil\n          end\n          major_version\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/darwin/cap/halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestDarwin\n    module Cap\n      class Halt\n        def self.halt(machine)\n          begin\n            # Darwin does not support the `-p` option like the rest of the\n            # BSD-based guests, so it needs its own cap.\n            machine.communicate.sudo(\"/sbin/shutdown -h now\")\n          rescue IOError, Vagrant::Errors::SSHDisconnected\n            # Do nothing because SSH connection closed and it probably\n            # means the VM just shut down really fast.\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/darwin/cap/mount_smb_shared_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/retryable\"\nrequire \"shellwords\"\n\nmodule VagrantPlugins\n  module GuestDarwin\n    module Cap\n      class MountSMBSharedFolder\n        extend Vagrant::Util::Retryable\n        def self.mount_smb_shared_folder(machine, name, guestpath, options)\n          expanded_guest_path = machine.guest.capability(:shell_expand_guest_path, guestpath)\n\n          mount_point_owner = options[:owner];\n          if mount_point_owner\n            machine.communicate.sudo(\"mkdir -p #{expanded_guest_path}\")\n            machine.communicate.sudo(\"chown #{mount_point_owner} #{expanded_guest_path}\")\n          else\n            # fallback to assumption that user has permission\n            # to create the specified mountpoint\n            machine.communicate.execute(\"mkdir -p #{expanded_guest_path}\")\n          end\n\n          smb_password = Shellwords.shellescape(options[:smb_password])\n          # Ensure password is scrubbed\n          Vagrant::Util::CredentialScrubber.sensitive(smb_password)\n\n          mount_options = options[:mount_options];\n          mount_command = \"mount -t smbfs \" +\n            (mount_options ? \"-o '#{mount_options.join(\",\")}' \" : \"\") +\n            \"//#{options[:smb_username]}:#{smb_password}@#{options[:smb_host]}/#{name} \" +\n            \"#{expanded_guest_path}\"\n          retryable(on: Vagrant::Errors::DarwinMountFailed, tries: 10, sleep: 2) do\n            result = machine.communicate.execute(mount_command)\n            if result.exit_code != 0\n              raise Vagrant::Errors::DarwinMountFailed,\n                command: mount_command,\n                output: result.stderr\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/darwin/cap/mount_vmware_shared_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"securerandom\"\n\nmodule VagrantPlugins\n  module GuestDarwin\n    module Cap\n      class MountVmwareSharedFolder\n\n        MACOS_BIGSUR_DARWIN_VERSION = 20.freeze\n\n        # Entry point for hook to called delayed actions\n        # which finalizing the synced folders setup on\n        # the guest\n        def self.write_apfs_firmlinks(env)\n          if env && env[:machine] && delayed = apfs_firmlinks_delayed.delete(env[:machine].id)\n            delayed.call\n          end\n        end\n\n        # @return [Hash] storage location for delayed actions\n        def self.apfs_firmlinks_delayed\n          if !@_apfs_firmlinks\n            @_apfs_firmlinks = {}\n          end\n          @_apfs_firmlinks\n        end\n\n        # we seem to be unable to ask 'mount -t vmhgfs' to mount the roots\n        # of specific shares, so instead we symlink from what is already\n        # mounted by the guest tools\n        # (ie. the behaviour of the VMware_fusion provider prior to 0.8.x)\n\n        def self.mount_vmware_shared_folder(machine, name, guestpath, options)\n          # Use this variable to determine which machines\n          # have been registered with after hook\n          @apply_firmlinks ||= Hash.new{ |h, k| h[k] = {bootstrap: false, content: []} }\n\n          machine.communicate.tap do |comm|\n            # check if we are dealing with an APFS root container\n            if comm.test(\"test -d /System/Volumes/Data\")\n              parts = Pathname.new(guestpath).descend.to_a\n              firmlink = parts[1].to_s\n              firmlink.slice!(0, 1) if firmlink.start_with?(\"/\")\n              if parts.size > 2\n                guestpath = File.join(\"/System/Volumes/Data\", guestpath)\n              else\n                guestpath = nil\n              end\n            end\n\n            # Remove existing symlink or directory if defined\n            if guestpath\n              if comm.test(\"test -L \\\"#{guestpath}\\\"\")\n                comm.sudo(\"rm -f \\\"#{guestpath}\\\"\")\n              elsif comm.test(\"test -d \\\"#{guestpath}\\\"\")\n                comm.sudo(\"rm -Rf \\\"#{guestpath}\\\"\")\n              end\n\n              # create intermediate directories if needed\n              intermediate_dir = File.dirname(guestpath)\n              if intermediate_dir != \"/\"\n                comm.sudo(\"mkdir -p \\\"#{intermediate_dir}\\\"\")\n              end\n\n              comm.sudo(\"ln -s \\\"/Volumes/VMware Shared Folders/#{name}\\\" \\\"#{guestpath}\\\"\")\n            end\n\n            if firmlink && !system_firmlink?(firmlink)\n              if guestpath.nil?\n                guestpath = \"/Volumes/VMware Shared Folders/#{name}\"\n              else\n                guestpath = File.join(\"/System/Volumes/Data\", firmlink)\n              end\n\n              share_line = \"#{firmlink}\\t#{guestpath}\"\n\n              # Check if the line is already defined. If so, bail since we are done\n              if !comm.test(\"[[ \\\"$(</etc/synthetic.conf)\\\" = *\\\"#{share_line}\\\"* ]]\")\n                @apply_firmlinks[machine.id][:bootstrap] = true\n              end\n\n              # If we haven't already added our hook to apply firmlinks, do it now\n              if @apply_firmlinks[machine.id][:content].empty?\n                apfs_firmlinks_delayed[machine.id] = proc do\n                  content = @apply_firmlinks[machine.id][:content].join(\"\\n\")\n                  # Write out the synthetic file\n                  comm.sudo(\"echo -e #{content.inspect} > /etc/synthetic.conf\")\n                  if @apply_firmlinks[machine.id][:bootstrap]\n                    if machine.guest.capability(\"darwin_major_version\").to_i < MACOS_BIGSUR_DARWIN_VERSION\n                      apfs_bootstrap_flag = \"-B\"\n                      expected_rc = 0\n                    else\n                      apfs_bootstrap_flag = \"-t\"\n                      expected_rc = 253\n                    end\n                    # Re-bootstrap the root container to pick up firmlink updates\n                    comm.sudo(\"/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util #{apfs_bootstrap_flag}\", good_exit: [expected_rc])\n                  end\n                end\n              end\n              @apply_firmlinks[machine.id][:content] << share_line\n            end\n          end\n        end\n\n        # Check if firmlink is provided by the system\n        #\n        # @param [String] firmlink Firmlink path\n        # @return [Boolean]\n        def self.system_firmlink?(firmlink)\n          if !@_firmlinks\n            if File.exist?(\"/usr/share/firmlinks\")\n              @_firmlinks = File.readlines(\"/usr/share/firmlinks\").map do |line|\n                line.split.first\n              end\n            else\n              @_firmlinks = []\n            end\n          end\n          firmlink = \"/#{firmlink}\" if !firmlink.start_with?(\"/\")\n          @_firmlinks.include?(firmlink)\n        end\n\n        # @private\n        # Reset the cached values for capability. This is not considered a public\n        # API and should only be used for testing.\n        def self.reset!\n          instance_variables.each(&method(:remove_instance_variable))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/darwin/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../synced_folders/rsync/default_unix_cap\"\n\nmodule VagrantPlugins\n  module GuestDarwin\n    module Cap\n      class RSync\n        extend VagrantPlugins::SyncedFolderRSync::DefaultUnixCap\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/darwin/cap/shell_expand_guest_path.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestDarwin\n    module Cap\n      class ShellExpandGuestPath\n        def self.shell_expand_guest_path(machine, path)\n          real_path = nil\n          path = path.gsub(/ /, '\\ ')\n          machine.communicate.execute(\"printf #{path}\") do |type, data|\n            if type == :stdout\n              real_path ||= \"\"\n              real_path += data\n            end\n          end\n\n          if !real_path\n            # If no real guest path was detected, this is really strange\n            # and we raise an exception because this is a bug.\n            raise Vagrant::Errors::ShellExpandFailed\n          end\n\n          # Chomp the string so that any trailing newlines are killed\n          return real_path.chomp\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/darwin/cap/verify_vmware_hgfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestDarwin\n    module Cap\n      class VerifyVmwareHgfs\n        def self.verify_vmware_hgfs(machine)\n          kext_bundle_id = \"com.vmware.kext.vmhgfs\"\n          machine.communicate.test(\"kextstat -b #{kext_bundle_id} -l | grep #{kext_bundle_id}\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/darwin/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestDarwin\n    # A general Vagrant system implementation for OS X (ie. \"Darwin\").\n    #\n    # Contributed by: - Brian Johnson <b2jrock@gmail.com>\n    #                 - Tim Sutton <tim@synthist.net>\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"uname -s | grep 'Darwin'\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/darwin/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestDarwin\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Darwin guest\"\n      description \"Darwin guest support.\"\n\n      action_hook(:apfs_firmlinks, :synced_folders) do |hook|\n        require_relative \"cap/mount_vmware_shared_folder\"\n        hook.prepend(Vagrant::Action::Builtin::Delayed, Cap::MountVmwareSharedFolder.method(:write_apfs_firmlinks))\n      end\n\n      guest(:darwin, :bsd)  do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:darwin, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:darwin, :choose_addressable_ip_addr) do\n        require_relative \"cap/choose_addressable_ip_addr\"\n        Cap::ChooseAddressableIPAddr\n      end\n\n      guest_capability(:darwin, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:darwin, :darwin_version) do\n        require_relative \"cap/darwin_version\"\n        Cap::DarwinVersion\n      end\n\n      guest_capability(:darwin, :darwin_major_version) do\n        require_relative \"cap/darwin_version\"\n        Cap::DarwinVersion\n      end\n\n      guest_capability(:darwin, :halt) do\n        require_relative \"cap/halt\"\n        Cap::Halt\n      end\n\n      guest_capability(:darwin, :mount_smb_shared_folder) do\n        require_relative \"cap/mount_smb_shared_folder\"\n        Cap::MountSMBSharedFolder\n      end\n\n      guest_capability(:darwin, :mount_vmware_shared_folder) do\n        require_relative \"cap/mount_vmware_shared_folder\"\n        Cap::MountVmwareSharedFolder\n      end\n\n      guest_capability(:darwin, :rsync_installed) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:darwin, :rsync_command) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:darwin, :rsync_post) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:darwin, :rsync_pre) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:darwin, :shell_expand_guest_path) do\n        require_relative \"cap/shell_expand_guest_path\"\n        Cap::ShellExpandGuestPath\n      end\n\n      guest_capability(:darwin, :verify_vmware_hgfs) do\n        require_relative \"cap/verify_vmware_hgfs\"\n        Cap::VerifyVmwareHgfs\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/debian/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire 'vagrant/util/guest_hosts'\nrequire 'vagrant/util/guest_inspection'\n\nrequire_relative \"../../linux/cap/network_interfaces\"\n\nmodule VagrantPlugins\n  module GuestDebian\n    module Cap\n      class ChangeHostName\n\n        extend Vagrant::Util::GuestInspection::Linux\n        extend Vagrant::Util::GuestHosts::Linux\n\n        def self.change_host_name(machine, name)\n          @logger = Log4r::Logger.new(\"vagrant::guest::debian::changehostname\")\n          comm = machine.communicate\n\n          if !comm.test(\"hostname -f | grep '^#{name}$'\", sudo: false)\n            network_with_hostname = machine.config.vm.networks.map {|_, c| c if c[:hostname] }.compact[0]\n            if network_with_hostname\n              replace_host(comm, name, network_with_hostname[:ip])\n            else\n              add_hostname_to_loopback_interface(comm, name)\n            end\n\n            basename = name.split(\".\", 2)[0]\n            comm.sudo <<-EOH.gsub(/^ {14}/, '')\n              # Set the hostname\n              echo '#{basename}' > /etc/hostname\n\n              # Update mailname\n              echo '#{name}' > /etc/mailname\n\n            EOH\n\n            if hostnamectl?(comm)\n              comm.sudo(\"hostnamectl set-hostname '#{basename}'\")\n            else\n              comm.sudo <<-EOH.gsub(/^ {14}/, '')\n              hostname -F /etc/hostname\n              # Restart hostname services\n              if test -f /etc/init.d/hostname; then\n                /etc/init.d/hostname start || true\n              fi\n\n              if test -f /etc/init.d/hostname.sh; then\n                /etc/init.d/hostname.sh start || true\n              fi\n              EOH\n            end\n\n            restart_command = nil\n            if systemd?(comm)\n              if systemd_networkd?(comm)\n                @logger.debug(\"Attempting to restart networking with systemd-networkd\")\n                restart_command = \"systemctl restart systemd-networkd.service\"\n              elsif systemd_controlled?(comm, \"NetworkManager.service\")\n                @logger.debug(\"Attempting to restart networking with NetworkManager\")\n                restart_command = \"systemctl restart NetworkManager.service\"\n              end\n            end\n\n            if restart_command\n              comm.sudo(restart_command)\n            else\n              restart_each_interface(machine, @logger)\n            end\n          end\n        end\n\n        protected\n\n        # Due to how most Debian systems and older Ubuntu systems handle restarting\n        # networking, we cannot simply run the networking init script or use the ifup/down\n        # tools to restart all interfaces to renew the machines DHCP lease when setting\n        # its hostname. This method is a workaround for those older systems that\n        # cannoy reliably restart networking. It restarts each individual interface\n        # on its own instead.\n        #\n        # @param [Vagrant::Machine] machine\n        # @param [Log4r::Logger] logger\n        def self.restart_each_interface(machine, logger)\n          comm = machine.communicate\n          interfaces = VagrantPlugins::GuestLinux::Cap::NetworkInterfaces.network_interfaces(machine)\n          nettools = true\n          if systemd?(comm)\n            logger.debug(\"Attempting to restart networking with systemctl\")\n            nettools = false\n          else\n            logger.debug(\"Attempting to restart networking with ifup/down nettools\")\n          end\n\n          interfaces.each do |iface|\n            logger.debug(\"Restarting interface #{iface} on guest #{machine.name}\")\n            if nettools\n             restart_command = \"ifdown #{iface};ifup #{iface}\"\n            else\n             restart_command = \"systemctl stop ifup@#{iface}.service;systemctl start ifup@#{iface}.service\"\n            end\n            comm.sudo(restart_command)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/debian/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestDebian\n    module Cap\n      class ConfigureNetworks\n        include Vagrant::Util\n        extend Vagrant::Util::GuestInspection::Linux\n        extend Vagrant::Util::Retryable\n\n        NETPLAN_DEFAULT_VERSION = 2\n        NETPLAN_DIRECTORY = \"/etc/netplan\".freeze\n        NETWORKD_DIRECTORY = \"/etc/systemd/network\".freeze\n\n\n        def self.configure_networks(machine, networks)\n          comm = machine.communicate\n          interfaces = machine.guest.capability(:network_interfaces)\n\n          if systemd?(comm)\n            if netplan?(comm)\n              configure_netplan(machine, interfaces, comm, networks)\n            elsif systemd_networkd?(comm)\n              configure_networkd(machine, interfaces, comm, networks)\n            else\n              configure_nettools(machine, interfaces, comm, networks)\n            end\n          else\n            configure_nettools(machine, interfaces, comm, networks)\n          end\n        end\n\n        # Configure networking using netplan\n        def self.configure_netplan(machine, interfaces, comm, networks)\n          ethernets = {}.tap do |e_nets|\n            networks.each do |network|\n              e_config = {}.tap do |entry|\n                if network[:type].to_s == \"dhcp\"\n                  entry[\"dhcp4\"] = true\n                else\n                  mask = network[:netmask]\n                  if mask && IPAddr.new(network[:ip]).ipv4?\n                    begin\n                      mask = IPAddr.new(mask).to_i.to_s(2).count(\"1\")\n                    rescue IPAddr::Error\n                      # ignore and use given value\n                    end\n                  end\n                  entry[\"addresses\"] = [[network[:ip], mask].compact.join(\"/\")]\n                end\n                if network[:gateway]\n                  entry[\"gateway4\"] = network[:gateway]\n                end\n              end\n              e_nets[interfaces[network[:interface]]] = e_config\n            end\n          end\n\n          # By default, netplan expects the renderer to be systemd-networkd,\n          # but if any device is managed by NetworkManager, then we use that renderer\n          # ref: https://netplan.io/reference\n          if systemd_networkd?(comm)\n            renderer = \"networkd\"\n            ethernets.keys.each do |k|\n              if nm_controlled?(comm, k)\n                renderer = \"NetworkManager\"\n                if !nmcli?(comm)\n                  raise Vagrant::Errors::NetworkManagerNotInstalled, device: k\n                end\n                break\n              end\n            end\n          elsif nmcli?(comm)\n            renderer = \"NetworkManager\"\n          else\n            raise Vagrant::Errors::NetplanNoAvailableRenderers\n          end\n\n          np_config = {\"network\" => {\"version\" => NETPLAN_DEFAULT_VERSION,\n            \"renderer\" => renderer, \"ethernets\" => ethernets}}\n\n          remote_path = upload_tmp_file(comm, np_config.to_yaml)\n          dest_path = \"#{NETPLAN_DIRECTORY}/50-vagrant.yaml\"\n          comm.sudo([\"mv -f '#{remote_path}' '#{dest_path}'\",\n            \"chown root:root '#{dest_path}'\",\n            \"chmod 0644 '#{dest_path}'\",\n            \"netplan apply\"].join(\"\\n\"))\n        end\n\n        # Configure guest networking using networkd\n        def self.configure_networkd(machine, interfaces, comm, networks)\n          networks.each do |network|\n            dev_name = interfaces[network[:interface]]\n            net_conf = []\n            net_conf << \"[Match]\"\n            net_conf << \"Name=#{dev_name}\"\n            net_conf << \"[Network]\"\n            if network[:type].to_s == \"dhcp\"\n              net_conf << \"DHCP=yes\"\n            else\n              mask = network[:netmask]\n              if mask && IPAddr.new(network[:ip]).ipv4?\n                begin\n                  mask = IPAddr.new(mask).to_i.to_s(2).count(\"1\")\n                rescue IPAddr::Error\n                  # ignore and use given value\n                end\n              end\n              address = [network[:ip], mask].compact.join(\"/\")\n              net_conf << \"DHCP=no\"\n              net_conf << \"Address=#{address}\"\n              net_conf << \"Gateway=#{network[:gateway]}\" if network[:gateway]\n            end\n\n            remote_path = upload_tmp_file(comm, net_conf.join(\"\\n\"))\n            dest_path = \"#{NETWORKD_DIRECTORY}/50-vagrant-#{dev_name}.network\"\n            comm.sudo([\"mkdir -p #{NETWORKD_DIRECTORY}\",\n              \"mv -f '#{remote_path}' '#{dest_path}'\",\n              \"chown root:root '#{dest_path}'\",\n              \"chmod 0644 '#{dest_path}'\"].join(\"\\n\"))\n          end\n\n          comm.sudo([\"systemctl restart systemd-networkd.service\"].join(\"\\n\"))\n        end\n\n        # Configure guest networking using net-tools\n        def self.configure_nettools(machine, interfaces, comm, networks)\n          commands = []\n          entries = []\n          root_device = interfaces.first\n          networks.each do |network|\n            network[:device] = interfaces[network[:interface]]\n\n            entry = TemplateRenderer.render(\"guests/debian/network_#{network[:type]}\",\n              options: network.merge(:root_device => root_device),\n            )\n            entries << entry\n          end\n\n          content = entries.join(\"\\n\")\n          remote_path = \"/tmp/vagrant-network-entry\"\n          upload_tmp_file(comm, content, remote_path)\n\n          networks.each do |network|\n            # Ubuntu 16.04+ returns an error when downing an interface that\n            # does not exist. The `|| true` preserves the behavior that older\n            # Ubuntu versions exhibit and Vagrant expects (GH-7155)\n            commands << \"/sbin/ifdown '#{network[:device]}' || true\"\n            commands << \"/sbin/ip addr flush dev '#{network[:device]}'\"\n          end\n\n          # Reconfigure /etc/network/interfaces.\n          commands << <<-EOH.gsub(/^ {12}/, \"\")\n            # Remove any previous network modifications from the interfaces file\n            sed -e '/^#VAGRANT-BEGIN/,$ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces.pre\n            sed -ne '/^#VAGRANT-END/,$ p' /etc/network/interfaces | tac | sed -e '/^#VAGRANT-END/,$ d' | tac > /tmp/vagrant-network-interfaces.post\n            cat \\\\\n              /tmp/vagrant-network-interfaces.pre \\\\\n              /tmp/vagrant-network-entry \\\\\n              /tmp/vagrant-network-interfaces.post \\\\\n              > /etc/network/interfaces\n            rm -f /tmp/vagrant-network-interfaces.pre\n            rm -f /tmp/vagrant-network-entry\n            rm -f /tmp/vagrant-network-interfaces.post\n          EOH\n\n          comm.sudo(commands.join(\"\\n\"))\n          network_up_commands = []\n         \n          # Bring back up each network interface, reconfigured.\n          networks.each do |network|\n            network_up_commands << \"/sbin/ifup '#{network[:device]}'\"\n          end\n          retryable(on: Vagrant::Errors::VagrantError, sleep: 2, tries: 2) do\n            comm.sudo(network_up_commands.join(\"\\n\"))\n          end\n        end\n\n        # Simple helper to upload content to guest temporary file\n        #\n        # @param [Vagrant::Plugin::Communicator] comm\n        # @param [String] content\n        # @return [String] remote path\n        def self.upload_tmp_file(comm, content, remote_path=nil)\n          if remote_path.nil?\n            remote_path = \"/tmp/vagrant-network-entry-#{Time.now.to_i}\"\n          end\n          Tempfile.open(\"vagrant-debian-configure-networks\") do |f|\n            f.binmode\n            f.write(content)\n            f.fsync\n            f.close\n            comm.upload(f.path, remote_path)\n          end\n          remote_path\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/debian/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestDebian\n    module Cap\n      class NFS\n        def self.nfs_client_install(machine)\n          comm = machine.communicate\n          comm.sudo <<-EOH.gsub(/^ {12}/, '')\n            apt-get -yqq update\n            DEBIAN_FRONTEND=noninteractive apt-get -yqq install nfs-common portmap\n            exit $?\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/debian/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestDebian\n    module Cap\n      class RSync\n        def self.rsync_install(machine)\n          comm = machine.communicate\n          comm.sudo <<-EOH.gsub(/^ {14}/, '')\n            apt-get -yqq update\n            apt-get -yqq install rsync\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/debian/cap/smb.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestDebian\n    module Cap\n      class SMB\n        def self.smb_install(machine)\n          comm = machine.communicate\n          if !comm.test(\"test -f /sbin/mount.cifs\")\n            comm.sudo <<-EOH.gsub(/^ {14}/, '')\n              apt-get -yqq update\n              apt-get -yqq install cifs-utils\n            EOH\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/debian/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../linux/guest'\n\nmodule VagrantPlugins\n  module GuestDebian\n    class Guest < VagrantPlugins::GuestLinux::Guest\n      # Name used for guest detection\n      GUEST_DETECTION_NAME = \"debian\".freeze\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/debian/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestDebian\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Debian guest\"\n      description \"Debian guest support.\"\n\n      guest(:debian, :linux) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:debian, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:debian, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:debian, :nfs_client_install) do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      guest_capability(:debian, :rsync_install) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:debian, :smb_install) do\n        require_relative \"cap/smb\"\n        Cap::SMB\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/dragonflybsd/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestDragonFlyBSD\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"uname -s | grep -i 'DragonFly'\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/dragonflybsd/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestDragonFlyBSD\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"DragonFly BSD guest\"\n      description \"DragonFly BSD guest support.\"\n\n      guest(:dragonflybsd, :freebsd) do\n        require_relative \"guest\"\n        Guest\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/elementary/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../linux/guest'\n\nmodule VagrantPlugins\n  module GuestElementary\n    class Guest < VagrantPlugins::GuestLinux::Guest\n      # Name used for guest detection\n      GUEST_DETECTION_NAME = \"elementary\".freeze\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/elementary/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestElementary\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Elementary guest\"\n      description \"Elementary guest support.\"\n\n      guest(:elementary, :ubuntu) do\n        require_relative \"guest\"\n        Guest\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/esxi/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestEsxi\n    module Cap\n      class ChangeHostName\n        def self.change_host_name(machine, name)\n          if !machine.communicate.test(\"localcli system hostname get | grep '#{name}'\")\n            machine.communicate.execute(\"localcli system hostname set -H '#{name}'\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/esxi/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestEsxi\n    module Cap\n      class ConfigureNetworks\n        def self.configure_networks(machine, networks)\n          networks.each do |network|\n            ix = network[:interface]\n            switch = \"vSwitch#{ix}\"\n            pg = \"VagrantNetwork#{ix}\"\n            vmnic = \"vmnic#{ix}\"\n            device = \"vmk#{ix}\"\n\n            if machine.communicate.test(\"localcli network ip interface ipv4 get -i #{device}\")\n              machine.communicate.execute(\"localcli network ip interface remove -i #{device}\")\n            end\n\n            if machine.communicate.test(\"localcli network vswitch standard list -v #{switch}\")\n              machine.communicate.execute(\"localcli network vswitch standard remove -v #{switch}\")\n            end\n\n            machine.communicate.execute(\"localcli network vswitch standard add -v #{switch}\")\n            machine.communicate.execute(\"localcli network vswitch standard uplink add -v #{switch} -u #{vmnic}\")\n            machine.communicate.execute(\"localcli network vswitch standard portgroup add -v #{switch} -p #{pg}\")\n            machine.communicate.execute(\"localcli network ip interface add -i #{device} -p #{pg}\")\n\n            ifconfig = \"localcli network ip interface ipv4 set -i #{device}\"\n\n            if network[:type].to_sym == :static\n              machine.communicate.execute(\"#{ifconfig} -t static --ipv4 #{network[:ip]} --netmask #{network[:netmask]}\")\n            elsif network[:type].to_sym == :dhcp\n              machine.communicate.execute(\"#{ifconfig} -t dhcp\")\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/esxi/cap/halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestEsxi\n    module Cap\n      class Halt\n        def self.halt(machine)\n          begin\n            machine.communicate.execute(\"/bin/halt -d 0\")\n          rescue IOError, Vagrant::Errors::SSHDisconnected\n            # Ignore, this probably means connection closed because it\n            # shut down.\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/esxi/cap/mount_nfs_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestEsxi\n    module Cap\n      class MountNFSFolder\n        extend Vagrant::Util::Retryable\n\n        def self.mount_nfs_folder(machine, ip, folders)\n          folders.each do |name, opts|\n            guestpath = opts[:guestpath]\n            volume = guestpath.gsub(\"/\", \"_\")\n            machine.communicate.tap do |comm|\n              if comm.test(\"localcli storage nfs list | grep '^#{volume}'\")\n                comm.execute(\"localcli storage nfs remove -v #{volume}\")\n              end\n              mount_command = \"localcli storage nfs add -H #{ip} -s '#{opts[:hostpath]}' -v '#{volume}'\"\n              retryable(on: Vagrant::Errors::NFSMountFailed, tries: 5, sleep: 2) do\n                comm.execute(mount_command,\n                             error_class: Vagrant::Errors::NFSMountFailed)\n              end\n\n              # symlink vmfs volume to :guestpath\n              if comm.test(\"test -L '#{guestpath}'\")\n                comm.execute(\"rm -f '#{guestpath}'\")\n              end\n              if comm.test(\"test -d '#{guestpath}'\")\n                comm.execute(\"rmdir '#{guestpath}'\")\n              end\n              dir = File.dirname(guestpath)\n              if !comm.test(\"test -d '#{dir}'\")\n                comm.execute(\"mkdir -p '#{dir}'\")\n              end\n\n              comm.execute(\"ln -s '/vmfs/volumes/#{volume}' '#{guestpath}'\")\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/esxi/cap/public_key.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire \"vagrant/util/shell_quote\"\n\nmodule VagrantPlugins\n  module GuestEsxi\n    module Cap\n      class PublicKey\n      \n        def self.insert_public_key(machine, contents)\n          comm = machine.communicate\n          contents = contents.strip << \"\\n\"\n\n          remote_path = \"/tmp/vagrant-insert-pubkey-#{Time.now.to_i}\"\n          Tempfile.open(\"vagrant-esxi-insert-public-key\") do |f|\n            f.binmode\n            f.write(contents)\n            f.fsync\n            f.close\n            comm.upload(f.path, remote_path)\n          end\n\n          comm.execute <<-EOH.gsub(/^ {12}/, \"\")\n            set -e\n            SSH_DIR=\"$(grep -q '^AuthorizedKeysFile\\s*\\/etc\\/ssh\\/keys-%u\\/authorized_keys$' /etc/ssh/sshd_config && echo -n /etc/ssh/keys-${USER} || echo -n ~/.ssh)\"\n            mkdir -p \"${SSH_DIR}\"\n            # NB in ESXi 7.0 we cannot change the /etc/ssh/keys-root directory\n            #    permissions, so ignore any errors.\n            chmod 0700 \"${SSH_DIR}\" || true\n            cat '#{remote_path}' >> \"${SSH_DIR}/authorized_keys\"\n            chmod 0600 \"${SSH_DIR}/authorized_keys\"\n            rm -f '#{remote_path}'\n          EOH\n        end\n\n        def self.remove_public_key(machine, contents)\n          comm = machine.communicate\n          contents = contents.strip << \"\\n\"\n\n          remote_path = \"/tmp/vagrant-remove-pubkey-#{Time.now.to_i}\"\n          Tempfile.open(\"vagrant-esxi-remove-public-key\") do |f|\n            f.binmode\n            f.write(contents)\n            f.fsync\n            f.close\n            comm.upload(f.path, remote_path)\n          end\n\n          # Use execute (not sudo) because we want to execute this as the SSH\n          # user (which is \"vagrant\" by default).\n          comm.execute <<-EOH.sub(/^ {12}/, \"\")\n            set -e\n            SSH_DIR=\"$(grep -q '^AuthorizedKeysFile\\s*\\/etc\\/ssh\\/keys-%u\\/authorized_keys$' /etc/ssh/sshd_config && echo -n /etc/ssh/keys-${USER} || echo -n ~/.ssh)\"\n            if test -f \"${SSH_DIR}/authorized_keys\"; then\n              grep -v -x -f '#{remote_path}' \"${SSH_DIR}/authorized_keys\" > \"${SSH_DIR}/authorized_keys.tmp\"\n              mv \"${SSH_DIR}/authorized_keys.tmp\" \"${SSH_DIR}/authorized_keys\"\n              chmod 0600 \"${SSH_DIR}/authorized_keys\"\n            fi\n            rm -f '#{remote_path}'\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/esxi/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestEsxi\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"uname -s | grep VMkernel\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/esxi/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestEsxi\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"ESXi guest.\"\n      description \"ESXi guest support.\"\n\n      guest(:esxi)  do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:esxi, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:esxi, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:esxi, :mount_nfs_folder) do\n        require_relative \"cap/mount_nfs_folder\"\n        Cap::MountNFSFolder\n      end\n\n      guest_capability(:esxi, :halt) do\n        require_relative \"cap/halt\"\n        Cap::Halt\n      end\n      \n      guest_capability(:esxi, :remove_public_key) do\n        require_relative \"cap/public_key\"\n        Cap::PublicKey\n      end\n      \n      guest_capability(:esxi, :insert_public_key) do\n        require_relative \"cap/public_key\"\n        Cap::PublicKey\n      end\n      \n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/fedora/cap/flavor.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestFedora\n    module Cap\n      class Flavor\n        def self.flavor(machine)\n          # Read the version file\n          version = nil\n          machine.communicate.sudo(\"grep VERSION_ID /etc/os-release\") do |type, data|\n            if type == :stdout\n              version = data.split(\"=\")[1].chomp.to_i\n            end\n          end\n\n          if version.nil?\n            return :fedora\n          else\n            return :\"fedora_#{version}\"\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/fedora/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\nrequire_relative '../linux/guest'\n\nmodule VagrantPlugins\n  module GuestFedora\n    class Guest < VagrantPlugins::GuestLinux::Guest\n      # Name used for guest detection\n      GUEST_DETECTION_NAME = \"fedora\".freeze\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/fedora/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestFedora\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Fedora guest\"\n      description \"Fedora guest support.\"\n\n      guest(:fedora, :redhat) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:fedora, :flavor) do\n        require_relative \"cap/flavor\"\n        Cap::Flavor\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/freebsd/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/guest_hosts'\n\nmodule VagrantPlugins\n  module GuestFreeBSD\n    module Cap\n      class ChangeHostName\n        extend Vagrant::Util::GuestHosts::BSD\n\n        def self.change_host_name(machine, name)\n          comm = machine.communicate\n\n          if !comm.test(\"hostname -f | grep '^#{name}$'\", sudo: false, shell: \"sh\")\n            basename = name.split(\".\", 2)[0]\n            command = <<-EOH.gsub(/^ {14}/, '')\n              # Set the hostname\n              hostname '#{name}'\n              sed -i '' 's/^hostname=.*$/hostname=\\\"#{name}\\\"/' /etc/rc.conf\n            EOH\n            comm.sudo(command, shell: \"sh\")\n          end\n          network_with_hostname = machine.config.vm.networks.map {|_, c| c if c[:hostname] }.compact[0]\n          if network_with_hostname\n            replace_host(comm, name, network_with_hostname[:ip])\n          else\n            add_hostname_to_loopback_interface(comm, name)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/freebsd/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestFreeBSD\n    module Cap\n      class ConfigureNetworks\n        include Vagrant::Util\n\n        def self.configure_networks(machine, networks)\n          options = { shell: \"sh\" }\n          comm = machine.communicate\n\n          commands   = []\n          interfaces = []\n\n          # Remove any previous network additions to the configuration file.\n          commands << \"sed -i '' -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' /etc/rc.conf\"\n\n          comm.sudo(\"ifconfig -l ether\", options) do |_, stdout|\n            interfaces = stdout.split\n          end\n\n          networks.each.with_index do |network, i|\n            network[:device] = interfaces[network[:interface]]\n\n            entry = TemplateRenderer.render(\"guests/freebsd/network_#{network[:type]}\",\n              options: network,\n            )\n\n            remote_path = \"/tmp/vagrant-network-#{network[:device]}-#{Time.now.to_i}-#{i}\"\n\n            Tempfile.open(\"vagrant-freebsd-configure-networks\") do |f|\n              f.binmode\n              f.write(entry)\n              f.fsync\n              f.close\n              comm.upload(f.path, remote_path)\n            end\n\n            commands << <<-EOH.gsub(/^ {14}/, '')\n              cat '#{remote_path}' >> /etc/rc.conf\n              rm -f '#{remote_path}'\n            EOH\n\n            # If the network is DHCP, then we have to start the dhclient, unless\n            # it is already running. See GH-5852 for more information\n            if network[:type].to_sym == :dhcp\n              file = \"/var/run/dhclient.#{network[:device]}.pid\"\n              commands << <<-EOH.gsub(/^ {16}/, '')\n                if ! test -f '#{file}' || ! kill -0 $(cat '#{file}'); then\n                  dhclient '#{network[:device]}'\n                fi\n              EOH\n            end\n\n            # For some reason, this returns status 1... every time\n            commands << \"/etc/rc.d/netif restart '#{network[:device]}' || true\"\n          end\n\n          comm.sudo(commands.join(\"\\n\"), options)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/freebsd/cap/mount_virtualbox_shared_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../synced_folders/unix_mount_helpers\"\n\nmodule VagrantPlugins\n  module GuestFreeBSD\n    module Cap\n      class MountVirtualBoxSharedFolder\n        extend SyncedFolder::UnixMountHelpers\n\n        def self.mount_virtualbox_shared_folder(machine, name, guestpath, options)\n          guest_path = Shellwords.escape(guestpath)\n\n          @@logger.debug(\"Mounting #{name} (#{options[:hostpath]} to #{guestpath})\")\n\n          builtin_mount_type = \"-cit vboxvfs\"\n          addon_mount_type = \"-t vboxvfs\"\n\n          mount_options = options.fetch(:mount_options, [])\n          detected_ids = detect_owner_group_ids(machine, guest_path, mount_options, options)\n          mount_uid = detected_ids[:uid]\n          mount_gid = detected_ids[:gid]\n\n          mount_options << \"uid=#{mount_uid}\"\n          mount_options << \"gid=#{mount_gid}\"\n          mount_options = mount_options.join(',')\n          mount_command = \"mount #{addon_mount_type} -o #{mount_options} #{name} #{guest_path}\"\n\n          # Create the guest path if it doesn't exist\n          machine.communicate.sudo(\"mkdir -p #{guest_path}\")\n\n          stderr = \"\"\n          result = machine.communicate.sudo(mount_command, error_check: false) do |type, data|\n            stderr << data if type == :stderr\n          end\n\n          if result != 0\n            if stderr.include?(\"-cit\")\n              @@logger.info(\"Detected builtin vboxvfs module, modifying mount command\")\n              mount_command.sub!(addon_mount_type, builtin_mount_type)\n            end\n\n            # Attempt to mount the folder. We retry here a few times because\n            # it can fail early on.\n            stderr = \"\"\n            retryable(on: Vagrant::Errors::VirtualBoxMountFailed, tries: 3, sleep: 5) do\n              machine.communicate.sudo(mount_command,\n                error_class: Vagrant::Errors::VirtualBoxMountFailed,\n                error_key: :virtualbox_mount_failed,\n                command: mount_command,\n                output: stderr,\n              ) { |type, data| stderr = data if type == :stderr }\n            end\n          end\n\n          # Chown the directory to the proper user. We skip this if the\n          # mount options contained a readonly flag, because it won't work.\n          if !options[:mount_options] || !options[:mount_options].include?(\"ro\")\n            chown_command = \"chown #{mount_uid}:#{mount_gid} #{guest_path}\"\n            machine.communicate.sudo(chown_command)\n          end\n\n          emit_upstart_notification(machine, guest_path)\n        end\n\n\n        def self.unmount_virtualbox_shared_folder(machine, guestpath, options)\n          guest_path = Shellwords.escape(guestpath)\n\n          result = machine.communicate.sudo(\"umount #{guest_path}\", error_check: false)\n          if result == 0\n            machine.communicate.sudo(\"rmdir #{guest_path}\", error_check: false)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/freebsd/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../synced_folders/rsync/default_unix_cap\"\n\nmodule VagrantPlugins\n  module GuestFreeBSD\n    module Cap\n      class RSync\n        extend VagrantPlugins::SyncedFolderRSync::DefaultUnixCap\n\n        def self.rsync_install(machine)\n          machine.communicate.sudo(\"pkg install -y rsync\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/freebsd/cap/shell_expand_guest_path.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestFreeBSD\n    module Cap\n      class ShellExpandGuestPath\n        def self.shell_expand_guest_path(machine, path)\n          real_path = nil\n          path = path.gsub(/ /, '\\ ')\n          machine.communicate.execute(\"printf #{path}\",\n                                      shell: \"sh\") do |type, data|\n            if type == :stdout\n              real_path ||= \"\"\n              real_path += data\n            end\n          end\n\n          if !real_path\n            # If no real guest path was detected, this is really strange\n            # and we raise an exception because this is a bug.\n            raise Vagrant::Errors::ShellExpandFailed\n          end\n\n          # Chomp the string so that any trailing newlines are killed\n          return real_path.chomp\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/freebsd/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/template_renderer'\n\nmodule VagrantPlugins\n  module GuestFreeBSD\n    # A general Vagrant system implementation for \"freebsd\".\n    #\n    # Contributed by Kenneth Vestergaard <kvs@binarysolutions.dk>\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"uname -s | grep 'FreeBSD'\", {shell: \"sh\"})\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/freebsd/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestFreeBSD\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"FreeBSD guest\"\n      description \"FreeBSD guest support.\"\n\n      guest(:freebsd, :bsd) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:freebsd, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:freebsd, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:freebsd, :rsync_install) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:freebsd, :rsync_installed) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:freebsd, :rsync_command) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:freebsd, :rsync_post) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:freebsd, :rsync_pre) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:freebsd, :shell_expand_guest_path) do\n        require_relative \"cap/shell_expand_guest_path\"\n        Cap::ShellExpandGuestPath\n      end\n\n      guest_capability(:freebsd, :mount_virtualbox_shared_folder) do\n        require_relative \"cap/mount_virtualbox_shared_folder\"\n        Cap::MountVirtualBoxSharedFolder\n      end\n\n      guest_capability(:freebsd, :unmount_virtualbox_shared_folder) do\n        require_relative \"cap/mount_virtualbox_shared_folder\"\n        Cap::MountVirtualBoxSharedFolder\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/funtoo/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestFuntoo\n    module Cap\n      class ConfigureNetworks\n        include Vagrant::Util\n\n        def self.configure_networks(machine, networks)\n          machine.communicate.tap do |comm|\n            # Configure each network interface\n            networks.each do |network|\n              # http://www.funtoo.org/Funtoo_Linux_Networking\n              # dhcpcd generally runs on all interfaces by default\n              # in the future we can change this, dhcpcd has lots of features\n              # it would be nice to expose more of its capabilities...\n              if not /dhcp/i.match(network[:type])\n                line = \"denyinterfaces eth#{network[:interface]}\"\n                cmd = \"grep '#{line}' /etc/dhcpcd.conf; if [ $? -ne 0 ]; then echo '#{line}' >> /etc/dhcpcd.conf ;  fi\"\n                comm.sudo(cmd)\n                ifFile = \"netif.eth#{network[:interface]}\"\n                entry = TemplateRenderer.render(\"guests/funtoo/network_#{network[:type]}\",\n                                                 options: network)\n\n                # Upload the entry to a temporary location\n                Tempfile.open(\"vagrant-funtoo-configure-networks\") do |f|\n                  f.binmode\n                  f.write(entry)\n                  f.fsync\n                  f.close\n                  comm.upload(f.path, \"/tmp/vagrant-#{ifFile}\")\n                end\n\n                comm.sudo(\"cp /tmp/vagrant-#{ifFile} /etc/conf.d/#{ifFile}\")\n                comm.sudo(\"chmod 0644 /etc/conf.d/#{ifFile}\")\n                comm.sudo(\"ln -fs /etc/init.d/netif.tmpl /etc/init.d/#{ifFile}\")\n                comm.sudo(\"/etc/init.d/#{ifFile} start\")\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/funtoo/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestFuntoo\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"grep Funtoo /etc/gentoo-release\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/funtoo/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestFuntoo\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Funtoo guest\"\n      description \"Funtoo guest support.\"\n\n      guest(:funtoo, :gentoo) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:funtoo, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/gentoo/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../../linux/cap/change_host_name'\n\nmodule VagrantPlugins\n  module GuestGentoo\n    module Cap\n      class ChangeHostName\n        extend VagrantPlugins::GuestLinux::Cap::ChangeHostName\n\n        def self.change_name_command(name)\n          basename = name.split(\".\", 2)[0]\n          return <<-EOH.gsub(/^ {14}/, '')\n            # Use hostnamectl on systemd\n            if [[ `systemctl` =~ -\\.mount ]]; then\n              systemctl set-hostname '#{name}'\n            else\n              hostname '#{basename}'\n              echo \"hostname=#{basename}\" > /etc/conf.d/hostname\n            fi\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/gentoo/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\nrequire \"ipaddr\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestGentoo\n    module Cap\n      class ConfigureNetworks\n        include Vagrant::Util\n\n        def self.configure_networks(machine, networks)\n          comm = machine.communicate\n\n          commands   = []\n          interfaces = machine.guest.capability(:network_interfaces, \"/bin/ip\")\n\n          networks.map! { |n| n[:device] = interfaces[n[:interface]]; n }\n\n          if comm.test('[[ `systemctl` =~ -\\.mount ]]')\n            # Configure networking for Systemd\n\n            # convert netmasks to CIDR by converting to a binary string and counting the '1's\n            networks.map! { |n| n[:netmask] = IPAddr.new(n[:netmask]).to_i.to_s(2).count(\"1\"); n }\n\n            # glob networks by device, so that we can write one file per device\n            # (result is hash[devicename] = [net, net, net...])\n            networks = networks.map { |n| [n[:device], n] }.reduce({}) { |h, (k, v)| (h[k] ||= []) << v; h }\n\n            # Write one .network file out for each device\n            networks.each_pair do |device_name, device_networks|\n              entry = TemplateRenderer.render('guests/gentoo/network_systemd', networks: device_networks)\n\n              filename = \"50_vagrant_#{device_name}.network\"\n              tmpfile = \"/tmp/#{filename}\"\n              destfile = \"/etc/systemd/network/#{filename}\"\n\n              Tempfile.open('vagrant-gentoo-configure-networks') do |f|\n                f.binmode\n                f.write(entry)\n                f.fsync\n                f.close\n                comm.upload(f.path, tmpfile)\n              end\n\n              commands << \"mv #{tmpfile} #{destfile} && chmod 644 #{destfile}\"\n            end\n\n            # tell systemd to reload the networking config\n            commands << 'systemctl daemon-reload && systemctl restart systemd-networkd.service'\n          else\n            # Configure networking for OpenRC\n\n            # Remove any previous network additions to the configuration file.\n            commands << \"sed -i'' -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' /etc/conf.d/net\"\n\n            networks.each_with_index do |network, i|\n              entry = TemplateRenderer.render(\"guests/gentoo/network_#{network[:type]}\",\n                options: network,\n              )\n\n              remote_path = \"/tmp/vagrant-network-#{network[:device]}-#{Time.now.to_i}-#{i}\"\n\n              Tempfile.open(\"vagrant-gentoo-configure-networks\") do |f|\n                f.binmode\n                f.write(entry)\n                f.fsync\n                f.close\n                comm.upload(f.path, remote_path)\n              end\n\n              commands << <<-EOH.gsub(/^ {14}/, '')\n                ln -sf /etc/init.d/net.lo /etc/init.d/net.#{network[:device]}\n                /etc/init.d/net.#{network[:device]} stop || true\n\n                cat '#{remote_path}' >> /etc/conf.d/net\n                rm -f '#{remote_path}'\n\n                /etc/init.d/net.#{network[:device]} start\n              EOH\n            end\n          end\n\n          comm.sudo(commands.join(\"\\n\"))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/gentoo/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestGentoo\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"grep Gentoo /etc/gentoo-release\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/gentoo/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestGentoo\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Gentoo guest\"\n      description \"Gentoo guest support.\"\n\n      guest(:gentoo, :linux) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:gentoo, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:gentoo, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/haiku/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestHaiku\n    module Cap\n      class ChangeHostName\n        def self.change_host_name(machine, name)\n          comm = machine.communicate\n\n          if !comm.test(\"hostname | grep '^#{name}$'\", sudo: false)\n            basename = name.split(\".\", 2)[0]\n            comm.execute <<-EOH.gsub(/^ {14}/, '')\n              # Ensure exit on command error\n              set -e\n\n              export SYS_SETTINGS_DIR=$(finddir B_SYSTEM_SETTINGS_DIRECTORY)\n\n              # Set the hostname\n              echo '#{basename}' > $SYS_SETTINGS_DIR/network/hostname\n              hostname '#{basename}'\n\n              # Remove comments and blank lines from /etc/hosts\n              sed -i'' -e 's/#.*$//' -e '/^$/d' $SYS_SETTINGS_DIR/network/hosts\n\n              # Prepend ourselves to $SYS_SETTINGS_DIR/network/hosts\n              grep -w '#{name}' $SYS_SETTINGS_DIR/network/hosts || {\n                sed -i'' '1i 127.0.0.1\\\\t#{name}\\\\t#{basename}' $SYS_SETTINGS_DIR/network/hosts\n              }\n            EOH\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/haiku/cap/halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestHaiku\n    module Cap\n      class Halt\n        def self.halt(machine)\n          begin\n            machine.communicate.execute(\"/bin/shutdown\")\n          rescue IOError, Vagrant::Errors::SSHDisconnected\n            # Ignore, this probably means connection closed because it\n            # shut down.\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/haiku/cap/insert_public_key.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/shell_quote\"\n\nmodule VagrantPlugins\n  module GuestHaiku\n    module Cap\n      class InsertPublicKey\n        def self.insert_public_key(machine, contents)\n          contents = contents.chomp\n          contents = Vagrant::Util::ShellQuote.escape(contents, \"'\")\n\n          machine.communicate.tap do |comm|\n            comm.execute(\"mkdir -p $(finddir B_USER_SETTINGS_DIRECTORY)/ssh\")\n            comm.execute(\"chmod 0700 $(finddir B_USER_SETTINGS_DIRECTORY)/ssh\")\n            comm.execute(\"printf '#{contents}\\\\n' >> $(finddir B_USER_SETTINGS_DIRECTORY)/ssh/authorized_keys\")\n            comm.execute(\"chmod 0600 $(finddir B_USER_SETTINGS_DIRECTORY)/ssh/authorized_keys\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/haiku/cap/remove_public_key.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/shell_quote\"\n\nmodule VagrantPlugins\n  module GuestHaiku\n    module Cap\n      class RemovePublicKey\n        def self.remove_public_key(machine, contents)\n          contents = contents.chomp\n          contents = Vagrant::Util::ShellQuote.escape(contents, \"'\")\n\n          machine.communicate.tap do |comm|\n            if comm.test(\"test -f $(finddir B_USER_SETTINGS_DIRECTORY)/ssh/authorized_keys\")\n              comm.execute(\n                \"sed -i '/^.*#{contents}.*$/d' $(finddir B_USER_SETTINGS_DIRECTORY)/ssh/authorized_keys\")\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/haiku/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestHaiku\n    module Cap\n      class RSync\n        def self.rsync_installed(machine)\n          machine.communicate.test(\"test -f /bin/rsync\")\n        end\n\n        def self.rsync_install(machine)\n          machine.communicate.execute(\"pkgman install -y rsync\")\n        end\n\n        def self.rsync_command(machine)\n          \"rsync -zz\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/haiku/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestHaiku\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"uname -o | grep 'Haiku'\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/haiku/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestHaiku\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Haiku guest\"\n      description \"Haiku guest support.\"\n\n      guest(:haiku) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:haiku, :halt) do\n        require_relative \"cap/halt\"\n        Cap::Halt\n      end\n\n      guest_capability(:haiku, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:haiku, :insert_public_key) do\n        require_relative \"cap/insert_public_key\"\n        Cap::InsertPublicKey\n      end\n\n      guest_capability(:haiku, :remove_public_key) do\n        require_relative \"cap/remove_public_key\"\n        Cap::RemovePublicKey\n      end\n\n      guest_capability(:haiku, :rsync_install) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:haiku, :rsync_installed) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:haiku, :rsync_command) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/kali/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../linux/guest'\n\nmodule VagrantPlugins\n  module GuestKali\n    class Guest < VagrantPlugins::GuestLinux::Guest\n      # Name used for guest detection\n      GUEST_DETECTION_NAME = \"kali\".freeze\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/kali/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestKali\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Kali guest\"\n      description \"Kali guest support.\"\n\n      guest(:kali, :debian) do\n        require_relative \"guest\"\n        Guest\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/guest_hosts'\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      module ChangeHostName\n        module Methods\n          def change_name_command(name)\n            return <<-EOH.gsub(/^ {14}/, '')\n                # Set the hostname\n                echo '#{name}' > /etc/hostname\n                hostname '#{name}'\n              EOH\n          end\n\n          def change_host_name?(comm, name)\n            !comm.test(\"hostname -f | grep '^#{name}$'\", sudo: false)\n          end\n\n          def change_host_name(machine, name)\n            comm = machine.communicate\n            \n            network_with_hostname = machine.config.vm.networks.map {|_, c| c if c[:hostname] }.compact[0]\n            if network_with_hostname\n              replace_host(comm, name, network_with_hostname[:ip])\n            else\n              add_hostname_to_loopback_interface(comm, name)\n            end\n\n            if change_host_name?(comm, name)\n              comm.sudo(change_name_command(name))\n            end\n          end\n        end\n\n        def self.extended(klass)\n          klass.extend(Vagrant::Util::GuestHosts::Linux)\n          klass.extend(Methods)\n        end\n\n        extend Vagrant::Util::GuestHosts::Linux\n        extend Methods\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/choose_addressable_ip_addr.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      module ChooseAddressableIPAddr\n        def self.choose_addressable_ip_addr(machine, possible)\n          comm = machine.communicate\n\n          possible.each do |ip|\n            if comm.test(\"ping -c1 -w1 -W1 #{ip}\")\n              return ip\n            end\n          end\n\n          return nil\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/file_system.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      class FileSystem\n        # Create a temporary file or directory on the guest\n        #\n        # @param [Vagrant::Machine] machine Vagrant guest machine\n        # @param [Hash] opts Path options\n        # @return [String] path to temporary file or directory\n        def self.create_tmp_path(machine, opts)\n          template = \"vagrant-XXXXXX\"\n          if opts[:extension]\n            template << opts[:extension].to_s\n          end\n          cmd = [\"mktemp\", \"--tmpdir\"]\n          if opts[:type] == :directory\n            cmd << \"-d\"\n          end\n          cmd << template\n          tmp_path = \"\"\n          machine.communicate.execute(cmd.join(\" \")) do |type, data|\n            if type == :stdout\n              tmp_path << data\n            end\n          end\n          tmp_path.strip\n        end\n\n        # Decompress tgz file on guest to given location\n        #\n        # @param [Vagrant::Machine] machine Vagrant guest machine\n        # @param [String] compressed_file Path to compressed file on guest\n        # @param [String] destination Path for decompressed files on guest\n        def self.decompress_tgz(machine, compressed_file, destination, opts={})\n          comm = machine.communicate\n          extract_dir = create_tmp_path(machine, type: :directory)\n          cmds = []\n          if opts[:type] == :directory\n            cmds << \"mkdir -p '#{destination}'\"\n          else\n            cmds << \"mkdir -p '#{File.dirname(destination)}'\"\n          end\n          cmds += [\n            \"tar -C '#{extract_dir}' -xzf '#{compressed_file}'\",\n            \"mv '#{extract_dir}'/* '#{destination}'\",\n            \"rm -f '#{compressed_file}'\",\n            \"rm -rf '#{extract_dir}'\"\n          ]\n          cmds.each{ |cmd| comm.execute(cmd) }\n          true\n        end\n\n        # Decompress zip file on guest to given location\n        #\n        # @param [Vagrant::Machine] machine Vagrant guest machine\n        # @param [String] compressed_file Path to compressed file on guest\n        # @param [String] destination Path for decompressed files on guest\n        def self.decompress_zip(machine, compressed_file, destination, opts={})\n          comm = machine.communicate\n          extract_dir = create_tmp_path(machine, type: :directory)\n          cmds = []\n          if opts[:type] == :directory\n            cmds << \"mkdir -p '#{destination}'\"\n          else\n            cmds << \"mkdir -p '#{File.dirname(destination)}'\"\n          end\n          cmds += [\n            \"unzip '#{compressed_file}' -d '#{extract_dir}'\",\n            \"mv '#{extract_dir}'/* '#{destination}'\",\n            \"rm -f '#{compressed_file}'\",\n            \"rm -rf '#{extract_dir}'\"\n          ]\n          cmds.each{ |cmd| comm.execute(cmd) }\n          true\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/guest_inspection'\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      class Halt\n        extend Vagrant::Util::GuestInspection::Linux\n\n        def self.halt(machine)\n          begin\n            if systemd?(machine.communicate)\n              machine.communicate.sudo(\"systemctl poweroff\")\n            else\n              machine.communicate.sudo(\"shutdown -h now\")\n            end\n          rescue IOError, Vagrant::Errors::SSHDisconnected\n            # Do nothing, because it probably means the machine shut down\n            # and SSH connection was lost.\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/mount_smb_shared_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\nrequire \"shellwords\"\n\nrequire_relative \"../../../synced_folders/unix_mount_helpers\"\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      class MountSMBSharedFolder\n\n        extend SyncedFolder::UnixMountHelpers\n\n        # Mounts and SMB folder on linux guest\n        #\n        # @param [Machine] machine\n        # @param [String] name of mount\n        # @param [String] path of mount on guest\n        # @param [Hash] hash of mount options \n        def self.mount_smb_shared_folder(machine, name, guestpath, options)\n          expanded_guest_path = machine.guest.capability(\n            :shell_expand_guest_path, guestpath)\n          options[:smb_id] ||= name\n\n          mount_device = options[:plugin].capability(:mount_name, name, options)\n          mount_options, _, _ = options[:plugin].capability(\n            :mount_options, name, expanded_guest_path, options)\n          mount_type = options[:plugin].capability(:mount_type)\n          # If a domain is provided in the username, separate it\n          username, domain = (options[:smb_username] || '').split('@', 2)\n          smb_password = options[:smb_password]\n          # Ensure password is scrubbed\n          Vagrant::Util::CredentialScrubber.sensitive(smb_password)\n        \n          if mount_options.include?(\"mfsymlinks\")\n            display_mfsymlinks_warning(machine.env)\n          end\n          \n          mount_command = \"mount -t #{mount_type} -o #{mount_options} #{mount_device} #{expanded_guest_path}\"\n\n          # Create the guest path if it doesn't exist\n          machine.communicate.sudo(\"mkdir -p #{expanded_guest_path}\")\n\n          # Write the credentials file\n          machine.communicate.sudo(<<-SCRIPT)\ncat <<\"EOF\" >/etc/smb_creds_#{name}\nusername=#{username}\npassword=#{smb_password}\n#{domain ? \"domain=#{domain}\" : \"\"}\nEOF\nchmod 0600 /etc/smb_creds_#{name}\nSCRIPT\n\n          # Attempt to mount the folder. We retry here a few times because\n          # it can fail early on.\n          begin\n            retryable(on: Vagrant::Errors::LinuxMountFailed, tries: 10, sleep: 2) do\n              no_such_device = false\n              stderr = \"\"\n              status = machine.communicate.sudo(mount_command, error_check: false) do |type, data|\n                if type == :stderr\n                  no_such_device = true if data =~ /No such device/i\n                  stderr += data.to_s\n                end\n              end\n              if status != 0 || no_such_device\n                raise Vagrant::Errors::LinuxMountFailed,\n                  command: mount_command,\n                  output: stderr\n              end\n            end\n          ensure\n            # Always remove credentials file after mounting attempts\n            # have been completed\n            if !machine.config.vm.allow_fstab_modification\n              machine.communicate.sudo(\"rm /etc/smb_creds_#{name}\")\n            end\n          end\n\n          emit_upstart_notification(machine, expanded_guest_path)\n        end\n\n        def self.display_mfsymlinks_warning(env)\n          d_file = env.data_dir.join(\"mfsymlinks_warning\")\n          if !d_file.exist?\n            FileUtils.touch(d_file.to_path)\n            env.ui.warn(I18n.t(\"vagrant.actions.vm.smb.mfsymlink_warning\"))\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../synced_folders/unix_mount_helpers\"\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      class MountVirtualBoxSharedFolder\n        extend SyncedFolder::UnixMountHelpers\n\n        # Mounts and virtualbox folder on linux guest\n        #\n        # @param [Machine] machine\n        # @param [String] name of mount\n        # @param [String] path of mount on guest\n        # @param [Hash] hash of mount options \n        def self.mount_virtualbox_shared_folder(machine, name, guestpath, options)\n          guest_path = Shellwords.escape(guestpath)\n          mount_type = options[:plugin].capability(:mount_type)\n\n          @@logger.debug(\"Mounting #{name} (#{options[:hostpath]} to #{guestpath})\")\n\n          builtin_mount_type = \"-cit #{mount_type}\"\n          addon_mount_type = \"-t #{mount_type}\"\n\n          mount_options, mount_uid, mount_gid = options[:plugin].capability(:mount_options, name, guest_path, options)\n          mount_command = \"mount #{addon_mount_type} -o #{mount_options} #{name} #{guest_path}\"\n\n          # Create the guest path if it doesn't exist\n          machine.communicate.sudo(\"mkdir -p #{guest_path}\")\n\n          stderr = \"\"\n          result = machine.communicate.sudo(mount_command, error_check: false) do |type, data|\n            stderr << data if type == :stderr\n          end\n\n          if result != 0\n            if stderr.include?(\"-cit\")\n              @@logger.info(\"Detected builtin vboxsf module, modifying mount command\")\n              mount_command.sub!(addon_mount_type, builtin_mount_type)\n            end\n\n            # Attempt to mount the folder. We retry here a few times because\n            # it can fail early on.\n            stderr = \"\"\n            retryable(on: Vagrant::Errors::VirtualBoxMountFailed, tries: 3, sleep: 5) do\n              machine.communicate.sudo(mount_command,\n                error_class: Vagrant::Errors::VirtualBoxMountFailed,\n                error_key: :virtualbox_mount_failed,\n                command: mount_command,\n                output: stderr,\n              ) { |type, data| stderr = data if type == :stderr }\n            end\n          end\n\n          # Chown the directory to the proper user. We skip this if the\n          # mount options contained a readonly flag, because it won't work.\n          if !options[:mount_options] || !options[:mount_options].include?(\"ro\")\n            chown_command = \"chown #{mount_uid}:#{mount_gid} #{guest_path}\"\n            machine.communicate.sudo(chown_command)\n          end\n\n          emit_upstart_notification(machine, guest_path)\n        end\n\n        def self.unmount_virtualbox_shared_folder(machine, guestpath, options)\n          guest_path = Shellwords.escape(guestpath)\n\n          result = machine.communicate.sudo(\"umount #{guest_path}\", error_check: false)\n          if result == 0\n            machine.communicate.sudo(\"rmdir #{guest_path}\", error_check: false)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/network_interfaces.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      class NetworkInterfaces\n        # Valid ethernet device prefix values.\n        # eth - classic prefix\n        # en  - predictable interface names prefix\n        POSSIBLE_ETHERNET_PREFIXES = [\"eth\".freeze, \"en\".freeze].freeze\n\n        @@logger = Log4r::Logger.new(\"vagrant::guest::linux::network_interfaces\")\n\n        # Get network interfaces as a list. The result will be something like:\n        #\n        #   eth0\\nenp0s8\\nenp0s9\n        #\n        # @return [Array<String>]\n        def self.network_interfaces(machine, path = \"/sbin/ip\")\n          s = \"\"\n          machine.communicate.sudo(\"#{path} -o -0 addr | grep -v LOOPBACK | awk '{print $2}' | sed 's/://'\") do |type, data|\n            s << data if type == :stdout\n          end\n\n          # In some cases net devices may be added to the guest and will not\n          # properly show up when using `ip`. This pulls any device information\n          # that can be found in /proc and adds it to the list of interfaces\n          s << \"\\n\"\n          machine.communicate.sudo(\"cat /proc/net/dev | grep -E '^[a-z0-9 ]+:' | awk '{print $1}' | sed 's/://'\", error_check: false) do |type, data|\n            s << data if type == :stdout\n          end\n\n          # Collect all loopback interfaces\n          los = \"\"\n          machine.communicate.sudo(\"#{path} -o -0 addr | grep LOOPBACK | awk '{print $2}' | sed 's/://'\") do |type, data|\n            los << data if type == :stdout\n          end\n          loifaces = los.split(\"\\n\")\n          @@logger.debug(\"loopback interfaces: #{loifaces.inspect}\")\n\n          ifaces = s.split(\"\\n\").reject { |x| x.empty? or loifaces.include?(x) }\n\n          @@logger.debug(\"Unsorted list: #{ifaces.inspect}\")\n          # Break out integers from strings and sort the arrays to provide\n          # a natural sort for the interface names\n          # NOTE: Devices named with a hex value suffix will _not_ be sorted\n          #  as expected. This is generally seen with veth* devices, and proper ordering\n          #  is currently not required\n          ifaces = ifaces.map do |iface|\n            iface.scan(/(.+?)(\\d+)?/).flatten.map do |iface_part|\n              if iface_part.to_i.to_s == iface_part\n                iface_part.to_i\n              else\n                iface_part\n              end\n            end\n          end\n          ifaces = ifaces.uniq.sort do |lhs, rhs|\n            result = 0\n            slice_length = [rhs.size, lhs.size].min\n            slice_length.times do |idx|\n              if(lhs[idx].is_a?(rhs[idx].class))\n                result = lhs[idx] <=> rhs[idx]\n              elsif(lhs[idx].is_a?(String))\n                result = 1\n              else\n                result = -1\n              end\n              break if result != 0\n            end\n            result\n          end.map(&:join)\n          @@logger.debug(\"Sorted list: #{ifaces.inspect}\")\n          # Extract ethernet devices and place at start of list\n          resorted_ifaces = []\n          resorted_ifaces += ifaces.find_all do |iface|\n            POSSIBLE_ETHERNET_PREFIXES.any?{|prefix| iface.start_with?(prefix)} &&\n              iface.match(/^[a-zA-Z0-9]+$/)\n          end\n          resorted_ifaces += ifaces - resorted_ifaces\n          ifaces = resorted_ifaces\n          @@logger.debug(\"Ethernet preferred sorted list: #{ifaces.inspect}\")\n          ifaces\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../synced_folders/unix_mount_helpers\"\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      class NFS\n        extend SyncedFolder::UnixMountHelpers\n\n        def self.nfs_client_installed(machine)\n          machine.communicate.test(\"test -x /sbin/mount.nfs\")\n        end\n\n        def self.mount_nfs_folder(machine, ip, folders)\n          comm = machine.communicate\n\n          # Mount each folder separately so we can retry.\n          folders.each do |name, opts|\n            # Shellescape the paths in case they do not have special characters.\n            guest_path = Shellwords.escape(opts[:guestpath])\n            host_path  = Shellwords.escape(opts[:hostpath])\n\n            # Build the list of mount options.\n            mount_opts =  []\n            mount_opts << \"vers=#{opts[:nfs_version]}\" if opts[:nfs_version]\n            mount_opts << \"udp\" if opts[:nfs_udp]\n            if opts[:mount_options]\n              mount_opts = mount_opts + opts[:mount_options].dup\n            end\n            mount_opts = mount_opts.join(\",\")\n\n            machine.communicate.sudo(\"mkdir -p #{guest_path}\")\n\n            command = \"mount -o #{mount_opts} #{ip}:#{host_path} #{guest_path}\"\n\n            # Run the command, raising a specific error.\n            retryable(on: Vagrant::Errors::NFSMountFailed, tries: 3, sleep: 5) do\n              machine.communicate.sudo(command,\n                error_class: Vagrant::Errors::NFSMountFailed,\n              )\n            end\n\n            emit_upstart_notification(machine, guest_path)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/persist_mount_shared_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util\"\n\nrequire_relative \"../../../synced_folders/unix_mount_helpers\"\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      class PersistMountSharedFolder\n        extend SyncedFolder::UnixMountHelpers\n\n        @@logger = Log4r::Logger.new(\"vagrant::guest::linux::persist_mount_shared_folders\")\n\n        # Inserts fstab entry for a set of synced folders. Will fully replace\n        # the currently managed group of Vagrant managed entries. Note, passing\n        # empty list of folders will just remove entries\n        #\n        # @param [Machine] machine The machine to run the action on\n        # @param [Map<String, Map>] A map of folders to add to fstab\n        def self.persist_mount_shared_folder(machine, folders)\n          if folders.nil?\n            @@logger.info(\"clearing /etc/fstab\")\n            self.remove_vagrant_managed_fstab(machine)\n            return\n          end\n\n          ssh_info = machine.ssh_info\n          export_folders = folders.map { |type, folder|\n            folder.map { |name, data|\n              if data[:plugin].capability?(:mount_type)\n                guest_path = Shellwords.escape(data[:guestpath])\n                data[:owner] ||= ssh_info[:username]\n                data[:group] ||= ssh_info[:username]\n                mount_type = data[:plugin].capability(:mount_type)\n                mount_options, _, _ = data[:plugin].capability(\n                  :mount_options, name, guest_path, data)\n                if data[:plugin].capability?(:mount_name)\n                  name = data[:plugin].capability(:mount_name, name, data)\n                end\n              else\n                next\n              end\n\n              {\n                name: name,\n                mount_point: guest_path,\n                mount_type: mount_type,\n                mount_options: mount_options,\n              }\n            }\n          }.flatten.compact\n\n\n          fstab_entry = Vagrant::Util::TemplateRenderer.render('guests/linux/etc_fstab', folders: export_folders)\n          self.remove_vagrant_managed_fstab(machine)\n          machine.communicate.sudo(\"echo '#{fstab_entry}' >> /etc/fstab\")\n        end\n\n        private\n\n        def self.fstab_exists?(machine)\n          machine.communicate.test(\"test -f /etc/fstab\")\n        end\n\n        def self.contains_vagrant_data?(machine)\n          machine.communicate.test(\"grep '#VAGRANT-BEGIN' /etc/fstab\")\n        end\n\n        def self.remove_vagrant_managed_fstab(machine)\n          if fstab_exists?(machine)\n            if contains_vagrant_data?(machine)\n                machine.communicate.sudo(\"sed -i '/\\#VAGRANT-BEGIN/,/\\#VAGRANT-END/d' /etc/fstab\")\n            else\n                @@logger.info(\"no vagrant data in fstab file, carrying on\")\n            end\n          else\n            @@logger.info(\"no fstab file found, carrying on\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/port.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      class Port\n        def self.port_open_check(machine, port)\n          machine.communicate.test(\"nc -z -w2 127.0.0.1 #{port}\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/public_key.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire \"vagrant/util/shell_quote\"\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      class PublicKey\n        def self.insert_public_key(machine, contents)\n          comm = machine.communicate\n          contents = contents.strip << \"\\n\"\n\n          remote_path = \"/tmp/vagrant-insert-pubkey-#{Time.now.to_i}\"\n          Tempfile.open(\"vagrant-linux-insert-public-key\") do |f|\n            f.binmode\n            f.write(contents)\n            f.fsync\n            f.close\n            comm.upload(f.path, remote_path)\n          end\n\n          # Use execute (not sudo) because we want to execute this as the SSH\n          # user (which is \"vagrant\" by default).\n          comm.execute <<-EOH.gsub(/^ */, \"\")\n            mkdir -p ~/.ssh\n            chmod 0700 ~/.ssh\n            cat '#{remote_path}' >> ~/.ssh/authorized_keys && chmod 0600 ~/.ssh/authorized_keys\n            result=$?\n            rm -f '#{remote_path}'\n            exit $result\n          EOH\n        end\n\n        def self.remove_public_key(machine, contents)\n          comm = machine.communicate\n          contents = contents.strip << \"\\n\"\n\n          remote_path = \"/tmp/vagrant-remove-pubkey-#{Time.now.to_i}\"\n          Tempfile.open(\"vagrant-linux-remove-public-key\") do |f|\n            f.binmode\n            f.write(contents)\n            f.fsync\n            f.close\n            comm.upload(f.path, remote_path)\n          end\n\n          # Use execute (not sudo) because we want to execute this as the SSH\n          # user (which is \"vagrant\" by default).\n          comm.execute <<-EOH.sub(/^ */, \"\")\n            if test -f ~/.ssh/authorized_keys; then\n              grep -v -x -f '#{remote_path}' ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.tmp\n              mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys && chmod 0600 ~/.ssh/authorized_keys\n              result=$?\n            fi\n            rm -f '#{remote_path}'\n            exit $result\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/read_ip_address.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      class ReadIPAddress\n        def self.read_ip_address(machine)\n\n          comm = machine.communicate\n\n          if comm.test(\"which ip\")\n            command = \"LANG=en ip addr  | grep -Po 'inet \\\\K[\\\\d.]+' | grep -v 127.0.0.1\"\n          else\n            command = \"LANG=en ifconfig  | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{ print $1 }'\"\n          end\n\n          result  = \"\"\n          comm.sudo(command) do |type, data|\n            result << data if type == :stdout\n          end\n\n          result.chomp.split(\"\\n\").first\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/reboot.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/guest_inspection'\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      class Reboot\n        extend Vagrant::Util::GuestInspection::Linux\n\n        DEFAULT_MAX_REBOOT_RETRY_DURATION = 120\n        WAIT_SLEEP_TIME = 5\n\n        def self.reboot(machine)\n          @logger = Log4r::Logger.new(\"vagrant::linux::reboot\")\n          reboot_script = \"ps -q 1 -o comm=,start= > /tmp/.vagrant-reboot\"\n\n          if systemd?(machine.communicate)\n            reboot_cmd = \"systemctl reboot\"\n          else\n            reboot_cmd = \"reboot\"\n          end\n\n          comm = machine.communicate\n          reboot_script += \"; #{reboot_cmd}\"\n\n          @logger.debug(\"Issuing reboot command for guest\")\n          comm.sudo(reboot_script)\n\n          machine.ui.info(I18n.t(\"vagrant.guests.capabilities.rebooting\"))\n\n          @logger.debug(\"Waiting for machine to finish rebooting\")\n\n          wait_remaining = ENV.fetch(\"VAGRANT_MAX_REBOOT_RETRY_DURATION\",\n            DEFAULT_MAX_REBOOT_RETRY_DURATION).to_i\n          wait_remaining = DEFAULT_MAX_REBOOT_RETRY_DURATION if wait_remaining < 1\n\n          begin\n            wait_for_reboot(machine)\n          rescue Vagrant::Errors::MachineGuestNotReady\n            raise if wait_remaining < 0\n            @logger.warn(\"Machine not ready, cannot start reboot yet. Trying again\")\n            sleep(WAIT_SLEEP_TIME)\n            wait_remaining -= WAIT_SLEEP_TIME\n            retry\n          end\n        end\n\n        def self.wait_for_reboot(machine)\n          caught = false\n          begin\n            check_script = 'grep \"$(ps -q 1 -o comm=,start=)\" /tmp/.vagrant-reboot'\n            while machine.guest.ready? && machine.communicate.execute(check_script, error_check: false) == 0\n              sleep 10\n            end\n          rescue\n            # The check script execution may result in an exception\n            # getting raised depending on the state of the communicator\n            # when executing. We'll allow for it to happen once, and then\n            # raise if we get an exception again\n            if caught\n              raise\n            end\n            caught = true\n            sleep 10\n            retry\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../synced_folders/rsync/default_unix_cap\"\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      class RSync\n        extend VagrantPlugins::SyncedFolderRSync::DefaultUnixCap\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/cap/shell_expand_guest_path.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestLinux\n    module Cap\n      class ShellExpandGuestPath\n        def self.shell_expand_guest_path(machine, path)\n          real_path = nil\n          path = path.gsub(/ /, '\\ ')\n          machine.communicate.execute(\"echo; printf #{path}\") do |type, data|\n            if type == :stdout\n              real_path ||= \"\"\n              real_path += data\n            end\n          end\n\n          if real_path\n            # The last line is the path we care about\n            real_path = real_path.split(\"\\n\").last.chomp\n          end\n\n          if !real_path\n            # If no real guest path was detected, this is really strange\n            # and we raise an exception because this is a bug.\n            raise Vagrant::Errors::ShellExpandFailed\n          end\n\n          # Chomp the string so that any trailing newlines are killed\n          return real_path.chomp\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestLinux\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      # Name used for guest detection\n      GUEST_DETECTION_NAME = \"linux\".freeze\n\n      def detect?(machine)\n        machine.communicate.test <<-EOH.gsub(/^ */, '')\n          if test -r /etc/os-release; then\n            source /etc/os-release && test 'x#{self.class.const_get(:GUEST_DETECTION_NAME)}' = \"x$ID\" && exit\n          fi\n          if test -x /usr/bin/lsb_release; then\n            /usr/bin/lsb_release -i 2>/dev/null | grep -qi '#{self.class.const_get(:GUEST_DETECTION_NAME)}' && exit\n          fi\n          if test -r /etc/issue; then\n            cat /etc/issue | grep -qi '#{self.class.const_get(:GUEST_DETECTION_NAME)}' && exit\n          fi\n          exit 1\n        EOH\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/linux/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestLinux\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Linux guest.\"\n      description \"Linux guest support.\"\n\n      guest(:linux) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:linux, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:linux, :choose_addressable_ip_addr) do\n        require_relative \"cap/choose_addressable_ip_addr\"\n        Cap::ChooseAddressableIPAddr\n      end\n\n      guest_capability(:linux, :create_tmp_path) do\n        require_relative \"cap/file_system\"\n        Cap::FileSystem\n      end\n\n      guest_capability(:linux, :decompress_tgz) do\n        require_relative \"cap/file_system\"\n        Cap::FileSystem\n      end\n\n      guest_capability(:linux, :decompress_zip) do\n        require_relative \"cap/file_system\"\n        Cap::FileSystem\n      end\n\n      guest_capability(:linux, :halt) do\n        require_relative \"cap/halt\"\n        Cap::Halt\n      end\n\n      guest_capability(:linux, :insert_public_key) do\n        require_relative \"cap/public_key\"\n        Cap::PublicKey\n      end\n\n      guest_capability(:linux, :shell_expand_guest_path) do\n        require_relative \"cap/shell_expand_guest_path\"\n        Cap::ShellExpandGuestPath\n      end\n\n      guest_capability(:linux, :mount_nfs_folder) do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      guest_capability(:linux, :mount_smb_shared_folder) do\n        require_relative \"cap/mount_smb_shared_folder\"\n        Cap::MountSMBSharedFolder\n      end\n\n      guest_capability(:linux, :mount_virtualbox_shared_folder) do\n        require_relative \"cap/mount_virtualbox_shared_folder\"\n        Cap::MountVirtualBoxSharedFolder\n      end\n\n      guest_capability(:linux, :persist_mount_shared_folder) do\n        require_relative \"cap/persist_mount_shared_folder\"\n        Cap::PersistMountSharedFolder\n      end\n\n      guest_capability(:linux, :network_interfaces) do\n        require_relative \"cap/network_interfaces\"\n        Cap::NetworkInterfaces\n      end\n\n      guest_capability(:linux, :nfs_client_installed) do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      # For the Docker provider\n      guest_capability(:linux, :port_open_check) do\n        require_relative \"cap/port\"\n        Cap::Port\n      end\n\n      guest_capability(:linux, :read_ip_address) do\n        require_relative \"cap/read_ip_address\"\n        Cap::ReadIPAddress\n      end\n\n      guest_capability(:linux, :wait_for_reboot) do\n        require_relative \"cap/reboot\"\n        Cap::Reboot\n      end\n\n      guest_capability(:linux, :reboot) do\n        require_relative \"cap/reboot\"\n        Cap::Reboot\n      end\n\n      guest_capability(:linux, :remove_public_key) do\n        require_relative \"cap/public_key\"\n        Cap::PublicKey\n      end\n\n      guest_capability(:linux, :rsync_installed) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:linux, :rsync_command) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:linux, :rsync_post) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:linux, :rsync_pre) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:linux, :unmount_virtualbox_shared_folder) do\n        require_relative \"cap/mount_virtualbox_shared_folder\"\n        Cap::MountVirtualBoxSharedFolder\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/mint/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../linux/guest'\n\nmodule VagrantPlugins\n  module GuestMint\n    class Guest < VagrantPlugins::GuestLinux::Guest\n      # Name used for guest detection\n      GUEST_DETECTION_NAME = \"Linux Mint\".freeze\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/mint/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestMint\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Mint guest\"\n      description \"Mint guest support.\"\n\n      guest(:mint, :ubuntu) do\n        require_relative \"guest\"\n        Guest\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/netbsd/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestNetBSD\n    module Cap\n      class ChangeHostName\n        def self.change_host_name(machine, name)\n          if !machine.communicate.test(\"hostname -s | grep '^#{name}$'\")\n            machine.communicate.sudo(<<CMDS, {shell: \"sh\"})\nsed -e 's/^hostname=.*$/hostname=#{name}/' /etc/rc.conf > /tmp/rc.conf.vagrant_changehostname_#{name} &&\nmv /tmp/rc.conf.vagrant_changehostname_#{name} /etc/rc.conf &&\nhostname #{name}\nCMDS\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/netbsd/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestNetBSD\n    module Cap\n      class ConfigureNetworks\n        include Vagrant::Util\n\n        def self.configure_networks(machine, networks)\n\n          # setup a new rc.conf file\n          newrcconf = \"/tmp/rc.conf.vagrant_configurenetworks\"\n          machine.communicate.sudo(\"sed -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' /etc/rc.conf > #{newrcconf}\")\n\n          networks.each do |network|\n\n            # create an interface configuration file fragment\n            entry = TemplateRenderer.render(\"guests/netbsd/network_#{network[:type]}\",\n                                            options: network)\n\n            Tempfile.open(\"vagrant-netbsd-configure-networks\") do |f|\n              f.binmode\n              f.write(entry)\n              f.fsync\n              f.close\n              machine.communicate.upload(f.path, \"/tmp/vagrant-network-entry\")\n            end\n\n            machine.communicate.sudo(\"cat /tmp/vagrant-network-entry >> #{newrcconf}\")\n            machine.communicate.sudo(\"rm -f /tmp/vagrant-network-entry\")\n\n            ifname = \"wm#{network[:interface]}\"\n            # remove old configuration\n            machine.communicate.sudo(\"/sbin/dhcpcd -x #{ifname}\", { error_check: false })\n            machine.communicate.sudo(\"/sbin/ifconfig #{ifname} inet delete\", { error_check: false })\n\n            # live new configuration\n            if network[:type].to_sym == :static\n              machine.communicate.sudo(\"/sbin/ifconfig #{ifname} media autoselect up;/sbin/ifconfig #{ifname} inet #{network[:ip]} netmask #{network[:netmask]}\")\n            elsif network[:type].to_sym == :dhcp\n              machine.communicate.sudo(\"/sbin/dhcpcd -n -q #{ifname}\")\n            end\n          end\n\n          # install new rc.conf\n          machine.communicate.sudo(\"install -c -o 0 -g 0 -m 644 #{newrcconf} /etc/rc.conf\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/netbsd/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../synced_folders/rsync/default_unix_cap\"\n\nmodule VagrantPlugins\n  module GuestNetBSD\n    module Cap\n      class RSync\n        extend VagrantPlugins::SyncedFolderRSync::DefaultUnixCap\n\n        def self.rsync_install(machine)\n          machine.communicate.sudo(\n            'PATH=$PATH:/usr/sbin '\\\n              'PKG_PATH=\"http://ftp.NetBSD.org/pub/pkgsrc/packages/NetBSD/' \\\n              '`uname -m`/`uname -r | cut -d. -f1-2`/All\" ' \\\n              'pkg_add rsync'\n          )\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/netbsd/cap/shell_expand_guest_path.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestNetBSD\n    module Cap\n      class ShellExpandGuestPath\n        def self.shell_expand_guest_path(machine, path)\n          real_path = nil\n          path = path.gsub(/ /, '\\ ')\n          machine.communicate.execute(\"printf #{path}\") do |type, data|\n            if type == :stdout\n              real_path ||= \"\"\n              real_path += data\n            end\n          end\n\n          if !real_path\n            # If no real guest path was detected, this is really strange\n            # and we raise an exception because this is a bug.\n            raise Vagrant::Errors::ShellExpandFailed\n          end\n\n          # Chomp the string so that any trailing newlines are killed\n          return real_path.chomp\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/netbsd/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestNetBSD\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"uname -s | grep NetBSD\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/netbsd/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestNetBSD\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"NetBSD guest\"\n      description \"NetBSD guest support.\"\n\n      guest(:netbsd, :bsd) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:netbsd, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:netbsd, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:netbsd, :rsync_install) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:netbsd, :rsync_installed) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:netbsd, :rsync_command) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:netbsd, :rsync_post) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:netbsd, :rsync_pre) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:netbsd, :shell_expand_guest_path) do\n        require_relative \"cap/shell_expand_guest_path\"\n        Cap::ShellExpandGuestPath\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/nixos/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestNixos\n    module Cap\n      class ChangeHostName\n        include Vagrant::Util\n\n        def self.change_host_name(machine, name)\n          # upload the config file\n          hostname_module = TemplateRenderer.render(\"guests/nixos/hostname\", name: name)\n          upload(machine, hostname_module, \"/etc/nixos/vagrant-hostname.nix\")\n        end\n\n        # Upload a file.\n        def self.upload(machine, content, remote_path)\n          remote_temp = mktemp(machine)\n\n          Tempfile.open(\"vagrant-nixos-change-host-name\") do |f|\n            f.binmode\n            f.write(content)\n            f.fsync\n            f.close\n            machine.communicate.upload(f.path, \"#{remote_temp}\")\n          end\n\n          machine.communicate.sudo(\"mv #{remote_temp} #{remote_path}\")\n        end\n\n        # Create a temp file.\n        def self.mktemp(machine)\n          path = nil\n\n          machine.communicate.execute(\"mktemp --suffix -vagrant-upload\") do |type, result|\n            path = result.chomp if type == :stdout\n          end\n          path\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/nixos/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"ipaddr\"\nrequire \"tempfile\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestNixos\n    module Cap\n      class ConfigureNetworks\n        include Vagrant::Util\n\n        def self.configure_networks(machine, networks)\n          # set the prefix length.\n          networks.each do |network|\n            network[:prefix_length] = (network[:netmask] && netmask_to_cidr(network[:netmask]))\n          end\n\n          # set the device names.\n          assign_device_names(machine, networks)\n\n          # upload the config file\n          network_module = TemplateRenderer.render(\"guests/nixos/network\", networks: networks)\n          upload(machine, network_module, \"/etc/nixos/vagrant-network.nix\")\n        end\n\n        # Set :device on each network.\n        # Attempts to use biosdevname when available to detect interface names,\n        # and falls back to ifconfig otherwise.\n        def self.assign_device_names(machine, networks)\n          if machine.communicate.test(\"command -v biosdevname\")\n            # use biosdevname to get info about the interfaces\n            interfaces = get_interfaces(machine)\n            if machine.provider.capability?(:nic_mac_addresses)\n              # find device name by MAC lookup.\n              mac_addresses = machine.provider.capability(:nic_mac_addresses)\n              networks.each do |network|\n                mac_address = mac_addresses[network[:interface]+1]\n                interface = interfaces.detect {|nic| nic[:mac_address].gsub(\":\",\"\") == mac_address} if mac_address\n                network[:device] = interface[:kernel] if interface\n              end\n            else\n              # assume interface numbers correspond to (ethN+1).\n              networks.each do |network|\n                interface = interfaces.detect {|nic| nic[:ethn] == network[:interface]}\n                network[:device] = interface[:kernel] if interface\n              end\n            end\n          else\n            # assume interface numbers correspond to the order of interfaces.\n            interfaces = get_interface_names(machine)\n            networks.each do |network|\n              network[:device] = interfaces[network[:interface]]\n            end\n          end\n        end\n\n        def self.get_interface_names(machine)\n          output = nil\n          machine.communicate.execute(\"ifconfig -a\") do |type, result|\n            output = result.chomp if type == :stdout\n          end\n          names = output.scan(/^[^:\\s]+/).reject {|name| name =~ /^lo/ }\n          names\n        end\n\n        # Upload a file.\n        def self.upload(machine, content, remote_path)\n          remote_temp = mktemp(machine)\n\n          Tempfile.open(\"vagrant-nixos-configure-networks\") do |f|\n            f.binmode\n            f.write(content)\n            f.fsync\n            f.close\n            machine.communicate.upload(f.path, \"#{remote_temp}\")\n          end\n\n          machine.communicate.sudo(\"mv #{remote_temp} #{remote_path}\")\n        end\n\n        # Create a temp file.\n        def self.mktemp(machine)\n          path = nil\n\n          machine.communicate.execute(\"mktemp --suffix -vagrant-upload\") do |type, result|\n            path = result.chomp if type == :stdout\n          end\n          path\n        end\n\n        # using biosdevname, get all interfaces as a list of hashes, where:\n        #   :kernel      - the kernel's name for the device,\n        #   :ethn        - the calculated ethN-style name converted to integer.\n        #   :mac_address - the permanent mac address. ethN-style name converted to integer.\n        def self.get_interfaces(machine)\n          interfaces = []\n\n          # get all adapters, as named by the kernel\n          output = nil\n          machine.communicate.sudo(\"biosdevname -d\") do |type, result|\n            output = result if type == :stdout\n          end\n          kernel_if_names = output.scan(/Kernel name: ([^\\n]+)/).flatten\n          mac_addresses   = output.scan(/Permanent MAC: ([^\\n]+)/).flatten\n\n          # get ethN-style names\n          ethns = []\n          kernel_if_names.each do |name|\n            machine.communicate.sudo(\"biosdevname --policy=all_ethN -i #{name}\") do |type, result|\n              ethns << result.gsub(/[^\\d]/,'').to_i if type == :stdout\n            end\n          end\n\n          # populate the interface list\n          kernel_if_names.each_index do |i|\n            interfaces << {\n              kernel:      kernel_if_names[i],\n              ethn:        ethns[i],\n              mac_address: mac_addresses[i]\n            }\n          end\n\n          interfaces\n        end\n\n        def self.netmask_to_cidr(mask)\n          IPAddr.new(mask).to_i.to_s(2).count(\"1\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/nixos/cap/nfs_client.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestNixos\n    module Cap\n      class NFSClient\n        def self.nfs_client_installed(machine)\n          machine.communicate.test(\"test -x /run/current-system/sw/sbin/mount.nfs\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/nixos/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestNixos\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        # For some reason our test passes on Windows, so just short\n        # circuit because we're not Windows.\n        if machine.config.vm.communicator == :winrm\n          return false\n        end\n\n        machine.communicate.test(\"test -f /run/current-system/nixos-version\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/nixos/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestNixos\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"NixOS guest\"\n      description \"NixOS guest support.\"\n\n      guest(:nixos, :linux) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:nixos, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:nixos, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:nixos, :nfs_client_installed) do\n        require_relative \"cap/nfs_client\"\n        Cap::NFSClient\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/omnios/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../../linux/cap/change_host_name'\n\nmodule VagrantPlugins\n  module GuestOmniOS\n    module Cap\n      class ChangeHostName\n        extend VagrantPlugins::GuestLinux::Cap::ChangeHostName\n\n        def self.change_name_command(name)\n          basename = name.split(\".\", 2)[0]\n          return <<-EOH.gsub(/^ {14}/, \"\")\n          # Set hostname\n          echo '#{name}' > /etc/nodename\n          hostname '#{name}'\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/omnios/cap/mount_nfs_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestOmniOS\n    module Cap\n      class MountNFSFolder\n        def self.mount_nfs_folder(machine, ip, folders)\n          comm   = machine.communicate\n          commands = []\n\n          folders.each do |_, opts|\n            commands << <<-EOH.gsub(/^ {14}/, '')\n              mkdir -p '#{opts[:guestpath]}'\n              /sbin/mount '#{ip}:#{opts[:hostpath]}' '#{opts[:guestpath]}'\n            EOH\n          end\n\n          comm.sudo(commands.join(\"\\n\"))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/omnios/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestOmniOS\n    module Cap\n      class RSync\n        def self.rsync_install(machine)\n          machine.communicate.sudo(\"pkg install rsync\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/omnios/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestOmniOS\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"cat /etc/release | grep -i OmniOS\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/omnios/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestOmniOS\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"OmniOS guest.\"\n      description \"OmniOS guest support.\"\n\n      guest(:omnios, :solaris) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:omnios, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:omnios, :mount_nfs_folder) do\n        require_relative \"cap/mount_nfs_folder\"\n        Cap::MountNFSFolder\n      end\n\n      guest_capability(:omnios, :rsync_install) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openbsd/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/guest_hosts'\n\nmodule VagrantPlugins\n  module GuestOpenBSD\n    module Cap\n      class ChangeHostName\n        extend Vagrant::Util::GuestHosts::BSD\n\n        def self.change_host_name(machine, name)\n          comm = machine.communicate\n\n          if !comm.test(\"hostname -f | grep '^#{name}$'\", sudo: false, shell: \"sh\")\n            basename = name.split(\".\", 2)[0]\n            command = <<-EOH.gsub(/^ {14}/, '')\n              # Set the hostname\n              hostname '#{name}'\n              sed -i'' 's/^hostname=.*$/hostname=\\\"#{name}\\\"/' /etc/rc.conf\n              echo '#{name}' > /etc/myname\n            EOH\n            comm.sudo(command, shell: \"sh\")\n          end\n          network_with_hostname = machine.config.vm.networks.map {|_, c| c if c[:hostname] }.compact[0]\n          if network_with_hostname\n            replace_host(comm, name, network_with_hostname[:ip])\n          else\n            add_hostname_to_loopback_interface(comm, name)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openbsd/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestOpenBSD\n    module Cap\n      class ConfigureNetworks\n        include Vagrant::Util\n\n        def self.configure_networks(machine, networks)\n          networks.each do |network|\n            entry = TemplateRenderer.render(\"guests/openbsd/network_#{network[:type]}\",\n                                            options: network)\n\n            Tempfile.open(\"vagrant-openbsd-configure-networks\") do |f|\n              f.binmode\n              f.write(entry)\n              f.fsync\n              f.close\n              machine.communicate.upload(f.path, \"/tmp/vagrant-network-entry\")\n            end\n\n            # Determine the interface prefix...\n            command = \"ifconfig -a | grep -o ^[0-9a-z]*\"\n            result = \"\"\n            ifname = \"\"\n            machine.communicate.execute(command) do |type, data|\n              result << data if type == :stdout\n              if result.split(/\\n/).any?{|i| i.match(/vio*/)}\n                ifname = \"vio#{network[:interface]}\"\n              else\n                ifname = \"em#{network[:interface]}\"\n              end\n            end\n\n            machine.communicate.sudo(\"mv /tmp/vagrant-network-entry /etc/hostname.#{ifname}\")\n\n            # apply new configurations\n            machine.communicate.sudo(\"sh /etc/netstart #{ifname}\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openbsd/cap/halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestOpenBSD\n    module Cap\n      class Halt\n        def self.halt(machine)\n          begin\n            # Versions of OpenBSD prior to 5.7 require the -h option to be\n            # provided with the -p option. Later options allow the -h to\n            # be optional.\n            machine.communicate.sudo(\"/sbin/shutdown -p -h now\", shell: \"sh\")\n          rescue IOError, Vagrant::Errors::SSHDisconnected\n            # Do nothing, because it probably means the machine shut down\n            # and SSH connection was lost.\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openbsd/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../synced_folders/rsync/default_unix_cap\"\n\nmodule VagrantPlugins\n  module GuestOpenBSD\n    module Cap\n      class RSync\n        extend VagrantPlugins::SyncedFolderRSync::DefaultUnixCap\n\n        def self.rsync_install(machine)\n          install_output = {:stderr => '', :stdout => ''}\n          command = 'PKG_PATH=\"http://ftp.openbsd.org/pub/OpenBSD/' \\\n            '`uname -r`/packages/`arch -s`/\" ' \\\n            'pkg_add -I rsync--'\n          machine.communicate.sudo(command) do |type, data|\n            install_output[type] << data if install_output.key?(type)\n          end\n          # pkg_add returns 0 even if package was not found, so\n          # validate package is actually installed\n          machine.communicate.sudo('pkg_info -cA | grep inst:rsync-[[:digit:]]',\n            error_class: Vagrant::Errors::RSyncNotInstalledInGuest,\n            command: command,\n            stderr: install_output[:stderr],\n            stdout: install_output[:stdout]\n          )\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openbsd/cap/shell_expand_guest_path.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestOpenBSD\n    module Cap\n      class ShellExpandGuestPath\n        def self.shell_expand_guest_path(machine, path)\n          real_path = nil\n          path = path.gsub(/ /, '\\ ')\n          machine.communicate.execute(\"printf #{path}\") do |type, data|\n            if type == :stdout\n              real_path ||= \"\"\n              real_path += data\n            end\n          end\n\n          if !real_path\n            # If no real guest path was detected, this is really strange\n            # and we raise an exception because this is a bug.\n            raise Vagrant::Errors::ShellExpandFailed\n          end\n\n          # Chomp the string so that any trailing newlines are killed\n          return real_path.chomp\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openbsd/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestOpenBSD\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"uname -s | grep 'OpenBSD'\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openbsd/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestOpenBSD\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"OpenBSD guest\"\n      description \"OpenBSD guest support.\"\n\n      guest(:openbsd, :bsd) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:openbsd, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:openbsd, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:openbsd, :halt) do\n        require_relative \"cap/halt\"\n        Cap::Halt\n      end\n\n      guest_capability(:openbsd, :rsync_install) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:openbsd, :rsync_installed) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:openbsd, :rsync_command) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:openbsd, :rsync_post) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:openbsd, :rsync_pre) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:openbsd, :shell_expand_guest_path) do\n        require_relative \"cap/shell_expand_guest_path\"\n        Cap::ShellExpandGuestPath\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openwrt/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestOpenWrt\n    module Cap\n      class ChangeHostName\n        def self.change_host_name(machine, name)\n          comm = machine.communicate\n\n          if !comm.test(\"uci get system.@system[0].hostname | grep '^#{name}$'\", sudo: false)\n            comm.execute <<~EOH\n              uci set system.@system[0].hostname='#{name}'\n              uci commit system\n              /etc/init.d/system reload\n            EOH\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openwrt/cap/halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestOpenWrt\n    module Cap\n      class Halt\n        def self.halt(machine)\n          begin\n            machine.communicate.execute(\"halt\")\n          rescue IOError, Vagrant::Errors::SSHDisconnected\n            # Ignore, this probably means connection closed because it\n            # shut down.\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openwrt/cap/insert_public_key.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/shell_quote\"\n\nmodule VagrantPlugins\n  module GuestOpenWrt\n    module Cap\n      class InsertPublicKey\n        def self.insert_public_key(machine, contents)\n          contents = contents.chomp\n          contents = Vagrant::Util::ShellQuote.escape(contents, \"'\")\n\n          machine.communicate.tap do |comm|\n            comm.execute <<~EOH\n              printf '#{contents}\\\\n' >> /etc/dropbear/authorized_keys\n            EOH\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openwrt/cap/remove_public_key.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/shell_quote\"\n\nmodule VagrantPlugins\n  module GuestOpenWrt\n    module Cap\n      class RemovePublicKey\n        def self.remove_public_key(machine, contents)\n          contents = contents.chomp\n          contents = Vagrant::Util::ShellQuote.escape(contents, \"'\")\n\n          machine.communicate.tap do |comm|\n            comm.execute <<~EOH\n              if test -f /etc/dropbear/authorized_keys ; then\n                sed -i '/^.*#{contents}.*$/d' /etc/dropbear/authorized_keys\n              fi\n            EOH\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openwrt/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestOpenWrt\n    module Cap\n      class RSync\n        def self.rsync_installed(machine)\n          machine.communicate.test(\"which rsync\")\n        end\n\n        def self.rsync_install(machine)\n          machine.communicate.tap do |comm|\n            comm.execute <<~EOH\n              opkg update\n              opkg install rsync\n            EOH\n          end\n        end\n\n        def self.rsync_pre(machine, opts)\n          machine.communicate.tap do |comm|\n            comm.execute(\"mkdir -p '#{opts[:guestpath]}'\")\n          end\n        end\n\n        def self.rsync_command(machine)\n          \"rsync -zz\"\n        end\n\n        def self.rsync_post(machine, opts)\n          # Don't do anything because BusyBox's `find` doesn't support the\n          # syntax in plugins/synced_folders/rsync/default_unix_cap.rb.\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openwrt/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestOpenWrt\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      # Name used for guest detection\n      GUEST_DETECTION_NAME = \"openwrt\".freeze\n\n      def detect?(machine)\n        machine.communicate.test <<~EOH\n          if test -e /etc/openwrt_release; then\n            exit\n          fi\n          if test -r /etc/os-release; then\n            source /etc/os-release && test 'x#{self.class.const_get(:GUEST_DETECTION_NAME)}' = \"x$ID\" && exit\n          fi\n          if test -r /etc/banner; then\n            cat /etc/banner | grep -qi '#{self.class.const_get(:GUEST_DETECTION_NAME)}' && exit\n          fi\n          exit 1\n        EOH\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/openwrt/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestOpenWrt\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"OpenWrt guest\"\n      description \"OpenWrt guest support.\"\n\n      guest(:openwrt, :linux) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:openwrt, :insert_public_key) do\n        require_relative \"cap/insert_public_key\"\n        Cap::InsertPublicKey\n      end\n\n      guest_capability(:openwrt, :remove_public_key) do\n        require_relative \"cap/remove_public_key\"\n        Cap::RemovePublicKey\n      end\n\n      guest_capability(:openwrt, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:openwrt, :rsync_installed) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:openwrt, :rsync_install) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:openwrt, :rsync_pre) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:openwrt, :rsync_command) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:openwrt, :rsync_post) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:openwrt, :halt) do\n        require_relative \"cap/halt\"\n        Cap::Halt\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "plugins/guests/photon/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../../linux/cap/change_host_name'\n\nmodule VagrantPlugins\n  module GuestPhoton\n    module Cap\n      class ChangeHostName\n        extend VagrantPlugins::GuestLinux::Cap::ChangeHostName\n\n        def self.change_name_command(name)\n          return <<-EOH.gsub(/^ {14}/, \"\")\n             # Set the hostname\n            echo '#{name}' > /etc/hostname\n            hostname '#{name}'\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/photon/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestPhoton\n    module Cap\n      class ConfigureNetworks\n        include Vagrant::Util\n\n        def self.configure_networks(machine, networks)\n          comm = machine.communicate\n\n          commands   = []\n          interfaces = []\n\n          comm.sudo(\"ifconfig | grep 'eth' | cut -f1 -d' '\") do |_, result|\n            interfaces = result.split(\"\\n\")\n          end\n\n          networks.each do |network|\n            device = interfaces[network[:interface]]\n            command =  \"ifconfig #{device}\"\n            command << \" #{network[:ip]}\" if network[:ip]\n            command << \" netmask #{network[:netmask]}\" if network[:netmask]\n            commands << command\n          end\n\n          comm.sudo(commands.join(\"\\n\"))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/photon/cap/docker.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestPhoton\n    module Cap\n      module Docker\n        def self.docker_daemon_running(machine)\n          machine.communicate.test('test -S /run/docker.sock')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/photon/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestPhoton\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"cat /etc/photon-release | grep 'VMware Photon'\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/photon/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestPhoton\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"VMware Photon guest\"\n      description \"VMware Photon guest support.\"\n\n      guest(:photon, :linux) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:photon, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:photon, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:photon, :docker_daemon_running) do\n        require_relative \"cap/docker\"\n        Cap::Docker\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/pld/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../../linux/cap/change_host_name'\n\nmodule VagrantPlugins\n  module GuestPld\n    module Cap\n      class ChangeHostName\n        extend VagrantPlugins::GuestLinux::Cap::ChangeHostName\n\n        def self.change_name_command(name)\n          return <<-EOH.gsub(/^ {14}/, \"\")\n          hostname '#{name}'\n          sed -i 's/\\\\(HOSTNAME=\\\\).*/\\\\1#{name}/' /etc/sysconfig/network\n\n          sed -i 's/\\\\(DHCP_HOSTNAME=\\\\).*/\\\\1\\\"#{name}\\\"/' /etc/sysconfig/interfaces/ifcfg-*\n\n          # Restart networking\n          service network restart\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/pld/cap/flavor.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestPld\n    module Cap\n      class Flavor\n        def self.flavor(machine)\n          return :pld\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/pld/cap/network_scripts_dir.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestPld\n    module Cap\n      class NetworkScriptsDir\n        def self.network_scripts_dir(machine)\n          \"/etc/sysconfig/interfaces\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/pld/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestPld\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"cat /etc/pld-release\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/pld/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestPld\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"PLD Linux guest\"\n      description \"PLD Linux guest support.\"\n\n      guest(:pld, :redhat) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:pld, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:pld, :network_scripts_dir) do\n        require_relative \"cap/network_scripts_dir\"\n        Cap::NetworkScriptsDir\n      end\n\n      guest_capability(:pld, :flavor) do\n        require_relative \"cap/flavor\"\n        Cap::Flavor\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/redhat/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/guest_hosts'\n\nmodule VagrantPlugins\n  module GuestRedHat\n    module Cap\n      class ChangeHostName\n\n        extend Vagrant::Util::GuestInspection::Linux\n        extend Vagrant::Util::GuestHosts::Linux\n\n        def self.change_host_name(machine, name)\n          comm = machine.communicate\n\n          if !comm.test(\"hostname -f | grep '^#{name}$'\", sudo: false)\n            basename = name.split('.', 2)[0]\n            comm.sudo <<-EOH.gsub(/^ {14}/, '')\n              # Update sysconfig\n              if [ -f /etc/sysconfig/network ]; then\n                sed -i 's/\\\\(HOSTNAME=\\\\).*/\\\\1#{name}/' /etc/sysconfig/network\n              fi\n              # Update DNS\n              find /etc/sysconfig/network-scripts -maxdepth 1 -type f -name 'ifcfg-*' | xargs -r sed -i 's/\\\\(DHCP_HOSTNAME=\\\\).*/\\\\1\\\"#{basename}\\\"/'\n              # Set the hostname - use hostnamectl if available\n              echo '#{name}' > /etc/hostname\n            EOH\n\n            if hostnamectl?(comm)\n              comm.sudo(\"hostnamectl set-hostname --static '#{name}' ; \" \\\n                \"hostnamectl set-hostname --transient '#{name}'\")\n            else\n              comm.sudo(\"hostname -F /etc/hostname\")\n            end\n\n            restart_command = \"service network restart\"\n\n            if systemd?(comm)\n              if systemd_networkd?(comm)\n                restart_command = \"systemctl restart systemd-networkd.service\"\n              elsif systemd_controlled?(comm, \"NetworkManager.service\")\n                restart_command = \"systemctl restart NetworkManager.service\"\n              end\n            end\n            comm.sudo(restart_command)\n          end\n          \n          network_with_hostname = machine.config.vm.networks.map {|_, c| c if c[:hostname] }.compact[0]\n          if network_with_hostname\n            replace_host(comm, name, network_with_hostname[:ip])\n          else\n            add_hostname_to_loopback_interface(comm, name)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/redhat/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\nrequire \"securerandom\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestRedHat\n    module Cap\n      class ConfigureNetworks\n        include Vagrant::Util\n        extend Vagrant::Util::GuestInspection::Linux\n        extend Vagrant::Util::GuestNetworks::Linux\n\n        def self.configure_networks(machine, networks)\n          @logger = Log4r::Logger.new(\"vagrant::guest::redhat::configurenetworks\")\n\n          # Start with the scripts directory to determine how to configure\n          network_scripts_dir = machine.guest.capability(:network_scripts_dir)\n          @logger.debug(\"guest network scripts directory: #{network_scripts_dir}\")\n\n          # The legacy configuration will handle rhel/centos pre-10\n          # versions. The newer versions have a different path for\n          # network configuration files.\n          if network_scripts_dir.end_with?(\"network-scripts\")\n            configure_networks_legacy(machine, networks)\n          else\n            # Recent versions use Network Manager\n            configure_network_manager(machine, networks)\n          end\n        end\n\n        def self.configure_networks_legacy(machine, networks)\n          comm = machine.communicate\n\n          network_scripts_dir = machine.guest.capability(:network_scripts_dir)\n\n          commands   = {:start => [], :middle => [], :end => []}\n          interfaces = machine.guest.capability(:network_interfaces)\n\n          # Check if NetworkManager is installed on the system\n          nmcli_installed = nmcli?(comm)\n          net_configs = machine.config.vm.networks.map do |type, opts|\n            opts if type.to_s.end_with?(\"_network\")\n          end.compact\n          networks.each.with_index do |network, i|\n            network[:device] = interfaces[network[:interface]]\n            extra_opts = net_configs[i] ? net_configs[i].dup : {}\n\n            if nmcli_installed\n              # Now check if the device is actively being managed by NetworkManager\n              nm_controlled = nm_controlled?(comm, network[:device])\n            end\n\n            if !extra_opts.key?(:nm_controlled)\n              extra_opts[:nm_controlled] = !!nm_controlled\n            end\n\n            extra_opts[:nm_controlled] = case extra_opts[:nm_controlled]\n                                      when true\n                                        \"yes\"\n                                      when false, nil\n                                        \"no\"\n                                      else\n                                        extra_opts[:nm_controlled].to_s\n                                      end\n\n            if extra_opts[:nm_controlled] == \"yes\" && !nmcli_installed\n              raise Vagrant::Errors::NetworkManagerNotInstalled, device: network[:device]\n            end\n\n            # Render a new configuration\n            entry = TemplateRenderer.render(\"guests/redhat/network_#{network[:type]}\",\n              options: extra_opts.merge(network),\n            )\n\n            # Upload the new configuration\n            remote_path = \"/tmp/vagrant-network-entry-#{network[:device]}-#{Time.now.to_i}-#{i}\"\n            Tempfile.open(\"vagrant-redhat-configure-networks\") do |f|\n              f.binmode\n              f.write(entry)\n              f.fsync\n              f.close\n              machine.communicate.upload(f.path, remote_path)\n            end\n\n            # Add the new interface and bring it back up\n            final_path = \"#{network_scripts_dir}/ifcfg-#{network[:device]}\"\n\n            if nm_controlled\n              commands[:start] << \"nmcli d disconnect iface '#{network[:device]}'\"\n            else\n              commands[:start] << \"/sbin/ifdown '#{network[:device]}'\"\n            end\n            commands[:middle] << \"mv -f '#{remote_path}' '#{final_path}'\"\n            if extra_opts[:nm_controlled] == \"no\"\n              commands[:end] << \"/sbin/ifup '#{network[:device]}'\"\n            end\n          end\n          if nmcli_installed\n            commands[:middle] << \"(test -f /etc/init.d/NetworkManager && /etc/init.d/NetworkManager restart) || \" \\\n              \"((systemctl | grep NetworkManager.service) && systemctl restart NetworkManager)\"\n          end\n          commands = commands[:start] + commands[:middle] + commands[:end]\n          comm.sudo(commands.join(\"\\n\"))\n          comm.wait_for_ready(5)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/redhat/cap/flavor.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestRedHat\n    module Cap\n      class Flavor\n        def self.flavor(machine)\n          # Pick up version info from `/etc/os-release`. This file started to exist\n          # in RHEL 7. For versions before that (i.e. RHEL 6) just plain `:rhel`\n          # should do.\n          version = nil\n          if machine.communicate.test(\"test -f /etc/os-release\")\n            begin\n              machine.communicate.execute(\"source /etc/os-release && printf $VERSION_ID\") do |type, data|\n                if type == :stdout\n                  version = data.split(\".\").first.to_i\n                end\n              end\n            rescue\n            end\n          end\n          if version.nil? || version < 1\n            return :rhel\n          else\n            return \"rhel_#{version}\".to_sym\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/redhat/cap/network_scripts_dir.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestRedHat\n    module Cap\n      class NetworkScriptsDir\n        def self.network_scripts_dir(machine)\n          if machine.communicate.test(\"test -d /etc/sysconfig/network-scripts\")\n            \"/etc/sysconfig/network-scripts\"\n          else\n            \"/etc/NetworkManager/system-connections\"\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/redhat/cap/nfs_client.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestRedHat\n    module Cap\n      class NFSClient\n        def self.nfs_client_install(machine)\n          machine.communicate.sudo <<-EOH.gsub(/^ {12}/, '')\n            if command -v dnf; then\n              if `dnf info -q libnfs-utils > /dev/null 2>&1` ; then\n                dnf -y install nfs-utils libnfs-utils portmap\n              elif `dnf info -q nfs-utils-lib > /dev/null 2>&1` ; then\n                dnf -y install nfs-utils nfs-utils-lib portmap\n              else\n                dnf -y install nfs-utils portmap\n              fi\n            else\n              yum -y install nfs-utils nfs-utils-lib portmap\n            fi\n\n            if test $(ps -o comm= 1) == 'systemd'; then\n              /bin/systemctl restart rpcbind nfs-server\n            else\n              /etc/init.d/rpcbind restart\n              /etc/init.d/nfs restart\n            fi\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/redhat/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestRedHat\n    module Cap\n      class RSync\n        def self.rsync_install(machine)\n          machine.communicate.sudo <<-EOH.gsub(/^ {12}/, '')\n            if command -v dnf; then\n              dnf -y install rsync\n            else\n              yum -y install rsync\n            fi\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/redhat/cap/smb.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestRedHat\n    module Cap\n      class SMB\n        def self.smb_install(machine)\n          comm = machine.communicate\n          if !comm.test(\"test -f /sbin/mount.cifs\")\n            comm.sudo <<-EOH.gsub(/^ {14}/, '')\n              if command -v dnf; then\n                dnf -y install cifs-utils\n              else\n                yum -y install cifs-utils\n              fi\n            EOH\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/redhat/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestRedHat\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"cat /etc/redhat-release\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/redhat/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestRedHat\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Red Hat Enterprise Linux guest\"\n      description \"Red Hat Enterprise Linux guest support.\"\n\n      guest(:redhat, :linux) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:redhat, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:redhat, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:redhat, :flavor) do\n        require_relative \"cap/flavor\"\n        Cap::Flavor\n      end\n\n      guest_capability(:redhat, :network_scripts_dir) do\n        require_relative \"cap/network_scripts_dir\"\n        Cap::NetworkScriptsDir\n      end\n\n      guest_capability(:redhat, :nfs_client_install) do\n        require_relative \"cap/nfs_client\"\n        Cap::NFSClient\n      end\n\n      guest_capability(:redhat, :rsync_install) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:redhat, :smb_install) do\n        require_relative \"cap/smb\"\n        Cap::SMB\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/rocky/cap/flavor.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestRocky\n    module Cap\n      class Flavor\n        def self.flavor(machine)\n          # Read the version file\n          version = \"\"\n          machine.communicate.sudo(\"source /etc/os-release && printf $VERSION_ID\") do |type, data|\n            if type == :stdout\n              version = data.split(\".\").first.to_i\n            end\n          end\n\n          if version.nil? || version < 1\n            :rocky\n          else\n            \"rocky_#{version}\".to_sym\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/rocky/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../linux/guest\"\n\nmodule VagrantPlugins\n  module GuestRocky\n    class Guest < VagrantPlugins::GuestLinux::Guest\n      # Name used for guest detection\n      GUEST_DETECTION_NAME = \"rocky\".freeze\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/rocky/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestRocky\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Rocky guest\"\n      description \"Rocky guest support.\"\n\n      guest(:rocky, :redhat) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:rocky, :flavor) do\n        require_relative \"cap/flavor\"\n        Cap::Flavor\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/slackware/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../../linux/cap/change_host_name'\n\nmodule VagrantPlugins\n  module GuestSlackware\n    module Cap\n      class ChangeHostName\n        extend VagrantPlugins::GuestLinux::Cap::ChangeHostName\n\n        def self.change_name_command(name)\n          return <<-EOH.gsub(/^ {14}/, \"\")\n          # Set the hostname\n          chmod o+w /etc/hostname\n          echo '#{name}' > /etc/hostname\n          chmod o-w /etc/hostname\n          hostname -F /etc/hostname\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/slackware/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestSlackware\n    module Cap\n      class ConfigureNetworks\n        include Vagrant::Util\n\n        def self.configure_networks(machine, networks)\n          comm = machine.communicate\n\n          commands   = []\n          interfaces = machine.guest.capability(:network_interfaces)\n\n          # Remove any previous configuration\n          commands << \"sed -i'' -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' /etc/rc.d/rc.inet1.conf\"\n\n          networks.each.with_index do |network, i|\n            network[:device] = interfaces[network[:interface]]\n\n            entry = TemplateRenderer.render(\"guests/slackware/network_#{network[:type]}\",\n              i: i+1,\n              options: network,\n            )\n\n            remote_path = \"/tmp/vagrant-network-#{network[:device]}-#{Time.now}-#{i}\"\n            Tempfile.open(\"vagrant-slackware-configure-networks\") do |f|\n              f.binmode\n              f.write(entry)\n              f.fsync\n              f.close\n              comm.upload(f.path, remote_path)\n            end\n\n            commands << \"cat '#{remote_path}' >> /etc/rc.d/rc.inet1.conf\"\n          end\n\n          # Restart networking\n          commands << \"/etc/rc.d/rc.inet1\"\n\n          comm.sudo(commands.join(\"\\n\"))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/slackware/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSlackware\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"cat /etc/slackware-version\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/slackware/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestSlackware\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Slackware guest\"\n      description \"Slackware guest support.\"\n\n      guest(:slackware, :linux) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:slackware, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:slackware, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/smartos/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSmartos\n    module Cap\n      class ChangeHostName\n        def self.change_host_name(machine, name)\n          sudo = machine.config.smartos.suexec_cmd\n\n          machine.communicate.tap do |comm|\n            comm.execute <<-EOH.sub(/^ */, '')\n              if hostname | grep '#{name}' ; then\n                exit 0\n              fi\n\n              if [ -d /usbkey ] && [ \"$(zonename)\" == \"global\" ] ; then\n                #{sudo} sed -i '' 's/hostname=.*/hostname=#{name}/' /usbkey/config\n              fi\n\n              #{sudo} echo '#{name}' > /etc/nodename\n              #{sudo} hostname #{name}\n            EOH\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/smartos/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSmartos\n    module Cap\n      class ConfigureNetworks\n        def self.configure_networks(machine, networks)\n          su_cmd = machine.config.smartos.suexec_cmd\n\n          networks.each do |network|\n            device = \"#{machine.config.smartos.device}#{network[:interface]}\"\n            ifconfig_cmd = \"#{su_cmd} /sbin/ifconfig #{device}\"\n\n            machine.communicate.execute(\"#{ifconfig_cmd} plumb\")\n\n            if network[:type].to_sym == :static\n              machine.communicate.execute(\"#{ifconfig_cmd} inet #{network[:ip]} netmask #{network[:netmask]}\")\n              machine.communicate.execute(\"#{ifconfig_cmd} up\")\n              machine.communicate.execute(\"#{su_cmd} sh -c \\\"echo '#{network[:ip]}' > /etc/hostname.#{device}\\\"\")\n            elsif network[:type].to_sym == :dhcp\n              machine.communicate.execute(\"#{ifconfig_cmd} dhcp start\")\n            end\n          end\n        end\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "plugins/guests/smartos/cap/halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSmartos\n    module Cap\n      class Halt\n        def self.halt(machine)\n          # There should be an exception raised if the line\n          #\n          #     vagrant::::profiles=Primary Administrator\n          #\n          # does not exist in /etc/user_attr. TODO\n          begin\n            machine.communicate.execute(\n              \"#{machine.config.smartos.suexec_cmd} /usr/sbin/poweroff\")\n          rescue IOError, Vagrant::Errors::SSHDisconnected\n            # Ignore, this probably means connection closed because it\n            # shut down.\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/smartos/cap/insert_public_key.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/shell_quote\"\n\nmodule VagrantPlugins\n  module GuestSmartos\n    module Cap\n      class InsertPublicKey\n        def self.insert_public_key(machine, contents)\n          contents = contents.chomp\n          contents = Vagrant::Util::ShellQuote.escape(contents, \"'\")\n\n          machine.communicate.tap do |comm|\n            comm.execute <<-EOH.sub(/^ */, '')\n              if [ -d /usbkey ] && [ \"$(zonename)\" == \"global\" ] ; then\n                printf '#{contents}\\\\n' >> /usbkey/config.inc/authorized_keys\n                cp /usbkey/config.inc/authorized_keys ~/.ssh/authorized_keys\n              else\n                mkdir -p ~/.ssh\n                chmod 0700 ~/.ssh\n                printf '#{contents}\\\\n' >> ~/.ssh/authorized_keys\n                chmod 0600 ~/.ssh/authorized_keys\n              fi\n            EOH\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/smartos/cap/mount_nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSmartos\n    module Cap\n      class MountNFS\n        def self.mount_nfs_folder(machine, ip, folders)\n          sudo = machine.config.smartos.suexec_cmd\n\n          folders.each do |name, opts|\n            machine.communicate.tap do |comm|\n              nfsDescription = \"#{ip}:#{opts[:hostpath]}:#{opts[:guestpath]}\"\n\n              comm.execute <<-EOH.sub(/^ */, '')\n                if [ -d /usbkey ] && [ \"$(zonename)\" == \"global\" ] ; then\n                  #{sudo} mkdir -p /usbkey/config.inc\n                  printf '#{nfsDescription}\\\\n' | #{sudo} tee -a /usbkey/config.inc/nfs_mounts\n                fi\n\n                #{sudo} mkdir -p #{opts[:guestpath]}\n                #{sudo} /usr/sbin/mount -F nfs '#{ip}:#{opts[:hostpath]}' '#{opts[:guestpath]}'\n              EOH\n            end\n          end\n        end\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "plugins/guests/smartos/cap/remove_public_key.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/shell_quote\"\n\nmodule VagrantPlugins\n  module GuestSmartos\n    module Cap\n      class RemovePublicKey\n        def self.remove_public_key(machine, contents)\n          contents = contents.chomp\n          contents = Vagrant::Util::ShellQuote.escape(contents, \"'\")\n\n          machine.communicate.tap do |comm|\n            comm.execute <<-EOH.sub(/^ */, '')\n              if test -f /usbkey/config.inc/authorized_keys ; then\n                sed -i '' '/^.*#{contents}.*$/d' /usbkey/config.inc/authorized_keys\n              fi\n              if test -f ~/.ssh/authorized_keys ; then\n                sed -i '' '/^.*#{contents}.*$/d' ~/.ssh/authorized_keys\n              fi\n            EOH\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/smartos/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../synced_folders/rsync/default_unix_cap\"\n\nmodule VagrantPlugins\n  module GuestSmartos\n    module Cap\n      class RSync\n        extend VagrantPlugins::SyncedFolderRSync::DefaultUnixCap\n\n        def self.rsync_installed(machine)\n          machine.communicate.test(\"which rsync\")\n        end\n\n        def self.rsync_command(machine)\n          \"#{machine.config.smartos.suexec_cmd} rsync\"\n        end\n\n        def self.rsync_pre(machine, opts)\n          machine.communicate.tap do |comm|\n            comm.execute(\"#{machine.config.smartos.suexec_cmd} mkdir -p '#{opts[:guestpath]}'\")\n          end\n        end\n\n        def self.rsync_post(machine, opts)\n          if opts.key?(:chown) && !opts[:chown]\n            return\n          end\n          suexec_cmd = machine.config.smartos.suexec_cmd\n          machine.communicate.execute(\"#{suexec_cmd} #{build_rsync_chown(opts)}\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/smartos/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSmartos\n    class Config < Vagrant.plugin(\"2\", :config)\n      # This sets the command to use to execute items as a superuser.\n      # @default sudo\n      attr_accessor :suexec_cmd\n      attr_accessor :device\n\n      def initialize\n        @suexec_cmd = 'pfexec'\n        @device     = \"e1000g\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/smartos/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestSmartos\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"cat /etc/release | grep -i SmartOS\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/smartos/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestSmartos\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"SmartOS guest.\"\n      description \"SmartOS guest support.\"\n\n      config(:smartos) do\n        require_relative \"config\"\n        Config\n      end\n\n      guest(:smartos, :solaris)  do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:smartos, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:smartos, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:smartos, :halt) do\n        require_relative \"cap/halt\"\n        Cap::Halt\n      end\n\n      guest_capability(:smartos, :insert_public_key) do\n        require_relative \"cap/insert_public_key\"\n        Cap::InsertPublicKey\n      end\n\n      guest_capability(:smartos, :mount_nfs_folder) do\n        require_relative \"cap/mount_nfs\"\n        Cap::MountNFS\n      end\n\n      guest_capability(:smartos, :remove_public_key) do\n        require_relative \"cap/remove_public_key\"\n        Cap::RemovePublicKey\n      end\n\n      guest_capability(:smartos, :rsync_installed) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:smartos, :rsync_command) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:smartos, :rsync_post) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:smartos, :rsync_pre) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "plugins/guests/solaris/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSolaris\n    module Cap\n      class ChangeHostName\n        def self.change_host_name(machine, name)\n          su_cmd = machine.config.solaris.suexec_cmd\n\n          # Only do this if the hostname is not already set\n          if !machine.communicate.test(\"#{su_cmd} hostname | grep '#{name}'\")\n            machine.communicate.execute(\"#{su_cmd} sh -c \\\"echo '#{name}' > /etc/nodename\\\"\")\n            machine.communicate.execute(\"#{su_cmd} uname -S #{name}\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSolaris\n    module Cap\n      class ConfigureNetworks\n        def self.configure_networks(machine, networks)\n          networks.each do |network|\n            device = \"#{machine.config.solaris.device}#{network[:interface]}\"\n            su_cmd = machine.config.solaris.suexec_cmd\n            ifconfig_cmd = \"#{su_cmd} /sbin/ifconfig #{device}\"\n\n            machine.communicate.execute(\"#{ifconfig_cmd} plumb\")\n\n            if network[:type].to_sym == :static\n              machine.communicate.execute(\"#{ifconfig_cmd} inet #{network[:ip]} netmask #{network[:netmask]}\")\n              machine.communicate.execute(\"#{ifconfig_cmd} up\")\n              machine.communicate.execute(\"#{su_cmd} sh -c \\\"echo '#{network[:ip]}' > /etc/hostname.#{device}\\\"\")\n            elsif network[:type].to_sym == :dhcp\n              machine.communicate.execute(\"#{ifconfig_cmd} dhcp start\")\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris/cap/file_system.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSolaris\n    module Cap\n      class FileSystem\n        # Create a temporary file or directory on the guest\n        #\n        # @param [Vagrant::Machine] machine Vagrant guest machine\n        # @param [Hash] opts Path options\n        # @return [String] path to temporary file or directory\n        def self.create_tmp_path(machine, opts)\n          template = \"vagrant-XXXXXX\"\n          cmd = [\"mktemp\"]\n          if opts[:type] == :directory\n            cmd << \"-d\"\n          end\n          cmd << \"-t\"\n          cmd << template\n          tmp_path = \"\"\n          machine.communicate.execute(cmd.join(\" \")) do |type, data|\n            if type == :stdout\n              tmp_path << data\n            end\n          end\n          tmp_path.strip\n        end\n\n        # Decompress tgz file on guest to given location\n        #\n        # @param [Vagrant::Machine] machine Vagrant guest machine\n        # @param [String] compressed_file Path to compressed file on guest\n        # @param [String] destination Path for decompressed files on guest\n        def self.decompress_tgz(machine, compressed_file, destination, opts={})\n          comm = machine.communicate\n          extract_dir = create_tmp_path(machine, type: :directory)\n          cmds = []\n          if opts[:type] == :directory\n            cmds << \"mkdir -p '#{destination}'\"\n          else\n            cmds << \"mkdir -p '#{File.dirname(destination)}'\"\n          end\n          cmds += [\n            \"tar xzf '#{compressed_file}' -C '#{extract_dir}'\",\n            \"mv '#{extract_dir}'/* '#{destination}'\",\n            \"rm -f '#{compressed_file}'\",\n            \"rm -rf '#{extract_dir}'\"\n          ]\n          cmds.each{ |cmd| comm.execute(cmd) }\n          true\n        end\n\n        # Decompress zip file on guest to given location\n        #\n        # @param [Vagrant::Machine] machine Vagrant guest machine\n        # @param [String] compressed_file Path to compressed file on guest\n        # @param [String] destination Path for decompressed files on guest\n        def self.decompress_zip(machine, compressed_file, destination, opts={})\n          comm = machine.communicate\n          extract_dir = create_tmp_path(machine, type: :directory)\n          cmds = []\n          if opts[:type] == :directory\n            cmds << \"mkdir -p '#{destination}'\"\n          else\n            cmds << \"mkdir -p '#{File.dirname(destination)}'\"\n          end\n          cmds += [\n            \"unzip '#{compressed_file}' -d '#{extract_dir}'\",\n            \"mv '#{extract_dir}'/* '#{destination}'\",\n            \"rm -f '#{compressed_file}'\",\n            \"rm -rf '#{extract_dir}'\"\n          ]\n          cmds.each{ |cmd| comm.execute(cmd) }\n          true\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris/cap/halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSolaris\n    module Cap\n      class Halt\n        def self.halt(machine)\n          # There should be an exception raised if the line\n          #\n          #     vagrant::::profiles=Primary Administrator\n          #\n          # does not exist in /etc/user_attr. TODO\n          begin\n            machine.communicate.execute(\n              \"#{machine.config.solaris.suexec_cmd} /usr/sbin/shutdown -y -i5 -g0\")\n          rescue IOError, Vagrant::Errors::SSHDisconnected\n            # Ignore, this probably means connection closed because it\n            # shut down.\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris/cap/insert_public_key.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/shell_quote\"\n\nmodule VagrantPlugins\n  module GuestSolaris\n    module Cap\n      class InsertPublicKey\n        def self.insert_public_key(machine, contents)\n          # TODO: Code is identical to linux/cap/insert_public_key\n          contents = contents.chomp\n          contents = Vagrant::Util::ShellQuote.escape(contents, \"'\")\n\n          machine.communicate.tap do |comm|\n            comm.execute(\"mkdir -p ~/.ssh\")\n            comm.execute(\"chmod 0700 ~/.ssh\")\n            comm.execute(\"printf '#{contents}\\\\n' >> ~/.ssh/authorized_keys\")\n            comm.execute(\"chmod 0600 ~/.ssh/authorized_keys\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris/cap/mount_virtualbox_shared_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSolaris\n    module Cap\n      class MountVirtualBoxSharedFolder\n        def self.mount_virtualbox_shared_folder(machine, name, guestpath, options)\n          # These are just far easier to use than the full options syntax\n          owner = options[:owner]\n          group = options[:group]\n\n          # Create the shared folder\n          machine.communicate.execute(\"#{machine.config.solaris.suexec_cmd} mkdir -p #{guestpath}\")\n\n          if owner.is_a? Integer\n            mount_uid = owner\n          else\n            # We have to use this `id` command instead of `/usr/bin/id` since this\n            # one accepts the \"-u\" and \"-g\" flags.\n            mount_uid = \"`/usr/xpg4/bin/id -u #{owner}`\"\n          end\n\n          if group.is_a? Integer\n            mount_gid = group\n          else\n            mount_gid = \"`/usr/xpg4/bin/id -g #{group}`\"\n          end\n\n          # Mount the folder with the proper owner/group\n          mount_options = \"-o uid=#{mount_uid},gid=#{mount_gid}\"\n          if options[:mount_options]\n            mount_options += \",#{options[:mount_options].join(\",\")}\"\n          end\n\n          machine.communicate.execute(\"#{machine.config.solaris.suexec_cmd} /sbin/mount -F vboxfs #{mount_options} #{name} #{guestpath}\")\n\n          # chown the folder to the proper owner/group\n          machine.communicate.execute(\"#{machine.config.solaris.suexec_cmd} chown #{mount_uid}:#{mount_gid} #{guestpath}\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris/cap/remove_public_key.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/shell_quote\"\n\nmodule VagrantPlugins\n  module GuestSolaris\n    module Cap\n      class RemovePublicKey\n        def self.remove_public_key(machine, contents)\n          # \"sed -i\" is specific to GNU sed and is not a posix standard option\n          contents = contents.chomp\n          contents = Vagrant::Util::ShellQuote.escape(contents, \"'\")\n\n          machine.communicate.tap do |comm|\n            if comm.test(\"test -f ~/.ssh/authorized_keys\")\n              comm.execute(\n                \"cp ~/.ssh/authorized_keys ~/.ssh/authorized_keys.temp && sed '/^.*#{contents}.*$/d' ~/.ssh/authorized_keys.temp > ~/.ssh/authorized_keys && rm ~/.ssh/authorized_keys.temp\")\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../synced_folders/rsync/default_unix_cap\"\n\nmodule VagrantPlugins\n  module GuestSolaris\n    module Cap\n      class RSync\n        extend VagrantPlugins::SyncedFolderRSync::DefaultUnixCap\n\n        def self.rsync_installed(machine)\n          machine.communicate.test(\"which rsync\")\n        end\n\n        def self.rsync_command(machine)\n          \"#{machine.config.solaris.suexec_cmd} rsync\"\n        end\n\n        def self.rsync_pre(machine, opts)\n          machine.communicate.tap do |comm|\n            comm.sudo(\"mkdir -p '#{opts[:guestpath]}'\")\n          end\n        end\n\n        def self.rsync_post(machine, opts)\n          if opts.key?(:chown) && !opts[:chown]\n            return\n          end\n          suexec_cmd = machine.config.solaris.suexec_cmd\n          machine.communicate.execute(\"#{suexec_cmd} #{build_rsync_chown(opts)}\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSolaris\n    class Config < Vagrant.plugin(\"2\", :config)\n      attr_accessor :halt_timeout\n      attr_accessor :halt_check_interval\n\n      attr_accessor :suexec_cmd\n      attr_accessor :device\n\n      def initialize\n        @halt_timeout = UNSET_VALUE\n        @halt_check_interval = UNSET_VALUE\n        @suexec_cmd = UNSET_VALUE\n        @device = UNSET_VALUE\n      end\n\n      def finalize!\n        if @halt_timeout != UNSET_VALUE\n          puts \"solaris.halt_timeout is deprecated and will be removed in Vagrant 1.7\"\n        end\n\n        if @halt_check_interval != UNSET_VALUE\n          puts \"solaris.halt_check_interval is deprecated and will be removed in Vagrant 1.7\"\n        end\n\n        @suexec_cmd = \"sudo\" if @suexec_cmd == UNSET_VALUE\n        @device     = \"e1000g\" if @device == UNSET_VALUE\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestSolaris\n    # A general Vagrant system implementation for \"solaris\".\n    #\n    # Contributed by Blake Irvin <b.irvin@modcloth.com>\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"uname -sr | grep SunOS | grep -v 5.11\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestSolaris\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Solaris guest.\"\n      description \"Solaris guest support.\"\n\n      config(:solaris) do\n        require_relative \"config\"\n        Config\n      end\n\n      guest(:solaris)  do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:solaris, :create_tmp_path) do\n        require_relative \"cap/file_system\"\n        Cap::FileSystem\n      end\n\n      guest_capability(:solaris, :decompress_tgz) do\n        require_relative \"cap/file_system\"\n        Cap::FileSystem\n      end\n\n      guest_capability(:solaris, :decompress_zip) do\n        require_relative \"cap/file_system\"\n        Cap::FileSystem\n      end\n\n      guest_capability(:solaris, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:solaris, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:solaris, :insert_public_key) do\n        require_relative \"cap/insert_public_key\"\n        Cap::InsertPublicKey\n      end\n\n      guest_capability(:solaris, :halt) do\n        require_relative \"cap/halt\"\n        Cap::Halt\n      end\n\n      guest_capability(:solaris, :mount_virtualbox_shared_folder) do\n        require_relative \"cap/mount_virtualbox_shared_folder\"\n        Cap::MountVirtualBoxSharedFolder\n      end\n\n      guest_capability(:solaris, :remove_public_key) do\n        require_relative \"cap/remove_public_key\"\n        Cap::RemovePublicKey\n      end\n\n      guest_capability(:solaris, :rsync_installed) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:solaris, :rsync_command) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:solaris, :rsync_post) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:solaris, :rsync_pre) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris11/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# A general Vagrant system implementation for \"solaris 11\".\n#\n# Contributed by Jan Thomas Moldung <janth@moldung.no>\n\nmodule VagrantPlugins\n  module GuestSolaris11\n    module Cap\n      class ChangeHostName\n        def self.change_host_name(machine, name)\n          su_cmd = machine.config.solaris11.suexec_cmd\n\n          # Only do this if the hostname is not already set\n          if !machine.communicate.test(\"/usr/sbin/svccfg -s system/identity:node listprop config/nodename | /usr/bin/grep '#{name}'\")\n            machine.communicate.execute(\"#{su_cmd} /usr/sbin/svccfg -s system/identity:node setprop config/nodename=\\\"#{name}\\\"\")\n            machine.communicate.execute(\"#{su_cmd} /usr/sbin/svccfg -s system/identity:node setprop config/loopback=\\\"#{name}\\\"\")\n            machine.communicate.execute(\"#{su_cmd} /usr/sbin/svccfg -s system/identity:node refresh \")\n            machine.communicate.execute(\"#{su_cmd} /usr/sbin/svcadm restart system/identity:node \")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris11/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# A general Vagrant system implementation for \"solaris 11\".\n#\n# Contributed by Jan Thomas Moldung <janth@moldung.no>\n\nmodule VagrantPlugins\n  module GuestSolaris11\n    module Cap\n      class ConfigureNetworks\n        def self.configure_networks(machine, networks)\n          networks.each do |network|\n            device = \"#{machine.config.solaris11.device}#{network[:interface]}\"\n            su_cmd = machine.config.solaris11.suexec_cmd\n            mask = \"#{network[:netmask]}\"\n            cidr = mask.split(\".\").map { |e| e.to_i.to_s(2).rjust(8, \"0\") }.join.count(\"1\").to_s\n\n            if network[:type].to_sym == :static\n              unless machine.communicate.test(\"ipadm show-if #{device}\")\n                machine.communicate.execute(\"#{su_cmd} ipadm create-ip #{device}\")\n              end\n              if machine.communicate.test(\"ipadm | grep #{device}/v4\")\n                machine.communicate.execute(\"#{su_cmd} ipadm delete-addr #{device}/v4\")\n              end\n              machine.communicate.execute(\"#{su_cmd} ipadm create-addr -T static -a #{network[:ip]}/#{cidr} #{device}/v4\")\n            elsif network[:type].to_sym == :dhcp\n              if machine.communicate.test(\"ipadm show-if -o all | grep #{device} | tr -s ' ' | cut -d ' ' -f 6  | grep '4\\|6'\")\n                machine.communicate.execute(\"#{su_cmd} ipadm create-addr -T addrconf #{device}/v4\")\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris11/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# A general Vagrant system implementation for \"solaris 11\".\n#\n# Contributed by Jan Thomas Moldung <janth@moldung.no>\n\nmodule VagrantPlugins\n  module GuestSolaris11\n    class Config < Vagrant.plugin(\"2\", :config)\n      attr_accessor :halt_timeout\n      attr_accessor :halt_check_interval\n      # This sets the command to use to execute items as a superuser. sudo is default\n      attr_accessor :suexec_cmd\n      attr_accessor :device\n\n      def initialize\n        @halt_timeout = UNSET_VALUE\n        @halt_check_interval = UNSET_VALUE\n        @suexec_cmd = UNSET_VALUE\n        @device = UNSET_VALUE\n      end\n\n      def finalize!\n        if @halt_timeout != UNSET_VALUE\n          puts \"solaris11.halt_timeout is deprecated and will be removed in Vagrant 1.7\"\n        end\n        if @halt_check_interval != UNSET_VALUE\n          puts \"solaris11.halt_check_interval is deprecated and will be removed in Vagrant 1.7\"\n        end\n\n        @suexec_cmd = \"sudo\" if @suexec_cmd == UNSET_VALUE\n        @device     = \"net\" if @device == UNSET_VALUE\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris11/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# A general Vagrant system implementation for \"solaris 11\".\n#\n# Contributed by Jan Thomas Moldung <janth@moldung.no>\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestSolaris11\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        success = machine.communicate.test(\"grep 'Solaris 11' /etc/release\")\n        return success if success\n\n        # for solaris derived guests like openindiana\n        machine.communicate.test(\"uname -sr | grep 'SunOS 5.11'\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/solaris11/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# A general Vagrant system implementation for \"solaris 11\".\n#\n# Contributed by Jan Thomas Moldung <janth@moldung.no>\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestSolaris11\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Solaris 11 guest.\"\n      description \"Solaris 11 guest support.\"\n\n      guest(:solaris11, :solaris) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      config(:solaris11) do\n        require_relative \"config\"\n        Config\n      end\n\n      guest_capability(:solaris11, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:solaris11, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:solaris11, :shell_expand_guest_path) do\n        require_relative \"../linux/cap/shell_expand_guest_path\"\n        VagrantPlugins::GuestLinux::Cap::ShellExpandGuestPath\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/suse/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/guest_hosts'\nrequire 'vagrant/util/guest_inspection'\n\nmodule VagrantPlugins\n  module GuestSUSE\n    module Cap\n      class ChangeHostName\n\n        extend Vagrant::Util::GuestInspection::Linux\n        extend Vagrant::Util::GuestHosts::Linux\n\n        def self.change_host_name(machine, name)\n          comm = machine.communicate\n          basename = name.split(\".\", 2)[0]\n\n          network_with_hostname = machine.config.vm.networks.map {|_, c| c if c[:hostname] }.compact[0]\n          if network_with_hostname\n            replace_host(comm, name, network_with_hostname[:ip])\n          else\n            add_hostname_to_loopback_interface(comm, name)\n          end\n\n          if hostnamectl?(comm)\n            if !comm.test(\"test \\\"$(hostnamectl --static status)\\\" = \\\"#{basename}\\\"\", sudo: false)\n              cmd = <<-EOH.gsub(/^ {14}/, \"\")\n              hostnamectl set-hostname '#{basename}'\n              echo #{name} > /etc/HOSTNAME\n              EOH\n              comm.sudo(cmd)\n            end\n          else\n            if !comm.test(\"hostname -f | grep '^#{name}$'\", sudo: false)\n              cmd = <<-EOH.gsub(/^ {14}/, \"\")\n              echo #{name} > /etc/HOSTNAME\n              hostname '#{basename}'\n              EOH\n              comm.sudo(cmd)\n            end\n          end\n\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/suse/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nmodule VagrantPlugins\n  module GuestSUSE\n    module Cap\n      class ConfigureNetworks\n        extend Vagrant::Util::Retryable\n        include Vagrant::Util\n\n        def self.configure_networks(machine, networks)\n          comm = machine.communicate\n\n          network_scripts_dir = machine.guest.capability(:network_scripts_dir)\n\n          commands   = []\n          interfaces = machine.guest.capability(:network_interfaces)\n\n          networks.each.with_index do |network, i|\n            network[:device] = interfaces[network[:interface]]\n\n            entry = TemplateRenderer.render(\"guests/suse/network_#{network[:type]}\",\n              options: network,\n            )\n\n            remote_path = \"/tmp/vagrant-network-#{network[:device]}-#{Time.now.to_i}-#{i}\"\n\n            Tempfile.open(\"vagrant-suse-configure-networks\") do |f|\n              f.binmode\n              f.write(entry)\n              f.fsync\n              f.close\n              comm.upload(f.path, remote_path)\n            end\n\n            local_path = \"#{network_scripts_dir}/ifcfg-#{network[:device]}\"\n            commands << <<-EOH.gsub(/^ {14}/, '')\n              /sbin/ifdown '#{network[:device]}' || true\n              mv '#{remote_path}' '#{local_path}'\n              /sbin/ifup '#{network[:device]}'\n            EOH\n          end\n\n          comm.sudo(commands.join(\"\\n\"))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/suse/cap/halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSUSE\n    module Cap\n      class Halt\n        def self.halt(machine)\n          begin\n            if machine.communicate.test(\"test -e /usr/bin/systemctl\")\n              machine.communicate.sudo(\"/usr/bin/systemctl poweroff &\")\n            else\n              machine.communicate.sudo(\"/sbin/shutdown -h now &\")\n            end\n          rescue IOError, Vagrant::Errors::SSHDisconnected\n            # Do nothing, because it probably means the machine shut down\n            # and SSH connection was lost.\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/suse/cap/network_scripts_dir.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSUSE\n    module Cap\n      class NetworkScriptsDir\n        def self.network_scripts_dir(machine)\n          \"/etc/sysconfig/network\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/suse/cap/nfs_client.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSUSE\n    module Cap\n      class NFSClient\n        def self.nfs_client_install(machine)\n          machine.communicate.sudo <<-EOH.gsub(/^ {12}/, '')\n            zypper -n install nfs-client\n            /usr/bin/systemctl restart rpcbind\n            /usr/bin/systemctl restart nfs-client.target\n          EOH\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/suse/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSUSE\n    module Cap\n      class RSync\n        def self.rsync_installed(machine)\n          machine.communicate.test(\"test -f /usr/bin/rsync\")\n        end\n\n        def self.rsync_install(machine)\n          machine.communicate.sudo(\"zypper -n install rsync\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/suse/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestSUSE\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"test -f /etc/SuSE-release || grep -q SUSE /etc/os-release\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/suse/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestSUSE\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"SUSE guest\"\n      description \"SUSE guest support.\"\n\n      guest(:suse, :linux) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:suse, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:suse, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:suse, :halt) do\n        require_relative \"cap/halt\"\n        Cap::Halt\n      end\n\n      guest_capability(:suse, :network_scripts_dir) do\n        require_relative \"cap/network_scripts_dir\"\n        Cap::NetworkScriptsDir\n      end\n\n      guest_capability(:suse, :nfs_client_install) do\n        require_relative \"cap/nfs_client\"\n        Cap::NFSClient\n      end\n\n      guest_capability(:suse, :rsync_install) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:suse, :rsync_installed) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/tinycore/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestTinyCore\n    module Cap\n      class ChangeHostName\n        def self.change_host_name(machine, name)\n          if !machine.communicate.test(\"hostname | grep '^#{name}$'\")\n            machine.communicate.sudo(\"/usr/bin/sethostname #{name}\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/tinycore/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"ipaddr\"\n\nmodule VagrantPlugins\n  module GuestTinyCore\n    module Cap\n      class ConfigureNetworks\n        def self.configure_networks(machine, networks)\n          machine.communicate.tap do |comm|\n            networks.each do |n|\n              if n[:type] == :dhcp\n                comm.sudo(\"/sbin/udhcpc -b -i eth#{n[:interface]} -p /var/run/udhcpc.eth#{n[:interface]}.pid\")\n                return\n              end\n\n              ifc = \"/sbin/ifconfig eth#{n[:interface]}\"\n              broadcast = (IPAddr.new(n[:ip]) | (~ IPAddr.new(n[:netmask]))).to_s\n              comm.sudo(\"#{ifc} down\")\n              comm.sudo(\"#{ifc} #{n[:ip]} netmask #{n[:netmask]} broadcast #{broadcast}\")\n              comm.sudo(\"#{ifc} up\")\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/tinycore/cap/halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestTinyCore\n    module Cap\n      class Halt\n        def self.halt(machine)\n          begin\n            machine.communicate.sudo(\"poweroff\")\n          rescue IOError, Vagrant::Errors::SSHDisconnected\n            # Do nothing, because it probably means the machine shut down\n            # and SSH connection was lost.\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/tinycore/cap/mount_nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../synced_folders/unix_mount_helpers\"\n\nmodule VagrantPlugins\n  module GuestTinyCore\n    module Cap\n      class MountNFS\n        extend SyncedFolder::UnixMountHelpers\n\n        def self.mount_nfs_folder(machine, ip, folders)\n          folders.each do |name, opts|\n            # Expand the guest path so we can handle things like \"~/vagrant\"\n            expanded_guest_path = machine.guest.capability(\n              :shell_expand_guest_path, opts[:guestpath])\n\n            # Do the actual creating and mounting\n            machine.communicate.sudo(\"mkdir -p #{expanded_guest_path}\")\n\n            # Mount\n            hostpath = opts[:hostpath].dup\n            hostpath.gsub!(\"'\", \"'\\\\\\\\''\")\n\n            # Figure out any options\n            mount_opts = [\"vers=#{opts[:nfs_version]}\"]\n            mount_opts << \"udp\" if opts[:nfs_udp]\n            if opts[:mount_options]\n              mount_opts = opts[:mount_options].dup\n            end\n\n            mount_command = \"mount.nfs -o '#{mount_opts.join(\",\")}' #{ip}:'#{hostpath}' #{expanded_guest_path}\"\n            retryable(on: Vagrant::Errors::NFSMountFailed, tries: 8, sleep: 3) do\n              machine.communicate.sudo(mount_command,\n                                       error_class: Vagrant::Errors::NFSMountFailed)\n            end\n\n            emit_upstart_notification(machine, expanded_guest_path)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/tinycore/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestTinyCore\n    module Cap\n      class RSync\n        def self.rsync_install(machine)\n          machine.communicate.tap do |comm|\n            # Run it but don't error check because this is always failing currently\n            comm.execute(\"tce-load -wi acl attr rsync\", error_check: false)\n\n            # Verify it by executing rsync\n            comm.execute(\"rsync --help\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/tinycore/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../linux/guest'\n\nmodule VagrantPlugins\n  module GuestTinyCore\n    class Guest < VagrantPlugins::GuestLinux::Guest\n      # Name used for guest detection\n      GUEST_DETECTION_NAME = \"Core Linux\".freeze\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/tinycore/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestTinyCore\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"TinyCore Linux guest.\"\n      description \"TinyCore Linux guest support.\"\n\n      guest(:tinycore, :linux) do\n        require_relative \"guest\"\n        Guest\n      end\n\n      guest_capability(:tinycore, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:tinycore, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:tinycore, :halt) do\n        require_relative \"cap/halt\"\n        Cap::Halt\n      end\n\n      guest_capability(:tinycore, :rsync_install) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:tinycore, :mount_nfs_folder) do\n        require_relative \"cap/mount_nfs\"\n        Cap::MountNFS\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/trisquel/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestTrisquel\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        machine.communicate.test(\"[ -x /usr/bin/lsb_release ] && /usr/bin/lsb_release -i 2>/dev/null | grep Trisquel\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/trisquel/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestTrisquel\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Trisquel guest\"\n      description \"Trisquel guest support.\"\n\n      guest(:trisquel, :ubuntu) do\n        require_relative \"guest\"\n        Guest\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/ubuntu/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../linux/guest'\n\nmodule VagrantPlugins\n  module GuestUbuntu\n    class Guest < VagrantPlugins::GuestLinux::Guest\n      # Name used for guest detection\n      GUEST_DETECTION_NAME = \"ubuntu\".freeze\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/ubuntu/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestUbuntu\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Ubuntu guest\"\n      description \"Ubuntu guest support.\"\n\n      guest(:ubuntu, :debian) do\n        require_relative \"guest\"\n        Guest\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/cap/change_host_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module GuestWindows\n    module Cap\n      module ChangeHostName\n        def self.change_host_name(machine, name)\n          change_host_name_and_wait(machine, name, machine.config.vm.graceful_halt_timeout)\n        end\n\n        def self.change_host_name_and_wait(machine, name, sleep_timeout)\n          # If the configured name matches the current name, then bail\n          # We cannot use %ComputerName% because it truncates at 15 chars\n          return if machine.communicate.test(\"if ([System.Net.Dns]::GetHostName() -eq '#{name}') { exit 0 } exit 1\")\n\n          # Rename and reboot host if rename succeeded\n          script = <<-EOH\n            $computer = Get-WmiObject -Class Win32_ComputerSystem\n            $retval = $computer.rename(\"#{name}\").returnvalue\n            exit $retval\n          EOH\n\n          machine.communicate.execute(\n            script,\n            error_class: Errors::RenameComputerFailed,\n            error_key: :rename_computer_failed)\n\n          machine.guest.capability(:reboot)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/cap/choose_addressable_ip_addr.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestWindows\n    module Cap\n      module ChooseAddressableIPAddr\n        def self.choose_addressable_ip_addr(machine, possible)\n          machine.communicate.tap do |comm|\n            possible.each do |ip|\n              command = \"ping -n 1 -w 1 #{ip}\"\n              if comm.test(command)\n                return ip\n              end\n            end\n          end\n\n          nil\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/cap/configure_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nrequire_relative \"../guest_network\"\n\nmodule VagrantPlugins\n  module GuestWindows\n    module Cap\n      module ConfigureNetworks\n        @@logger = Log4r::Logger.new(\"vagrant::guest::windows::configure_networks\")\n\n        def self.configure_networks(machine, networks)\n          @@logger.debug(\"Networks: #{networks.inspect}\")\n\n          guest_network = GuestNetwork.new(machine.communicate)\n          if machine.provider_name.to_s.start_with?(\"vmware\")\n            machine.ui.warn(\"Configuring secondary network adapters through VMware \")\n            machine.ui.warn(\"on Windows is not yet supported. You will need to manually\")\n            machine.ui.warn(\"configure the network adapter.\")\n          else\n            vm_interface_map = create_vm_interface_map(machine, guest_network)\n            networks.each do |network|\n              interface = vm_interface_map[network[:interface]+1]\n              if interface.nil?\n                @@logger.warn(\"Could not find interface for network #{network.inspect}\")\n                next\n              end\n\n              network_type = network[:type].to_sym\n              if network_type == :static\n                guest_network.configure_static_interface(\n                  interface[:index],\n                  interface[:net_connection_id],\n                  network[:ip],\n                  network[:netmask])\n              elsif network_type == :dhcp\n                guest_network.configure_dhcp_interface(\n                  interface[:index],\n                  interface[:net_connection_id])\n              else\n                raise \"#{network_type} network type is not supported, try static or dhcp\"\n              end\n            end\n          end\n\n          if machine.config.windows.set_work_network\n            guest_network.set_all_networks_to_work\n          end\n        end\n\n        def self.create_vm_interface_map(machine, guest_network)\n          if !machine.provider.capability?(:nic_mac_addresses)\n            raise Vagrant::Errors::CantReadMACAddresses,\n              provider: machine.provider_name.to_s\n          end\n\n          driver_mac_address = machine.provider.capability(:nic_mac_addresses).invert\n          @@logger.debug(\"mac addresses: #{driver_mac_address.inspect}\")\n\n          vm_interface_map = {}\n          guest_network.network_adapters.each do |nic|\n            @@logger.debug(\"nic: #{nic.inspect}\")\n            naked_mac = nic[:mac_address].gsub(':','')\n            # If the :net_connection_id entry is nil then it is probably a virtual connection\n            # and should be ignored.\n            if driver_mac_address[naked_mac] && !nic[:net_connection_id].nil?\n              vm_interface_map[driver_mac_address[naked_mac]] = {\n                net_connection_id: nic[:net_connection_id],\n                mac_address: naked_mac,\n                interface_index: nic[:interface_index],\n                index: nic[:index] }\n            end\n          end\n\n          @@logger.debug(\"vm_interface_map: #{vm_interface_map.inspect}\")\n          vm_interface_map\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/cap/file_system.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestWindows\n    module Cap\n      class FileSystem\n        # Create a temporary file or directory on the guest\n        #\n        # @param [Vagrant::Machine] machine Vagrant guest machine\n        # @param [Hash] opts Path options\n        # @return [String] path to temporary file or directory\n        def self.create_tmp_path(machine, opts)\n          comm = machine.communicate\n          path = \"\"\n          cmd = \"Write-Output ([System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), \" \\\n            \"[System.IO.Path]::GetRandomFileName())) | Out-String -Width 2048\"\n          comm.execute(cmd, shell: :powershell) do |type, data|\n            if type == :stdout\n              path << data\n            end\n          end\n          path.strip!\n          if opts[:extension]\n            path << opts[:extension].to_s\n          end\n          if opts[:type] == :directory\n            comm.execute(\"[System.IO.Directory]::CreateDirectory('#{path}')\")\n          end\n          path\n        end\n\n        # Decompress zip file on guest to given location\n        #\n        # @param [Vagrant::Machine] machine Vagrant guest machine\n        # @param [String] compressed_file Path to compressed file on guest\n        # @param [String] destination Path for decompressed files on guest\n        def self.decompress_zip(machine, compressed_file, destination, opts={})\n          comm = machine.communicate\n          extract_dir = create_tmp_path(machine, type: :directory)\n          cmds = []\n          destination = destination.tr(\"/\", \"\\\\\")\n          if opts[:type] == :directory\n            cmds << \"New-Item -ItemType Directory -Force -Path \\\"#{destination}\\\"\"\n          else\n            d_parts = destination.split(\"\\\\\")\n            d_parts.pop\n            parent_dir = d_parts.join(\"\\\\\") + \"\\\\\"\n            cmds << \"New-Item -ItemType Directory -Force -Path \\\"#{parent_dir}\\\"\"\n          end\n          cmd = \"$f = \\\"#{compressed_file}\\\"; $d = \\\"#{extract_dir}\\\"; \"\n          cmd << '$s = New-Object -ComObject \"Shell.Application\"; $z = $s.NameSpace($f); '\n          cmd << 'foreach($i in $z.items()){ $s.Namespace($d).copyhere($i); }'\n          cmds << cmd\n          cmds += [\n            \"Move-Item -Force -Path \\\"#{extract_dir}\\\\*\\\" -Destination \\\"#{destination}\\\\\\\"\",\n            \"Remove-Item -Path \\\"#{compressed_file}\\\" -Force\",\n            \"Remove-Item -Path \\\"#{extract_dir}\\\" -Recurse -Force\"\n          ]\n          cmds.each do |cmd|\n            comm.execute(cmd, shell: :powershell)\n          end\n          true\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/cap/halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestWindows\n    module Cap\n      module Halt\n        def self.halt(machine)\n          # Fix vagrant-windows GH-129, if there's an existing scheduled\n          # reboot cancel it so shutdown succeeds\n          machine.communicate.execute(\"shutdown -a\", error_check: false)\n\n          # Force shutdown the machine now\n          machine.communicate.execute(\"shutdown /s /t 1 /c \\\"Vagrant Halt\\\" /f /d p:4:1\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/cap/mount_shared_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/template_renderer\"\nrequire \"base64\"\n\nmodule VagrantPlugins\n  module GuestWindows\n    module Cap\n      class MountSharedFolder\n        def self.mount_virtualbox_shared_folder(machine, name, guestpath, options)\n          mount_shared_folder(machine, name, guestpath, \"\\\\\\\\vboxsvr\\\\\")\n        end\n\n        def self.mount_vmware_shared_folder(machine, name, guestpath, options)\n          mount_shared_folder(machine, name, guestpath, \"\\\\\\\\vmware-host\\\\Shared Folders\\\\\")\n        end\n\n        def self.mount_parallels_shared_folder(machine, name, guestpath, options)\n          mount_shared_folder(machine, name, guestpath, \"\\\\\\\\psf\\\\\")\n        end\n\n        def self.mount_smb_shared_folder(machine, name, guestpath, options)\n          if !options[:smb_password].to_s.empty?\n            # Ensure password is scrubbed\n            Vagrant::Util::CredentialScrubber.sensitive(options[:smb_password])\n          end\n          machine.communicate.execute(\"cmdkey /add:#{options[:smb_host]} /user:#{options[:smb_username]} /pass:\\\"#{options[:smb_password]}\\\"\", {shell: :powershell, elevated: true})\n          mount_shared_folder(machine, name, guestpath, \"\\\\\\\\#{options[:smb_host]}\\\\\")\n        end\n\n        protected\n\n        def self.mount_shared_folder(machine, name, guestpath, vm_provider_unc_base)\n          name = name.gsub(/[\\/\\/]/,'_').sub(/^_/, '')\n\n          path = File.expand_path(\"../../scripts/mount_volume.ps1\", __FILE__)\n          script = Vagrant::Util::TemplateRenderer.render(path, options: {\n            mount_point: guestpath,\n            share_name: name,\n            vm_provider_unc_path: vm_provider_unc_base + name,\n          })\n\n          if machine.config.vm.communicator == :winrm || machine.config.vm.communicator == :winssh\n            machine.communicate.execute(script, shell: :powershell)\n          else\n            # Convert script to double byte unicode string then base64 encode\n            # just like PowerShell -EncodedCommand expects.\n            # Suppress the progress stream from leaking to stderr.\n            wrapped_encoded_command = Base64.strict_encode64(\n              \"$ProgressPreference='SilentlyContinue'; #{script}; exit $LASTEXITCODE\".encode('UTF-16LE', 'UTF-8'))\n            # Execute encoded PowerShell script via OpenSSH shell\n            machine.communicate.execute(\"powershell.exe -noprofile -executionpolicy bypass \" +\n              \"-encodedcommand '#{wrapped_encoded_command}'\", shell: \"sh\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/cap/public_key.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire_relative '../../../communicators/winssh/communicator'\n\nmodule VagrantPlugins\n  module GuestWindows\n    module Cap\n      class PublicKey\n        def self.insert_public_key(machine, contents)\n          if machine.communicate.is_a?(CommunicatorWinSSH::Communicator)\n            contents = contents.strip\n            winssh_modify_authorized_keys machine do |keys|\n              if !keys.include?(contents)\n                keys << contents\n              end\n            end\n          else\n            raise Vagrant::Errors::SSHInsertKeyUnsupported\n          end\n        end\n\n        def self.remove_public_key(machine, contents)\n          if machine.communicate.is_a?(CommunicatorWinSSH::Communicator)\n            winssh_modify_authorized_keys machine do |keys|\n              keys.delete(contents)\n            end\n          else\n            raise Vagrant::Errors::SSHInsertKeyUnsupported\n          end\n        end\n\n        def self.winssh_modify_authorized_keys(machine)\n          comm = machine.communicate\n          directories = fetch_guest_paths(comm)\n          home_dir = directories[:home]\n          temp_dir = directories[:temp]\n\n          # Ensure the user's ssh directory exists\n          remote_ssh_dir = \"#{home_dir}\\\\.ssh\"\n          comm.execute(\"New-Item -Path '#{remote_ssh_dir}' -ItemType directory -Force\", shell: \"powershell\")\n          remote_upload_path = \"#{temp_dir}\\\\vagrant-insert-pubkey-#{Time.now.to_i}\"\n          remote_authkeys_path = \"#{remote_ssh_dir}\\\\authorized_keys\"\n\n          keys_file = Tempfile.new(\"vagrant-windows-insert-public-key\")\n          keys_file.close\n          # Check if an authorized_keys file already exists\n          result = comm.execute(\"dir \\\"#{remote_authkeys_path}\\\"\", shell: \"cmd\", error_check: false)\n          if result == 0\n            comm.download(remote_authkeys_path, keys_file.path)\n            keys = File.read(keys_file.path).split(/[\\r\\n]+/)\n          else\n            keys = []\n          end\n          yield keys\n          File.write(keys_file.path, keys.join(\"\\r\\n\") + \"\\r\\n\")\n          comm.upload(keys_file.path, remote_upload_path)\n          keys_file.delete\n          comm.execute(<<-EOC.gsub(/^\\s*/, \"\"), shell: \"powershell\")\n            Set-Acl \"#{remote_upload_path}\" (Get-Acl \"#{remote_authkeys_path}\")\n            Move-Item -Force \"#{remote_upload_path}\" \"#{remote_authkeys_path}\"\n          EOC\n        end\n\n        # Fetch user's temporary and home directory paths from the Windows guest\n        #\n        # @param [Communicator]\n        # @return [Hash] {:temp, :home}\n        def self.fetch_guest_paths(communicator)\n          output = \"\"\n          communicator.execute(\"Write-Output $env:TEMP\\nWrite-Output $env:USERPROFILE\", shell: \"powershell\") do |type, data|\n            if type == :stdout\n              output << data\n            end\n          end\n          temp_dir, home_dir = output.strip.split(/[\\r\\n]+/)\n          if temp_dir.nil? || home_dir.nil?\n            raise Errors::PublicKeyDirectoryFailure\n          end\n          {temp: temp_dir, home: home_dir}\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/cap/reboot.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module GuestWindows\n    module Cap\n      class Reboot\n        DEFAULT_MAX_REBOOT_RETRY_DURATION = 120\n        WAIT_SLEEP_TIME = 5\n\n        def self.reboot(machine)\n          @logger = Log4r::Logger.new(\"vagrant::windows::reboot\")\n          reboot_script = \"shutdown /r /t 5 /f /d p:4:1 /c \\\"Vagrant Reboot Computer\\\"\"\n\n          comm = machine.communicate\n\n          script  = File.expand_path(\"../../scripts/reboot_detect.ps1\", __FILE__)\n          script  = File.read(script)\n          if comm.test(script, error_check: false, shell: :powershell)\n            @logger.debug(\"Issuing reboot command for guest\")\n            comm.execute(reboot_script, shell: :powershell)\n          else\n            @logger.debug(\"A reboot is already in progress\")\n          end\n\n          machine.ui.info(I18n.t(\"vagrant.guests.capabilities.rebooting\"))\n\n          @logger.debug(\"Waiting for machine to finish rebooting\")\n\n          wait_remaining = ENV.fetch(\"VAGRANT_MAX_REBOOT_RETRY_DURATION\",\n            DEFAULT_MAX_REBOOT_RETRY_DURATION).to_i\n          wait_remaining = DEFAULT_MAX_REBOOT_RETRY_DURATION if wait_remaining < 1\n\n          begin\n            wait_for_reboot(machine)\n          rescue => err\n            raise if wait_remaining < 0\n            @logger.debug(\"Exception caught while waiting for reboot: #{err}\")\n            @logger.warn(\"Machine not ready, cannot start reboot yet. Trying again\")\n            sleep(WAIT_SLEEP_TIME)\n            wait_remaining -= WAIT_SLEEP_TIME\n            retry\n          end\n        end\n\n        def self.wait_for_reboot(machine)\n          script  = File.expand_path(\"../../scripts/reboot_detect.ps1\", __FILE__)\n          script  = File.read(script)\n\n          while machine.guest.ready? && machine.communicate.execute(script, error_check: false, shell: :powershell) != 0\n            sleep 10\n          end\n\n          # This re-establishes our symbolic links if they were\n          # created between now and a reboot\n          machine.communicate.execute(\"net use\", error_check: false, shell: :powershell)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/cap/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestWindows\n    module Cap\n      class RSync\n        def self.rsync_scrub_guestpath( machine, opts )\n          # Windows guests most often use cygwin-dependent rsync utilities\n          # that expect \"/cygdrive/c\" instead of \"c:\" as the path prefix\n          # some vagrant code may pass guest paths with drive-lettered paths here\n          opts[:guestpath].gsub( /^([a-zA-Z]):/, '/cygdrive/\\1' )\n        end\n\n        def self.rsync_pre(machine, opts)\n          machine.communicate.tap do |comm|\n            # rsync does not construct any gaps in the path to the target directory\n            # make sure that all subdirectories are created\n            # NB: Per #11878, the `mkdir` command on Windows is different than used on Unix.\n            # This formulation matches the form used in the WinRM communicator plugin.\n            # This will ignore any -p switches, which are redundant in PowerShell,\n            # and ambiguous in PowerShell 4+\n            comm.execute(\"mkdir \\\"#{opts[:guestpath]}\\\" -force\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestWindows\n    class Config < Vagrant.plugin(\"2\", :config)\n      attr_accessor :set_work_network\n\n      def initialize\n        @set_work_network = UNSET_VALUE\n      end\n\n      def validate(machine)\n        errors = []\n\n        errors << \"windows.set_work_network cannot be nil.\" if @set_work_network.nil?\n\n        { \"Windows Guest\" => errors }\n      end\n\n      def finalize!\n        @set_work_network = false if @set_work_network == UNSET_VALUE\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestWindows\n    module Errors\n      # A convenient superclass for all our errors.\n      class WindowsError < Vagrant::Errors::VagrantError\n        error_namespace(\"vagrant_windows.errors\")\n      end\n\n      class NetworkWinRMRequired < WindowsError\n        error_key(:network_winrm_required)\n      end\n\n      class RenameComputerFailed < WindowsError\n        error_key(:rename_computer_failed)\n      end\n\n      class PublicKeyDirectoryFailure < WindowsError\n        error_key(:public_key_directory_failure)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module GuestWindows\n    class Guest < Vagrant.plugin(\"2\", :guest)\n      def detect?(machine)\n        # See if the Windows directory is present.\n        machine.communicate.test(\"test -d $Env:SystemRoot\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/guest_network.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module GuestWindows\n    # Manages the remote Windows guest network.\n    class GuestNetwork\n      PS_GET_WSMAN_VER = '((test-wsman).productversion.split(\" \") | select -last 1).split(\"\\.\")[0]'\n      WQL_NET_ADAPTERS_V2 = 'SELECT * FROM Win32_NetworkAdapter WHERE MACAddress IS NOT NULL'\n\n      def initialize(communicator)\n        @logger       = Log4r::Logger.new(\"vagrant::windows::guestnetwork\")\n        @communicator = communicator\n      end\n\n      # Returns an array of all NICs on the guest. Each array entry is a\n      # Hash of the NICs properties.\n      #\n      # @return [Array]\n      def network_adapters\n        wsman_version == 2? network_adapters_v2_winrm : network_adapters_v3_winrm\n      end\n\n      # Checks to see if the specified NIC is currently configured for DHCP.\n      #\n      # @return [Boolean]\n      def is_dhcp_enabled(nic_index)\n        cmd = <<-EOH\n          if (Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter \"Index=#{nic_index} and DHCPEnabled=True\") {\n            exit 0\n          }\n          exit 1\n        EOH\n        @communicator.test(cmd)\n      end\n\n      # Configures the specified interface for DHCP\n      #\n      # @param [Integer] The interface index.\n      # @param [String] The unique name of the NIC, such as 'Local Area Connection'.\n      def configure_dhcp_interface(nic_index, net_connection_id)\n        @logger.info(\"Configuring NIC #{net_connection_id} for DHCP\")\n        if !is_dhcp_enabled(nic_index)\n          netsh = \"netsh interface ip set address \\\"#{net_connection_id}\\\" dhcp\"\n          @communicator.execute(netsh)\n        end\n      end\n\n      # Configures the specified interface using a static address\n      #\n      # @param [Integer] The interface index.\n      # @param [String] The unique name of the NIC, such as 'Local Area Connection'.\n      # @param [String] The static IP address to assign to the specified NIC.\n      # @param [String] The network mask to use with the static IP.\n      def configure_static_interface(nic_index, net_connection_id, ip, netmask)\n        @logger.info(\"Configuring NIC #{net_connection_id} using static ip #{ip}\")\n        #netsh interface ip set address \"Local Area Connection 2\" static 192.168.33.10 255.255.255.0\n        netsh = \"netsh interface ip set address \\\"#{net_connection_id}\\\" static #{ip} #{netmask}\"\n        @communicator.execute(netsh)\n      end\n\n      # Sets all networks on the guest to 'Work Network' mode. This is\n      # to allow guest access from the host via a private IP on Win7\n      # https://github.com/WinRb/vagrant-windows/issues/63\n      def set_all_networks_to_work\n        @logger.info(\"Setting all networks to 'Work Network'\")\n        command = File.read(File.expand_path(\"../scripts/set_work_network.ps1\", __FILE__))\n        @communicator.execute(command, { shell: :powershell })\n      end\n\n      protected\n\n      # Checks the WinRS version on the guest. Usually 2 on Windows 7/2008\n      # and 3 on Windows 8/2012.\n      #\n      # @return [Integer]\n      def wsman_version\n        @logger.debug(\"querying WSMan version\")\n        version = ''\n        @communicator.execute(PS_GET_WSMAN_VER, { shell: :powershell }) do |type, line|\n          version = version + \"#{line}\" if type == :stdout && !line.nil?\n        end\n        @logger.debug(\"wsman version: #{version}\")\n        Integer(version)\n      end\n\n      # Returns an array of all NICs on the guest. Each array entry is a\n      # Hash of the NICs properties. This method should only be used on\n      # guests that have WinRS version 2.\n      #\n      # @return [Array]\n      def network_adapters_v2_winrm\n        @logger.debug(\"querying network adapters\")\n\n        # Get all NICs that have a MAC address\n        # https://msdn.microsoft.com/en-us/library/windows/desktop/aa394216(v=vs.85).aspx\n        adapters = @communicator.execute(WQL_NET_ADAPTERS_V2, { shell: :wql } )[:win32_network_adapter]\n        @logger.debug(\"#{adapters.inspect}\")\n        return adapters\n      end\n\n      # Returns an array of all NICs on the guest. Each array entry is a\n      # Hash of the NICs properties. This method should only be used on\n      # guests that have WinRS version 3.\n      #\n      # This method is a workaround until the WinRM gem supports WinRS version 3.\n      #\n      # @return [Array]\n      def network_adapters_v3_winrm\n        command = File.read(File.expand_path(\"../scripts/winrs_v3_get_adapters.ps1\", __FILE__))\n        output = \"\"\n        @communicator.execute(command, { shell: :powershell }) do |type, line|\n          output = output + \"#{line}\" if type == :stdout && !line.nil?\n        end\n\n        adapters = []\n        JSON.parse(output).each do |nic|\n          adapters << nic.inject({}){ |memo,(k,v)| memo[k.to_sym] = v; memo }\n        end\n\n        @logger.debug(\"#{adapters.inspect}\")\n        return adapters\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module GuestWindows\n    autoload :Errors, File.expand_path(\"../errors\", __FILE__)\n\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Windows guest.\"\n      description \"Windows guest support.\"\n\n      config(:windows) do\n        require_relative \"config\"\n        Config\n      end\n\n      guest(:windows)  do\n        require_relative \"guest\"\n        init!\n        Guest\n      end\n\n      guest_capability(:windows, :change_host_name) do\n        require_relative \"cap/change_host_name\"\n        Cap::ChangeHostName\n      end\n\n      guest_capability(:windows, :configure_networks) do\n        require_relative \"cap/configure_networks\"\n        Cap::ConfigureNetworks\n      end\n\n      guest_capability(:windows, :halt) do\n        require_relative \"cap/halt\"\n        Cap::Halt\n      end\n\n      guest_capability(:windows, :create_tmp_path) do\n        require_relative \"cap/file_system\"\n        Cap::FileSystem\n      end\n\n      guest_capability(:windows, :decompress_zip) do\n        require_relative \"cap/file_system\"\n        Cap::FileSystem\n      end\n\n      guest_capability(:windows, :mount_virtualbox_shared_folder) do\n        require_relative \"cap/mount_shared_folder\"\n        Cap::MountSharedFolder\n      end\n\n      guest_capability(:windows, :mount_vmware_shared_folder) do\n        require_relative \"cap/mount_shared_folder\"\n        Cap::MountSharedFolder\n      end\n\n      guest_capability(:windows, :mount_parallels_shared_folder) do\n        require_relative \"cap/mount_shared_folder\"\n        Cap::MountSharedFolder\n      end\n\n      guest_capability(:windows, :wait_for_reboot) do\n        require_relative \"cap/reboot\"\n        Cap::Reboot\n      end\n\n      guest_capability(:windows, :reboot) do\n        require_relative \"cap/reboot\"\n        Cap::Reboot\n      end\n\n      guest_capability(:windows, :choose_addressable_ip_addr) do\n        require_relative \"cap/choose_addressable_ip_addr\"\n        Cap::ChooseAddressableIPAddr\n      end\n\n      guest_capability(:windows, :mount_smb_shared_folder) do\n        require_relative \"cap/mount_shared_folder\"\n        Cap::MountSharedFolder\n      end\n\n      guest_capability(:windows, :rsync_scrub_guestpath) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:windows, :rsync_pre) do\n        require_relative \"cap/rsync\"\n        Cap::RSync\n      end\n\n      guest_capability(:windows, :insert_public_key) do\n        require_relative \"cap/public_key\"\n        Cap::PublicKey\n      end\n\n      guest_capability(:windows, :remove_public_key) do\n        require_relative \"cap/public_key\"\n        Cap::PublicKey\n      end\n\n      protected\n\n      def self.init!\n        return if defined?(@_init)\n        I18n.load_path << File.expand_path(\n          \"templates/locales/guest_windows.yml\", Vagrant.source_root)\n        I18n.reload!\n        @_init = true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/guests/windows/scripts/mount_volume.ps1.erb",
    "content": "function Test-ReparsePoint([string]$path) {\r\n  $file = Get-Item $path -Force -ea 0\r\n  return [bool]($file.Attributes -band [IO.FileAttributes]::ReparsePoint)\r\n}\r\n\r\n$MountPoint = [System.IO.Path]::GetFullPath(\"<%= options[:mount_point] %>\")\r\n$ShareName = \"<%= options[:share_name] %>\"\r\n$VmProviderUncPath = \"<%= options[:vm_provider_unc_path] %>\"\r\n\r\n# https://github.com/BIAINC/vagrant-windows/issues/4\r\n# Not sure why this works, but it does.\r\n\r\n& net use $ShareName 2>&1 | Out-Null\r\n\r\nWrite-Debug \"Attempting to mount $ShareName to $MountPoint\"\r\nif( (Test-Path \"$MountPoint\") -and (Test-ReparsePoint \"$MountPoint\") )\r\n{\r\n  Write-Debug \"Junction already exists, so I will delete it\"\r\n  # Powershell refuses to delete junctions, oh well use cmd\r\n  cmd.exe /c rd \"$MountPoint\"\r\n\r\n  if ( $LASTEXITCODE -ne 0 )\r\n  {\r\n    Write-Error \"Failed to delete symbolic link at $MountPoint\"\r\n    exit 1\r\n  }\r\n\r\n}\r\nelseif(Test-Path $MountPoint)\r\n{\r\n  Write-Error \"Mount point already exists and is not a symbolic link\"\r\n  exit 1\r\n}\r\n\r\n$BaseDirectory = [System.IO.Path]::GetDirectoryName($MountPoint)\r\n\r\nif (-not (Test-Path $BaseDirectory))\r\n{\r\n  Write-Debug \"Creating parent directory for mount point $BaseDirectory\"\r\n  New-Item $BaseDirectory -Type Directory -Force | Out-Null\r\n}\r\n\r\ncmd.exe /c mklink /D \"$MountPoint\" \"$VmProviderUncPath\" | out-null\r\n\r\nif ( $LASTEXITCODE -ne 0 )\r\n{\r\n  exit 1\r\n}\r\n"
  },
  {
    "path": "plugins/guests/windows/scripts/reboot_detect.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# Function to check whether machine is currently shutting down\nfunction ShuttingDown {\n    [string]$sourceCode = @\"\nusing System;\nusing System.Runtime.InteropServices;\n\nnamespace Vagrant {\n    public static class RemoteManager {\n        private const int SM_SHUTTINGDOWN = 0x2000;\n\n        [DllImport(\"User32.dll\", CharSet = CharSet.Unicode)]\n        private static extern int GetSystemMetrics(int Index);\n\n        public static bool Shutdown() {\n            return (0 != GetSystemMetrics(SM_SHUTTINGDOWN));\n        }\n    }\n}\n\"@\n    $type = Add-Type -TypeDefinition $sourceCode -PassThru\n    return $type::Shutdown()\n}\n\nif (ShuttingDown) {\n  exit 1\n} else {\n  # See if a reboot is scheduled in the future by trying to schedule a reboot\n  . shutdown.exe -f -r -t 60\n\n  if ($LASTEXITCODE -eq 1190) {\n    # reboot is already pending\n    exit 2\n  }\n\n  if ($LASTEXITCODE -eq 1115) {\n    # A system shutdown is in progress\n    exit 2\n  }\n\n  # Remove the pending reboot we just created above\n  if ($LASTEXITCODE -eq 0) {\n    . shutdown.exe -a\n  }\n}\n\n# no reboot in progress or scheduled\nexit 0\n"
  },
  {
    "path": "plugins/guests/windows/scripts/set_work_network.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# Get network connections\r\n$networkListManager = [Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]\"{DCB00C01-570F-4A9B-8D69-199FDBA5723B}\"))\r\n$connections = $networkListManager.GetNetworkConnections()\r\n\r\n# Set network location to Private for all networks\r\n$connections | % {$_.GetNetwork().SetCategory(1)}\r\n"
  },
  {
    "path": "plugins/guests/windows/scripts/winrs_v3_get_adapters.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n$adapters = get-ciminstance win32_networkadapter -filter \"macaddress is not null\"\r\n$processed = @()\r\nforeach ($adapter in $adapters) {\r\n  $Processed += new-object PSObject -Property @{\r\n    mac_address = $adapter.macaddress\r\n    net_connection_id = $adapter.netconnectionid\r\n    interface_index = $adapter.interfaceindex\r\n    index = $adapter.index\r\n  }\r\n}\r\nconvertto-json -inputobject $processed\r\n"
  },
  {
    "path": "plugins/hosts/alt/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/subprocess\"\nrequire \"vagrant/util/which\"\n\nmodule VagrantPlugins\n  module HostALT\n    module Cap\n      class NFS\n        def self.nfs_check_command(env)\n          if systemd?\n            return \"systemctl status --no-pager nfs-server.service\"\n          else\n            return \"/etc/init.d/nfs status\"\n          end\n        end\n\n        def self.nfs_start_command(env)\n          if systemd?\n            return \"systemctl start rpcbind nfs-server.service\"\n          else\n            return \"/etc/init.d/nfs restart\"\n          end\n        end\n\n        def self.nfs_installed(environment)\n          if systemd?\n            system(\"systemctl --no-pager --no-legend --plain list-unit-files --all --type=service | grep --fixed-strings --quiet nfs-server.service\")\n          else\n            system(\"rpm -q nfs-server --quiet 2>&1\")\n          end\n        end\n\n        protected\n\n        # This tests to see if systemd is used on the system. This is used\n        # in newer versions of ALT, and requires a change in behavior.\n        def self.systemd?\n          result = Vagrant::Util::Subprocess.execute(\"ps\", \"-o\", \"comm=\", \"1\")\n          return result.stdout.chomp == \"systemd\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/alt/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostALT\n    class Host < Vagrant.plugin(\"2\", :host)\n      def detect?(env)\n        File.exist?(\"/etc/altlinux-release\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/alt/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostALT\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"ALT Platform host\"\n      description \"ALT Platform host support.\"\n\n      host(\"alt\", \"linux\") do\n        require_relative \"host\"\n        Host\n      end\n\n      host_capability(\"alt\", \"nfs_installed\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      # Linux-specific helpers we need to determine paths that can\n      # be overridden.\n      host_capability(\"alt\", \"nfs_check_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"alt\", \"nfs_start_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/arch/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostArch\n    module Cap\n      class NFS\n        def self.nfs_check_command(env)\n          return \"/usr/sbin/systemctl status --no-pager nfs-server.service\"\n        end\n\n        def self.nfs_start_command(env)\n          return \"/usr/sbin/systemctl start nfs-server.service\"\n        end\n\n        def self.nfs_installed(environment)\n          Kernel.system(\"systemctl --no-pager --no-legend --plain list-unit-files --all --type=service | grep --fixed-strings --quiet nfs-server.service\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/arch/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostArch\n    class Host < Vagrant.plugin(\"2\", :host)\n      def detect?(env)\n        File.exist?(\"/etc/arch-release\") && !File.exist?(\"/etc/artix-release\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/arch/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostArch\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Arch host\"\n      description \"Arch host support.\"\n\n      host(\"arch\", \"linux\") do\n        require_relative \"host\"\n        Host\n      end\n\n      host_capability(\"arch\", \"nfs_installed\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      # Linux-specific helpers we need to determine paths that can\n      # be overridden.\n      host_capability(\"arch\", \"nfs_check_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"arch\", \"nfs_start_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/bsd/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nrequire \"vagrant/util\"\nrequire \"vagrant/util/shell_quote\"\nrequire \"vagrant/util/which\"\n\nmodule VagrantPlugins\n  module HostBSD\n    module Cap\n      class NFS\n        def self.nfs_export(environment, ui, id, ips, folders)\n          nfs_exports_template = environment.host.capability(:nfs_exports_template)\n          nfs_restart_command  = environment.host.capability(:nfs_restart_command)\n          nfs_status_command  = environment.host.capability(:nfs_status_command)\n          nfs_update_command  = environment.host.capability(:nfs_update_command)\n          logger = Log4r::Logger.new(\"vagrant::hosts::bsd\")\n\n          nfs_checkexports! if File.file?(\"/etc/exports\")\n\n          # We need to build up mapping of directories that are enclosed\n          # within each other because the exports file has to have subdirectories\n          # of an exported directory on the same line. e.g.:\n          #\n          #   \"/foo\" \"/foo/bar\" ...\n          #   \"/bar\"\n          #\n          # We build up this mapping within the following hash.\n          logger.debug(\"Compiling map of sub-directories for NFS exports...\")\n          dirmap = {}\n          folders.sort_by { |_, opts| opts[:hostpath] }.each do |_, opts|\n            opts[:hostpath] = environment.host.capability(:resolve_host_path, opts[:hostpath].gsub('\"', '\\\"'))\n            hostpath = opts[:hostpath].dup\n\n            found = false\n            dirmap.each do |dirs, diropts|\n              dirs.each do |dir|\n                if dir.start_with?(hostpath) || hostpath.start_with?(dir)\n                  # TODO: verify opts and diropts are _identical_, raise an error\n                  # if not. NFS mandates subdirectories have identical options.\n                  dirs << hostpath\n                  found = true\n                  break\n                end\n              end\n\n              break if found\n            end\n\n            if !found\n              dirmap[[hostpath]] = opts.dup\n            end\n          end\n\n          # Sort all the keys by length so that the directory closest to\n          # the root is exported first. Also, remove duplicates so that\n          # checkexports will work properly.\n          dirmap.each do |dirs, _|\n            dirs.uniq!\n            dirs.sort_by! { |d| d.length }\n          end\n\n          # Setup the NFS options\n          dirmap.each do |dirs, opts|\n            if !opts[:bsd__nfs_options]\n              opts[:bsd__nfs_options] = [\"alldirs\"]\n            end\n\n            hasmapall = false\n            opts[:bsd__nfs_options].each do |opt|\n              # mapall/maproot are mutually exclusive, so we have to check\n              # for both here.\n              if opt =~ /^mapall=/ || opt =~ /^maproot=/\n                hasmapall = true\n                break\n              end\n            end\n\n            if !hasmapall\n              opts[:bsd__nfs_options] << \"mapall=#{opts[:map_uid]}:#{opts[:map_gid]}\"\n            end\n\n            opts[:bsd__compiled_nfs_options] = opts[:bsd__nfs_options].map do |opt|\n              \"-#{opt}\"\n            end.join(\" \")\n          end\n\n          logger.info(\"Exporting the following for NFS...\")\n          dirmap.each do |dirs, opts|\n            logger.info(\"NFS DIR: #{dirs.inspect}\")\n            logger.info(\"NFS OPTS: #{opts.inspect}\")\n          end\n\n          output = Vagrant::Util::TemplateRenderer.render(nfs_exports_template,\n                                           uuid: id,\n                                           ips: ips,\n                                           folders: dirmap,\n                                           user: Process.uid)\n\n          # The sleep ensures that the output is truly flushed before any `sudo`\n          # commands are issued.\n          ui.info I18n.t(\"vagrant.hosts.bsd.nfs_export\")\n          sleep 0.5\n\n          # First, clean up the old entry\n          nfs_cleanup(id)\n\n          # Only use \"sudo\" if we can't write to /etc/exports directly\n          sudo_command = \"\"\n          sudo_command = \"sudo \" if !File.writable?(\"/etc/exports\")\n\n          # Output the rendered template into the exports\n          output.split(\"\\n\").each do |line|\n            line = Vagrant::Util::ShellQuote.escape(line, \"'\")\n            system(\n              \"echo '#{line}' | \" +\n              \"#{sudo_command}/usr/bin/tee -a /etc/exports >/dev/null\")\n          end\n\n          # Check if nfsd is running, and update or restart depending on the result\n          if nfs_running?(nfs_status_command)\n            system(*nfs_update_command)\n          else\n            system(*nfs_restart_command)\n          end\n        end\n\n        def self.nfs_exports_template(environment)\n          \"nfs/exports_bsd\"\n        end\n\n        def self.nfs_installed(environment)\n          !!Vagrant::Util::Which.which(\"nfsd\")\n        end\n\n        def self.nfs_prune(environment, ui, valid_ids)\n          return if !File.exist?(\"/etc/exports\")\n\n          logger = Log4r::Logger.new(\"vagrant::hosts::bsd\")\n          logger.info(\"Pruning invalid NFS entries...\")\n\n          output = false\n          user = Process.uid\n\n          File.read(\"/etc/exports\").lines.each do |line|\n            if id = line[/^# VAGRANT-BEGIN:( #{user})? ([\\.\\/A-Za-z0-9\\-_:]+?)$/, 2]\n              if valid_ids.include?(id)\n                logger.debug(\"Valid ID: #{id}\")\n              else\n                if !output\n                  # We want to warn the user but we only want to output once\n                  ui.info I18n.t(\"vagrant.hosts.bsd.nfs_prune\")\n                  output = true\n                end\n\n                logger.info(\"Invalid ID, pruning: #{id}\")\n                nfs_cleanup(id)\n              end\n            end\n          end\n        rescue Errno::EACCES\n          raise Vagrant::Errors::NFSCantReadExports\n        end\n\n        def self.nfs_running?(check_command)\n          Vagrant::Util::Subprocess.execute(*check_command).exit_code == 0\n        end\n\n        def self.nfs_restart_command(environment)\n          [\"sudo\", \"nfsd\", \"restart\"]\n        end\n\n        def self.nfs_update_command(environment)\n          [\"sudo\", \"nfsd\", \"update\"]\n        end\n\n        def self.nfs_status_command(environment)\n          [\"sudo\", \"nfsd\", \"status\"]\n        end\n\n        protected\n\n        def self.nfs_cleanup(id)\n          return if !File.exist?(\"/etc/exports\")\n\n          # Escape sed-sensitive characters:\n          id = id.gsub(\"/\", \"\\\\/\")\n          id = id.gsub(\".\", \"\\\\.\")\n\n          user = Process.uid\n\n          command = []\n          command << \"sudo\" if !File.writable?(\"/etc/exports\")\n          command += [\n            \"sed\", \"-E\", \"-e\",\n            \"/^# VAGRANT-BEGIN:( #{user})? #{id}/,\" +\n            \"/^# VAGRANT-END:( #{user})? #{id}/ d\",\n            \"-ibak\",\n            \"/etc/exports\"\n          ]\n\n          # Use sed to just strip out the block of code which was inserted\n          # by Vagrant, and restart NFS.\n          system(*command)\n        end\n\n        def self.nfs_checkexports!\n          r = Vagrant::Util::Subprocess.execute(\"nfsd\", \"checkexports\")\n          if r.exit_code != 0\n            raise Vagrant::Errors::NFSBadExports, output: r.stderr\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/bsd/cap/path.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostBSD\n    module Cap\n      class Path\n        def self.resolve_host_path(env, path)\n          path\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/bsd/cap/ssh.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostBSD\n    module Cap\n      class SSH\n        # Set the ownership and permissions for SSH\n        # private key\n        #\n        # @param [Vagrant::Environment] env\n        # @param [Pathname] key_path\n        def self.set_ssh_key_permissions(env, key_path)\n          key_path.chmod(0600)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/bsd/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostBSD\n    # Represents a BSD host, such as FreeBSD.\n    class Host < Vagrant.plugin(\"2\", :host)\n      def detect?(env)\n        Vagrant::Util::Platform.darwin?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/bsd/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostBSD\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"BSD host\"\n      description \"BSD host support.\"\n\n      host(\"bsd\") do\n        require File.expand_path(\"../host\", __FILE__)\n        Host\n      end\n\n      host_capability(\"bsd\", \"nfs_export\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"bsd\", \"nfs_exports_template\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"bsd\", \"nfs_installed\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"bsd\", \"nfs_prune\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"bsd\", \"nfs_restart_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"bsd\", \"nfs_update_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"bsd\", \"nfs_status_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"bsd\", \"resolve_host_path\") do\n        require_relative \"cap/path\"\n        Cap::Path\n      end\n\n      host_capability(\"bsd\", \"set_ssh_key_permissions\") do\n        require_relative \"cap/ssh\"\n        Cap::SSH\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/darwin/cap/configured_ip_addresses.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"socket\"\n\nmodule VagrantPlugins\n  module HostDarwin\n    module Cap\n      class ConfiguredIPAddresses\n\n        def self.configured_ip_addresses(env)\n          Socket.getifaddrs.map do |interface|\n            if interface.addr.ipv4? && !interface.addr.ipv4_loopback?\n              interface.addr.ip_address\n            end\n          end.compact\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/darwin/cap/fs_iso.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"vagrant/util/caps\"\n\nmodule VagrantPlugins\n  module HostDarwin\n    module Cap\n      class FsISO\n        extend Vagrant::Util::Caps::BuildISO\n        \n        @@logger = Log4r::Logger.new(\"vagrant::host::darwin::fs_iso\")\n\n        BUILD_ISO_CMD = \"hdiutil\".freeze\n\n        # Check that the host has the ability to generate ISOs\n        #\n        # @param [Vagrant::Environment] env\n        # @return [Boolean]\n        def self.isofs_available(env)\n          !!Vagrant::Util::Which.which(BUILD_ISO_CMD)\n        end\n\n        # Generate an ISO file of the given source directory\n        #\n        # @param [Vagrant::Environment] env\n        # @param [String] source_directory Contents of ISO\n        # @param [Map] extra arguments to pass to the iso building command\n        #              :file_destination (string) location to store ISO\n        #              :volume_id (String) to set the volume name \n        # @return [Pathname] ISO location\n        # @note If file_destination exists, source_directory will be checked\n        #       for recent modifications and a new ISO will be generated if requried.\n        def self.create_iso(env, source_directory, extra_opts={})\n          source_directory = Pathname.new(source_directory)\n          file_destination = self.ensure_output_iso(extra_opts[:file_destination])\n\n          iso_command = [BUILD_ISO_CMD, \"makehybrid\", \"-iso\", \"-joliet\", \"-ov\"]\n          iso_command.concat([\"-default-volume-name\", extra_opts[:volume_id]]) if extra_opts[:volume_id]\n          iso_command << \"-o\"\n          iso_command << file_destination.to_s\n          iso_command << source_directory.to_s\n          self.build_iso(iso_command, source_directory, file_destination)\n\n          @@logger.info(\"ISO available at #{file_destination}\")\n          file_destination\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/darwin/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostDarwin\n    module Cap\n      class NFS\n        def self.nfs_exports_template(environment)\n          \"nfs/exports_darwin\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/darwin/cap/path.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostDarwin\n    module Cap\n      class Path\n        @@logger = Log4r::Logger.new(\"vagrant::host::darwin::path\")\n\n        FIRMLINK_DEFS = \"/usr/share/firmlinks\".freeze\n        FIRMLINK_DATA_PATH = \"/System/Volumes/Data\".freeze\n        CATALINA_CONSTRAINT = Gem::Requirement.new(\"~> 10.15\")\n\n        # Resolve the given host path to the actual\n        # usable system path by detecting firmlinks\n        # if available on the current system\n        #\n        # @param [String] path Host system path\n        # @return [String] resolved path\n        def self.resolve_host_path(env, path)\n          path = File.expand_path(path)\n          # Only expand firmlink paths on Catalina\n          host_version = env.host.capability(:version)\n          return path if !CATALINA_CONSTRAINT.satisfied_by?(host_version)\n\n          firmlink = firmlink_map.detect do |mount_path, data_path|\n            path.start_with?(mount_path)\n          end\n          return path if firmlink.nil?\n          current_prefix, new_suffix = firmlink\n          new_prefix = File.join(FIRMLINK_DATA_PATH, new_suffix)\n          new_path = path.sub(current_prefix, new_prefix)\n          @@logger.debug(\"Resolved given path `#{path}` to `#{new_path}`\")\n          new_path\n        end\n\n        # Generate mapping of firmlinks if available on the host\n        #\n        # @return [Hash<String,String>]\n        def self.firmlink_map\n          if !@firmlink_map\n            return @firmlink_map = {} if !File.exist?(FIRMLINK_DEFS)\n            begin\n              @firmlink_map = Hash[\n                File.readlines(FIRMLINK_DEFS).map { |d|\n                  d.strip.split(/\\s+/, 2)\n                }\n              ]\n            rescue => err\n              @@logger.warn(\"Failed to parse firmlink definitions: #{err}\")\n              @firmlink_map = {}\n            end\n          end\n          @firmlink_map\n        end\n\n        # @private\n        # Reset the cached values for capability. This is not considered a public\n        # API and should only be used for testing.\n        def self.reset!\n          instance_variables.each(&method(:remove_instance_variable))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/darwin/cap/provider_install_virtualbox.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tempfile\"\n\nrequire \"vagrant/util/downloader\"\nrequire \"vagrant/util/file_checksum\"\nrequire \"vagrant/util/subprocess\"\n\nmodule VagrantPlugins\n  module HostDarwin\n    module Cap\n      class ProviderInstallVirtualBox\n        # The URL to download VirtualBox is hardcoded so we can have a\n        # known-good version to download.\n        URL = \"http://download.virtualbox.org/virtualbox/5.0.10/VirtualBox-5.0.10-104061-OSX.dmg\".freeze\n        VERSION = \"5.0.10\".freeze\n        SHA256SUM = \"62f933115498e51ddf5f2dab47dc1eebb42eb78ea1a7665cb91c53edacc847c6\".freeze\n\n        def self.provider_install_virtualbox(env)\n          path = Dir::Tmpname.create(\"vagrant-provider-install-virtualbox\") {}\n\n          # Prefixed UI for prettiness\n          ui = Vagrant::UI::Prefixed.new(env.ui, \"\")\n\n          # Start by downloading the file using the standard mechanism\n          ui.output(I18n.t(\n            \"vagrant.hosts.darwin.virtualbox_install_download\",\n            version: VERSION))\n          ui.detail(I18n.t(\n            \"vagrant.hosts.darwin.virtualbox_install_detail\"))\n          dl = Vagrant::Util::Downloader.new(URL, path, ui: ui)\n          dl.download!\n\n          # Validate that the file checksum matches\n          actual = FileChecksum.new(path, Digest::SHA2).checksum\n          if actual != SHA256SUM\n            raise Vagrant::Errors::ProviderChecksumMismatch,\n              provider: \"virtualbox\",\n              actual: actual,\n              expected: SHA256SUM\n          end\n\n          # Launch it\n          ui.output(I18n.t(\n            \"vagrant.hosts.darwin.virtualbox_install_install\"))\n          ui.detail(I18n.t(\n            \"vagrant.hosts.darwin.virtualbox_install_install_detail\"))\n          script = File.expand_path(\"../../scripts/install_virtualbox.sh\", __FILE__)\n          result = Vagrant::Util::Subprocess.execute(\"bash\", script, path)\n          if result.exit_code != 0\n            raise Vagrant::Errors::ProviderInstallFailed,\n              provider: \"virtualbox\",\n              stdout: result.stdout,\n              stderr: result.stderr\n          end\n\n          ui.success(I18n.t(\"vagrant.hosts.darwin.virtualbox_install_success\"))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/darwin/cap/rdp.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tmpdir\"\n\nrequire \"vagrant/util/subprocess\"\n\nmodule VagrantPlugins\n  module HostDarwin\n    module Cap\n      class RDP\n        def self.rdp_client(env, rdp_info)\n          config_path = self.generate_config_file(rdp_info)\n          begin\n            Vagrant::Util::Subprocess.execute(\"open\", config_path.to_s)\n          ensure\n            # Note: this technically will never get run; neither would an\n            # at_exit call. The reason is that `exec` replaces this process,\n            # effectively the same as `kill -9`. This is solely here to prove\n            # that and so that future developers do not waste a ton of time\n            # try to identify why Vagrant is leaking RDP connection files.\n            # There is a catch-22 here in that we can't delete the file before\n            # we exec, and we can't delete the file after we exec :(.\n            File.unlink(config_path) if File.file?(config_path)\n          end\n        end\n\n        protected\n\n        # Generates an RDP connection file and returns the resulting path.\n        # @return [String]\n        def self.generate_config_file(rdp_info)\n          opts   = {\n            \"drivestoredirect:s\"       => \"*\",\n            \"full address:s\"           => \"#{rdp_info[:host]}:#{rdp_info[:port]}\",\n            \"prompt for credentials:i\" => \"1\",\n            \"username:s\"               => rdp_info[:username],\n          }\n\n          # Create the \".rdp\" file\n          t = ::Tempfile.new([\"vagrant-rdp\", \".rdp\"]).tap do |f|\n            f.binmode\n\n            opts.each do |k, v|\n              f.puts(\"#{k}:#{v}\")\n            end\n\n            if rdp_info[:extra_args]\n              rdp_info[:extra_args].each do |arg|\n                f.puts(\"#{arg}\")\n              end\n            end\n\n            f.fsync\n            f.close\n          end\n\n          return t.path\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/darwin/cap/smb.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostDarwin\n    module Cap\n      class SMB\n\n        @@logger = Log4r::Logger.new(\"vagrant::host::darwin::smb\")\n\n        # If we have the sharing binary available, smb is installed\n        def self.smb_installed(env)\n          File.exist?(\"/usr/sbin/sharing\")\n        end\n\n        # Check if the required SMB services are loaded and enabled. If they are\n        # not, then start them up\n        def self.smb_start(env)\n          result = Vagrant::Util::Subprocess.execute(\"pwpolicy\", \"gethashtypes\")\n          if result.exit_code == 0 && !result.stdout.include?(\"SMB-NT\")\n            @@logger.error(\"SMB compatible password has not been stored\")\n            raise SyncedFolderSMB::Errors::SMBCredentialsMissing\n          end\n          result = Vagrant::Util::Subprocess.execute(\"launchctl\", \"list\", \"com.apple.smb.preferences\")\n          if result.exit_code != 0\n            @@logger.warn(\"smb preferences service not enabled. enabling and starting...\")\n            cmd = [\"/bin/launchctl\", \"load\", \"-w\", \"/System/Library/LaunchDaemons/com.apple.smb.preferences.plist\"]\n            result = Vagrant::Util::Subprocess.execute(\"/usr/bin/sudo\", *cmd)\n            if result.exit_code != 0\n              raise SyncedFolderSMB::Errors::SMBStartFailed,\n                command: cmd.join(\" \"),\n                stderr: result.stderr,\n                stdout: result.stdout\n            end\n          end\n          result = Vagrant::Util::Subprocess.execute(\"launchctl\", \"list\", \"com.apple.smbd\")\n          if result.exit_code != 0\n            @@logger.warn(\"smbd service not enabled. enabling and starting...\")\n            cmd = [\"/bin/launchctl\", \"load\", \"-w\", \"/System/Library/LaunchDaemons/com.apple.smbd.plist\"]\n            result = Vagrant::Util::Subprocess.execute(\"/usr/bin/sudo\", *cmd)\n            if result.exit_code != 0\n              raise SyncedFolderSMB::Errors::SMBStartFailed,\n                command: cmd.join(\" \"),\n                stderr: result.stderr,\n                stdout: result.stdout\n            end\n            Vagrant::Util::Subprocess.execute(\"/usr/bin/sudo\", \"/bin/launchctl\", \"start\", \"com.apple.smbd\")\n          end\n        end\n\n        # Required options for mounting a share hosted\n        # on macos.\n        def self.smb_mount_options(env)\n          [\"sec=ntlmssp\", \"nounix\", \"noperm\"]\n        end\n\n        def self.smb_cleanup(env, machine, opts)\n          m_id = machine_id(machine)\n          result = Vagrant::Util::Subprocess.execute(\"/usr/bin/sudo\", \"/usr/sbin/sharing\", \"-l\")\n          if result.exit_code != 0\n            @@logger.warn(\"failed to locate any shares for cleanup\")\n          end\n          shares = result.stdout.split(\"\\n\").map do |line|\n            if line.start_with?(\"name:\")\n              share_name = line.sub(\"name:\", \"\").strip\n              share_name if share_name.start_with?(\"vgt-#{m_id}\")\n            end\n          end.compact\n          @@logger.debug(\"shares to be removed: #{shares}\")\n          shares.each do |share_name|\n            @@logger.info(\"removing share name=#{share_name}\")\n            share_name.strip!\n            result = Vagrant::Util::Subprocess.execute(\"/usr/bin/sudo\",\n              \"/usr/sbin/sharing\", \"-r\", share_name)\n            if result.exit_code != 0\n              # Removing always returns 0 even if there are currently\n              # guests attached so if we get a non-zero value just\n              # log it as unexpected\n              @@logger.warn(\"removing share `#{share_name}` returned non-zero\")\n            end\n          end\n        end\n\n        def self.smb_prepare(env, machine, folders, opts)\n          folders.each do |id, data|\n            hostpath = data[:hostpath]\n\n            chksum_id = Digest::MD5.hexdigest(id)\n            name = \"vgt-#{machine_id(machine)}-#{chksum_id}\"\n            data[:smb_id] ||= name\n\n            @@logger.info(\"creating new share name=#{name} id=#{data[:smb_id]}\")\n\n            cmd = [\n              \"/usr/bin/sudo\",\n              \"/usr/sbin/sharing\",\n              \"-a\", hostpath,\n              \"-S\", data[:smb_id],\n              \"-s\", \"001\",\n              \"-g\", \"000\",\n              \"-n\", name\n            ]\n\n            r = Vagrant::Util::Subprocess.execute(*cmd)\n\n            if r.exit_code != 0\n              raise VagrantPlugins::SyncedFolderSMB::Errors::DefineShareFailed,\n                host: hostpath.to_s,\n                stderr: r.stderr,\n                stdout: r.stdout\n            end\n          end\n        end\n\n        # Generates a unique identifier for the given machine\n        # based on the name, provider name, and working directory\n        # of the environment.\n        #\n        # @param [Vagrant::Machine] machine\n        # @return [String]\n        def self.machine_id(machine)\n          @@logger.debug(\"generating machine ID name=#{machine.name} cwd=#{machine.env.cwd}\")\n          Digest::MD5.hexdigest(\"#{machine.name}-#{machine.provider_name}-#{machine.env.cwd}\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/darwin/cap/version.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostDarwin\n    module Cap\n      class Version\n        def self.version(env)\n          r = Vagrant::Util::Subprocess.execute(\"sw_vers\", \"-productVersion\")\n          if r.exit_code != 0\n            raise Vagrant::Errors::DarwinVersionFailed,\n              version: r.stdout,\n              error: r.stderr\n          end\n          begin\n            Gem::Version.new(r.stdout)\n          rescue => err\n            raise Vagrant::Errors::DarwinVersionFailed,\n              version: r.stdout,\n              error: err.message\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/darwin/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/platform\"\n\nmodule VagrantPlugins\n  module HostDarwin\n    class Host < Vagrant.plugin(\"2\", :host)\n      def detect?(env)\n        Vagrant::Util::Platform.darwin?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/darwin/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostDarwin\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Mac OS X host\"\n      description \"Mac OS X host support.\"\n\n      host(\"darwin\", \"bsd\") do\n        require_relative \"host\"\n        Host\n      end\n\n      host_capability(\"darwin\", \"isofs_available\") do\n        require_relative \"cap/fs_iso\"\n        Cap::FsISO\n      end\n\n      host_capability(\"darwin\", \"create_iso\") do\n        require_relative \"cap/fs_iso\"\n        Cap::FsISO\n      end\n\n      host_capability(\"darwin\", \"provider_install_virtualbox\") do\n        require_relative \"cap/provider_install_virtualbox\"\n        Cap::ProviderInstallVirtualBox\n      end\n\n      host_capability(\"darwin\", \"resolve_host_path\") do\n        require_relative \"cap/path\"\n        Cap::Path\n      end\n\n      host_capability(\"darwin\", \"rdp_client\") do\n        require_relative \"cap/rdp\"\n        Cap::RDP\n      end\n\n      host_capability(\"darwin\", \"smb_installed\") do\n        require_relative \"cap/smb\"\n        Cap::SMB\n      end\n\n      host_capability(\"darwin\", \"smb_prepare\") do\n        require_relative \"cap/smb\"\n        Cap::SMB\n      end\n\n      host_capability(\"darwin\", \"smb_mount_options\") do\n        require_relative \"cap/smb\"\n        Cap::SMB\n      end\n\n      host_capability(\"darwin\", \"smb_cleanup\") do\n        require_relative \"cap/smb\"\n        Cap::SMB\n      end\n\n      host_capability(\"darwin\", \"smb_start\") do\n        require_relative \"cap/smb\"\n        Cap::SMB\n      end\n\n      host_capability(\"darwin\", \"configured_ip_addresses\") do\n        require_relative \"cap/configured_ip_addresses\"\n        Cap::ConfiguredIPAddresses\n      end\n\n      host_capability(\"darwin\", \"nfs_exports_template\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"darwin\", \"version\") do\n        require_relative \"cap/version\"\n        Cap::Version\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/darwin/scripts/install_virtualbox.sh",
    "content": "#!/bin/bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nset -e\n\nhdiutil attach $1\ncd /Volumes/VirtualBox/\nsudo installer -pkg VirtualBox.pkg -target \"/\"\ncd /tmp\nflag=1\nwhile [ $flag -ne 0 ]; do\n    sleep 1\n    set +e\n    hdiutil detach /Volumes/VirtualBox/\n    flag=$?\n    set -e\ndone\n"
  },
  {
    "path": "plugins/hosts/freebsd/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util\"\nrequire \"vagrant/util/retryable\"\n\nrequire Vagrant.source_root.join(\"plugins\", \"hosts\", \"bsd\", \"cap\", \"nfs\")\n\nmodule VagrantPlugins\n  module HostFreeBSD\n    module Cap\n      class NFS\n        def self.nfs_export(environment, ui, id, ips, folders)\n          folders.each do |folder_name, folder_values|\n            if folder_values[:hostpath] =~ /\\s+/\n              raise Vagrant::Errors::VagrantError,\n                _key: :freebsd_nfs_whitespace\n            end\n          end\n\n          HostBSD::Cap::NFS.nfs_export(environment, ui, id, ips, folders)\n        end\n\n        def self.nfs_exports_template(environment)\n          \"nfs/exports_bsd\"\n        end\n\n        def self.nfs_restart_command(environment)\n          [\"sudo\", \"/etc/rc.d/mountd\", \"onereload\"]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/freebsd/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\nrequire 'vagrant/util/platform'\n\nmodule VagrantPlugins\n  module HostFreeBSD\n    class Host < Vagrant.plugin(\"2\", :host)\n      def detect?(env)\n        Vagrant::Util::Platform.freebsd?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/freebsd/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostFreeBSD\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"FreeBSD host\"\n      description \"FreeBSD host support.\"\n\n      host(\"freebsd\", \"bsd\") do\n        require_relative \"host\"\n        Host\n      end\n\n      host_capability(\"freebsd\", \"nfs_export\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      # BSD-specific helpers\n      host_capability(\"freebsd\", \"nfs_exports_template\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"freebsd\", \"nfs_restart_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/gentoo/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/subprocess\"\nrequire \"vagrant/util/which\"\n\nmodule VagrantPlugins\n  module HostGentoo\n    module Cap\n      class NFS\n        def self.nfs_check_command(env)\n          if Vagrant::Util::Platform.systemd?\n            \"#{systemctl_path} status --no-pager nfs-server.service\"\n          else\n            \"/etc/init.d/nfs status\"\n          end\n        end\n\n        def self.nfs_start_command(env)\n          if Vagrant::Util::Platform.systemd?\n            \"#{systemctl_path} start rpcbind nfs-server.service\"\n          else\n            \"/etc/init.d/nfs restart\"\n          end\n        end\n\n        protected\n\n        def self.systemctl_path\n          path = Vagrant::Util::Which.which(\"systemctl\")\n          return path if path\n\n          folders = [\"/usr/bin\", \"/usr/sbin\"]\n          folders.each do |folder|\n            path = \"#{folder}/systemctl\"\n            return path if File.file?(path)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/gentoo/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostGentoo\n    class Host < Vagrant.plugin(\"2\", :host)\n      def detect?(env)\n        File.exist?(\"/etc/gentoo-release\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/gentoo/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostGentoo\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Gentoo host\"\n      description \"Gentoo host support.\"\n\n      host(\"gentoo\", \"linux\") do\n        require_relative \"host\"\n        Host\n      end\n\n      # Linux-specific helpers we need to determine paths that can\n      # be overridden.\n      host_capability(\"gentoo\", \"nfs_check_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"gentoo\", \"nfs_start_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/linux/cap/fs_iso.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"vagrant/util/caps\"\n\nmodule VagrantPlugins\n  module HostLinux\n    module Cap\n      class FsISO\n        extend Vagrant::Util::Caps::BuildISO\n\n        @@logger = Log4r::Logger.new(\"vagrant::host::linux::fs_iso\")\n\n        BUILD_ISO_CMD = \"mkisofs\".freeze\n\n        # Check that the host has the ability to generate ISOs\n        #\n        # @param [Vagrant::Environment] env\n        # @return [Boolean]\n        def self.isofs_available(env)\n          !!Vagrant::Util::Which.which(BUILD_ISO_CMD)\n        end\n\n        # Generate an ISO file of the given source directory\n        #\n        # @param [Vagrant::Environment] env\n        # @param [String] source_directory Contents of ISO\n        # @param [Map] extra arguments to pass to the iso building command\n        #              :file_destination (string) location to store ISO\n        #              :volume_id (String) to set the volume name\n        # @return [Pathname] ISO location\n        # @note If file_destination exists, source_directory will be checked\n        #       for recent modifications and a new ISO will be generated if requried.\n        def self.create_iso(env, source_directory, extra_opts={})\n          source_directory = Pathname.new(source_directory)\n          file_destination = self.ensure_output_iso(extra_opts[:file_destination])\n\n          iso_command = [BUILD_ISO_CMD, \"-joliet\"]\n          iso_command.concat([\"-volid\", extra_opts[:volume_id]]) if extra_opts[:volume_id]\n          iso_command << \"-o\"\n          iso_command << file_destination.to_s\n          iso_command << source_directory.to_s\n          self.build_iso(iso_command, source_directory, file_destination)\n\n          @@logger.info(\"ISO available at #{file_destination}\")\n          file_destination\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/linux/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"shellwords\"\nrequire \"vagrant/util\"\nrequire \"vagrant/util/shell_quote\"\nrequire \"vagrant/util/retryable\"\n\nmodule VagrantPlugins\n  module HostLinux\n    module Cap\n      class NFS\n\n        NFS_EXPORTS_PATH = \"/etc/exports\".freeze\n        NFS_DEFAULT_NAME_SYSTEMD = \"nfs-server.service\".freeze\n        NFS_DEFAULT_NAME_SYSV = \"nfs-kernel-server\".freeze\n        extend Vagrant::Util::Retryable\n\n        def self.nfs_service_name_systemd\n          if !defined?(@_nfs_systemd)\n            result = Vagrant::Util::Subprocess.execute(\"systemctl\", \"list-units\",\n              \"*nfs*server*\", \"--no-pager\", \"--no-legend\")\n            if result.exit_code == 0\n              @_nfs_systemd = result.stdout.to_s.split(/\\s+/).first\n            end\n            if @_nfs_systemd.to_s.empty?\n              @_nfs_systemd = NFS_DEFAULT_NAME_SYSTEMD\n            end\n          end\n          @_nfs_systemd\n        end\n\n        def self.nfs_service_name_sysv\n          if !defined?(@_nfs_sysv)\n            @_nfs_sysv = Dir.glob(\"/etc/init.d/*nfs*server*\").first.to_s\n            if @_nfs_sysv.empty?\n              @_nfs_sysv = NFS_DEFAULT_NAME_SYSV\n            else\n              @_nfs_sysv = File.basename(@_nfs_sysv)\n            end\n          end\n          @_nfs_sysv\n        end\n\n        def self.nfs_apply_command(env)\n          \"exportfs -ar\"\n        end\n\n        def self.nfs_check_command(env)\n          if Vagrant::Util::Platform.systemd?\n            \"systemctl status --no-pager #{nfs_service_name_systemd}\"\n          else\n            \"/etc/init.d/#{nfs_service_name_sysv} status\"\n          end\n        end\n\n        def self.nfs_start_command(env)\n          if Vagrant::Util::Platform.systemd?\n            \"systemctl start #{nfs_service_name_systemd}\"\n          else\n            \"/etc/init.d/#{nfs_service_name_sysv} start\"\n          end\n        end\n\n        def self.nfs_export(env, ui, id, ips, folders)\n          # Get some values we need before we do anything\n          nfs_apply_command = env.host.capability(:nfs_apply_command)\n          nfs_check_command = env.host.capability(:nfs_check_command)\n          nfs_start_command = env.host.capability(:nfs_start_command)\n\n          nfs_opts_setup(folders)\n          folders = folder_dupe_check(folders)\n          ips = ips.uniq\n          output = Vagrant::Util::TemplateRenderer.render('nfs/exports_linux',\n                                           uuid: id,\n                                           ips: ips,\n                                           folders: folders,\n                                           user: Process.uid)\n\n          ui.info I18n.t(\"vagrant.hosts.linux.nfs_export\")\n          sleep 0.5\n\n          nfs_cleanup(\"#{Process.uid} #{id}\")\n          output = nfs_exports_content + output\n          nfs_write_exports(output)\n\n          if nfs_running?(nfs_check_command)\n            Vagrant::Util::Subprocess.execute(\"sudo\", *Shellwords.split(nfs_apply_command)).exit_code == 0\n          else\n            Vagrant::Util::Subprocess.execute(\"sudo\", *Shellwords.split(nfs_start_command)).exit_code == 0\n          end\n        end\n\n        def self.nfs_installed(environment)\n          if Vagrant::Util::Platform.systemd?\n            Vagrant::Util::Subprocess.execute(\"/bin/sh\", \"-c\",\n              \"systemctl --no-pager --no-legend --plain list-unit-files --all --type=service \" \\\n                \"| grep #{nfs_service_name_systemd}\").exit_code == 0\n          else\n            Vagrant::Util::Subprocess.execute(modinfo_path, \"nfsd\").exit_code == 0 ||\n              Vagrant::Util::Subprocess.execute(\"grep\", \"nfsd\", \"/proc/filesystems\").exit_code == 0\n          end\n        end\n\n        def self.nfs_prune(environment, ui, valid_ids)\n          return if !File.exist?(NFS_EXPORTS_PATH)\n\n          logger = Log4r::Logger.new(\"vagrant::hosts::linux\")\n          logger.info(\"Pruning invalid NFS entries...\")\n\n          user = Process.uid\n\n          # Create editor instance for removing invalid IDs\n          editor = Vagrant::Util::StringBlockEditor.new(nfs_exports_content)\n\n          # Build composite IDs with UID information and discover invalid entries\n          composite_ids = valid_ids.map do |v_id|\n            \"#{user} #{v_id}\"\n          end\n          remove_ids = editor.keys - composite_ids\n\n          logger.debug(\"Known valid NFS export IDs: #{valid_ids}\")\n          logger.debug(\"Composite valid NFS export IDs with user: #{composite_ids}\")\n          logger.debug(\"NFS export IDs to be removed: #{remove_ids}\")\n          if !remove_ids.empty?\n            ui.info I18n.t(\"vagrant.hosts.linux.nfs_prune\")\n            nfs_cleanup(remove_ids)\n          end\n        end\n\n        protected\n\n        # Takes a hash of folders and removes any duplicate exports that\n        # share the same hostpath to avoid duplicate entries in /etc/exports\n        # ref: GH-4666\n        def self.folder_dupe_check(folders)\n          return_folders = {}\n          # Group by hostpath to see if there are multiple exports coming\n          # from the same folder\n          export_groups = folders.values.group_by { |h| h[:hostpath] }\n\n          # We need to check that each group key only has 1 value,\n          # and if not, check each nfs option. If all nfs options are the same\n          # we're good, otherwise throw an exception\n          export_groups.each do |path,group|\n            if group.size > 1\n              # if the linux nfs options aren't all the same throw an exception\n              group1_opts = group.first[:linux__nfs_options]\n\n              if !group.all? {|g| g[:linux__nfs_options] == group1_opts}\n                raise Vagrant::Errors::NFSDupePerms, hostpath: group.first[:hostpath]\n              else\n                # if they're the same just pick the first one\n                return_folders[path] = group.first\n              end\n            else\n              # just return folder, there are no duplicates\n              return_folders[path] = group.first\n            end\n          end\n          return_folders\n        end\n\n        def self.nfs_cleanup(remove_ids)\n          return if !File.exist?(NFS_EXPORTS_PATH)\n\n          editor = Vagrant::Util::StringBlockEditor.new(nfs_exports_content)\n          remove_ids = Array(remove_ids)\n\n          # Remove all invalid ID entries\n          remove_ids.each do |r_id|\n            editor.delete(r_id)\n          end\n          nfs_write_exports(editor.value)\n        end\n\n        def self.nfs_write_exports(new_exports_content)\n          if(nfs_exports_content != new_exports_content.strip)\n            begin\n              exports_path = Pathname.new(NFS_EXPORTS_PATH)\n\n              # Write contents out to temporary file\n              new_exports_path = File.join(Dir.tmpdir, \"vagrant-exports\")\n              FileUtils.rm_f(new_exports_path)\n              new_exports_file = File.open(new_exports_path, \"w+\")\n              new_exports_file.puts(new_exports_content)\n              new_exports_file.close\n\n              # Ensure new file mode and uid/gid match existing file to replace\n              existing_stat = File.stat(NFS_EXPORTS_PATH)\n              new_stat = File.stat(new_exports_path)\n              if existing_stat.mode != new_stat.mode\n                File.chmod(existing_stat.mode, new_exports_path)\n              end\n              if existing_stat.uid != new_stat.uid || existing_stat.gid != new_stat.gid\n                chown_cmd = \"sudo chown #{existing_stat.uid}:#{existing_stat.gid} #{new_exports_path}\"\n                result = Vagrant::Util::Subprocess.execute(*Shellwords.split(chown_cmd))\n                if result.exit_code != 0\n                  raise Vagrant::Errors::NFSExportsFailed,\n                    command: chown_cmd,\n                    stderr: result.stderr,\n                    stdout: result.stdout\n                end\n              end\n              # Always force move the file to prevent overwrite prompting\n              sudo_command = \"sudo \" if !exports_path.writable? || !exports_path.dirname.writable?\n              mv_cmd = \"#{sudo_command}mv -f #{new_exports_path} #{NFS_EXPORTS_PATH}\"\n              result = Vagrant::Util::Subprocess.execute(*Shellwords.split(mv_cmd))\n              if result.exit_code != 0\n                raise Vagrant::Errors::NFSExportsFailed,\n                  command: mv_cmd,\n                  stderr: result.stderr,\n                  stdout: result.stdout\n              end\n            ensure\n              if !new_exports_path.nil? && File.exist?(new_exports_path)\n                File.unlink(new_exports_path)\n              end\n            end\n          end\n        end\n\n        def self.nfs_exports_content\n          if(File.exist?(NFS_EXPORTS_PATH))\n            if(File.readable?(NFS_EXPORTS_PATH))\n              File.read(NFS_EXPORTS_PATH)\n            else\n              cmd = \"sudo cat #{NFS_EXPORTS_PATH}\"\n              result = Vagrant::Util::Subprocess.execute(*Shellwords.split(cmd))\n              if result.exit_code != 0\n                raise Vagrant::Errors::NFSExportsFailed,\n                  command: cmd,\n                  stderr: result.stderr,\n                  stdout: result.stdout\n              else\n                result.stdout\n              end\n            end\n          else\n            \"\"\n          end\n        end\n\n        def self.nfs_opts_setup(folders)\n          folders.each do |k, opts|\n            if !opts[:linux__nfs_options]\n              opts[:linux__nfs_options] ||= [\"rw\", \"no_subtree_check\", \"all_squash\"]\n            end\n\n            # Only automatically set anonuid/anongid if they weren't\n            # explicitly set by the user.\n            hasgid = false\n            hasuid = false\n            opts[:linux__nfs_options].each do |opt|\n              hasgid = !!(opt =~ /^anongid=/) if !hasgid\n              hasuid = !!(opt =~ /^anonuid=/) if !hasuid\n            end\n\n            opts[:linux__nfs_options] << \"anonuid=#{opts[:map_uid]}\" if !hasuid\n            opts[:linux__nfs_options] << \"anongid=#{opts[:map_gid]}\" if !hasgid\n            opts[:linux__nfs_options] << \"fsid=#{opts[:uuid]}\"\n          end\n        end\n\n        def self.nfs_running?(check_command)\n          Vagrant::Util::Subprocess.execute(*Shellwords.split(check_command)).exit_code == 0\n        end\n\n        def self.modinfo_path\n          if !defined?(@_modinfo_path)\n            @_modinfo_path = Vagrant::Util::Which.which(\"modinfo\")\n\n            if @_modinfo_path.to_s.empty?\n              path = \"/sbin/modinfo\"\n              if File.file?(path)\n                @_modinfo_path = path\n              end\n            end\n\n            if @_modinfo_path.to_s.empty?\n              @_modinfo_path = \"modinfo\"\n            end\n          end\n          @_modinfo_path\n        end\n\n        # @private\n        # Reset the cached values for capability. This is not considered a public\n        # API and should only be used for testing.\n        def self.reset!\n          instance_variables.each(&method(:remove_instance_variable))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/linux/cap/rdp.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/which\"\n\nmodule VagrantPlugins\n  module HostLinux\n    module Cap\n      class RDP\n        def self.rdp_client(env, rdp_info)\n          # Detect if an RDP client is available.\n          # Prefer xfreerdp as it supports newer versions of RDP.\n          rdp_client =\n            if Vagrant::Util::Which.which(\"xfreerdp\")\n              \"xfreerdp\"\n            elsif Vagrant::Util::Which.which(\"rdesktop\")\n              \"rdesktop\"\n            else\n              if Vagrant::Util::Platform.wsl?\n                \"mstsc.exe\"\n              else\n                raise Vagrant::Errors::LinuxRDPClientNotFound\n              end\n            end\n\n          args = []\n\n          # Build appropriate arguments for the RDP client.\n          case rdp_client\n          when \"xfreerdp\"\n            args << \"/u:#{rdp_info[:username]}\"\n            args << \"/p:#{rdp_info[:password]}\" if rdp_info[:password]\n            args << \"/v:#{rdp_info[:host]}:#{rdp_info[:port]}\"\n            args += rdp_info[:extra_args] if rdp_info[:extra_args]\n          when \"rdesktop\"\n            args << \"-u\" << rdp_info[:username]\n            args << \"-p\" << rdp_info[:password] if rdp_info[:password]\n            args += rdp_info[:extra_args] if rdp_info[:extra_args]\n            args << \"#{rdp_info[:host]}:#{rdp_info[:port]}\"\n          when \"mstsc.exe\"\n            # Setup password\n            cmdKeyArgs = [\n              \"/add:#{rdp_info[:host]}:#{rdp_info[:port]}\",\n              \"/user:#{rdp_info[:username]}\",\n              \"/pass:#{rdp_info[:password]}\",\n            ]\n            Vagrant::Util::Subprocess.execute(\"cmdkey.exe\", *cmdKeyArgs)\n\n            args = [\"/v:#{rdp_info[:host]}:#{rdp_info[:port]}\"]\n            args += rdp_info[:extra_args] if rdp_info[:extra_args]\n          end\n\n          # Finally, run the client.\n          Vagrant::Util::Subprocess.execute(rdp_client, *args, {:detach => true})\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/linux/cap/ssh.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostLinux\n    module Cap\n      class SSH\n        # Set the ownership and permissions for SSH\n        # private key\n        #\n        # @param [Vagrant::Environment] env\n        # @param [Pathname] key_path\n        def self.set_ssh_key_permissions(env, key_path)\n          key_path.chmod(0600)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/linux/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostLinux\n    # Represents a Linux based host, such as Ubuntu.\n    class Host < Vagrant.plugin(\"2\", :host)\n      def detect?(env)\n        Vagrant::Util::Platform.linux?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/linux/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostLinux\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Linux host\"\n      description \"Linux host support.\"\n\n      host(\"linux\") do\n        require_relative \"host\"\n        Host\n      end\n\n      host_capability(\"linux\", \"isofs_available\") do\n        require_relative \"cap/fs_iso\"\n        Cap::FsISO\n      end\n\n      host_capability(\"linux\", \"create_iso\") do\n        require_relative \"cap/fs_iso\"\n        Cap::FsISO\n      end\n\n      host_capability(\"linux\", \"nfs_export\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"linux\", \"nfs_installed\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"linux\", \"nfs_prune\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"linux\", \"rdp_client\") do\n        require_relative \"cap/rdp\"\n        Cap::RDP\n      end\n\n      # Linux-specific helpers we need to determine paths that can\n      # be overridden.\n      host_capability(\"linux\", \"nfs_apply_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"linux\", \"nfs_check_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"linux\", \"nfs_start_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"linux\", \"set_ssh_key_permissions\") do\n        require_relative \"cap/ssh\"\n        Cap::SSH\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/null/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostNull\n    class Host < Vagrant.plugin(\"2\", :host)\n      def detect?(env)\n        # This host can only be explicitly chosen.\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/null/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostNull\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"null host\"\n      description \"A host that implements no capabilities.\"\n\n      host(\"null\") do\n        require_relative \"host\"\n        Host\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/redhat/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nmodule VagrantPlugins\n  module HostRedHat\n    module Cap\n      class NFS\n        def self.nfs_check_command(env)\n          if Vagrant::Util::Platform.systemd?\n            \"systemctl status --no-pager nfs-server.service\"\n          else\n            \"#{nfs_server_binary} status\"\n          end\n        end\n\n        def self.nfs_start_command(env)\n          if Vagrant::Util::Platform.systemd?\n            \"systemctl start nfs-server.service\"\n          else\n            \"#{nfs_server_binary} start\"\n          end\n        end\n\n        protected\n\n        def self.nfs_server_binary\n          nfs_server_binary = \"/etc/init.d/nfs\"\n\n          # On Fedora 16+, systemd replaced init.d, so we have to use the\n          # proper NFS binary. This checks to see if we need to do that.\n          release_file = Pathname.new(\"/etc/redhat-release\")\n          begin\n            release_file.open(\"r:ISO-8859-1:UTF-8\") do |f|\n              match = /(Red Hat|CentOS|Fedora).* release ([0-9]+)/.match(f.gets)\n              if match\n                distribution = match[1]\n                version_number = match[2].to_i\n                if (distribution =~ /Fedora/ && version_number >= 16) ||\n                   (distribution =~ /Red Hat|CentOS/ && version_number >= 7)\n                  # \"service nfs-server\" will redirect properly to systemctl\n                  # when \"service nfs-server restart\" is called.\n                  nfs_server_binary = \"/usr/sbin/service nfs-server\"\n                end\n              end\n            end\n          rescue Errno::ENOENT\n            # File doesn't exist, not a big deal, assume we're on a\n            # lower version.\n          end\n\n          nfs_server_binary\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/redhat/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nmodule VagrantPlugins\n  module HostRedHat\n    class Host < Vagrant.plugin(\"2\", :host)\n      def detect?(env)\n        release_file = Pathname.new(\"/etc/redhat-release\")\n\n        if release_file.exist?\n          release_file.open(\"r:ISO-8859-1:UTF-8\") do |f|\n            contents = f.gets\n            return true if contents =~ /^CentOS/ # CentOS\n            return true if contents =~ /^Fedora/ # Fedora\n            return true if contents =~ /^Korora/ # Korora\n\n            # Oracle Linux < 5.3\n            return true if contents =~ /^Enterprise Linux Enterprise Linux/\n\n            # Red Hat Enterprise Linux and Oracle Linux >= 5.3\n            return true if contents =~ /^Red Hat Enterprise Linux/\n          end\n        end\n\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/redhat/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostRedHat\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Red Hat Enterprise Linux host\"\n      description \"Red Hat Enterprise Linux host support.\"\n\n      host(\"redhat\", \"linux\") do\n        require File.expand_path(\"../host\", __FILE__)\n        Host\n      end\n\n      # Linux-specific helpers we need to determine paths that can\n      # be overridden.\n      host_capability(\"redhat\", \"nfs_check_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"redhat\", \"nfs_start_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/slackware/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostSlackware\n    module Cap\n      class NFS\n        def self.nfs_check_command(env)\n          \"/sbin/pidof nfsd >/dev/null\"\n        end\n\n        def self.nfs_start_command(env)\n          \"/etc/rc.d/rc.nfsd start\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/slackware/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostSlackware\n    class Host < Vagrant.plugin(\"2\", :host)\n      def detect?(env)\n        return File.exist?(\"/etc/slackware-version\") ||\n          !Dir.glob(\"/usr/lib/setup/Plamo-*\").empty?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/slackware/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostSlackware\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Slackware host\"\n      description \"Slackware and derivatives host support.\"\n\n      host(\"slackware\", \"linux\") do\n        require File.expand_path(\"../host\", __FILE__)\n        Host\n      end\n\n      # Linux-specific helpers we need to determine paths that can\n      # be overridden.\n      host_capability(\"slackware\", \"nfs_check_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"slackware\", \"nfs_start_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/suse/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostSUSE\n    module Cap\n      class NFS\n        def self.nfs_installed(env)\n          system(\"rpm -q nfs-kernel-server > /dev/null 2>&1\")\n        end\n\n        def self.nfs_check_command(env)\n          \"systemctl status --no-pager nfs-server\"\n        end\n\n        def self.nfs_start_command(env)\n          \"systemctl start --no-pager nfs-server\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/suse/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nmodule VagrantPlugins\n  module HostSUSE\n    class Host < Vagrant.plugin(\"2\", :host)\n      def detect?(env)\n        old_release_file = Pathname.new(\"/etc/SuSE-release\")\n\n        if old_release_file.exist?\n          old_release_file.open(\"r\") do |f|\n            return true if f.gets =~ /^(openSUSE|SUSE Linux Enterprise)/\n          end\n        end\n\n        new_release_file = Pathname.new(\"/etc/os-release\")\n\n        if new_release_file.exist?\n          new_release_file.open(\"r\") do |f|\n            return true if f.gets =~ /(openSUSE|SLES)/\n          end\n        end\n\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/suse/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostSUSE\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"SUSE host\"\n      description \"SUSE host support.\"\n\n      host(\"suse\", \"linux\") do\n        require_relative \"host\"\n        Host\n      end\n\n      host_capability(\"suse\", \"nfs_installed\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"suse\", \"nfs_check_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"suse\", \"nfs_start_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/void/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostVoid\n    module Cap\n      class NFS\n        def self.nfs_check_command(env)\n          \"sudo /usr/bin/sv status nfs-server\"\n        end\n\n        def self.nfs_start_command(env)\n          <<-EOF\n            /usr/bin/ln -s /etc/sv/statd      /var/service/ && \\\n            /usr/bin/ln -s /etc/sv/rpcbind    /var/service/ && \\\n            /usr/bin/ln -s /etc/sv/nfs-server /var/service/\n          EOF\n        end\n\n        def self.nfs_installed(env)\n          result = Vagrant::Util::Subprocess.execute(\"/usr/bin/xbps-query\", \"nfs-utils\")\n          result.exit_code == 0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/void/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'pathname'\n\nmodule VagrantPlugins\n  module HostVoid\n    class Host < Vagrant.plugin(\"2\", :host)\n      def detect?(env)\n        os_file = Pathname.new(\"/etc/os-release\")\n\n        if os_file.exist?\n          file = os_file.open\n          while (line = file.gets) do\n            return true if line =~ /^ID=\"void\"/\n          end\n        end\n\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/void/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostVoid\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Void host\"\n      description \"Void linux host support.\"\n\n      host(\"void\", \"linux\") do\n        require_relative \"host\"\n        Host\n      end\n\n      host_capability(\"void\", \"nfs_installed\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"void\", \"nfs_check_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"void\", \"nfs_start_command\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/windows/cap/configured_ip_addresses.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tempfile\"\n\nrequire \"vagrant/util/downloader\"\nrequire \"vagrant/util/file_checksum\"\nrequire \"vagrant/util/powershell\"\nrequire \"vagrant/util/subprocess\"\n\nmodule VagrantPlugins\n  module HostWindows\n    module Cap\n      class ConfiguredIPAddresses\n\n        def self.configured_ip_addresses(env)\n          script_path = File.expand_path(\"../../scripts/host_info.ps1\", __FILE__)\n          r = Vagrant::Util::PowerShell.execute(script_path)\n          if r.exit_code != 0\n            raise Vagrant::Errors::PowerShellError,\n              script: script_path,\n              stderr: r.stderr\n          end\n\n          res = JSON.parse(r.stdout)[\"ip_addresses\"]\n          Array(res)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/windows/cap/fs_iso.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"vagrant/util/caps\"\n\nmodule VagrantPlugins\n  module HostWindows\n    module Cap\n      class FsISO\n        extend Vagrant::Util::Caps::BuildISO\n\n        @@logger = Log4r::Logger.new(\"vagrant::host::windows::fs_iso\")\n\n        BUILD_ISO_CMD = \"oscdimg.exe\".freeze\n        DEPLOYMENT_KIT_PATHS = [\n          \"C:/Program Files (x86)/Windows Kits/10/Assessment and Deployment Kit/Deployment Tools\".freeze,\n        ].freeze\n\n        # Check that the host has the ability to generate ISOs\n        #\n        # @param [Vagrant::Environment] env\n        # @return [Boolean]\n        def self.isofs_available(env)\n          begin\n            oscdimg_path\n            true\n          rescue Vagrant::Errors::OscdimgCommandMissingError\n            false\n          end\n        end\n\n        # Generate an ISO file of the given source directory\n        #\n        # @param [Vagrant::Environment] env\n        # @param [String] source_directory Contents of ISO\n        # @param [Map] extra arguments to pass to the iso building command\n        #              :file_destination (string) location to store ISO\n        #              :volume_id (String) to set the volume name\n        # @return [Pathname] ISO location\n        # @note If file_destination exists, source_directory will be checked\n        #       for recent modifications and a new ISO will be generated if requried.\n        def self.create_iso(env, source_directory, extra_opts={})\n          source_directory = Pathname.new(source_directory)\n          file_destination = self.ensure_output_iso(extra_opts[:file_destination])\n\n          iso_command = [oscdimg_path, \"-j1\", \"-o\", \"-m\"]\n          iso_command << \"-l#{extra_opts[:volume_id]}\" if extra_opts[:volume_id]\n          iso_command << source_directory.to_s\n          iso_command << file_destination.to_s\n          self.build_iso(iso_command, source_directory, file_destination)\n\n          @@logger.info(\"ISO available at #{file_destination}\")\n          file_destination\n        end\n\n        # @return [String] oscdimg executable\n        def self.oscdimg_path\n          return BUILD_ISO_CMD if Vagrant::Util::Which.which(BUILD_ISO_CMD)\n          @@logger.debug(\"#{BUILD_ISO_CMD} not found on PATH\")\n          DEPLOYMENT_KIT_PATHS.each do |base|\n            path = File.join(base, Vagrant::Util::Platform.architecture,\n              \"Oscdimg\", BUILD_ISO_CMD)\n            @@logger.debug(\"#{BUILD_ISO_CMD} check at #{path}\")\n            return path if File.executable?(path)\n          end\n\n          raise Vagrant::Errors::OscdimgCommandMissingError\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/windows/cap/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostWindows\n    module Cap\n      class NFS\n        def self.nfs_installed(env)\n          false\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/windows/cap/provider_install_virtualbox.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tempfile\"\n\nrequire \"vagrant/util/downloader\"\nrequire \"vagrant/util/file_checksum\"\nrequire \"vagrant/util/powershell\"\nrequire \"vagrant/util/subprocess\"\n\nmodule VagrantPlugins\n  module HostWindows\n    module Cap\n      class ProviderInstallVirtualBox\n        # The URL to download VirtualBox is hardcoded so we can have a\n        # known-good version to download.\n        URL = \"http://download.virtualbox.org/virtualbox/5.0.10/VirtualBox-5.0.10-104061-Win.exe\".freeze\n        VERSION = \"5.0.10\".freeze\n        SHA256SUM = \"3e5ed8fe4ada6eef8dfb4fe6fd79fcab4b242acf799f7d3ab4a17b43838b1e04\".freeze\n\n        def self.provider_install_virtualbox(env)\n          path = Dir::Tmpname.create(\"vagrant-provider-install-virtualbox\") {}\n\n          # Prefixed UI for prettiness\n          ui = Vagrant::UI::Prefixed.new(env.ui, \"\")\n\n          # Start by downloading the file using the standard mechanism\n          ui.output(I18n.t(\n            \"vagrant.hosts.windows.virtualbox_install_download\",\n            version: VERSION))\n          ui.detail(I18n.t(\n            \"vagrant.hosts.windows.virtualbox_install_detail\"))\n          dl = Vagrant::Util::Downloader.new(URL, path, ui: ui)\n          dl.download!\n\n          # Validate that the file checksum matches\n          actual = FileChecksum.new(path, Digest::SHA2).checksum\n          if actual != SHA256SUM\n            raise Vagrant::Errors::ProviderChecksumMismatch,\n              provider: \"virtualbox\",\n              actual: actual,\n              expected: SHA256SUM\n          end\n\n          # Launch it\n          ui.output(I18n.t(\n            \"vagrant.hosts.windows.virtualbox_install_install\"))\n          ui.detail(I18n.t(\n            \"vagrant.hosts.windows.virtualbox_install_install_detail\"))\n          script = File.expand_path(\"../../scripts/install_virtualbox.ps1\", __FILE__)\n          result = Vagrant::Util::PowerShell.execute(script, path)\n          if result.exit_code != 0\n            raise Vagrant::Errors::ProviderInstallFailed,\n              provider: \"virtualbox\",\n              stdout: result.stdout,\n              stderr: result.stderr\n          end\n\n          ui.success(I18n.t(\"vagrant.hosts.windows.virtualbox_install_success\"))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/windows/cap/ps.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tmpdir\"\n\nrequire \"vagrant/util/safe_exec\"\n\nmodule VagrantPlugins\n  module HostWindows\n    module Cap\n      class PS\n        def self.ps_client(env, ps_info)\n          logger = Log4r::Logger.new(\"vagrant::hosts::windows\")\n\n          command = <<-EOS\n            $plain_password = \"#{ps_info[:password]}\"\n            $username = \"#{ps_info[:username]}\"\n            $port = \"#{ps_info[:port]}\"\n            $hostname = \"#{ps_info[:host]}\"\n            $password = ConvertTo-SecureString $plain_password -asplaintext -force\n            $creds = New-Object System.Management.Automation.PSCredential (\"$hostname\\\\$username\", $password)\n            function prompt { kill $PID }\n            Enter-PSSession -ComputerName $hostname -Credential $creds -Port $port\n          EOS\n\n          logger.debug(\"Starting remote powershell with command:\\n#{command}\")\n\n          args = [\"-NoProfile\"]\n          args << \"-ExecutionPolicy\"\n          args << \"Bypass\"\n          args << \"-NoExit\"\n          args << \"-EncodedCommand\"\n          args << encoded(command)\n          if ps_info[:extra_args]\n            args << ps_info[:extra_args]\n          end\n\n          # Launch it\n          Vagrant::Util::SafeExec.exec(\"powershell\", *args)\n        end\n\n        def self.encoded(script)\n          encoded_script = script.encode('UTF-16LE', 'UTF-8')\n          Base64.strict_encode64(encoded_script)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/windows/cap/rdp.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tmpdir\"\n\nrequire \"vagrant/util/subprocess\"\n\nmodule VagrantPlugins\n  module HostWindows\n    module Cap\n      class RDP\n        def self.rdp_client(env, rdp_info)\n          # Setup password\n          cmdKeyArgs = [\n            \"/add:#{rdp_info[:host]}:#{rdp_info[:port]}\",\n            \"/user:#{rdp_info[:username]}\",\n            \"/pass:#{rdp_info[:password]}\",\n          ]\n          Vagrant::Util::Subprocess.execute(\"cmdkey\", *cmdKeyArgs)\n\n          # Build up the args to mstsc\n          args = [\"/v:#{rdp_info[:host]}:#{rdp_info[:port]}\"]\n          if rdp_info[:extra_args]\n            args = rdp_info[:extra_args] + args\n          end\n          # Launch it\n          Vagrant::Util::Subprocess.execute(\"mstsc\", *args, {:detach => true})\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/windows/cap/smb.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostWindows\n    module Cap\n      class SMB\n\n        # Number of seconds to display UAC warning to user\n        UAC_PROMPT_WAIT = 4\n\n        @@logger = Log4r::Logger.new(\"vagrant::host::windows::smb\")\n\n        def self.smb_installed(env)\n          psv = Vagrant::Util::PowerShell.version.to_i\n          if psv < 3\n            return false\n          end\n\n          true\n        end\n\n        # Required options for mounting a share hosted on Windows\n        # NOTE: Windows deprecated smb 1.0 so a minimum of 2.0 must be enabled\n        def self.smb_mount_options(env)\n          [\"vers=2.0\"]\n        end\n\n        def self.smb_validate_password(env, machine, username, password)\n          script_path = File.expand_path(\"../../scripts/check_credentials.ps1\", __FILE__)\n          args = []\n          args << \"-username\" << \"'#{username.gsub(\"'\", \"''\")}'\"\n          args << \"-password\" << \"'#{password.gsub(\"'\", \"''\")}'\"\n\n          r = Vagrant::Util::PowerShell.execute(script_path, *args)\n          r.exit_code == 0\n        end\n\n        def self.smb_cleanup(env, machine, opts)\n          script_path = File.expand_path(\"../../scripts/unset_share.ps1\", __FILE__)\n\n          m_id = machine_id(machine)\n          prune_shares = existing_shares.map do |share_name, share_info|\n            if share_info[\"Description\"].to_s.start_with?(\"vgt-#{m_id}-\")\n              @@logger.info(\"removing smb share name=#{share_name} id=#{m_id}\")\n              share_name\n            else\n              @@logger.info(\"skipping smb share removal, not owned name=#{share_name}\")\n              @@logger.debug(\"smb share ID not present name=#{share_name} id=#{m_id} description=#{share_info[\"Description\"]}\")\n              nil\n            end\n          end.compact\n\n          @@logger.debug(\"shares to be removed: #{prune_shares}\")\n\n          if prune_shares.size > 0\n            machine.env.ui.warn(\"\\n\" + I18n.t(\"vagrant_sf_smb.uac.prune_warning\") + \"\\n\")\n            sleep UAC_PROMPT_WAIT\n            @@logger.info(\"remove shares: #{prune_shares}\")\n            result = Vagrant::Util::PowerShell.execute(script_path, *prune_shares, sudo: true)\n            if result.exit_code != 0\n              failed_name = result.stdout.to_s.sub(\"share name: \", \"\")\n              raise SyncedFolderSMB::Errors::PruneShareFailed,\n                name: failed_name,\n                stderr: result.stderr,\n                stdout: result.stdout\n            end\n          end\n        end\n\n        def self.smb_prepare(env, machine, folders, opts)\n          script_path = File.expand_path(\"../../scripts/set_share.ps1\", __FILE__)\n\n          shares = []\n          current_shares = existing_shares\n          folders.each do |id, data|\n            hostpath = data[:hostpath].to_s\n\n            chksum_id = Digest::MD5.hexdigest(id)\n            name = \"vgt-#{machine_id(machine)}-#{chksum_id}\"\n            data[:smb_id] ||= name\n\n            # Check if this name is already in use\n            if share_info = current_shares[data[:smb_id]]\n              exist_path = File.expand_path(share_info[\"Path\"]).downcase\n              request_path = File.expand_path(hostpath).downcase\n              if !hostpath.empty? && exist_path != request_path\n                raise SyncedFolderSMB::Errors::SMBNameError,\n                  path: hostpath,\n                  existing_path: share_info[\"Path\"],\n                  name: data[:smb_id]\n              end\n              @@logger.info(\"skip creation of existing share name=#{name} id=#{data[:smb_id]}\")\n              next\n            end\n\n            @@logger.info(\"creating new share name=#{name} id=#{data[:smb_id]}\")\n\n            shares << [\n              \"\\\"#{hostpath.gsub(\"/\", \"\\\\\")}\\\"\",\n              name,\n              data[:smb_id]\n            ]\n          end\n          if !shares.empty?\n            uac_notified = false\n            shares.each_slice(10) do |s_shares|\n              if !uac_notified\n                machine.env.ui.warn(\"\\n\" + I18n.t(\"vagrant_sf_smb.uac.create_warning\") + \"\\n\")\n                uac_notified = true\n                sleep(UAC_PROMPT_WAIT)\n              end\n              result = Vagrant::Util::PowerShell.execute(script_path, *s_shares, sudo: true)\n              if result.exit_code != 0\n                share_path = result.stdout.to_s.sub(\"share path: \", \"\")\n                raise SyncedFolderSMB::Errors::DefineShareFailed,\n                  host: share_path,\n                  stderr: result.stderr,\n                  stdout: result.stdout\n              end\n            end\n          end\n        end\n\n        # Generate a list of existing local smb shares\n        #\n        # @return [Hash]\n        def self.existing_shares\n          shares = get_smbshares || get_netshares\n          if shares.nil?\n            raise SyncedFolderSMB::Errors::SMBListFailed\n          end\n          @@logger.debug(\"local share listing: #{shares}\")\n          shares\n        end\n\n        # Get current SMB share list using Get-SmbShare\n        #\n        # @return [Hash]\n        def self.get_smbshares\n          result = Vagrant::Util::PowerShell.execute_cmd(\"Get-SmbShare|Format-List|Out-String -Width 4096\")\n          if result.nil?\n            return nil\n          end\n          share_data = result.strip.lines\n          shares = {}\n          name = nil\n          until share_data.empty?\n            content = share_data.take_while{|line| !line.strip.empty? }\n            share_name = content[0].strip.split(\":\", 2).last.strip\n            shares[share_name] = {\n              \"Path\" => content[-2].strip.split(\":\", 2).last.strip,\n              \"Description\" => content[-1].strip.split(\":\", 2).last.strip\n            }\n            share_data.slice!(0, content.length + 1)\n          end\n          shares\n        end\n\n        # Get current SMB share list using net.exe\n        #\n        # @return [Hash]\n        def self.get_netshares\n          result = Vagrant::Util::PowerShell.execute_cmd(\"net share | Out-String -Width 4096\")\n          if result.nil?\n            return nil\n          end\n          share_data = result.strip.lines\n          # Remove header information\n          share_data.slice!(0, 2)\n          # Remove footer information\n          share_data.slice!(share_data.size - 1, share_data.size)\n          share_names = share_data.map do |line|\n            line.strip.split(/\\s+/).first.strip\n          end\n          shares = {}\n          share_names.each do |share_name|\n            result = Vagrant::Util::PowerShell.execute_cmd(\"net share #{share_name} |  Out-String -Width 4096\")\n            next if result.nil?\n            result.strip!\n            share_info = result.lines\n            shares[share_name] = {\n              \"Path\" => share_info[1].split(/\\s+/, 2).last.strip,\n              \"Description\" => share_info[2].split(/\\s+/, 2).last.strip\n            }\n          end\n          shares\n        end\n\n        # Generates a unique identifier for the given machine\n        # based on the name, provider name, and working directory\n        # of the environment.\n        #\n        # @param [Vagrant::Machine] machine\n        # @return [String]\n        def self.machine_id(machine)\n          @@logger.debug(\"generating machine ID name=#{machine.name} cwd=#{machine.env.cwd}\")\n          Digest::MD5.hexdigest(\"#{machine.name}-#{machine.provider_name}-#{machine.env.cwd}\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/windows/cap/ssh.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HostWindows\n    module Cap\n      class SSH\n        # Set the ownership and permissions for SSH\n        # private key\n        #\n        # @param [Vagrant::Environment] env\n        # @param [Pathname] key_path\n        def self.set_ssh_key_permissions(env, key_path)\n          script_path = Host.scripts_path.join(\"set_ssh_key_permissions.ps1\")\n          result = Vagrant::Util::PowerShell.execute(\n            script_path.to_s, \"-KeyPath\", key_path.to_s.gsub(' ', '` '),\n            module_path: Host.modules_path.to_s\n          )\n          if result.exit_code != 0\n            raise Vagrant::Errors::PowerShellError,\n              script: script_path,\n              stderr: result.stderr\n          end\n          result\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/windows/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nrequire 'vagrant/util/platform'\n\nmodule VagrantPlugins\n  module HostWindows\n    class Host < Vagrant.plugin(\"2\", :host)\n      def detect?(env)\n        Vagrant::Util::Platform.windows?\n      end\n\n      # @return [Pathname] Path to scripts directory\n      def self.scripts_path\n        Pathname.new(File.expand_path(\"../scripts\", __FILE__))\n      end\n\n      # @return [Pathname] Path to modules directory\n      def self.modules_path\n        scripts_path.join(\"utils\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/windows/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HostWindows\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Windows host\"\n      description \"Windows host support.\"\n\n      host(\"windows\") do\n        require_relative \"host\"\n        Host\n      end\n\n      host_capability(\"windows\", \"isofs_available\") do\n        require_relative \"cap/fs_iso\"\n        Cap::FsISO\n      end\n\n      host_capability(\"windows\", \"create_iso\") do\n        require_relative \"cap/fs_iso\"\n        Cap::FsISO\n      end\n\n      host_capability(\"windows\", \"provider_install_virtualbox\") do\n        require_relative \"cap/provider_install_virtualbox\"\n        Cap::ProviderInstallVirtualBox\n      end\n\n      host_capability(\"windows\", \"nfs_installed\") do\n        require_relative \"cap/nfs\"\n        Cap::NFS\n      end\n\n      host_capability(\"windows\", \"rdp_client\") do\n        require_relative \"cap/rdp\"\n        Cap::RDP\n      end\n\n      host_capability(\"windows\", \"ps_client\") do\n        require_relative \"cap/ps\"\n        Cap::PS\n      end\n\n      host_capability(\"windows\", \"smb_installed\") do\n        require_relative \"cap/smb\"\n        Cap::SMB\n      end\n\n      host_capability(\"windows\", \"smb_prepare\") do\n        require_relative \"cap/smb\"\n        Cap::SMB\n      end\n\n      host_capability(\"windows\", \"smb_cleanup\") do\n        require_relative \"cap/smb\"\n        Cap::SMB\n      end\n\n      host_capability(\"windows\", \"smb_mount_options\") do\n        require_relative \"cap/smb\"\n        Cap::SMB\n      end\n\n      host_capability(\"windows\", \"configured_ip_addresses\") do\n        require_relative \"cap/configured_ip_addresses\"\n        Cap::ConfiguredIPAddresses\n      end\n\n      host_capability(\"windows\", \"set_ssh_key_permissions\") do\n        require_relative \"cap/ssh\"\n        Cap::SSH\n      end\n\n      host_capability(\"windows\", \"smb_validate_password\") do\n        require_relative \"cap/smb\"\n        Cap::SMB\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/hosts/windows/scripts/check_credentials.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nParam(\r\n    [Parameter(Mandatory=$true)]\r\n    [string]$username,\r\n    [Parameter(Mandatory=$true)]\r\n    [string]$password\r\n)\r\n\r\nAdd-Type -AssemblyName System.DirectoryServices.AccountManagement\r\n\r\n$DSContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext(\r\n    [System.DirectoryServices.AccountManagement.ContextType]::Machine,\r\n    $env:COMPUTERNAME\r\n)\r\nif ( $DSContext.ValidateCredentials( $username, $password ) ) {\r\n    exit 0\r\n} \r\n\r\n$DSContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext(\r\n    [System.DirectoryServices.AccountManagement.ContextType]::Domain,\r\n    $env:COMPUTERNAME\r\n)\r\nif ( $DSContext.ValidateCredentials( $username, $password ) ) {\r\n    exit 0\r\n} \r\n\r\n$DSContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext(\r\n    [System.DirectoryServices.AccountManagement.ContextType]::ApplicationDirectory,\r\n    $env:COMPUTERNAME\r\n)\r\nif ( $DSContext.ValidateCredentials( $username, $password ) ) {\r\n    exit 0\r\n} \r\n\r\nexit 1\r\n"
  },
  {
    "path": "plugins/hosts/windows/scripts/host_info.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n$ErrorAction = \"Stop\"\n\n# Find all of the NICsq\n$nics = [System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces()\n\n# Save the IP addresses somewhere\n$nic_ip_addresses = @()\n\nforeach ($nic in $nics) {\n    $nic_ip_addresses += $nic.GetIPProperties().UnicastAddresses | Where-Object {\n      ($_.Address.IPAddressToString -ne \"127.0.0.1\") -and ($_.Address.IPAddressToString -ne \"::1\")\n    } | Select -ExpandProperty Address\n}\n\n$nic_ip_addresses = $nic_ip_addresses | Sort-Object $_.AddressFamily\n\n$result = @{\n\tip_addresses = $nic_ip_addresses.IPAddressToString\n}\n\nWrite-Output $(ConvertTo-Json $result)\n"
  },
  {
    "path": "plugins/hosts/windows/scripts/install_virtualbox.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nParam(\n    [Parameter(Mandatory=$True)]\n    [string]$path\n)\n\n# Stop on first error\n$ErrorActionPreference = \"Stop\"\n\n# Make the path complete\n$path = Resolve-Path $path\n\n# Determine if this is a 64-bit or 32-bit CPU\n$architecture=\"x86\"\nif ((Get-WmiObject -Class Win32_OperatingSystem).OSArchitecture -eq \"64-bit\") {\n    $architecture = \"amd64\"\n}\n\n# Extract the contents of the installer\nStart-Process -FilePath $path `\n    -ArgumentList ('--extract','--silent','--path','.') `\n    -Wait `\n    -NoNewWindow\n\n# Find the installer\n$matches = Get-ChildItem | Where-Object { $_.Name -match \"VirtualBox-.*_$($architecture).msi\" }\nif ($matches.Count -ne 1) {\n    Write-Host \"Multiple matches for VirtualBox MSI found: $($matches.Count)\"\n    exit 1\n}\n$installerPath = Resolve-Path $matches[0]\n\n# Run the installer\nStart-Process -FilePath \"$($env:systemroot)\\System32\\msiexec.exe\" `\n    -ArgumentList \"/i `\"$installerPath`\" /qn /norestart /l*v `\"$($pwd)\\install.log`\"\" `\n    -Verb RunAs `\n    -Wait `\n    -WorkingDirectory \"$pwd\"\n"
  },
  {
    "path": "plugins/hosts/windows/scripts/set_share.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# The names of the user are language dependent!\r\n$objSID = New-Object System.Security.Principal.SecurityIdentifier(\"S-1-1-0\")\r\n$objUser = $objSID.Translate([System.Security.Principal.NTAccount])\r\n\r\n$grant = \"$objUser,Full\"\r\n\r\nfor ($i=0; $i -le $args.length; $i = $i + 3) {\r\n    $path = $args[$i]\r\n    $share_name = $args[$i+1]\r\n    $share_id = $args[$i+2]\r\n\r\n\r\n    if ($path -eq $null) {\r\n        Write-Warning \"empty path argument encountered - complete\"\r\n        exit 0\r\n    }\r\n\r\n    if ($share_name -eq $null) {\r\n        Write-Output \"share path: ${path}\"\r\n        Write-Error \"error - no share name provided\"\r\n        exit 1\r\n    }\r\n\r\n    if ($share_id -eq $null) {\r\n        Write-Output \"share path: ${path}\"\r\n        Write-Error \"error - no share ID provided\"\r\n        exit 1\r\n    }\r\n\r\n    $result = net share $share_id=$path /unlimited /GRANT:$grant /REMARK:\"${share_name}\"\r\n    if ($LastExitCode -ne 0) {\r\n        $host.ui.WriteLine(\"share path: ${path}\")\r\n        $host.ui.WriteErrorLine(\"error ${result}\")\r\n        exit 1\r\n    }\r\n}\r\nexit 0\r\n"
  },
  {
    "path": "plugins/hosts/windows/scripts/set_ssh_key_permissions.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantSSH\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string] $KeyPath,\n    [Parameter(Mandatory=$false)]\n    [string] $Principal=$null\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    Set-SSHKeyPermissions -SSHKeyPath $KeyPath -Principal $Principal\n} catch {\n    Write-Error \"Failed to set permissions on key: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/hosts/windows/scripts/unset_share.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nForEach ($share_name in $args) {\n    $result = net share $share_name /DELETE /YES\n    if ($LastExitCode -ne 0) {\n        Write-Output \"share name: ${share_name}\"\n        Write-Error \"error - ${result}\"\n        exit 1\n    }\n}\nWrite-Output \"share removal completed\"\nexit 0\n"
  },
  {
    "path": "plugins/hosts/windows/scripts/utils/VagrantSSH/VagrantSSH.psm1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# Vagrant SSH capability functions\n\nfunction Set-SSHKeyPermissions {\n    param (\n        [parameter(Mandatory=$true)]\n        [string] $SSHKeyPath,\n        [parameter(Mandatory=$false)]\n        [string] $Principal=$null\n    )\n\n    if(!$Principal) {\n        $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name\n    }\n\n    # Create the new ACL we want to apply\n    $NewAccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule(\n        $Principal, \"FullControl\", \"None\", \"None\", \"Allow\")\n    $ACL = Get-ACL \"${SSHKeyPath}\"\n    # Disable inherited rules\n    $ACL.SetAccessRuleProtection($true, $false)\n    # Scrub all existing ACLs from the file\n    $ACL.Access | %{$ACL.RemoveAccessRule($_)}\n    # Apply the new ACL\n    $ACL.SetAccessRule($NewAccessRule)\n    Set-ACL \"${SSHKeyPath}\" $ACL\n}\n"
  },
  {
    "path": "plugins/kernel_v1/config/nfs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Kernel_V1\n    class NFSConfig < Vagrant.plugin(\"1\", :config)\n      attr_accessor :map_uid\n      attr_accessor :map_gid\n\n      def initialize\n        @map_uid = UNSET_VALUE\n        @map_gid = UNSET_VALUE\n      end\n\n      def upgrade(new)\n        new.nfs.map_uid = @map_uid if @map_uid != UNSET_VALUE\n        new.nfs.map_gid = @map_gid if @map_gid != UNSET_VALUE\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v1/config/package.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Kernel_V1\n    class PackageConfig < Vagrant.plugin(\"1\", :config)\n      attr_accessor :name\n\n      def initialize\n        @name = UNSET_VALUE\n      end\n\n      def upgrade(new)\n        new.package.name = @name if @name != UNSET_VALUE\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v1/config/ssh.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Kernel_V1\n    class SSHConfig < Vagrant.plugin(\"1\", :config)\n      attr_accessor :username\n      attr_accessor :password\n      attr_accessor :host\n      attr_accessor :port\n      attr_accessor :guest_port\n      attr_accessor :max_tries\n      attr_accessor :timeout\n      attr_accessor :private_key_path\n      attr_accessor :forward_agent\n      attr_accessor :forward_x11\n      attr_accessor :forward_env\n      attr_accessor :shell\n\n      def initialize\n        @username         = UNSET_VALUE\n        @password         = UNSET_VALUE\n        @host             = UNSET_VALUE\n        @port             = UNSET_VALUE\n        @guest_port       = UNSET_VALUE\n        @max_tries        = UNSET_VALUE\n        @timeout          = UNSET_VALUE\n        @private_key_path = UNSET_VALUE\n        @forward_agent    = UNSET_VALUE\n        @forward_x11      = UNSET_VALUE\n        @forward_env      = UNSET_VALUE\n        @shell            = UNSET_VALUE\n      end\n\n      def upgrade(new)\n        new.ssh.username         = @username if @username != UNSET_VALUE\n        new.ssh.host             = @host if @host != UNSET_VALUE\n        new.ssh.port             = @port if @port != UNSET_VALUE\n        new.ssh.guest_port       = @guest_port if @guest_port != UNSET_VALUE\n        new.ssh.private_key_path = @private_key_path if @private_key_path != UNSET_VALUE\n        new.ssh.forward_agent    = @forward_agent if @forward_agent != UNSET_VALUE\n        new.ssh.forward_x11      = @forward_x11 if @forward_x11 != UNSET_VALUE\n        new.ssh.forward_env      = @forward_env if @forward_env != UNSET_VALUE\n        new.ssh.shell            = @shell if @shell != UNSET_VALUE\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v1/config/vagrant.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Kernel_V1\n    class VagrantConfig < Vagrant.plugin(\"1\", :config)\n      attr_accessor :dotfile_name\n      attr_accessor :host\n\n      def initialize\n        @dotfile_name = UNSET_VALUE\n        @host         = UNSET_VALUE\n      end\n\n      def finalize!\n        @dotfile_name = nil if @dotfile_name == UNSET_VALUE\n        @host = nil if @host == UNSET_VALUE\n      end\n\n      def upgrade(new)\n        new.vagrant.host = @host if @host.nil?\n\n        warnings = []\n        if @dotfile_name\n          warnings << \"`config.vm.dotfile_name` has no effect anymore.\"\n        end\n\n        [warnings, []]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v1/config/vm.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module Kernel_V1\n    # This is the Version 1.0.x Vagrant VM configuration. This is\n    # _outdated_ and exists purely to be upgraded over to the new V2\n    # format.\n    class VMConfig < Vagrant.plugin(\"1\", :config)\n      DEFAULT_VM_NAME = :default\n\n      attr_accessor :name\n      attr_accessor :auto_port_range\n      attr_accessor :base_mac\n      attr_accessor :boot_mode\n      attr_accessor :box\n      attr_accessor :box_url\n      attr_accessor :guest\n      attr_accessor :host_name\n      attr_reader :customizations\n      attr_reader :networks\n      attr_reader :provisioners\n      attr_reader :shared_folders\n\n      def initialize\n        @shared_folders = {}\n        @networks = []\n        @provisioners = []\n        @customizations = []\n        @define_calls = []\n      end\n\n      def forward_port(guestport, hostport, options=nil)\n        options ||= {}\n\n        # Build up the network options for V2\n        network_options = {}\n        network_options[:virtualbox__adapter] = options[:adapter]\n        network_options[:virtualbox__protocol] = options[:protocol]\n\n        # Just append the forwarded port to the networks\n        @networks << [:forwarded_port, [guestport, hostport, network_options]]\n      end\n\n      def share_folder(name, guestpath, hostpath, opts=nil)\n        @shared_folders[name] = {\n          guestpath: guestpath.to_s,\n          hostpath: hostpath.to_s,\n          create: false,\n          owner: nil,\n          group: nil,\n          nfs:   false,\n          transient: false,\n          extra: nil\n        }.merge(opts || {})\n      end\n\n      def network(type, *args)\n        # Convert to symbol so we can allow most anything...\n        type = type.to_sym if type\n\n        if type == :hostonly\n          @networks << [:private_network, args]\n        elsif type == :bridged\n          @networks << [:public_network, args]\n        else\n          @networks << [:unknown, type]\n        end\n      end\n\n      def provision(name, options=nil, &block)\n        @provisioners << [name, options, block]\n      end\n\n      # This argument is nil only because the old style was deprecated and\n      # we didn't want to break Vagrantfiles. This was never removed and\n      # since we've moved onto V2 configuration, we might as well keep this\n      # around forever.\n      def customize(command=nil)\n        @customizations << command if command\n      end\n\n      def define(name, options=nil, &block)\n        # Force the V1 config on these calls\n        options ||= {}\n        options[:config_version] = \"1\"\n\n        @define_calls << [name, options, block]\n      end\n\n      def finalize!\n        # If we haven't defined a single VM, then we need to define a\n        # default VM which just inherits the rest of the configuration.\n        define(DEFAULT_VM_NAME) if defined_vm_keys.empty?\n      end\n\n      # Upgrade to a V2 configuration\n      def upgrade(new)\n        warnings = []\n\n        new.vm.base_mac          = self.base_mac if self.base_mac\n        new.vm.box               = self.box if self.box\n        new.vm.box_url           = self.box_url if self.box_url\n        new.vm.guest             = self.guest if self.guest\n        new.vm.hostname          = self.host_name if self.host_name\n        new.vm.usable_port_range = self.auto_port_range if self.auto_port_range\n\n        if self.boot_mode\n          new.vm.provider :virtualbox do |vb|\n            # Enable the GUI if the boot mode is GUI.\n            vb.gui = (self.boot_mode.to_s == \"gui\")\n          end\n        end\n\n        # If we have VM customizations, then we enable them on the\n        # VirtualBox provider on the new VM.\n        if !self.customizations.empty?\n          warnings << \"`config.vm.customize` calls are VirtualBox-specific. If you're\\n\" +\n            \"using any other provider, you'll have to use config.vm.provider in a\\n\" +\n            \"v2 configuration block.\"\n\n          new.vm.provider :virtualbox do |vb|\n            self.customizations.each do |customization|\n              vb.customize(customization)\n            end\n          end\n        end\n\n        # Re-define all networks.\n        self.networks.each do |type, args|\n          if type == :unknown\n            warnings << \"Unknown network type '#{args}' will be ignored.\"\n            next\n          end\n\n          options = {}\n          options = args.pop.dup if args.last.is_a?(Hash)\n\n          # Determine the extra options we need to set for each type\n          if type == :forwarded_port\n            options[:guest] = args[0]\n            options[:host]  = args[1]\n          elsif type == :private_network\n            options[:ip] = args[0]\n          end\n\n          new.vm.network(type, options)\n        end\n\n        # Provisioners\n        self.provisioners.each do |name, options, block|\n          options ||= {}\n          new.vm.provision(name, **options, &block)\n        end\n\n        # Shared folders\n        self.shared_folders.each do |name, sf|\n          options      = sf.dup\n          options[:id] = name\n          guestpath    = options.delete(:guestpath)\n          hostpath     = options.delete(:hostpath)\n\n          # This was the name of the old default /vagrant shared folder.\n          # We warn the use that this changed, but also silently change\n          # it to try to make things work properly.\n          if options[:id] == \"v-root\"\n            warnings << \"The 'v-root' shared folders have been renamed to 'vagrant-root'.\\n\" +\n              \"Assuming you meant 'vagrant-root'...\"\n\n            options[:id] = \"vagrant-root\"\n          end\n\n          new.vm.synced_folder(hostpath, guestpath, options)\n        end\n\n        # Defined sub-VMs\n        @define_calls.each do |name, options, block|\n          new.vm.define(name, options, &block)\n        end\n\n        # If name is used, warn that it has no effect anymore\n        if @name\n          warnings << \"`config.vm.name` has no effect anymore. Names are derived\\n\" +\n            \"directly from any `config.vm.define` calls.\"\n        end\n\n        [warnings, []]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v1/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Kernel_V1\n    # This is the \"kernel\" of Vagrant and contains the configuration classes\n    # that make up the core of Vagrant.\n    class Plugin < Vagrant.plugin(\"1\")\n      name \"kernel\"\n      description <<-DESC\n      The kernel of Vagrant. This plugin contains required items for even\n      basic functionality of Vagrant version 1.\n      DESC\n\n      # Core configuration keys provided by the kernel. Note that all\n      # the kernel configuration classes are marked as _upgrade safe_ (the\n      # true 2nd param). This means that these can be loaded in ANY version\n      # of the core of Vagrant.\n      config(\"ssh\", true) do\n        require File.expand_path(\"../config/ssh\", __FILE__)\n        SSHConfig\n      end\n\n      config(\"nfs\", true) do\n        require File.expand_path(\"../config/nfs\", __FILE__)\n        NFSConfig\n      end\n\n      config(\"package\", true) do\n        require File.expand_path(\"../config/package\", __FILE__)\n        PackageConfig\n      end\n\n      config(\"vagrant\", true) do\n        require File.expand_path(\"../config/vagrant\", __FILE__)\n        VagrantConfig\n      end\n\n      config(\"vm\", true) do\n        require File.expand_path(\"../config/vm\", __FILE__)\n        VMConfig\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v2/config/cloud_init.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"securerandom\"\n\nmodule VagrantPlugins\n  module Kernel_V2\n    class VagrantConfigCloudInit < Vagrant.plugin(\"2\", :config)\n      #-------------------------------------------------------------------\n      # Config class for cloud-init\n      #-------------------------------------------------------------------\n\n      DEFAULT_CONTENT_TYPES = [\"text/cloud-boothook\", \"text/cloud-config\",\n                               \"text/cloud-config-archive\", \"text/jinja2\",\n                               \"text/part-handler\", \"text/upstart-job\",\n                               \"text/x-include-once-url\", \"text/x-include-url\",\n                               \"text/x-shellscript\"].map(&:freeze).freeze\n\n      DEFAULT_CONFIG_TYPE = :user_data\n\n      # @note This value is for internal use only\n      #\n      # @return [String]\n      attr_reader :id\n\n      # The 'type' of data being stored. If not defined,\n      # will default to :user_data\n      #\n      # @return [Symbol]\n      attr_accessor :type\n\n      # @return [String]\n      attr_accessor :content_type\n\n      # The optional mime-part content-disposition filename.\n      #\n      # @return [String]\n      attr_accessor :content_disposition_filename\n\n      # @return [String]\n      attr_accessor :path\n\n      # @return [String]\n      attr_accessor :inline\n\n      def initialize(type=nil)\n        @logger = Log4r::Logger.new(\"vagrant::config::vm::cloud_init\")\n\n        @type = type if type\n\n        @content_type = UNSET_VALUE\n        @content_disposition_filename = UNSET_VALUE\n        @path = UNSET_VALUE\n        @inline = UNSET_VALUE\n\n        # Internal options\n        @id = SecureRandom.uuid\n      end\n\n      def finalize!\n        if !@type\n          @type = DEFAULT_CONFIG_TYPE\n        else\n          @type = @type.to_sym\n        end\n\n        @content_type = nil if @content_type == UNSET_VALUE\n        @content_disposition_filename = nil if @content_disposition_filename == UNSET_VALUE\n        @path = nil if @path == UNSET_VALUE\n        @inline = nil if @inline == UNSET_VALUE\n      end\n\n      # @return [Array] array of strings of error messages from config option validation\n      def validate(machine)\n        errors = _detected_errors\n\n        if @type && @type != DEFAULT_CONFIG_TYPE\n          errors << I18n.t(\"vagrant.cloud_init.incorrect_type_set\",\n                           type: @type,\n                           machine: machine.name,\n                           default_type: DEFAULT_CONFIG_TYPE)\n        end\n\n        if !@content_type\n          errors << I18n.t(\"vagrant.cloud_init.content_type_not_set\",\n                           machine: machine.name,\n                           accepted_types: DEFAULT_CONTENT_TYPES.join(', '))\n        elsif !DEFAULT_CONTENT_TYPES.include?(@content_type)\n          errors << I18n.t(\"vagrant.cloud_init.incorrect_content_type\",\n                           machine: machine.name,\n                           content_type: @content_type,\n                           accepted_types: DEFAULT_CONTENT_TYPES.join(', '))\n        end\n\n        if @path && @inline\n          errors << I18n.t(\"vagrant.cloud_init.path_and_inline_set\",\n                           machine: machine.name)\n        end\n\n        if @path\n          if !@path.is_a?(String)\n            errors << I18n.t(\"vagrant.cloud_init.incorrect_path_type\",\n                              machine: machine.name,\n                              path: @path,\n                              type: @path.class.name)\n          else\n            expanded_path = Pathname.new(@path).expand_path(machine.env.root_path)\n            if !expanded_path.file?\n              errors << I18n.t(\"vagrant.cloud_init.path_invalid\",\n                               path: expanded_path,\n                               machine: machine.name)\n            end\n          end\n        end\n\n        if @inline\n          if !@inline.is_a?(String)\n            errors << I18n.t(\"vagrant.cloud_init.incorrect_inline_type\",\n                             machine: machine.name,\n                             type: @inline.class.name)\n          end\n        end\n\n        errors\n      end\n\n      # The String representation of this config.\n      #\n      # @return [String]\n      def to_s\n        \"cloud_init config\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v2/config/disk.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"securerandom\"\n\nrequire \"vagrant/util/numeric\"\n\nmodule VagrantPlugins\n  module Kernel_V2\n    class VagrantConfigDisk < Vagrant.plugin(\"2\", :config)\n      #-------------------------------------------------------------------\n      # Config class for a given Disk\n      #-------------------------------------------------------------------\n\n      DEFAULT_DISK_TYPES = [:disk, :dvd, :floppy].freeze\n\n      FILE_CHAR_REGEX = /[^-a-z0-9_]/i.freeze\n\n      # Note: This value is for internal use only\n      #\n      # @return [String]\n      attr_reader :id\n\n      # File name for the given disk. Defaults to a generated name that is:\n      #\n      #  vagrant_<disk_type>_<short_uuid>\n      #\n      # @return [String]\n      attr_accessor :name\n\n      # Type of disk to create. Defaults to `:disk`\n      #\n      # @return [Symbol]\n      attr_accessor :type\n\n      # Type of disk extension to create. Defaults to `vdi`\n      #\n      # @return [String]\n      attr_accessor :disk_ext\n\n      # Size of disk to create\n      #\n      # @return [Integer,String]\n      attr_accessor :size\n\n      # Path to the location of the disk file (Optional for `:disk` type,\n      # required for `:dvd` type.)\n      #\n      # @return [String]\n      attr_accessor :file\n\n      # Determines if this disk is the _main_ disk, or an attachment.\n      # Defaults to true.\n      #\n      # @return [Boolean]\n      attr_accessor :primary\n\n      # Provider specific options\n      #\n      # @return [Hash]\n      attr_accessor :provider_config\n\n      def initialize(type)\n        @logger = Log4r::Logger.new(\"vagrant::config::vm::disk\")\n\n        @type = type\n        @provider_config = {}\n\n        @name = UNSET_VALUE\n        @provider_type = UNSET_VALUE\n        @size = UNSET_VALUE\n        @primary = UNSET_VALUE\n        @file = UNSET_VALUE\n        @disk_ext = UNSET_VALUE\n\n        # Internal options\n        @id = SecureRandom.uuid\n      end\n\n      # Helper method for storing provider specific config options\n      #\n      # Expected format is:\n      #\n      # - `provider__diskoption: value`\n      # - `{provider: {diskoption: value, otherdiskoption: value, ...}`\n      #\n      # Duplicates will be overriden\n      #\n      # @param [Hash] options\n      def add_provider_config(**options, &block)\n        current = {}\n        options.each do |k,v|\n          opts = k.to_s.split(\"__\")\n\n          if opts.size == 2\n            current[opts[0].to_sym] = {opts[1].to_sym => v}\n          elsif v.is_a?(Hash)\n            current[k] = v\n          else\n            @logger.warn(\"Disk option '#{k}' found that does not match expected provider disk config schema.\")\n          end\n        end\n\n        current = @provider_config.merge(current) if !@provider_config.empty?\n        if current\n          @provider_config = current\n        else\n          @provider_config = {}\n        end\n      end\n\n      def finalize!\n        # Ensure all config options are set to nil or default value if untouched\n        # by user\n        @type = :disk if @type == UNSET_VALUE\n        @size = nil if @size == UNSET_VALUE\n        @file = nil if @file == UNSET_VALUE\n\n        if @primary == UNSET_VALUE\n          @primary = false\n        end\n\n        if @name.is_a?(String) && @name.match(FILE_CHAR_REGEX)\n            @logger.warn(\"Vagrant will remove detected invalid characters in '#{@name}' and convert the disk name into something usable for a file\")\n            @name.gsub!(FILE_CHAR_REGEX, \"_\")\n        elsif @name == UNSET_VALUE\n          if @primary\n            @name = \"vagrant_primary\"\n          else\n            @name = nil\n          end\n        end\n      end\n\n      # @return [Array] array of strings of error messages from config option validation\n      def validate(machine)\n        errors = _detected_errors\n        # validate type with list of known disk types\n\n        if !DEFAULT_DISK_TYPES.include?(@type)\n          errors << I18n.t(\"vagrant.config.disk.invalid_type\", type: @type,\n                           types: DEFAULT_DISK_TYPES.join(', '))\n        end\n\n        if @disk_ext == UNSET_VALUE\n          if machine.provider.capability?(:set_default_disk_ext)\n            @disk_ext = machine.provider.capability(:set_default_disk_ext)\n          else\n            @logger.warn(\"No provider capability defined to set default 'disk_ext' type. Will use 'vdi' for disk extension.\")\n            @disk_ext = \"vdi\"\n          end\n        elsif @disk_ext\n          @disk_ext = @disk_ext.downcase\n\n          if machine.provider.capability?(:validate_disk_ext)\n            if !machine.provider.capability(:validate_disk_ext, @disk_ext)\n              if machine.provider.capability?(:default_disk_exts)\n                disk_exts = machine.provider.capability(:default_disk_exts).join(', ')\n              else\n                disk_exts = \"not found\"\n              end\n              errors << I18n.t(\"vagrant.config.disk.invalid_ext\", ext: @disk_ext,\n                               name: @name,\n                               exts: disk_exts)\n            end\n          else\n            @logger.warn(\"No provider capability defined to validate 'disk_ext' type\")\n          end\n        end\n\n        if @size && !@size.is_a?(Integer)\n          if @size.is_a?(String)\n            @size = Vagrant::Util::Numeric.string_to_bytes(@size)\n          end\n        end\n\n        if !@size && type == :disk\n          errors << I18n.t(\"vagrant.config.disk.invalid_size\", name: @name, machine: machine.name)\n        end\n\n        if @type == :dvd && !@file\n          errors << I18n.t(\"vagrant.config.disk.dvd_type_file_required\", name: @name, machine: machine.name)\n        end\n\n        if @type == :dvd && @primary\n          errors << I18n.t(\"vagrant.config.disk.dvd_type_primary\", name: @name, machine: machine.name)\n        end\n\n        if @file\n          if !@file.is_a?(String)\n            errors << I18n.t(\"vagrant.config.disk.invalid_file_type\", file: @file, machine: machine.name)\n          elsif !File.file?(@file)\n            errors << I18n.t(\"vagrant.config.disk.missing_file\", file_path: @file,\n                             name: @name, machine: machine.name)\n          end\n        end\n\n        if @provider_config\n          if !@provider_config.empty?\n            if !@provider_config.key?(machine.provider_name)\n              machine.env.ui.warn(I18n.t(\"vagrant.config.disk.missing_provider\",\n                                         machine: machine.name,\n                                         provider_name: machine.provider_name))\n            end\n          end\n        end\n\n        if !@name\n          errors << I18n.t(\"vagrant.config.disk.no_name_set\", machine: machine.name)\n        end\n\n        errors\n      end\n\n      # The String representation of this Disk.\n      #\n      # @return [String]\n      def to_s\n        \"disk config\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v2/config/package.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Kernel_V2\n    class PackageConfig < Vagrant.plugin(\"2\", :config)\n      attr_accessor :name\n\n      def initialize\n        @name = UNSET_VALUE\n      end\n\n      def finalize!\n        @name = nil if @name == UNSET_VALUE\n      end\n\n      def to_s\n        \"Package\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v2/config/push.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Kernel_V2\n    class PushConfig < Vagrant.plugin(\"2\", :config)\n      VALID_OPTIONS = [:strategy].freeze\n\n      attr_accessor :name\n\n      def initialize\n        @logger = Log4r::Logger.new(\"vagrant::config::push\")\n\n        # Internal state\n        @__defined_pushes  = {}\n        @__compiled_pushes = {}\n        @__finalized       = false\n      end\n\n      def finalize!\n        @logger.debug(\"finalizing\")\n\n        # Compile all the provider configurations\n        @__defined_pushes.each do |name, tuples|\n          # Capture the strategy so we can use it later. This will be used in\n          # the block iteration for merging/overwriting\n          strategy = name\n          strategy = tuples[0][0] if tuples[0]\n\n          # Find the configuration class for this push\n          config_class = Vagrant.plugin(\"2\").manager.push_configs[strategy]\n          config_class ||= Vagrant::Config::V2::DummyConfig\n\n          # Load it up\n          config = config_class.new\n\n          begin\n            tuples.each do |s, b|\n              # Update the strategy if it has changed, resetting the current\n              # config object.\n              if s != strategy\n                @logger.warn(\"duplicate strategy defined, overwriting config\")\n                strategy = s\n                config = config_class.new\n              end\n\n              # If we don't have any blocks, then ignore it\n              next if b.nil?\n\n              new_config = config_class.new\n              b.call(new_config, Vagrant::Config::V2::DummyConfig.new)\n              config = config.merge(new_config)\n            end\n          rescue Exception => e\n            raise Vagrant::Errors::VagrantfileLoadError,\n              path: \"<push config: #{name}>\",\n              message: e.message\n          end\n\n          config.finalize!\n          # It's important that we call _finalize! here also, because pushes get\n          # plucked out of the config in Environment#push without the larger\n          # root.finalize! walk having been done. This means that push configs\n          # were coming out unfinalized, which can cause havoc when they're\n          # passed through functions that attempt to capture keyword arguments,\n          # as they'll cause ruby to call .to_hash on the config, get a\n          # DummyConfig, and then blow up. That havoc was happening in server\n          # mode, and this call fixes it.\n          config._finalize!\n\n          # Store it for retrieval later\n          @__compiled_pushes[name] = [strategy, config]\n        end\n\n        @__finalized = true\n      end\n\n      # Define a new push in the Vagrantfile with the given name.\n      #\n      # @example\n      #   vm.push.define \"ftp\"\n      #\n      # @example\n      #   vm.push.define \"ftp\" do |s|\n      #     s.host = \"...\"\n      #   end\n      #\n      # @example\n      #   vm.push.define \"production\", strategy: \"docker\" do |s|\n      #     # ...\n      #   end\n      #\n      # @param [#to_sym] name The name of the this strategy. By default, this\n      #   is also the name of the strategy, but the `:strategy` key can be given\n      #   to customize this behavior\n      # @param [Hash] options The list of options\n      #\n      def define(name, **options, &block)\n        name = name.to_sym\n        strategy = options[:strategy] || name\n\n        @__defined_pushes[name] ||= []\n        @__defined_pushes[name] << [strategy.to_sym, block]\n      end\n\n      # The String representation of this Push.\n      #\n      # @return [String]\n      def to_s\n        \"Push\"\n      end\n\n      # Custom merge method\n      def merge(other)\n        super.tap do |result|\n          other_pushes = other.instance_variable_get(:@__defined_pushes)\n          new_pushes   = @__defined_pushes.dup\n\n          other_pushes.each do |key, tuples|\n            new_pushes[key] ||= []\n            new_pushes[key] += tuples\n          end\n\n          result.instance_variable_set(:@__defined_pushes, new_pushes)\n        end\n      end\n\n      # Validate all pushes\n      def validate(machine)\n        errors = { \"push\" => _detected_errors }\n\n        __compiled_pushes.each do |_, push|\n          config = push[1]\n          push_errors = config.validate(machine)\n\n          if push_errors\n            errors = Vagrant::Config::V2::Util.merge_errors(errors, push_errors)\n          end\n        end\n\n        errors\n      end\n\n      # This returns the list of compiled pushes as a hash by name.\n      #\n      # @return [Hash<Symbol, Array<Class, Object>>]\n      def __compiled_pushes\n        raise \"Must finalize first!\" if !@__finalized\n        @__compiled_pushes.dup\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v2/config/ssh.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nrequire_relative \"ssh_connect\"\n\nmodule VagrantPlugins\n  module Kernel_V2\n    class SSHConfig < SSHConnectConfig\n      attr_accessor :forward_agent\n      attr_accessor :forward_x11\n      attr_accessor :forward_env\n      attr_accessor :guest_port\n      attr_accessor :keep_alive\n      attr_accessor :shell\n      attr_accessor :proxy_command\n      attr_accessor :ssh_command\n      attr_accessor :pty\n      attr_accessor :sudo_command\n      attr_accessor :export_command_template\n\n      attr_reader :default\n\n      def initialize\n        super\n\n        @forward_agent           = UNSET_VALUE\n        @forward_x11             = UNSET_VALUE\n        @forward_env             = UNSET_VALUE\n        @guest_port              = UNSET_VALUE\n        @keep_alive              = UNSET_VALUE\n        @proxy_command           = UNSET_VALUE\n        @ssh_command             = UNSET_VALUE\n        @pty                     = UNSET_VALUE\n        @shell                   = UNSET_VALUE\n        @sudo_command            = UNSET_VALUE\n        @export_command_template = UNSET_VALUE\n        @default                 = SSHConnectConfig.new\n      end\n\n      def merge(other)\n        super.tap do |result|\n          merged_defaults = @default.merge(other.default)\n          result.instance_variable_set(:@default, merged_defaults)\n        end\n      end\n\n      def finalize!\n        super\n\n        @forward_agent = false if @forward_agent == UNSET_VALUE\n        @forward_x11   = false if @forward_x11 == UNSET_VALUE\n        @forward_env   = false if @forward_env == UNSET_VALUE\n        @guest_port = 22 if @guest_port == UNSET_VALUE\n        @keep_alive = true if @keep_alive == UNSET_VALUE\n        @proxy_command = nil if @proxy_command == UNSET_VALUE\n        @ssh_command = nil if @ssh_command == UNSET_VALUE\n        @pty        = false if @pty == UNSET_VALUE\n        @shell      = \"bash -l\" if @shell == UNSET_VALUE\n\n        if @export_command_template == UNSET_VALUE\n          @export_command_template = 'export %ENV_KEY%=\"%ENV_VALUE%\"'\n        end\n\n        if @sudo_command == UNSET_VALUE\n          @sudo_command = \"sudo -E -H %c\"\n        end\n\n        @default.username = \"vagrant\" if @default.username == UNSET_VALUE\n        @default.port     = @guest_port if @default.port == UNSET_VALUE\n        @default.finalize!\n      end\n\n      def to_s\n        \"SSH\"\n      end\n\n      def validate(machine)\n        errors = super\n\n        # Return the errors\n        result = { to_s => errors }\n\n        # Figure out the errors for the defaults\n        default_errors = @default.validate(machine)\n        result[\"SSH Defaults\"] = default_errors if !default_errors.empty?\n\n        result\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v2/config/ssh_connect.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module Kernel_V2\n    class SSHConnectConfig < Vagrant.plugin(\"2\", :config)\n      DEFAULT_SSH_CONNECT_RETRIES = 5\n      DEFAULT_SSH_CONNECT_RETRY_DELAY = 2\n      DEFAULT_SSH_CONNECT_TIMEOUT = 15\n\n      attr_accessor :host\n      attr_accessor :port\n      attr_accessor :config\n      attr_accessor :connect_retries\n      attr_accessor :connect_retry_delay\n      attr_accessor :connect_timeout\n      attr_accessor :private_key_path\n      attr_accessor :username\n      attr_accessor :password\n      attr_accessor :insert_key\n      attr_accessor :keys_only\n      attr_accessor :key_type\n      attr_accessor :paranoid\n      attr_accessor :verify_host_key\n      attr_accessor :compression\n      attr_accessor :dsa_authentication\n      attr_accessor :extra_args\n      attr_accessor :remote_user\n      attr_accessor :disable_deprecated_algorithms\n\n      def initialize\n        @host             = UNSET_VALUE\n        @port             = UNSET_VALUE\n        @config           = UNSET_VALUE\n        @connect_retries  = UNSET_VALUE\n        @connect_retry_delay = UNSET_VALUE\n        @connect_timeout  = UNSET_VALUE\n        @private_key_path = UNSET_VALUE\n        @username         = UNSET_VALUE\n        @password         = UNSET_VALUE\n        @insert_key       = UNSET_VALUE\n        @keys_only        = UNSET_VALUE\n        @key_type         = UNSET_VALUE\n        @paranoid         = UNSET_VALUE\n        @verify_host_key  = UNSET_VALUE\n        @compression      = UNSET_VALUE\n        @dsa_authentication = UNSET_VALUE\n        @extra_args       = UNSET_VALUE\n        @remote_user      = UNSET_VALUE\n        @disable_deprecated_algorithms = UNSET_VALUE\n      end\n\n      def finalize!\n        @host             = nil if @host == UNSET_VALUE\n        @port             = nil if @port == UNSET_VALUE\n        @private_key_path = nil if @private_key_path == UNSET_VALUE\n        @username         = nil if @username == UNSET_VALUE\n        @password         = nil if @password == UNSET_VALUE\n        @insert_key       = true if @insert_key == UNSET_VALUE\n        @keys_only        = true if @keys_only == UNSET_VALUE\n        @key_type         = :auto if @key_type == UNSET_VALUE\n        @paranoid         = false if @paranoid == UNSET_VALUE\n        @verify_host_key  = :never if @verify_host_key == UNSET_VALUE\n        @compression      = true if @compression == UNSET_VALUE\n        @dsa_authentication = true if @dsa_authentication == UNSET_VALUE\n        @extra_args       = nil if @extra_args == UNSET_VALUE\n        @config           = nil if @config == UNSET_VALUE\n        @disable_deprecated_algorithms = false if @disable_deprecated_algorithms == UNSET_VALUE\n        @connect_timeout  = DEFAULT_SSH_CONNECT_TIMEOUT if @connect_timeout == UNSET_VALUE\n        @connect_retries  = DEFAULT_SSH_CONNECT_RETRIES if @connect_retries == UNSET_VALUE\n        @connect_retry_delay = DEFAULT_SSH_CONNECT_RETRY_DELAY if @connect_retry_delay == UNSET_VALUE\n\n        if @private_key_path && !@private_key_path.is_a?(Array)\n          @private_key_path = [@private_key_path]\n        end\n\n        if @remote_user == UNSET_VALUE\n          if @username\n            @remote_user = @username\n          else\n            @remote_user = nil\n          end\n        end\n\n        if @paranoid\n          @verify_host_key = @paranoid\n        end\n\n        # Values for verify_host_key changed in 5.0.0 of net-ssh. If old value\n        # detected, update with new value\n        case @verify_host_key\n        when true\n          @verify_host_key = :accepts_new_or_local_tunnel\n        when false\n          @verify_host_key = :never\n        when :very\n          @verify_host_key = :accept_new\n        when :secure\n          @verify_host_key = :always\n        end\n\n        # Attempt to convert timeout to integer value\n        # If we can't convert the connect timeout into an integer or\n        # if the value is less than 1, set it to the default value\n        begin\n          @connect_timeout = @connect_timeout.to_i\n        rescue\n          # ignore\n        end\n\n        if @key_type\n          @key_type = @key_type.to_sym\n        end\n      end\n\n      # NOTE: This is _not_ a valid config validation method, since it\n      # returns an _array_ of strings rather than a Hash. This is meant to\n      # be used with a subclass that handles this.\n      #\n      # @return [Array<String>]\n      def validate(machine)\n        errors = _detected_errors\n\n        if @private_key_path\n          @private_key_path.each do |raw_path|\n            path = File.expand_path(raw_path, machine.env.root_path)\n            if !File.file?(path)\n              errors << I18n.t(\n                \"vagrant.config.ssh.private_key_missing\",\n                path: raw_path)\n            end\n          end\n        end\n\n        if @config\n          config_path = File.expand_path(@config, machine.env.root_path)\n          if !File.file?(config_path)\n            errors << I18n.t(\n              \"vagrant.config.ssh.ssh_config_missing\",\n              path: @config)\n          end\n        end\n\n        if @paranoid\n          machine.env.ui.warn(I18n.t(\"vagrant.config.ssh.paranoid_deprecated\"))\n        end\n\n        if !@connect_timeout.is_a?(Integer)\n          errors << I18n.t(\n            \"vagrant.config.ssh.connect_timeout_invalid_type\",\n            given: @connect_timeout.class.name)\n        elsif @connect_timeout < 1\n          errors << I18n.t(\n            \"vagrant.config.ssh.connect_timeout_invalid_value\",\n            given: @connect_timeout.to_s)\n        end\n\n        if !@connect_retries.is_a?(Integer)\n          errors << I18n.t(\n            \"vagrant.config.ssh.connect_retries_invalid_type\",\n            given: @connect_retries.class.name\n          )\n        elsif @connect_retries < 0\n          errors << I18n.t(\n            \"vagrant.config.ssh.connect_retries_invalid_value\",\n            given: @connect_retries.to_s\n          )\n        end\n\n        if !@connect_retry_delay.is_a?(Numeric)\n          errors << I18n.t(\n            \"vagrant.config.ssh.connect_retry_delay_invalid_type\",\n            given: @connect_retry_delay.class.name\n          )\n        elsif @connect_retry_delay < 0\n          errors << I18n.t(\n            \"vagrant.config.ssh.connect_retry_delay_invalid_value\",\n            given: @connect_retry_delay.to_s\n          )\n        end\n\n        if @key_type != :auto && !Vagrant::Util::Keypair.valid_type?(@key_type)\n          errors << I18n.t(\n            \"vagrant.config.ssh.connect_invalid_key_type\",\n            given: @key_type.to_s,\n            supported: Vagrant::Util::Keypair.available_types.join(\", \")\n          )\n        end\n\n        errors\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v2/config/trigger.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\nrequire File.expand_path(\"../vm_trigger\", __FILE__)\n\nmodule VagrantPlugins\n  module Kernel_V2\n    class TriggerConfig < Vagrant.plugin(\"2\", :config)\n      # The TriggerConfig class is what gets called when a user\n      # defines a new trigger in their Vagrantfile. The two entry points are\n      # either `config.trigger.before` or `config.trigger.after`.\n\n      def initialize\n        @logger = Log4r::Logger.new(\"vagrant::config::trigger\")\n\n        # Internal State\n        @_before_triggers = [] # An array of VagrantConfigTrigger objects\n        @_after_triggers  = [] # An array of VagrantConfigTrigger objects\n      end\n\n      #-------------------------------------------------------------------\n      # Trigger before/after functions\n      #-------------------------------------------------------------------\n      #\n      # Commands are expected to be ether:\n      #   - splat\n      #     + config.trigger.before :up, :destroy, :halt do |trigger|....\n      #   - array\n      #     + config.trigger.before [:up, :destroy, :halt] do |trigger|....\n      #\n      # Config is expected to be given as a block, or the last parameter as a hash\n      #\n      #   - block\n      #     + config.trigger.before :up, :destroy, :halt do |trigger|\n      #         trigger.option = \"option\"\n      #       end\n      #   - hash\n      #     + config.trigger.before :up, :destroy, :halt, options: \"option\"\n\n      # Reads in and parses Vagrant command whitelist and settings for a defined\n      # trigger\n      #\n      # @param [Symbol] command Vagrant command to create trigger on\n      # @param [Block] block The defined before block\n      def before(*command, &block)\n        command.flatten!\n        blk = block\n\n        if command.last.is_a?(Hash)\n          if block_given?\n            extra_cfg = command.pop\n          else\n            # We were given a hash rather than a block,\n            # so the last element should be the \"config block\"\n            # and the rest are commands for the trigger\n            blk = command.pop\n          end\n        elsif !block_given?\n          raise Vagrant::Errors::TriggersNoBlockGiven,\n            command: command\n        end\n\n        command.each do |cmd|\n          trigger = create_trigger(cmd, blk, extra_cfg)\n          @_before_triggers << trigger\n        end\n      end\n\n      # Reads in and parses Vagrant command whitelist and settings for a defined\n      # trigger\n      #\n      # @param [Symbol] command Vagrant command to create trigger on\n      # @param [Block] block The defined after block\n      def after(*command, &block)\n        command.flatten!\n        blk = block\n\n        if command.last.is_a?(Hash)\n          if block_given?\n            extra_cfg = command.pop\n          else\n            # We were given a hash rather than a block,\n            # so the last element should be the \"config block\"\n            # and the rest are commands for the trigger\n            blk = command.pop\n          end\n        elsif !block_given?\n          raise Vagrant::Errors::TriggersNoBlockGiven,\n            command: command\n        end\n\n        command.each do |cmd|\n          trigger = create_trigger(cmd, blk, extra_cfg)\n          @_after_triggers << trigger\n        end\n      end\n\n      #-------------------------------------------------------------------\n      # Internal methods, don't call these.\n      #-------------------------------------------------------------------\n\n      # Creates a new trigger config. If a block is given, parse that block\n      # by calling it with the created trigger. Otherwise set the options if it's\n      # a hash.\n      #\n      # @param [Symbol] command Vagrant command to create trigger on\n      # @param [Block] block The defined config block\n      # @param [Hash] extra_cfg Extra configurations for a block defined trigger (Optional)\n      # @return [VagrantConfigTrigger]\n      def create_trigger(command, block, extra_cfg=nil)\n        trigger = VagrantConfigTrigger.new(command)\n        if block.is_a?(Hash)\n          trigger.set_options(block)\n        else\n          block.call(trigger, VagrantConfigTrigger)\n          trigger.set_options(extra_cfg) if extra_cfg\n        end\n        return trigger\n      end\n\n      def merge(other)\n        super.tap do |result|\n          new_before_triggers = []\n          new_after_triggers = []\n          other_defined_before_triggers = other.instance_variable_get(:@_before_triggers)\n          other_defined_after_triggers = other.instance_variable_get(:@_after_triggers)\n\n          @_before_triggers.each do |bt|\n            other_bft = other_defined_before_triggers.find { |o| bt.id == o.id }\n            if other_bft\n              # Override, take it\n              other_bft = bt.merge(other_bft)\n\n              # Preserve order, always\n              bt = other_bft\n              other_defined_before_triggers.delete(other_bft)\n            end\n\n            new_before_triggers << bt.dup\n          end\n\n          other_defined_before_triggers.each do |obt|\n            new_before_triggers << obt.dup\n          end\n          result.instance_variable_set(:@_before_triggers, new_before_triggers)\n\n          @_after_triggers.each do |at|\n            other_aft = other_defined_after_triggers.find { |o| at.id == o.id }\n            if other_aft\n              # Override, take it\n              other_aft = at.merge(other_aft)\n\n              # Preserve order, always\n              at = other_aft\n              other_defined_after_triggers.delete(other_aft)\n            end\n\n            new_after_triggers << at.dup\n          end\n\n          other_defined_after_triggers.each do |oat|\n            new_after_triggers << oat.dup\n          end\n          result.instance_variable_set(:@_after_triggers, new_after_triggers)\n        end\n      end\n\n      # Iterates over all defined triggers and finalizes their config objects\n      def finalize!\n        if !@_before_triggers.empty?\n          @_before_triggers.map { |t| t.finalize! }\n        end\n\n        if !@_after_triggers.empty?\n          @_after_triggers.map { |t| t.finalize! }\n        end\n      end\n\n      # Validate Trigger Arrays\n      def validate(machine)\n        errors = _detected_errors\n        @_before_triggers.each do |bt|\n          error = bt.validate(machine)\n          errors.concat error if !error.empty?\n        end\n\n        @_after_triggers.each do |at|\n          error = at.validate(machine)\n          errors.concat error if !error.empty?\n        end\n\n        {\"trigger\" => errors}\n      end\n\n      # return [Array]\n      def before_triggers\n        @_before_triggers\n      end\n\n      # return [Array]\n      def after_triggers\n        @_after_triggers\n      end\n\n      # The String representation of this Trigger.\n      #\n      # @return [String]\n      def to_s\n        \"trigger\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v2/config/vagrant.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Kernel_V2\n    class VagrantConfig < Vagrant.plugin(\"2\", :config)\n      attr_accessor :host\n      attr_accessor :sensitive\n      attr_accessor :plugins\n\n      VALID_PLUGIN_KEYS = [\"sources\", \"version\", \"entry_point\"].map(&:freeze).freeze\n      INVALID_PLUGIN_FORMAT = :invalid_plugin_format\n\n      def initialize\n        @host = UNSET_VALUE\n        @sensitive = UNSET_VALUE\n        @plugins = UNSET_VALUE\n      end\n\n      def finalize!\n        @host = :detect if @host == UNSET_VALUE\n        @host = @host.to_sym if @host\n        @sensitive = nil if @sensitive == UNSET_VALUE\n        if @plugins == UNSET_VALUE\n          @plugins = {}\n        else\n          @plugins = format_plugins(@plugins)\n        end\n\n        if @sensitive.is_a?(Array) || @sensitive.is_a?(String)\n          Array(@sensitive).each do |value|\n            Vagrant::Util::CredentialScrubber.sensitive(value.to_s)\n          end\n        end\n      end\n\n      # Validate the configuration\n      #\n      # @param [Vagrant::Machine, NilClass] machine Machine instance or nil\n      # @return [Hash]\n      def validate(machine)\n        errors = _detected_errors\n\n        if @sensitive && (!@sensitive.is_a?(Array) && !@sensitive.is_a?(String))\n          errors << I18n.t(\"vagrant.config.root.sensitive_bad_type\")\n        end\n\n        if @plugins == INVALID_PLUGIN_FORMAT\n          errors << I18n.t(\"vagrant.config.root.plugins_invalid_format\")\n        else\n          @plugins.each do |plugin_name, plugin_info|\n            if plugin_info.is_a?(Hash)\n              invalid_keys = plugin_info.keys - VALID_PLUGIN_KEYS\n              if !invalid_keys.empty?\n                errors << I18n.t(\"vagrant.config.root.plugins_bad_key\",\n                  plugin_name: plugin_name,\n                  plugin_key: invalid_keys.join(\", \")\n                )\n              end\n            else\n              errors << I18n.t(\"vagrant.config.root.plugins_invalid_format\")\n            end\n          end\n        end\n\n        {\"vagrant\" => errors}\n      end\n\n      def to_s\n        \"Vagrant\"\n      end\n\n      def format_plugins(val)\n        case val\n        when String\n          {val => Vagrant::Util::HashWithIndifferentAccess.new}\n        when Array\n          val.inject(Vagrant::Util::HashWithIndifferentAccess.new) { |memo, item|\n            memo.merge(format_plugins(item))\n          }\n        when Hash\n          Vagrant::Util::HashWithIndifferentAccess.new.tap { |h|\n            val.each_pair { |k, v|\n              if v.is_a?(Hash)\n                h[k] = Vagrant::Util::HashWithIndifferentAccess.new(v)\n              else\n                h[k] = v\n              end\n            }\n          }\n        else\n          INVALID_PLUGIN_FORMAT\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v2/config/vm.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"securerandom\"\nrequire \"set\"\n\nrequire \"vagrant\"\nrequire \"vagrant/action/builtin/mixin_synced_folders\"\nrequire \"vagrant/config/v2/util\"\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/presence\"\nrequire \"vagrant/util/experimental\"\nrequire \"vagrant/util/map_command_options\"\n\nrequire File.expand_path(\"../vm_provisioner\", __FILE__)\nrequire File.expand_path(\"../vm_subvm\", __FILE__)\nrequire File.expand_path(\"../disk\", __FILE__)\nrequire File.expand_path(\"../cloud_init\", __FILE__)\n\nmodule VagrantPlugins\n  module Kernel_V2\n    class VMConfig < Vagrant.plugin(\"2\", :config)\n      include Vagrant::Util::Presence\n\n      DEFAULT_VM_NAME = :default\n\n      attr_accessor :allowed_synced_folder_types\n      attr_accessor :allow_fstab_modification\n      attr_accessor :allow_hosts_modification\n      attr_accessor :base_mac\n      attr_accessor :base_address\n      attr_accessor :boot_timeout\n      attr_accessor :box\n      attr_accessor :box_architecture\n      attr_accessor :ignore_box_vagrantfile\n      attr_accessor :box_check_update\n      attr_accessor :box_url\n      attr_accessor :box_server_url\n      attr_accessor :box_version\n      attr_accessor :box_download_ca_cert\n      attr_accessor :box_download_ca_path\n      attr_accessor :box_download_checksum\n      attr_accessor :box_download_checksum_type\n      attr_accessor :box_download_client_cert\n      attr_accessor :box_download_disable_ssl_revoke_best_effort\n      attr_accessor :box_download_insecure\n      attr_accessor :box_download_location_trusted\n      attr_accessor :box_download_options\n      attr_accessor :cloud_init_first_boot_only\n      attr_accessor :communicator\n      attr_accessor :graceful_halt_timeout\n      attr_accessor :guest\n      attr_accessor :hostname\n      attr_accessor :post_up_message\n      attr_accessor :usable_port_range\n      attr_reader :provisioners\n      attr_reader :disks\n      attr_reader :cloud_init_configs\n      attr_reader :box_extra_download_options\n\n      # This is an experimental feature that isn't public yet.\n      attr_accessor :clone\n\n      def initialize\n        @logger = Log4r::Logger.new(\"vagrant::config::vm\")\n\n        @allowed_synced_folder_types   = UNSET_VALUE\n        @allow_fstab_modification      = UNSET_VALUE\n        @base_mac                      = UNSET_VALUE\n        @base_address                  = UNSET_VALUE\n        @boot_timeout                  = UNSET_VALUE\n        @box                           = UNSET_VALUE\n        @box_architecture              = UNSET_VALUE\n        @ignore_box_vagrantfile        = UNSET_VALUE\n        @box_check_update              = UNSET_VALUE\n        @box_download_ca_cert          = UNSET_VALUE\n        @box_download_ca_path          = UNSET_VALUE\n        @box_download_checksum         = UNSET_VALUE\n        @box_download_checksum_type    = UNSET_VALUE\n        @box_download_client_cert      = UNSET_VALUE\n        @box_download_disable_ssl_revoke_best_effort = UNSET_VALUE\n        @box_download_insecure         = UNSET_VALUE\n        @box_download_location_trusted = UNSET_VALUE\n        @box_download_options          = UNSET_VALUE\n        @box_extra_download_options    = UNSET_VALUE\n        @box_url                       = UNSET_VALUE\n        @box_version                   = UNSET_VALUE\n        @allow_hosts_modification      = UNSET_VALUE\n        @clone                         = UNSET_VALUE\n        @cloud_init_first_boot_only    = UNSET_VALUE\n        @communicator                  = UNSET_VALUE\n        @graceful_halt_timeout         = UNSET_VALUE\n        @guest                         = UNSET_VALUE\n        @hostname                      = UNSET_VALUE\n        @post_up_message               = UNSET_VALUE\n        @provisioners                  = []\n        @disks                         = []\n        @cloud_init_configs            = []\n        @usable_port_range             = UNSET_VALUE\n\n        # Internal state\n        @__compiled_provider_configs   = {}\n        @__defined_vm_keys             = []\n        @__defined_vms                 = {}\n        @__finalized                   = false\n        @__networks                    = {}\n        @__providers                   = {}\n        @__provider_order              = []\n        @__provider_overrides          = {}\n        @__synced_folders              = {}\n      end\n\n      # This was from V1, but we just kept it here as an alias for hostname\n      # because too many people mess this up.\n      def host_name=(value)\n        @hostname = value\n      end\n\n      # Custom merge method since some keys here are merged differently.\n      def merge(other)\n        super.tap do |result|\n          other_networks = other.instance_variable_get(:@__networks)\n\n          result.instance_variable_set(:@__networks, @__networks.merge(other_networks))\n\n          # Merge defined VMs by first merging the defined VM keys,\n          # preserving the order in which they were defined.\n          other_defined_vm_keys = other.instance_variable_get(:@__defined_vm_keys)\n          other_defined_vm_keys -= @__defined_vm_keys\n          new_defined_vm_keys   = @__defined_vm_keys + other_defined_vm_keys\n\n          # Merge the actual defined VMs.\n          other_defined_vms = other.instance_variable_get(:@__defined_vms)\n          new_defined_vms   = {}\n\n          @__defined_vms.each do |key, subvm|\n            new_defined_vms[key] = subvm.clone\n          end\n\n          other_defined_vms.each do |key, subvm|\n            if !new_defined_vms.key?(key)\n              new_defined_vms[key] = subvm.clone\n            else\n              new_defined_vms[key].config_procs.concat(subvm.config_procs)\n              new_defined_vms[key].options.merge!(subvm.options)\n            end\n          end\n\n          # Merge defined disks\n          other_disks = other.instance_variable_get(:@disks)\n          new_disks   = []\n          @disks.each do |p|\n            other_p = other_disks.find { |o| p.id == o.id }\n            if other_p\n              # there is an override. take it.\n              other_p.config = p.config.merge(other_p.config)\n\n              # Remove duplicate disk config from other\n              p = other_p\n              other_disks.delete(other_p)\n            end\n\n            # there is an override, merge it into the\n            new_disks << p.dup\n          end\n          other_disks.each do |p|\n            new_disks << p.dup\n          end\n          result.instance_variable_set(:@disks, new_disks)\n\n          # Merge defined cloud_init_configs\n          other_cloud_init_configs = other.instance_variable_get(:@cloud_init_configs)\n          new_cloud_init_configs   = []\n          @cloud_init_configs.each do |p|\n            other_p = other_cloud_init_configs.find { |o| p.id == o.id }\n            if other_p\n              # there is an override. take it.\n              other_p.config = p.config.merge(other_p.config)\n\n              # Remove duplicate disk config from other\n              p = other_p\n              other_cloud_init_configs.delete(other_p)\n            end\n\n            # there is an override, merge it into the\n            new_cloud_init_configs << p.dup\n          end\n          other_cloud_init_configs.each do |p|\n            new_cloud_init_configs << p.dup\n          end\n          result.instance_variable_set(:@cloud_init_configs, new_cloud_init_configs)\n\n          # Merge the providers by prepending any configuration blocks we\n          # have for providers onto the new configuration.\n          other_providers = other.instance_variable_get(:@__providers)\n          new_providers   = @__providers.dup\n          other_providers.each do |key, blocks|\n            new_providers[key] ||= []\n            new_providers[key] += blocks\n          end\n\n          # Merge the provider ordering. Anything defined in our CURRENT\n          # scope is before anything else.\n          other_order = other.instance_variable_get(:@__provider_order)\n          new_order   = @__provider_order.dup\n          new_order   = (new_order + other_order).uniq\n\n          # Merge the provider overrides by appending them...\n          other_overrides = other.instance_variable_get(:@__provider_overrides)\n          new_overrides   = @__provider_overrides.dup\n          other_overrides.each do |key, blocks|\n            new_overrides[key] ||= []\n            new_overrides[key] += blocks\n          end\n\n          # Merge provisioners. First we deal with overrides and making\n          # sure the ordering is good there. Then we merge them.\n          new_provs   = []\n          other_provs = other.provisioners.dup\n          @provisioners.each do |p|\n            other_p = other_provs.find { |o| p.id == o.id }\n            if other_p\n              # There is an override. Take it.\n              other_p.config = p.config.merge(other_p.config)\n              other_p.run    ||= p.run\n              next if !other_p.preserve_order\n\n              # We're preserving order, delete from other\n              p = other_p\n              other_provs.delete(other_p)\n            end\n\n            # There is an override, merge it into the\n            new_provs << p.dup\n          end\n          other_provs.each do |p|\n            new_provs << p.dup\n          end\n          result.instance_variable_set(:@provisioners, new_provs)\n\n          # Merge synced folders.\n          other_folders = other.instance_variable_get(:@__synced_folders)\n          new_folders = {}\n          @__synced_folders.each do |key, value|\n            new_folders[key] = value.dup\n          end\n\n          other_folders.each do |id, options|\n            new_folders[id] ||= {}\n            new_folders[id].merge!(options)\n          end\n\n          result.instance_variable_set(:@__defined_vm_keys, new_defined_vm_keys)\n          result.instance_variable_set(:@__defined_vms, new_defined_vms)\n          result.instance_variable_set(:@__providers, new_providers)\n          result.instance_variable_set(:@__provider_order, new_order)\n          result.instance_variable_set(:@__provider_overrides, new_overrides)\n          result.instance_variable_set(:@__synced_folders, new_folders)\n        end\n      end\n\n      # Defines a synced folder pair. This pair of folders will be synced\n      # to/from the machine. Note that if the machine you're using doesn't\n      # support multi-directional syncing (perhaps an rsync backed synced\n      # folder) then the host is always synced to the guest but guest data\n      # may not be synced back to the host.\n      #\n      # @param [String] hostpath Path to the host folder to share. If this\n      #   is a relative path, it is relative to the location of the\n      #   Vagrantfile.\n      # @param [String] guestpath Path on the guest to mount the shared\n      #   folder.\n      # @param [Hash] options Additional options.\n      def synced_folder(hostpath, guestpath, options=nil)\n        if Vagrant::Util::Platform.windows?\n          # On Windows, Ruby just uses normal '/' for path seps, so\n          # just replace normal Windows style seps with Unix ones.\n          hostpath = hostpath.to_s.gsub(\"\\\\\", \"/\")\n        end\n\n        if guestpath.is_a?(Hash)\n          options = guestpath\n          guestpath = nil\n        end\n\n        options ||= {}\n\n        if options[:nfs]\n          options[:type] = :nfs\n          options.delete(:nfs)\n        end\n\n        if options.has_key?(:name)\n          synced_folder_name = options.delete(:name)\n        else\n          synced_folder_name = guestpath\n        end\n\n        options[:guestpath] = guestpath.to_s.gsub(/\\/$/, '') if guestpath\n        options[:hostpath]  = hostpath\n        options[:disabled]  = false if !options.key?(:disabled)\n        options = (@__synced_folders[options[:guestpath]] || {}).\n          merge(options.dup)\n\n        # Make sure the type is a symbol\n        options[:type] = options[:type].to_sym if options[:type]\n\n        @__synced_folders[synced_folder_name] = options\n      end\n\n      # Define a way to access the machine via a network. This exposes a\n      # high-level abstraction for networking that may not directly map\n      # 1-to-1 for every provider. For example, AWS has no equivalent to\n      # \"port forwarding.\" But most providers will attempt to implement this\n      # in a way that behaves similarly.\n      #\n      # `type` can be one of:\n      #\n      #   * `:forwarded_port` - A port that is accessible via localhost\n      #     that forwards into the machine.\n      #   * `:private_network` - The machine gets an IP that is not directly\n      #     publicly accessible, but ideally accessible from this machine.\n      #   * `:public_network` - The machine gets an IP on a shared network.\n      #\n      # @param [Symbol] type Type of network\n      # @param [Hash] options Options for the network.\n      def network(type, **options)\n        options = options.dup\n        options[:protocol] ||= \"tcp\"\n\n        # Convert to symbol to allow strings\n        type = type.to_sym\n\n        if !options[:id]\n          default_id = nil\n\n          if type == :forwarded_port\n            # For forwarded ports, set the default ID to be the\n            # concat of host_ip, proto and host_port. This would ensure Vagrant\n            # caters for port forwarding in an IP aliased environment where\n            # different host IP addresses are to be listened on the same port.\n            default_id = \"#{options[:host_ip]}#{options[:protocol]}#{options[:host]}\"\n          end\n\n          options[:id] = default_id || SecureRandom.uuid\n        end\n\n        # Scope the ID by type so that different types can share IDs\n        id      = options[:id]\n        id      = \"#{type}-#{id}\"\n\n        # Merge in the previous settings if we have them.\n        if @__networks.key?(id)\n          options = @__networks[id][1].merge(options)\n        end\n\n        # Merge in the latest settings and set the internal state\n        @__networks[id] = [type.to_sym, options]\n      end\n\n      # Configures a provider for this VM.\n      #\n      # @param [Symbol] name The name of the provider.\n      def provider(name, &block)\n        name = name.to_sym\n        @__providers[name] ||= []\n        @__provider_overrides[name] ||= []\n\n        # Add the provider to the ordering list\n        @__provider_order << name\n\n        if block_given?\n          @__providers[name] << block if block_given?\n\n          # If this block takes two arguments, then we curry it and store\n          # the configuration override for use later.\n          if block.arity == 2\n            @__provider_overrides[name] << block.curry[Vagrant::Config::V2::DummyConfig.new]\n          end\n        end\n      end\n\n      def provision(name, **options, &block)\n        type = name\n        if options.key?(:type)\n          type = options.delete(:type)\n        else\n          name = nil\n        end\n\n        if options.key?(:id)\n          puts \"Setting `id` on a provisioner is deprecated. Please use the\"\n          puts \"new syntax of `config.vm.provision \\\"name\\\", type: \\\"type\\\"\"\n          puts \"where \\\"name\\\" is the replacement for `id`. This will be\"\n          puts \"fully removed in Vagrant 1.8.\"\n\n          name = id\n        end\n\n        prov = nil\n        if name\n          name = name.to_sym\n          prov = @provisioners.find { |p| p.name == name }\n        end\n\n        if !prov\n          if options.key?(:before)\n            before = options.delete(:before)\n          end\n          if options.key?(:after)\n            after = options.delete(:after)\n          end\n\n          opts = {before: before, after: after}\n          prov = VagrantConfigProvisioner.new(name, type.to_sym, **opts)\n          @provisioners << prov\n        end\n\n        prov.preserve_order = !!options.delete(:preserve_order) if \\\n          options.key?(:preserve_order)\n        prov.run = options.delete(:run) if options.key?(:run)\n        prov.communicator_required = options.delete(:communicator_required) if options.key?(:communicator_required)\n\n        prov.add_config(**options, &block)\n        nil\n      end\n\n      def defined_vms\n        @__defined_vms\n      end\n\n      # This returns the keys of the sub-vms in the order they were\n      # defined.\n      def defined_vm_keys\n        @__defined_vm_keys\n      end\n\n      def define(name, options=nil, &block)\n        name = name.to_sym\n        options ||= {}\n        options = options.dup\n        options[:config_version] ||= \"2\"\n\n        # Add the name to the array of VM keys. This array is used to\n        # preserve the order in which VMs are defined.\n        @__defined_vm_keys << name if !@__defined_vm_keys.include?(name)\n\n        # Add the SubVM to the hash of defined VMs\n        if !@__defined_vms[name]\n          @__defined_vms[name] = VagrantConfigSubVM.new\n        end\n\n        @__defined_vms[name].options.merge!(options)\n        @__defined_vms[name].config_procs << [options[:config_version], block] if block\n      end\n\n      # Stores disk config options from Vagrantfile\n      #\n      # @param [Symbol] type\n      # @param [Hash]   options\n      # @param [Block]  block\n      def disk(type, **options, &block)\n        disk_config = VagrantConfigDisk.new(type)\n\n        # Remove provider__option options before set_options, otherwise will\n        # show up as missing setting\n        # Extract provider hash options as well\n        provider_options = {}\n        options.delete_if do |p,o|\n          if o.is_a?(Hash) || p.to_s.include?(\"__\")\n            provider_options[p] = o\n            true\n          end\n        end\n\n        disk_config.set_options(options)\n\n        # Add provider config\n        disk_config.add_provider_config(**provider_options, &block)\n\n        @disks << disk_config\n      end\n\n      # Stores config options for cloud_init\n      #\n      # @param [Symbol] type\n      # @param [Hash]   options\n      # @param [Block]  block\n      def cloud_init(type=nil, **options, &block)\n        type = type.to_sym if type\n\n        cloud_init_config = VagrantConfigCloudInit.new(type)\n\n        if block_given?\n          block.call(cloud_init_config, VagrantConfigCloudInit)\n        else\n          # config is hash\n          cloud_init_config.set_options(options)\n        end\n\n        @cloud_init_configs << cloud_init_config\n      end\n\n      #-------------------------------------------------------------------\n      # Internal methods, don't call these.\n      #-------------------------------------------------------------------\n\n      def finalize!\n        # Defaults\n        @allowed_synced_folder_types = nil if @allowed_synced_folder_types == UNSET_VALUE\n        @base_mac = nil if @base_mac == UNSET_VALUE\n        @base_address = nil if @base_address == UNSET_VALUE\n        @boot_timeout = 300 if @boot_timeout == UNSET_VALUE\n        @box = nil if @box == UNSET_VALUE\n        @box_architecture = :auto if @box_architecture == UNSET_VALUE\n        # If box architecture value was set, force to string\n        if @box_architecture && @box_architecture != :auto\n          @box_architecture = @box_architecture.to_s\n        end\n        @ignore_box_vagrantfile = false if @ignore_box_vagrantfile == UNSET_VALUE\n\n        if @box_check_update == UNSET_VALUE\n          @box_check_update = !present?(ENV[\"VAGRANT_BOX_UPDATE_CHECK_DISABLE\"])\n        end\n\n        @box_download_ca_cert = nil if @box_download_ca_cert == UNSET_VALUE\n        @box_download_ca_path = nil if @box_download_ca_path == UNSET_VALUE\n        @box_download_checksum = nil if @box_download_checksum == UNSET_VALUE\n        @box_download_checksum_type = nil if @box_download_checksum_type == UNSET_VALUE\n        @box_download_client_cert = nil if @box_download_client_cert == UNSET_VALUE\n        @box_download_disable_ssl_revoke_best_effort = false if @box_download_disable_ssl_revoke_best_effort == UNSET_VALUE\n        @box_download_insecure = false if @box_download_insecure == UNSET_VALUE\n        @box_download_location_trusted = false if @box_download_location_trusted == UNSET_VALUE\n        @box_url = nil if @box_url == UNSET_VALUE\n        @box_version = nil if @box_version == UNSET_VALUE\n        @box_download_options = {} if @box_download_options == UNSET_VALUE\n        @box_extra_download_options = Vagrant::Util::MapCommandOptions.map_to_command_options(@box_download_options)\n        @allow_hosts_modification = true if @allow_hosts_modification == UNSET_VALUE\n        @clone = nil if @clone == UNSET_VALUE\n        @cloud_init_first_boot_only = @cloud_init_first_boot_only == UNSET_VALUE ? true : !!@cloud_init_first_boot_only\n        @communicator = nil if @communicator == UNSET_VALUE\n        @graceful_halt_timeout = 60 if @graceful_halt_timeout == UNSET_VALUE\n        @guest = nil if @guest == UNSET_VALUE\n        @hostname = nil if @hostname == UNSET_VALUE\n        @hostname = @hostname.to_s if @hostname\n        @post_up_message = \"\" if @post_up_message == UNSET_VALUE\n\n        if @usable_port_range == UNSET_VALUE\n          @usable_port_range = (2200..2250)\n        end\n\n        if @allowed_synced_folder_types\n          @allowed_synced_folder_types = Array(@allowed_synced_folder_types).map(&:to_sym)\n        end\n\n        # Make sure that the download checksum is a string and that\n        # the type is a symbol\n        @box_download_checksum = \"\" if !@box_download_checksum\n        if @box_download_checksum_type\n          @box_download_checksum_type = @box_download_checksum_type.to_sym\n        end\n\n        # Make sure the box URL is an array if it is set\n        @box_url = Array(@box_url) if @box_url\n\n        # Set the communicator properly\n        @communicator = @communicator.to_sym if @communicator\n\n        # Set the guest properly\n        @guest = @guest.to_sym if @guest\n\n        # If we haven't defined a single VM, then we need to define a\n        # default VM which just inherits the rest of the configuration.\n        define(DEFAULT_VM_NAME) if defined_vm_keys.empty?\n\n        # Make sure the SSH forwarding is added if it doesn't exist\n        if @communicator == :winrm\n          if !@__networks[\"forwarded_port-winrm\"]\n            network :forwarded_port,\n              guest: 5985,\n              host: 55985,\n              host_ip: \"127.0.0.1\",\n              id: \"winrm\",\n              auto_correct: true\n          end\n          if !@__networks[\"forwarded_port-winrm-ssl\"]\n            network :forwarded_port,\n              guest: 5986,\n              host: 55986,\n              host_ip: \"127.0.0.1\",\n              id: \"winrm-ssl\",\n              auto_correct: true\n          end\n        end\n        # forward SSH ports regardless of communicator\n        if !@__networks[\"forwarded_port-ssh\"]\n          network :forwarded_port,\n            guest: 22,\n            host: 2222,\n            host_ip: \"127.0.0.1\",\n            id: \"ssh\",\n            auto_correct: true\n        end\n\n        # Clean up some network configurations\n        @__networks.values.each do |type, opts|\n          if type == :forwarded_port\n            opts[:guest] = opts[:guest].to_i if opts[:guest]\n            opts[:host] = opts[:host].to_i if opts[:host]\n          end\n        end\n\n        # Compile all the provider configurations\n        @__providers.each do |name, blocks|\n          # TODO(spox): this is a hack that needs to be resolved elsewhere\n\n          name = name.to_sym\n\n\n          # If we don't have any configuration blocks, then ignore it\n          next if blocks.empty?\n\n          # Find the configuration class for this provider\n          config_class = Vagrant.plugin(\"2\").manager.provider_configs[name]\n          config_class ||= Vagrant::Config::V2::DummyConfig\n\n          l = Log4r::Logger.new(self.class.name.downcase)\n          l.info(\"config class lookup for provider #{name.inspect} gave us base class: #{config_class}\")\n\n          # Load it up\n          config = config_class.new\n\n          begin\n            blocks.each do |b|\n              new_config = config_class.new\n              b.call(new_config, Vagrant::Config::V2::DummyConfig.new)\n              config = config.merge(new_config)\n            end\n          rescue Exception => e\n            @logger.error(\"Vagrantfile load error: #{e.message}\")\n            @logger.error(e.inspect)\n            @logger.error(e.message)\n            @logger.error(e.backtrace.join(\"\\n\"))\n\n            line = \"(unknown)\"\n            if e.backtrace && e.backtrace[0]\n              line = e.backtrace.first.slice(0, e.backtrace.first.rindex(':')).rpartition(':').last\n            end\n\n            raise Vagrant::Errors::VagrantfileLoadError,\n              path: \"<provider config: #{name}>\",\n              line: line,\n              exception_class: e.class,\n              message: e.message\n          end\n\n          config.finalize!\n\n          # Store it for retrieval later\n          @__compiled_provider_configs[name]   = config\n        end\n\n        # Finalize all the provisioners\n        @provisioners.each do |p|\n          p.config.finalize! if !p.invalid?\n          p.run = p.run.to_sym if p.run\n        end\n\n        current_dir_shared = false\n        @__synced_folders.each do |id, options|\n          if options[:hostpath]  == '.'\n            current_dir_shared = true\n          end\n        end\n\n        @disks.each do |d|\n          d.finalize!\n        end\n\n        @cloud_init_configs.each do |c|\n          c.finalize!\n        end\n\n        if !current_dir_shared && !@__synced_folders[\"/vagrant\"]\n          synced_folder(\".\", \"/vagrant\")\n        end\n\n        # Flag that we finalized\n        @__finalized = true\n      end\n\n      # This returns the compiled provider-specific configuration for the\n      # given provider.\n      #\n      # @param [Symbol] name Name of the provider.\n      def get_provider_config(name)\n        raise \"Must finalize first.\" if !@__finalized\n\n        @logger = Log4r::Logger.new(self.class.name.downcase)\n        @logger.info(\"looking up provider config for: #{name.inspect}\")\n\n        result = @__compiled_provider_configs[name]\n\n        @logger.info(\"provider config value that was stored: #{result.inspect}\")\n\n        # If no compiled configuration was found, then we try to just\n        # use the default configuration from the plugin.\n        if !result\n          @logger.info(\"no result so doing plugin config lookup using name: #{name.inspect}\")\n          config_class = Vagrant.plugin(\"2\").manager.provider_configs[name]\n          @logger.info(\"config class that we got for the lookup: #{config_class}\")\n          if config_class\n            result = config_class.new\n            result.finalize!\n          end\n        end\n\n        return result\n      end\n\n      # This returns a list of VM configurations that are overrides\n      # for this provider.\n      #\n      # @param [Symbol] name Name of the provider\n      # @return [Array<Proc>]\n      def get_provider_overrides(name)\n        (@__provider_overrides[name] || []).map do |p|\n          [\"2\", p]\n        end\n      end\n\n      # This returns the list of networks configured.\n      def networks\n        @__networks.values\n      end\n\n      # This returns the list of synced folders\n      def synced_folders\n        @__synced_folders\n      end\n\n      def validate(machine, ignore_provider=nil)\n        errors = _detected_errors\n\n        if @allow_fstab_modification == UNSET_VALUE\n          machine.synced_folders.types.each do |impl_name|\n            inst = machine.synced_folders.type(impl_name)\n            if inst.capability?(:default_fstab_modification) && inst.capability(:default_fstab_modification) == false\n              @allow_fstab_modification = false\n              break\n            end\n          end\n          @allow_fstab_modification = true if @allow_fstab_modification == UNSET_VALUE\n        end\n\n        if !box && !clone && !machine.provider_options[:box_optional]\n          errors << I18n.t(\"vagrant.config.vm.box_missing\")\n        end\n\n        if box && clone\n          errors << I18n.t(\"vagrant.config.vm.clone_and_box\")\n        end\n\n        if box && box.empty?\n          errors << I18n.t(\"vagrant.config.vm.box_empty\", machine_name: machine.name)\n        end\n\n        errors << I18n.t(\"vagrant.config.vm.hostname_invalid_characters\", name: machine.name) if \\\n          @hostname && @hostname !~ /^[a-z0-9][-.a-z0-9]*$/i\n\n        if @box_version\n          @box_version.to_s.split(\",\").each do |v|\n            begin\n              Gem::Requirement.new(v.strip)\n            rescue Gem::Requirement::BadRequirementError\n              errors << I18n.t(\n                \"vagrant.config.vm.bad_version\", version: v)\n            end\n          end\n        end\n\n        if box_download_ca_cert\n          path = Pathname.new(box_download_ca_cert).\n            expand_path(machine.env.root_path)\n          if !path.file?\n            errors << I18n.t(\n              \"vagrant.config.vm.box_download_ca_cert_not_found\",\n              path: box_download_ca_cert)\n          end\n        end\n\n        if box_download_ca_path\n          path = Pathname.new(box_download_ca_path).\n            expand_path(machine.env.root_path)\n          if !path.directory?\n            errors << I18n.t(\n              \"vagrant.config.vm.box_download_ca_path_not_found\",\n              path: box_download_ca_path)\n          end\n        end\n\n        if box_download_checksum_type\n          if box_download_checksum == \"\"\n            errors << I18n.t(\"vagrant.config.vm.box_download_checksum_blank\")\n          end\n        else\n          if box_download_checksum != \"\"\n            errors << I18n.t(\"vagrant.config.vm.box_download_checksum_notblank\")\n          end\n        end\n\n        if !box_download_options.is_a?(Hash)\n          errors <<  I18n.t(\"vagrant.config.vm.box_download_options_type\", type: box_download_options.class.to_s)\n        end\n\n        box_download_options.each do |k, v|\n          # If the value is truthy and\n          # if `box_extra_download_options` does not include the key\n          # then the conversion to extra download options produced an error\n          if v && !box_extra_download_options.include?(\"--#{k}\")\n            errors <<  I18n.t(\"vagrant.config.vm.box_download_options_not_converted\", missing_key: k)\n          end\n        end\n\n        used_guest_paths = Set.new\n        @__synced_folders.each do |id, options|\n          # If the shared folder is disabled then don't worry about validating it\n          next if options[:disabled]\n\n          guestpath = Pathname.new(options[:guestpath]) if options[:guestpath]\n          hostpath  = Pathname.new(options[:hostpath]).expand_path(machine.env.root_path)\n\n          if guestpath.to_s == \"\" && id.to_s == \"\"\n            errors << I18n.t(\"vagrant.config.vm.shared_folder_requires_guestpath_or_name\")\n          elsif guestpath.to_s != \"\"\n            if guestpath.relative? && guestpath.to_s !~ /^\\w+:/\n              errors << I18n.t(\"vagrant.config.vm.shared_folder_guestpath_relative\",\n                               path: options[:guestpath])\n            else\n              if used_guest_paths.include?(options[:guestpath])\n                errors << I18n.t(\"vagrant.config.vm.shared_folder_guestpath_duplicate\",\n                                 path: options[:guestpath])\n              end\n\n              used_guest_paths.add(options[:guestpath])\n            end\n          end\n\n          if !hostpath.directory? && !options[:create]\n            errors << I18n.t(\"vagrant.config.vm.shared_folder_hostpath_missing\",\n                             path: options[:hostpath])\n          end\n\n          if options[:type] == :nfs && !options[:nfs__quiet]\n            if options[:owner] || options[:group]\n              # Owner/group don't work with NFS\n              errors << I18n.t(\"vagrant.config.vm.shared_folder_nfs_owner_group\",\n                               path: options[:hostpath])\n            end\n          end\n\n          if options[:mount_options] && !options[:mount_options].is_a?(Array)\n            errors << I18n.t(\"vagrant.config.vm.shared_folder_mount_options_array\")\n          end\n\n          if options[:type]\n            plugins = Vagrant.plugin(\"2\").manager.synced_folders\n            impl_class = plugins[options[:type]]\n            if !impl_class\n              errors << I18n.t(\"vagrant.config.vm.shared_folder_invalid_option_type\",\n                              type: options[:type],\n                              options: plugins.keys.join(', '))\n            end\n          end\n        end\n\n        # Validate networks\n        has_fp_port_error = false\n        fp_used = Set.new\n        valid_network_types = [:forwarded_port, :private_network, :public_network]\n\n        port_range=(1..65535)\n        has_hostname_config = false\n        networks.each do |type, options|\n          if options[:hostname]\n            if has_hostname_config\n              errors << I18n.t(\"vagrant.config.vm.multiple_networks_set_hostname\")\n            end\n            if options[:ip] == nil\n              errors << I18n.t(\"vagrant.config.vm.network_with_hostname_must_set_ip\")\n            end\n            has_hostname_config = true\n          end\n          if !valid_network_types.include?(type)\n            errors << I18n.t(\"vagrant.config.vm.network_type_invalid\",\n                            type: type.to_s)\n          end\n\n          if type == :forwarded_port\n            if !has_fp_port_error && (!options[:guest] || !options[:host])\n              errors << I18n.t(\"vagrant.config.vm.network_fp_requires_ports\")\n              has_fp_port_error = true\n            end\n\n            if options[:host]\n              key = \"#{options[:host_ip]}#{options[:protocol]}#{options[:host]}\"\n              if fp_used.include?(key)\n                errors << I18n.t(\"vagrant.config.vm.network_fp_host_not_unique\",\n                                host: options[:host].to_s,\n                                protocol: options[:protocol].to_s)\n              end\n\n              fp_used.add(key)\n            end\n\n            if !port_range.include?(options[:host]) || !port_range.include?(options[:guest])\n              errors << I18n.t(\"vagrant.config.vm.network_fp_invalid_port\")\n            end\n          end\n\n          if type == :private_network\n            if options[:type] && options[:type].to_sym != :dhcp\n              if !options[:ip]\n                errors << I18n.t(\"vagrant.config.vm.network_ip_required\")\n              end\n            end\n\n            if options[:ip] && (options[:ip].end_with?(\".1\") || options[:ip].end_with?(\":1\")) && (options[:type] || \"\").to_sym != :dhcp\n              machine.ui.warn(I18n.t(\n                \"vagrant.config.vm.network_ip_ends_in_one\"))\n            end\n          end\n        end\n\n        # Validate disks\n        # Check if there is more than one primary disk defined and throw an error\n        primary_disks = @disks.select { |d| d.primary && d.type == :disk }\n        if primary_disks.size > 1\n          errors << I18n.t(\"vagrant.config.vm.multiple_primary_disks_error\",\n                           name: machine.name)\n        end\n\n        disk_names = @disks.map { |d| d.name }\n        duplicate_names = disk_names.find_all { |d| disk_names.count(d) > 1 }\n        if duplicate_names.any?\n          errors << I18n.t(\"vagrant.config.vm.multiple_disk_names_error\",\n                           name: machine.name,\n                           disk_names: duplicate_names.uniq.join(\"\\n\"))\n        end\n\n        disk_files = @disks.map { |d| d.file }\n        duplicate_files = disk_files.find_all { |d| d && disk_files.count(d) > 1 }\n        if duplicate_files.any?\n          errors << I18n.t(\"vagrant.config.vm.multiple_disk_files_error\",\n                           name: machine.name,\n                           disk_files: duplicate_files.uniq.join(\"\\n\"))\n        end\n\n        @disks.each do |d|\n          error = d.validate(machine)\n          errors.concat(error) if !error.empty?\n        end\n\n        # Validate clout_init_configs\n        @cloud_init_configs.each do |c|\n          error = c.validate(machine)\n          errors.concat error if !error.empty?\n        end\n\n        # We're done with VM level errors so prepare the section\n        errors = { \"vm\" => errors }\n\n        # Validate only the _active_ provider\n        if machine.provider_config\n          if !ignore_provider\n            provider_errors = machine.provider_config.validate(machine)\n            if provider_errors\n              errors = Vagrant::Config::V2::Util.merge_errors(errors, provider_errors)\n            end\n          else\n            machine.ui.warn(I18n.t(\"vagrant.config.vm.ignore_provider_config\"))\n          end\n        end\n\n        # Validate provisioners\n        @provisioners.each do |vm_provisioner|\n          if vm_provisioner.invalid?\n            name = vm_provisioner.name.to_s\n            name = vm_provisioner.type.to_s if name.empty?\n            errors[\"vm\"] << I18n.t(\"vagrant.config.vm.provisioner_not_found\",\n                                   name: name)\n            next\n          end\n\n          provisioner_errors = vm_provisioner.validate(machine, @provisioners)\n          if provisioner_errors\n            errors = Vagrant::Config::V2::Util.merge_errors(errors, provisioner_errors)\n          end\n\n          if vm_provisioner.config\n            provisioner_errors = vm_provisioner.config.validate(machine)\n            if provisioner_errors\n              errors = Vagrant::Config::V2::Util.merge_errors(errors, provisioner_errors)\n            end\n          end\n        end\n\n        # If running from the Windows Subsystem for Linux, validate that configured\n        # hostpaths for synced folders are on DrvFs file systems, or the synced\n        # folder implementation explicitly supports non-DrvFs file system types\n        # within the WSL\n        if Vagrant::Util::Platform.wsl?\n          # Create a helper that will with the synced folders mixin\n          # from the builtin action to get the correct implementation\n          # to be used for each folder\n          sf_helper = Class.new do\n            include Vagrant::Action::Builtin::MixinSyncedFolders\n          end.new\n          folders = sf_helper.synced_folders(machine, config: self)\n          folders.each do |impl_name, data|\n            data.each do |_, fs|\n              hostpath = File.expand_path(fs[:hostpath], machine.env.root_path)\n              if !Vagrant::Util::Platform.wsl_drvfs_path?(hostpath)\n                sf_klass = sf_helper.plugins[impl_name.to_sym].first\n                if sf_klass.respond_to?(:wsl_allow_non_drvfs?) && sf_klass.wsl_allow_non_drvfs?\n                  next\n                end\n                errors[\"vm\"] << I18n.t(\"vagrant.config.vm.shared_folder_wsl_not_drvfs\",\n                  path: fs[:hostpath])\n              end\n            end\n          end\n        end\n\n        # Validate sub-VMs if there are any\n        @__defined_vms.each do |name, _|\n          if name =~ /[\\[\\]\\{\\}\\/]/\n            errors[\"vm\"] << I18n.t(\n              \"vagrant.config.vm.name_invalid\",\n              name: name)\n          end\n        end\n\n        if ![TrueClass, FalseClass].include?(@allow_fstab_modification.class)\n          errors[\"vm\"] << I18n.t(\"vagrant.config.vm.config_type\",\n            option: \"allow_fstab_modification\", given: @allow_fstab_modification.class, required: \"Boolean\"\n          )\n        end\n\n        if ![TrueClass, FalseClass].include?(@allow_hosts_modification.class)\n          errors[\"vm\"] << I18n.t(\"vagrant.config.vm.config_type\",\n            option: \"allow_hosts_modification\", given: @allow_hosts_modification.class, required: \"Boolean\"\n          )\n        end\n\n        errors\n      end\n\n      def __providers\n        @__provider_order\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v2/config/vm_provisioner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'log4r'\n\nmodule VagrantPlugins\n  module Kernel_V2\n    # Represents a single configured provisioner for a VM.\n    class VagrantConfigProvisioner < Vagrant.plugin(\"2\", :config)\n      # Defaults\n      VALID_BEFORE_AFTER_TYPES = [:each, :all].freeze\n\n      # Unique name for this provisioner\n      #\n      # Accepts a string, but is ultimately forced into a symbol in the top level method inside\n      # #Config::VM.provision method while being parsed from a Vagrantfile\n      #\n      # @return [Symbol]\n      attr_reader :name\n\n      # Internal unique name for this provisioner\n      # Set to the given :name if exists, otherwise\n      # it's set as a UUID.\n      #\n      # Note: This is for internal use only.\n      #\n      # @return [String]\n      attr_reader :id\n\n      # The type of the provisioner that should be registered\n      # as a plugin.\n      #\n      # @return [Symbol]\n      attr_reader :type\n\n      # The configuration associated with the provisioner, if there is any.\n      #\n      # @return [Object]\n      attr_accessor :config\n\n      # When to run this provisioner. Either \"once\", \"always\", or \"never\"\n      #\n      # @return [String]\n      attr_accessor :run\n\n      # Whether or not to preserve the order when merging this with a\n      # parent scope.\n      #\n      # @return [Boolean]\n      attr_accessor :preserve_order\n\n      # The name of a provisioner to run before it has started\n      #\n      # @return [String, Symbol]\n      attr_accessor :before\n\n      # The name of a provisioner to run after it is finished\n      #\n      # @return [String, Symbol]\n      attr_accessor :after\n\n      # Boolean, when true signifies that some communicator must\n      # be available in order for the provisioner to run.\n      #\n      # @return [Boolean]\n      attr_accessor :communicator_required\n\n      def initialize(name, type, **options)\n        @logger = Log4r::Logger.new(\"vagrant::config::vm::provisioner\")\n        @logger.debug(\"Provisioner defined: #{name}\")\n\n        @id = name || SecureRandom.uuid\n        @config  = nil\n        @invalid = false\n        @name    = name\n        @preserve_order = false\n        @run     = nil\n        @type    = type\n        @before  = options[:before]\n        @after   = options[:after]\n        @communicator_required = options.fetch(:communicator_required, true)\n\n        # Attempt to find the provisioner...\n        if !Vagrant.plugin(\"2\").manager.provisioners[type]\n          @logger.warn(\"Provisioner '#{type}' not found.\")\n          @invalid = true\n        end\n\n        # Attempt to find the configuration class for this provider\n        # if it exists and load the configuration.\n        @config_class = Vagrant.plugin(\"2\").manager.\n          provisioner_configs[@type]\n        if !@config_class\n          @logger.info(\n            \"Provisioner config for '#{@type}' not found. Ignoring config.\")\n          @config_class = Vagrant::Config::V2::DummyConfig\n        end\n      end\n\n      def initialize_copy(orig)\n        super\n        @config = @config.dup if @config\n      end\n\n      def add_config(**options, &block)\n        # Don't skip if config is invalid. It might be a valid non-Ruby plugin\n        current = @config_class.new\n        current.set_options(options) if options\n        block.call(current) if block\n        current = @config.merge(current) if @config\n        @config = current\n      end\n\n      def finalize!\n        return if invalid?\n\n        @config.finalize!\n      end\n\n      # Validates the before/after options\n      #\n      # @param [Vagrant::Machine] machine - machine to validate against\n      # @param [Array] provisioners - Array of defined provisioners for the guest machine\n      # @return [Array] array of strings of error messages from config option validation\n      def validate(machine, provisioners)\n        errors = _detected_errors\n\n        provisioner_names = provisioners.map { |i| i.name.to_s if i.name != name }.compact\n\n        if ![TrueClass, FalseClass].include?(@communicator_required.class)\n          errors << I18n.t(\"vagrant.provisioners.base.wrong_type\", opt: \"communicator_required\", type: \"boolean\")\n        end\n\n        if @before && @after\n          errors << I18n.t(\"vagrant.provisioners.base.both_before_after_set\")\n        end\n\n        if @before\n          if !VALID_BEFORE_AFTER_TYPES.include?(@before)\n            if @before.is_a?(Symbol) && !VALID_BEFORE_AFTER_TYPES.include?(@before)\n              errors << I18n.t(\"vagrant.provisioners.base.invalid_alias_value\", opt: \"before\", alias: VALID_BEFORE_AFTER_TYPES.join(\", \"))\n            elsif !@before.is_a?(String) && !VALID_BEFORE_AFTER_TYPES.include?(@before)\n              errors << I18n.t(\"vagrant.provisioners.base.wrong_type\", opt: \"before\", type: \"string\")\n            end\n\n            if !provisioner_names.include?(@before)\n              errors << I18n.t(\"vagrant.provisioners.base.missing_provisioner_name\",\n                               name: @before,\n                               machine_name: machine.name,\n                               action: \"before\",\n                               provisioner_name: @name)\n            end\n\n            dep_prov = provisioners.find_all { |i| i.name.to_s == @before && (i.before || i.after) }\n\n            if !dep_prov.empty?\n              errors << I18n.t(\"vagrant.provisioners.base.dependency_provisioner_dependency\",\n                               name: @name,\n                               dep_name: dep_prov.first.name.to_s)\n            end\n          end\n        end\n\n        if @after\n          if !VALID_BEFORE_AFTER_TYPES.include?(@after)\n            if @after.is_a?(Symbol)\n              errors << I18n.t(\"vagrant.provisioners.base.invalid_alias_value\", opt: \"after\", alias: VALID_BEFORE_AFTER_TYPES.join(\", \"))\n            elsif !@after.is_a?(String)\n              errors << I18n.t(\"vagrant.provisioners.base.wrong_type\", opt: \"after\", type: \"string\")\n            end\n\n            if !provisioner_names.include?(@after)\n              errors << I18n.t(\"vagrant.provisioners.base.missing_provisioner_name\",\n                               name: @after,\n                               machine_name: machine.name,\n                               action: \"after\",\n                               provisioner_name: @name)\n            end\n\n            dep_prov = provisioners.find_all { |i| i.name.to_s == @after && (i.before || i.after) }\n\n            if !dep_prov.empty?\n              errors << I18n.t(\"vagrant.provisioners.base.dependency_provisioner_dependency\",\n                               name: @name,\n                               dep_name: dep_prov.first.name.to_s)\n            end\n          end\n        end\n\n        {\"provisioner\" => errors}\n      end\n\n      # Returns whether the provisioner used was invalid or not. A provisioner\n      # is invalid if it can't be found.\n      #\n      # @return [Boolean]\n      def invalid?\n        @invalid\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v2/config/vm_subvm.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/stacked_proc_runner\"\n\nmodule VagrantPlugins\n  module Kernel_V2\n    # Represents a single sub-VM in a multi-VM environment.\n    class VagrantConfigSubVM\n      include Vagrant::Util::StackedProcRunner\n\n      # Returns an array of the configuration procs in [version, proc]\n      # format.\n      #\n      # @return [Array]\n      attr_reader :config_procs\n\n      attr_reader :options\n\n      def initialize\n        @config_procs = []\n        @options      = {}\n      end\n\n      def initialize_copy(other)\n        super\n\n        @config_procs = other.config_procs.clone\n        @options      = other.options.clone\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v2/config/vm_trigger.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"securerandom\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/shell/config\")\n\nmodule VagrantPlugins\n  module Kernel_V2\n    # Represents a single configured provisioner for a VM.\n    class VagrantConfigTrigger < Vagrant.plugin(\"2\", :config)\n      # Defaults\n      DEFAULT_ON_ERROR = :halt\n      DEFAULT_EXIT_CODE = 0\n      VALID_TRIGGER_TYPES = [:command, :action, :hook].freeze\n\n      #-------------------------------------------------------------------\n      # Config class for a given Trigger\n      #-------------------------------------------------------------------\n\n      # Internal unique name for this trigger\n      #\n      # Note: This is for internal use only.\n      #\n      # @return [String]\n      attr_reader :id\n\n      # Name for the given Trigger. Defaults to nil.\n      #\n      # @return [String]\n      attr_accessor :name\n\n      # Command to fire the trigger on\n      #\n      # @return [Symbol]\n      attr_reader :command\n\n      # A string to print at the WARN level\n      #\n      # @return [String]\n      attr_accessor :info\n\n      # A string to print at the WARN level\n      #\n      # @return [String]\n      attr_accessor :warn\n\n      # Determines what how a Trigger should behave if it runs into an error.\n      # Defaults to :halt, otherwise can only be set to :continue.\n      #\n      # @return [Symbol]\n      attr_accessor :on_error\n\n      # If set, will not run trigger for the configured Vagrant commands.\n      #\n      # @return [Symbol, Array]\n      attr_accessor :ignore\n\n      # If set, will only run trigger for guests that match keys for this parameter.\n      #\n      # @return [String, Regex, Array]\n      attr_accessor :only_on\n\n      # A local inline or file script to execute for the trigger\n      #\n      # @return [Hash]\n      attr_accessor :run\n\n      # A remote inline or file script to execute for the trigger\n      #\n      # @return [Hash]\n      attr_accessor :run_remote\n\n      # If set, will not run trigger for the configured Vagrant commands.\n      #\n      # @return [Integer, Array]\n      attr_accessor :exit_codes\n\n      # If set to true, trigger will halt Vagrant immediately and exit 0\n      # Can also be configured to have a custom exit code\n      #\n      # @return [Integer]\n      attr_accessor :abort\n\n      # Internal reader for the internal variable ruby_block\n      #\n      # @return [Proc]\n      attr_reader :ruby_block\n\n      # Variable used to store ruby proc when defining a ruby trigger\n      # with the \"hash\" syntax\n      #\n      # @return [Proc]\n      attr_accessor :ruby\n\n      # The type of trigger, which defines where it will fire. If not defined,\n      # the option will default to `:action`\n      #\n      # @return [Symbol]\n      attr_accessor :type\n\n      def initialize(command)\n        @logger = Log4r::Logger.new(\"vagrant::config::vm::trigger::config\")\n\n        @name = UNSET_VALUE\n        @info = UNSET_VALUE\n        @warn = UNSET_VALUE\n        @on_error = UNSET_VALUE\n        @ignore = UNSET_VALUE\n        @only_on = UNSET_VALUE\n        @run = UNSET_VALUE\n        @run_remote = UNSET_VALUE\n        @exit_codes = UNSET_VALUE\n        @abort = UNSET_VALUE\n        @ruby = UNSET_VALUE\n        @type = UNSET_VALUE\n\n        # Internal options\n        @id = SecureRandom.uuid\n        if command.respond_to?(:to_sym)\n          @command = command.to_sym\n        else\n          @command = command\n        end\n        @ruby_block = UNSET_VALUE\n\n        @logger.debug(\"Trigger defined for: #{command}\")\n      end\n\n      # Config option `ruby` for a trigger which reads in a ruby block and sets\n      # it to be evaluated when the configured trigger fires. This method is only\n      # invoked when the regular \"block\" syntax is used. Otherwise the proc is\n      # set through the attr_accessor if the hash syntax is used.\n      #\n      # @param [Proc] block\n      def ruby(&block)\n        @ruby_block = block\n      end\n\n      def finalize!\n        # Ensure all config options are set to nil or default value if untouched\n        # by user\n        @name = nil if @name == UNSET_VALUE\n        @info = nil if @info == UNSET_VALUE\n        @warn = nil if @warn == UNSET_VALUE\n        @on_error = DEFAULT_ON_ERROR if @on_error == UNSET_VALUE\n        @ignore = [] if @ignore == UNSET_VALUE\n        @run = nil if @run == UNSET_VALUE\n        @run_remote = nil if @run_remote == UNSET_VALUE\n        @only_on = nil if @only_on == UNSET_VALUE\n        @exit_codes = DEFAULT_EXIT_CODE if @exit_codes == UNSET_VALUE\n        @abort = nil if @abort == UNSET_VALUE\n        @type = :action if @type == UNSET_VALUE\n\n        @ruby_block = nil if @ruby_block == UNSET_VALUE\n        @ruby = nil if @ruby == UNSET_VALUE\n        @ruby_block = @ruby if @ruby\n\n        # These values are expected to always be an Array internally,\n        # but can be set as a single String or Symbol\n        #\n        # Guests are stored internally as strings\n        if @only_on\n          @only_on = Array(@only_on)\n        end\n\n        # Commands must be stored internally as symbols\n        if @ignore\n          @ignore = Array(@ignore)\n          @ignore.map! { |i| i.to_sym }\n        end\n\n        if @exit_codes\n          @exit_codes = Array(@exit_codes)\n        end\n\n        # Convert @run and @run_remote to be a \"Shell provisioner\" config\n        if @run && @run.is_a?(Hash)\n          # Powershell args and privileged for run commands is currently not supported\n          # so by default use empty string or false if unset. This helps the validate\n          # function determine if the setting was purposefully set, to print a warning\n          if !@run.key?(:powershell_args)\n            @run[:powershell_args] = \"\"\n          end\n\n          if !@run.key?(:privileged)\n            @run[:privileged] = false\n          end\n\n          new_run = VagrantPlugins::Shell::Config.new\n          new_run.set_options(@run)\n          new_run.finalize!\n          @run = new_run\n        end\n\n        if @run_remote && @run_remote.is_a?(Hash)\n          new_run = VagrantPlugins::Shell::Config.new\n          new_run.set_options(@run_remote)\n          new_run.finalize!\n          @run_remote = new_run\n        end\n\n        if @abort == true\n          @abort = 1\n        end\n\n        if @type\n          @type = @type.to_sym\n        end\n      end\n\n      # @return [Array] array of strings of error messages from config option validation\n      def validate(machine)\n        errors = _detected_errors\n\n        if @type && !VALID_TRIGGER_TYPES.include?(@type)\n          errors << I18n.t(\"vagrant.config.triggers.bad_trigger_type\",\n                           type: @type,\n                           trigger: @command,\n                           types: VALID_TRIGGER_TYPES.join(', '))\n        end\n\n        if @type == :command || !@type\n          commands = Vagrant.plugin(\"2\").manager.commands.keys.map(&:to_s)\n\n          if !commands.include?(@command) && @command != :all\n            machine.ui.warn(I18n.t(\"vagrant.config.triggers.bad_command_warning\",\n                                  cmd: @command))\n          end\n        end\n\n        if @run\n          errorz = @run.validate(machine)\n          errors.concat errorz[\"shell provisioner\"] if !errorz.empty?\n\n          if @run.privileged == true\n            machine.ui.warn(I18n.t(\"vagrant.config.triggers.privileged_ignored\",\n                                  command: @command))\n          end\n\n          if @run.powershell_args != \"\"\n            machine.ui.warn(I18n.t(\"vagrant.config.triggers.powershell_args_ignored\"))\n          end\n        end\n\n        if @run_remote\n          errorz = @run_remote.validate(machine)\n          errors.concat errorz[\"shell provisioner\"] if !errorz.empty?\n        end\n\n        if @name && !@name.is_a?(String)\n          errors << I18n.t(\"vagrant.config.triggers.name_bad_type\", cmd: @command)\n        end\n\n        if @info && !@info.is_a?(String)\n          errors << I18n.t(\"vagrant.config.triggers.info_bad_type\", cmd: @command)\n        end\n\n        if @warn && !@warn.is_a?(String)\n          errors << I18n.t(\"vagrant.config.triggers.warn_bad_type\", cmd: @command)\n        end\n\n        if @on_error != :halt\n          if @on_error != :continue\n            errors << I18n.t(\"vagrant.config.triggers.on_error_bad_type\", cmd: @command)\n          end\n        end\n\n        if @exit_codes\n          if !@exit_codes.all? {|i| i.is_a?(Integer)}\n            errors << I18n.t(\"vagrant.config.triggers.exit_codes_bad_type\", cmd: @command)\n          end\n        end\n\n        if @abort && !@abort.is_a?(Integer)\n          errors << I18n.t(\"vagrant.config.triggers.abort_bad_type\", cmd: @command)\n        elsif @abort == false\n          machine.ui.warn(I18n.t(\"vagrant.config.triggers.abort_false_type\"))\n        end\n\n        if @ruby_block && !ruby_block.is_a?(Proc)\n          errors << I18n.t(\"vagrant.config.triggers.ruby_bad_type\", cmd: @command)\n        end\n\n        errors\n      end\n\n      # The String representation of this Trigger.\n      #\n      # @return [String]\n      def to_s\n        \"trigger config\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/kernel_v2/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Kernel_V2\n    # This is the \"kernel\" of Vagrant and contains the configuration classes\n    # that make up the core of Vagrant for V2.\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"kernel\"\n      description <<-DESC\n      The kernel of Vagrant. This plugin contains required items for even\n      basic functionality of Vagrant version 2.\n      DESC\n\n      # Core configuration keys provided by the kernel. Note that unlike\n      # \"kernel_v1\", none of these configuration classes are upgradable.\n      # This is by design, since we can't be sure if they're upgradable\n      # until another version is available.\n      config(\"ssh\") do\n        require File.expand_path(\"../config/ssh\", __FILE__)\n        SSHConfig\n      end\n\n      config(\"package\") do\n        require File.expand_path(\"../config/package\", __FILE__)\n        PackageConfig\n      end\n\n      config(\"push\") do\n        require File.expand_path(\"../config/push\", __FILE__)\n        PushConfig\n      end\n\n      config(\"vagrant\") do\n        require File.expand_path(\"../config/vagrant\", __FILE__)\n        VagrantConfig\n      end\n\n      config(\"vm\") do\n        require File.expand_path(\"../config/vm\", __FILE__)\n        VMConfig\n      end\n\n      plugins = Vagrant::Plugin::Manager.instance.installed_plugins\n      if !plugins.keys.include?(\"vagrant-triggers\")\n        config(\"trigger\") do\n          require File.expand_path(\"../config/trigger\", __FILE__)\n          TriggerConfig\n        end\n      else\n        if !ENV[\"VAGRANT_USE_VAGRANT_TRIGGERS\"]\n        $stderr.puts <<-EOF\nWARNING: Vagrant has detected the `vagrant-triggers` plugin. This plugin conflicts\nwith the internal triggers implementation. Please uninstall the `vagrant-triggers`\nplugin and run the command again if you wish to use the core trigger feature. To\nuninstall the plugin, run the command shown below:\n\n  vagrant plugin uninstall vagrant-triggers\n\nNote that the community plugin `vagrant-triggers` and the core trigger feature\nin Vagrant do not have compatible syntax.\n\nTo disable this warning, set the environment variable `VAGRANT_USE_VAGRANT_TRIGGERS`.\nEOF\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/build.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nrequire \"vagrant/util/ansi_escape_code_remover\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class Build\n        include Vagrant::Util::ANSIEscapeCodeRemover\n\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::docker::build\")\n        end\n\n        def call(env)\n          machine   = env[:machine]\n          build_dir = env[:build_dir]\n          build_dir ||= machine.provider_config.build_dir\n          git_repo  = env[:git_repo]\n          git_repo  ||= machine.provider_config.git_repo\n\n          # If we're not building a container, then just skip this step\n          return @app.call(env) if (!build_dir && !git_repo)\n\n          # Try to read the image ID from the cache file if we've\n          # already built it.\n          image_file = machine.data_dir.join(\"docker_build_image\")\n          image      = nil\n          if image_file.file?\n            image = image_file.read.chomp\n          end\n\n          # Verify the image exists if we have one\n          if image && !machine.provider.driver.image?(image)\n            machine.ui.output(I18n.t(\"docker_provider.build_image_invalid\"))\n            image = nil\n          end\n\n          # If we have no image or we're rebuilding, we rebuild\n          if !image || env[:build_rebuild]\n            # Build it\n            args = machine.provider_config.build_args.clone\n            if machine.provider_config.dockerfile\n              dockerfile      = machine.provider_config.dockerfile\n              dockerfile_path = build_dir ? File.join(build_dir, dockerfile) : dockerfile\n\n              args.push(\"--file\").push(dockerfile_path)\n              if build_dir\n                machine.ui.output(\n                  I18n.t(\"docker_provider.building_named_dockerfile\",\n                  file: machine.provider_config.dockerfile))\n              else\n                machine.ui.output(\n                  I18n.t(\"docker_provider.building_git_repo_named_dockerfile\",\n                  file: machine.provider_config.dockerfile,\n                  repo: git_repo))\n              end\n            else\n              if build_dir\n                machine.ui.output(I18n.t(\"docker_provider.building\"))\n              else\n                machine.ui.output(\n                  I18n.t(\"docker_provider.building_git_repo\",\n                  repo: git_repo))\n              end\n            end\n\n            image = machine.provider.driver.build(\n              build_dir || git_repo,\n              extra_args: args) do |type, data|\n              data = remove_ansi_escape_codes(data.chomp).chomp\n              env[:ui].detail(data) if data != \"\"\n            end\n\n            # Output the final image\n            machine.ui.detail(\"\\nImage: #{image}\")\n\n            # Store the image ID\n            image_file.open(\"w\") do |f|\n              f.binmode\n              f.write(\"#{image}\\n\")\n            end\n          else\n            machine.ui.output(I18n.t(\"docker_provider.already_built\"))\n          end\n\n          # Set the image for creation\n          env[:create_image] = image\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/compare_synced_folders.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/action/builtin/mixin_synced_folders\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class CompareSyncedFolders\n        include Vagrant::Action::Builtin::MixinSyncedFolders\n\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          machine = env[:machine]\n\n          # Get the synced folders that are cached, and those that aren't\n          cached = synced_folders(machine, cached: true)\n          fresh  = synced_folders(machine)\n\n          # Build up a mapping of existing setup synced folders\n          existing = {}\n          cached.each do |_, fs|\n            fs.each do |_, data|\n              existing[data[:guestpath]] = data[:hostpath]\n            end\n          end\n\n          # Remove the matching folders, and build up non-matching or\n          # new synced folders.\n          invalids = {}\n          fresh.each do |_, fs|\n            fs.each do |_, data|\n              invalid = false\n              old     = existing.delete(data[:guestpath])\n              if !old\n                invalid = true\n              else\n                old = File.expand_path(old)\n              end\n\n              if !invalid && old\n                invalid = true if old != File.expand_path(data[:hostpath])\n              end\n\n              if invalid\n                invalids[File.expand_path(data[:guestpath])] = File.expand_path(data[:hostpath])\n              end\n            end\n          end\n\n          # If we have invalid entries, these are changed or new entries.\n          # If we have existing entries, then we removed some entries.\n          if !invalids.empty? || !existing.empty?\n            machine.ui.warn(I18n.t(\"docker_provider.synced_folders_changed\"))\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/connect_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'ipaddr'\nrequire 'log4r'\n\nrequire 'vagrant/util/scoped_hash_override'\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class ConnectNetworks\n\n        include Vagrant::Util::ScopedHashOverride\n\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new('vagrant::plugins::docker::connectnetworks')\n        end\n\n        # Generate CLI arguments for creating the docker network.\n        #\n        # @param [Hash] options Options from the network config\n        # @returns[Array<String> Network create arguments\n        def generate_connect_cli_arguments(options)\n          options.map do |key, value|\n            # If value is false, option is not set\n            next if value.to_s == \"false\"\n            # If value is true, consider feature flag with no value\n            opt = value.to_s == \"true\" ? [] : [value]\n            opt.unshift(\"--#{key.to_s.tr(\"_\", \"-\")}\")\n          end.flatten.compact\n        end\n\n        # Execute the action\n        def call(env)\n          # If we are using a host VM, then don't worry about it\n          machine = env[:machine]\n          if machine.provider.host_vm?\n            @logger.debug(\"Not setting up networks because docker host_vm is in use\")\n            return @app.call(env)\n          end\n\n          env[:ui].info(I18n.t(\"docker_provider.network_connect\"))\n\n          connections = env[:docker_connects] || {}\n\n          machine.config.vm.networks.each_with_index do |args, idx|\n            type, options = args\n            next if type != :private_network && type != :public_network\n\n            network_options = scoped_hash_override(options, :docker_connect)\n            network_options.delete_if{|k,_| options.key?(k)}\n            network_name = connections[idx]\n\n            if !network_name\n              raise Errors::NetworkNameMissing,\n                index: idx,\n                container: machine.name\n            end\n\n            @logger.debug(\"Connecting network #{network_name} to container guest #{machine.name}\")\n            if options[:ip] && options[:type] != \"dhcp\"\n              if IPAddr.new(options[:ip]).ipv4?\n                network_options[:ip] = options[:ip]\n              else\n                network_options[:ip6] = options[:ip]\n              end\n            end\n            network_options[:alias] = options[:alias] if options[:alias]\n            connect_opts = generate_connect_cli_arguments(network_options)\n            machine.provider.driver.connect_network(network_name, machine.id, connect_opts)\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/create.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class Create\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          @env             = env\n          @machine         = env[:machine]\n          @provider_config = @machine.provider_config\n          @machine_config  = @machine.config\n          @driver          = @machine.provider.driver\n\n          params = create_params\n\n          # If we're running a single command, we modify the params a bit\n          if env[:machine_action] == :run_command\n            # Use the command that is given to us\n            params[:cmd] = env[:run_command]\n\n            # Don't detach, we want to watch the command run\n            params[:detach] = false\n\n            # No ports should be shared to the host\n            params[:ports] = []\n\n            # Allocate a pty if it was requested\n            params[:pty] = true if env[:run_pty]\n\n            # Remove container after execution\n            params[:rm] = true if env[:run_rm]\n\n            # Name should be unique\n            params[:name] = \"#{params[:name]}_#{Time.now.to_i}\"\n\n            # We link to our original container\n            # TODO\n          end\n\n          env[:ui].output(I18n.t(\"docker_provider.creating\"))\n          env[:ui].detail(\"  Name: #{params[:name]}\")\n\n          env[:ui].detail(\" Image: #{params[:image]}\")\n          if params[:cmd] && !params[:cmd].empty?\n            env[:ui].detail(\"   Cmd: #{params[:cmd].join(\" \")}\")\n          end\n          params[:volumes].each do |volume|\n            env[:ui].detail(\"Volume: #{volume}\")\n          end\n          params[:ports].each do |pair|\n            env[:ui].detail(\"  Port: #{pair}\")\n          end\n          params[:links].each do |name, other|\n            env[:ui].detail(\"  Link: #{name}:#{other}\")\n          end\n\n          if env[:machine_action] != :run_command\n            # For regular \"ups\" create it and get the CID\n            cid = @driver.create(params)\n            env[:ui].detail(\" \\n\"+I18n.t(\n              \"docker_provider.created\", id: cid[0...16]))\n            @machine.id = cid\n          elsif params[:detach]\n            env[:ui].detail(\" \\n\"+I18n.t(\"docker_provider.running_detached\"))\n          else\n            ui_opts = {}\n\n            # If we're running with a pty, we want the output to look as\n            # authentic as possible. We don't prefix things and we don't\n            # output a newline.\n            if env[:run_pty]\n              ui_opts[:prefix] = false\n              ui_opts[:new_line] = false\n            end\n\n            # For run commands, we run it and stream back the output\n            env[:ui].detail(\" \\n\"+I18n.t(\"docker_provider.running\")+\"\\n \")\n            @driver.create(params, stdin: env[:run_pty]) do |type, data|\n              env[:ui].detail(data.chomp, **ui_opts)\n            end\n          end\n\n          @app.call(env)\n        end\n\n        def create_params\n          container_name = @provider_config.name\n          if !container_name\n            container_name = generate_container_name\n          end\n\n          image = @env[:create_image]\n          image ||= @provider_config.image\n\n          links = []\n          @provider_config._links.each do |link|\n            parts = link.split(\":\", 2)\n            links << parts\n          end\n\n          {\n            cmd:        @provider_config.cmd,\n            detach:     true,\n            env:        @provider_config.env,\n            expose:     @provider_config.expose,\n            extra_args: @provider_config.create_args,\n            hostname:   @machine_config.vm.hostname,\n            image:      image,\n            links:      links,\n            name:       container_name,\n            ports:      forwarded_ports(@provider_config.has_ssh),\n            privileged: @provider_config.privileged,\n            pty:        false,\n            volumes:    @provider_config.volumes,\n          }\n        end\n\n        def forwarded_ports(include_ssh=false)\n          mappings = {}\n          random = []\n\n          @machine.config.vm.networks.each do |type, options|\n            next if type != :forwarded_port\n\n            # Don't include SSH if we've explicitly asked not to\n            next if options[:id] == \"ssh\" && !include_ssh\n\n            # Skip port if it is disabled\n            next if options[:disabled]\n\n            # If the guest port is 0, put it in the random group\n            if options[:guest] == 0\n              random << options[:host]\n              next\n            end\n\n            mappings[\"#{options[:host]}/#{options[:protocol]}\"] = options\n          end\n\n          # Build the results\n          result = random.map(&:to_s)\n          result += mappings.values.map do |fp|\n            protocol = \"\"\n            protocol = \"/udp\" if fp[:protocol].to_s == \"udp\"\n            host_ip = \"\"\n            host_ip = \"#{fp[:host_ip]}:\" if fp[:host_ip]\n            \"#{host_ip}#{fp[:host]}:#{fp[:guest]}#{protocol}\"\n          end.compact\n\n          result\n        end\n\n        def generate_container_name\n          container_name = \"#{@env[:root_path].basename.to_s}_#{@machine.name}\"\n          # Remove leading -/_ and any non-alphanumeric, non-hyphen/underscore characters\n          container_name.gsub!(/\\A[^a-zA-Z0-9]+|[^-a-z0-9_]/i, \"\")\n          container_name << \"_#{Time.now.to_i}\"\n          container_name\n        end\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/destroy.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class Destroy\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[:ui].info I18n.t(\"docker_provider.messages.destroying\")\n\n          machine = env[:machine]\n          driver  = machine.provider.driver\n\n          # If we have a build image, store that\n          image_file = machine.data_dir.join(\"docker_build_image\")\n          image      = nil\n          if image_file.file?\n            image = image_file.read.chomp\n          end\n          env[:build_image] = image\n\n          driver.rm(machine.id)\n          machine.id = nil\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/destroy_build_image.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class DestroyBuildImage\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::docker::destroybuildimage\")\n        end\n\n        def call(env)\n          machine = env[:machine]\n          image   = env[:build_image]\n          image_file = nil\n\n          if !image\n            # Try to read the image ID from the cache file if we've\n            # already built it.\n            image_file = machine.data_dir.join(\"docker_build_image\")\n            image      = nil\n            if image_file.file?\n              image = image_file.read.chomp\n            end\n          end\n\n          if image\n            machine.ui.output(I18n.t(\"docker_provider.build_image_destroy\"))\n            if !machine.provider.driver.rmi(image)\n              machine.ui.detail(I18n.t(\n                \"docker_provider.build_image_destroy_in_use\"))\n            end\n          end\n\n          if image_file && image_file.file?\n            begin\n              image_file.delete\n            rescue Errno::ENOENT\n              # Its okay\n            end\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/destroy_network.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'log4r'\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class DestroyNetwork\n\n        @@lock = Mutex.new\n\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new('vagrant::plugins::docker::network')\n        end\n\n        def call(env)\n          # If we are using a host VM, then don't worry about it\n          machine = env[:machine]\n          if machine.provider.host_vm?\n            @logger.debug(\"Not setting up networks because docker host_vm is in use\")\n            return @app.call(env)\n          end\n\n          @@lock.synchronize do\n            machine.env.lock(\"docker-network-destroy\", retry: true) do\n              machine.config.vm.networks.each do |type, options|\n                next if type != :private_network && type != :public_network\n\n                vagrant_networks = machine.provider.driver.list_network_names.find_all do |n|\n                  n.start_with?(\"vagrant_network\")\n                end\n\n                vagrant_networks.each do |network_name|\n                  if machine.provider.driver.existing_named_network?(network_name) &&\n                      !machine.provider.driver.network_used?(network_name)\n                    env[:ui].info(I18n.t(\"docker_provider.network_destroy\", network_name: network_name))\n                    machine.provider.driver.rm_network(network_name)\n                  else\n                    @logger.debug(\"Network #{network_name} not found or in use\")\n                  end\n                end\n              end\n            end\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/forwarded_ports.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class ForwardedPorts\n        def initialize(app, env)\n          @app = app\n        end\n\n        # Converts the `ports` docker provider param into proper network configs\n        # of type :forwarded_port\n        def call(env)\n          env[:machine].provider_config.ports.each do |p|\n            host_ip = nil\n            protocol = \"tcp\"\n            host, guest = p.split(\":\", 2)\n            if guest.include?(\":\")\n              host_ip = host\n              host, guest = guest.split(\":\", 2)\n            end\n\n            guest, protocol = guest.split(\"/\", 2) if guest.include?(\"/\")\n            env[:machine].config.vm.network \"forwarded_port\",\n              host: host.to_i, guest: guest.to_i,\n              host_ip: host_ip,\n              protocol: protocol\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/has_ssh.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      # This middleware is used with Call to test if this machine supports\n      # SSH.\n      class HasSSH\n        def initialize(app, env)\n          @app    = app\n        end\n\n        def call(env)\n          env[:result] = env[:machine].provider_config.has_ssh\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/host_machine.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      # This action is responsible for creating the host machine if\n      # we need to. The host machine is where Docker containers will\n      # live.\n      class HostMachine\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::docker::hostmachine\")\n        end\n\n        def call(env)\n          if !env[:machine].provider.host_vm?\n            @logger.info(\"No host machine needed.\")\n            return @app.call(env)\n          end\n\n          env[:machine].ui.output(I18n.t(\n            \"docker_provider.host_machine_needed\"))\n\n          host_machine = env[:machine].provider.host_vm\n\n          begin\n            env[:machine].provider.host_vm_lock do\n              setup_host_machine(host_machine, env)\n            end\n          rescue Vagrant::Errors::EnvironmentLockedError\n            sleep 1\n            retry\n          end\n\n          @app.call(env)\n        end\n\n        protected\n\n        def setup_host_machine(host_machine, env)\n          # Create a UI for this machine that stays at the detail level\n          proxy_ui = host_machine.ui.dup\n          proxy_ui.opts[:bold] = false\n          proxy_ui.opts[:prefix_spaces] = true\n          proxy_ui.opts[:target] = env[:machine].name.to_s\n\n          # Reload the machine so that if it was created while we didn't\n          # hold the lock, we'll see the updated state.\n          host_machine.reload\n\n          # See if the machine is ready already. If not, start it.\n          if host_machine.communicate.ready?\n            env[:machine].ui.detail(I18n.t(\"docker_provider.host_machine_ready\"))\n          else\n            env[:machine].ui.detail(\n              I18n.t(\"docker_provider.host_machine_starting\"))\n            env[:machine].ui.detail(\" \")\n            host_machine.with_ui(proxy_ui) do\n              host_machine.action(:up)\n            end\n\n            # Verify communication is ready. If not, we have a problem.\n            if !host_machine.communicate.ready?\n              raise Errors::HostVMCommunicatorNotReady,\n                id: host_machine.id\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/host_machine_build_dir.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"digest/md5\"\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class HostMachineBuildDir\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::docker::hostmachinebuilddir\")\n        end\n\n        def call(env)\n          machine   = env[:machine]\n          build_dir = machine.provider_config.build_dir\n\n          # If we're not building a Dockerfile, ignore\n          return @app.call(env) if !build_dir\n\n          # If we're building a docker file, expand the directory\n          build_dir = File.expand_path(build_dir, env[:machine].env.root_path)\n          env[:build_dir] = build_dir\n\n          # If we're not on a host VM, we're done\n          return @app.call(env) if !machine.provider.host_vm?\n\n          # We're on a host VM, so we need to move our build dir to\n          # that machine. We do this by putting the synced folder on\n          # ourself and letting HostMachineSyncFolders handle it.\n          new_build_dir = \"/var/lib/docker/docker_build_#{Digest::MD5.hexdigest(build_dir)}\"\n          options       = {\n            docker__ignore: true,\n            docker__exact: true,\n          }.merge(machine.provider_config.host_vm_build_dir_options || {})\n          machine.config.vm.synced_folder(build_dir, new_build_dir, options)\n\n          # Set the build dir to be the correct one\n          env[:build_dir] = new_build_dir\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/host_machine_port_checker.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      # This sets up the middleware env var to check for ports in use.\n      class HostMachinePortChecker\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::docker::hostmachineportchecker\")\n        end\n\n        def call(env)\n          return @app.call(env) if !env[:machine].provider.host_vm?\n\n          @machine = env[:machine]\n          env[:port_collision_port_check] = method(:port_check)\n\n          @app.call(env)\n        end\n\n        protected\n\n        def port_check(port)\n          host_machine = @machine.provider.host_vm\n          host_machine.guest.capability(:port_open_check, port)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/host_machine_port_warning.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class HostMachinePortWarning\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          if !env[:machine].provider.host_vm?\n            return @app.call(env)\n          end\n\n          # If we have forwarded ports, then notify the user that they\n          # won't be immediately available unless a private network\n          # is created.\n          if has_forwarded_ports?(env[:machine])\n            env[:machine].ui.warn(I18n.t(\n              \"docker_provider.host_machine_forwarded_ports\"))\n          end\n\n          @app.call(env)\n        end\n\n        protected\n\n        def has_forwarded_ports?(machine)\n          machine.config.vm.networks.each do |type, _|\n            return true if type == :forwarded_port\n          end\n\n          false\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/host_machine_required.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      # This middleware is used with Call to test if we're using a host VM.\n      class HostMachineRequired\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[:result] = env[:machine].provider.host_vm?\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/host_machine_sync_folders.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"digest/md5\"\nrequire \"securerandom\"\nrequire \"log4r\"\n\nrequire \"vagrant/action/builtin/mixin_synced_folders\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      # This action is responsible for creating the host machine if\n      # we need to. The host machine is where Docker containers will\n      # live.\n      class HostMachineSyncFolders\n        include Vagrant::Action::Builtin::MixinSyncedFolders\n\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::docker::hostmachine\")\n        end\n\n        def call(env)\n          return @app.call(env) if !env[:machine].provider.host_vm?\n\n          if !env.key?(:host_machine_sync_folders)\n            env[:host_machine_sync_folders] = true\n          end\n\n          host_machine = env[:machine].provider.host_vm\n\n          # Lock while we make changes\n          begin\n            env[:machine].provider.host_vm_lock do\n              setup_synced_folders(host_machine, env)\n            end\n          rescue Vagrant::Errors::EnvironmentLockedError\n            sleep 1\n            retry\n          end\n\n          @app.call(env)\n        end\n\n        protected\n\n        def setup_synced_folders(host_machine, env)\n          # Write the host machine SFID if we have one\n          id_path = env[:machine].data_dir.join(\"host_machine_sfid\")\n          if !id_path.file?\n            host_sfid = SecureRandom.uuid\n            id_path.open(\"w\") do |f|\n              f.binmode\n              f.write(\"#{host_sfid}\\n\")\n            end\n          else\n            host_sfid = id_path.read.chomp\n          end\n\n          # Create a UI for this machine that stays at the detail level\n          proxy_ui = host_machine.ui.dup\n          proxy_ui.opts[:bold] = false\n          proxy_ui.opts[:prefix_spaces] = true\n          proxy_ui.opts[:target] = env[:machine].name.to_s\n\n          # Read the existing folders that are setup\n          existing_folders = synced_folders(host_machine, cached: true)\n          existing_ids = {}\n          if existing_folders\n            existing_folders.each do |impl, fs|\n              fs.each do |_name, data|\n                if data[:docker_sfid] && data[:docker_host_sfid] == host_sfid\n                  existing_ids[data[:docker_sfid]] = data\n                end\n              end\n            end\n          end\n\n          # Sync some folders so that our volumes work later.\n          new_config  = VagrantPlugins::Kernel_V2::VMConfig.new\n          our_folders = synced_folders(env[:machine])\n          our_folders.each do |type, folders|\n            folders.each do |id, data|\n              data = data.dup\n\n              if type == :docker\n                # We don't use the Docker type explicitly on the host VM\n                data.delete(:type)\n              end\n\n              # Expand the hostpath relative to _our_ root path. Otherwise,\n              # it expands it relative to the proxy VM, which is not what\n              # we want.\n              data[:hostpath] = File.expand_path(\n                data[:hostpath], env[:machine].env.root_path)\n\n              # Generate an ID that is deterministic based on our machine\n              # and Vagrantfile path...\n              id = Digest::MD5.hexdigest(\n                \"#{env[:machine].env.root_path}\" +\n                \"#{data[:hostpath]}\" +\n                \"#{data[:guestpath]}\" +\n                \"#{env[:machine].name}\")\n\n              # Generate a new guestpath\n              data[:docker_guestpath] = data[:guestpath]\n              data[:docker_sfid] = id\n              data[:docker_host_sfid] = host_sfid\n              data[:id] = id[0...6] + rand(10000).to_s\n\n              # If we specify exact then we know what we're doing\n              if !data[:docker__exact]\n                data[:guestpath] = \"/var/lib/docker/docker_#{id}\"\n              end\n\n              # Add this synced folder onto the new config if we haven't\n              # already shared it before.\n              if !existing_ids.key?(id)\n                # A bit of a hack for VirtualBox to mount our\n                # folder as transient. This can be removed once\n                # the VirtualBox synced folder mechanism is smarter.\n                data[:virtualbox__transient] = true\n\n                new_config.synced_folder(\n                  data[:hostpath],\n                  data[:guestpath],\n                  data)\n              else\n                # We already have the folder, so just load its data\n                data = existing_ids[id]\n              end\n\n              # Remove from our machine\n              env[:machine].config.vm.synced_folders.delete(id)\n\n              # Add the \"fixed\" folder to our machine\n              data = data.merge({\n                hostpath_exact: true,\n                type: :docker,\n              })\n              env[:machine].config.vm.synced_folder(\n                data[:guestpath],\n                data[:docker_guestpath],\n                data)\n            end\n          end\n\n          if !env[:host_machine_sync_folders]\n            @logger.info(\"Not syncing folders because container created.\")\n          end\n\n          if !new_config.synced_folders.empty?\n            # Sync the folders!\n            env[:machine].ui.output(I18n.t(\n              \"docker_provider.host_machine_syncing_folders\"))\n            host_machine.with_ui(proxy_ui) do\n              action_env = { synced_folders_config: new_config }\n              begin\n                host_machine.action(:sync_folders, action_env)\n              rescue Vagrant::Errors::MachineActionLockedError\n                sleep 1\n                retry\n              rescue Vagrant::Errors::UnimplementedProviderAction\n                callable = Vagrant::Action::Builder.new\n                callable.use Vagrant::Action::Builtin::SyncedFolders\n                host_machine.action_raw(:sync_folders, callable, action_env)\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/host_machine_sync_folders_disable.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nrequire \"vagrant/action/builtin/mixin_synced_folders\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      # This action disables the synced folders we created.\n      class HostMachineSyncFoldersDisable\n        include Vagrant::Action::Builtin::MixinSyncedFolders\n\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::docker::hostmachine\")\n        end\n\n        def call(env)\n          return @app.call(env) if !env[:machine].provider.host_vm?\n\n          # Read our random ID for this instance\n          id_path   = env[:machine].data_dir.join(\"host_machine_sfid\")\n          return @app.call(env) if !id_path.file?\n          host_sfid = id_path.read.chomp\n\n          host_machine = env[:machine].provider.host_vm\n\n          @app.call(env)\n\n          begin\n            env[:machine].provider.host_vm_lock do\n              setup_synced_folders(host_machine, host_sfid, env)\n            end\n          rescue Vagrant::Errors::EnvironmentLockedError\n            sleep 1\n            retry\n          end\n        end\n\n        protected\n\n        def setup_synced_folders(host_machine, host_sfid, env)\n          to_disable = []\n\n          # Read the existing folders that are setup\n          existing_folders = synced_folders(host_machine, cached: true)\n          if existing_folders\n            existing_folders.each do |impl, fs|\n              fs.each do |id, data|\n                if data[:docker_host_sfid] == host_sfid\n                  to_disable << id\n                end\n              end\n            end\n          end\n\n          # Nothing to do if we have no bad folders\n          return if to_disable.empty?\n\n          # Create a UI for this machine that stays at the detail level\n          proxy_ui = host_machine.ui.dup\n          proxy_ui.opts[:bold] = false\n          proxy_ui.opts[:prefix_spaces] = true\n          proxy_ui.opts[:target] = env[:machine].name.to_s\n\n          env[:machine].ui.output(I18n.t(\n            \"docker_provider.host_machine_disabling_folders\"))\n          host_machine.with_ui(proxy_ui) do\n            action_env = {\n              synced_folders_cached: true,\n              synced_folders_disable: to_disable,\n            }\n\n            begin\n              host_machine.action(:sync_folders, action_env)\n            rescue Vagrant::Errors::MachineActionLockedError\n              sleep 1\n              retry\n            rescue Vagrant::Errors::UnimplementedProviderAction\n              callable = Vagrant::Action::Builder.new\n              callable.use Vagrant::Action::Builtin::SyncedFolders\n              host_machine.action_raw(:sync_folders, callable, action_env)\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/init_state.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class InitState\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          # We set the ID of the machine to \"preparing\" so that we can use\n          # the data dir without it being deleted with the not_created state.\n          env[:machine].id = nil\n          env[:machine].id = \"preparing\"\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/is_build.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class IsBuild\n        def initialize(app, env)\n          @app    = app\n        end\n\n        def call(env)\n          env[:result] = (!!env[:machine].provider_config.build_dir || !!env[:machine].provider_config.git_repo)\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/is_host_machine_created.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class IsHostMachineCreated\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          if !env[:machine].provider.host_vm?\n            env[:result] = true\n            return @app.call(env)\n          end\n\n          host_machine = env[:machine].provider.host_vm\n          env[:result] =\n            host_machine.state.id != Vagrant::MachineState::NOT_CREATED_ID\n\n          # If the host machine isn't created, neither are we. It is\n          # important we set this to nil here so that global-status\n          # sees the right thing.\n          env[:machine].id = nil if !env[:result]\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/login.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class Login\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::docker::login\")\n        end\n\n        def login(env, config, driver)\n          # Login!\n          env[:ui].output(I18n.t(\"docker_provider.logging_in\"))\n          driver.login(\n            config.email, config.username,\n            config.password, config.auth_server)\n\n          # Continue, so that the auth is protected\n          # from meddling.\n          @app.call(env)\n\n          # Log out\n          driver.logout(config.auth_server)\n        end\n\n        def call(env)\n          config = env[:machine].provider_config\n          driver = env[:machine].provider.driver\n\n          # If we don't have a password set, don't auth\n          return @app.call(env) if config.password == \"\"\n\n          if !env[:machine].provider.host_vm?\n            # no host vm in use, using docker directly\n            login(env, config, driver)\n          else\n            # Grab a host VM lock to do the login so that we only login\n            # once per container for the rest of this process.\n            env[:machine].provider.host_vm_lock do\n              login(env, config, driver)\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/prepare_forwarded_port_collision_params.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class PrepareForwardedPortCollisionParams\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          machine = env[:machine]\n\n          # Get the forwarded ports used by other containers and\n          # consider those in use as well.\n          other_used_ports = machine.provider.driver.read_used_ports\n          env[:port_collision_extra_in_use] = other_used_ports\n\n          # Build the remap for any existing collision detections\n          #\n          # Note: This remap might not be required yet (as it is with the virtualbox provider)\n          # so for now we leave the remap hash empty.\n          remap = {}\n          env[:port_collision_remap] = remap\n\n          # This port checker method calls the custom port_check method\n          # defined below. If its false, it will go ahead and use the built-in\n          # port_check method to see if there are any live containers with bound\n          # ports\n          docker_port_check = proc { |host_ip, host_port|\n                                      result = port_check(env, host_port)\n                                      if !result\n                                        result = Vagrant::Action::Builtin::HandleForwardedPortCollisions.port_check(machine, host_ip, host_port)\n                                      end\n                                      result}\n          env[:port_collision_port_check] = docker_port_check\n\n          @app.call(env)\n        end\n\n        protected\n\n        # This check is required the docker provider. Containers\n        # can bind ports but be halted. We don't want new containers to\n        # grab these bound ports, so this check is here for that since\n        # the checks above won't detect it\n        #\n        # @param [Vagrant::Environment] env\n        # @param [String] host_port\n        # @returns [Bool]\n        def port_check(env, host_port)\n          extra_in_use = env[:port_collision_extra_in_use]\n\n          if extra_in_use\n            return extra_in_use.include?(host_port.to_s)\n          else\n            return false\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/prepare_networks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'ipaddr'\nrequire 'log4r'\n\nrequire 'vagrant/util/scoped_hash_override'\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class PrepareNetworks\n\n        include Vagrant::Util::ScopedHashOverride\n\n        @@lock = Mutex.new\n\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new('vagrant::plugins::docker::preparenetworks')\n        end\n\n        # Generate CLI arguments for creating the docker network.\n        #\n        # @param [Hash] options Options from the network config\n        # @returns[Array<String>] Network create arguments\n        def generate_create_cli_arguments(options)\n          options.map do |key, value|\n            # If value is false, option is not set\n            next if value.to_s == \"false\"\n            # If value is true, consider feature flag with no value\n            opt = value.to_s == \"true\" ? [] : [value]\n            opt.unshift(\"--#{key.to_s.tr(\"_\", \"-\")}\")\n          end.flatten.compact\n        end\n\n        # @return [Array<Socket::Ifaddr>] interface list\n        def list_interfaces\n          Socket.getifaddrs.find_all do |i|\n            !i.addr.nil? && i.addr.ip? && !i.addr.ipv4_loopback? &&\n              !i.addr.ipv6_loopback? && !i.addr.ipv6_linklocal?\n          end\n        end\n\n        # Validates that a network name exists. If it does not\n        # exist, an exception is raised.\n        #\n        # @param [String] network_name Name of existing network\n        # @param [Hash] env Local call env\n        # @return [Boolean]\n        def validate_network_name!(network_name, env)\n          if !env[:machine].provider.driver.existing_named_network?(network_name)\n            raise Errors::NetworkNameUndefined,\n              network_name: network_name\n          end\n          true\n        end\n\n        # Validates that the provided options are compatible with a\n        # pre-existing network. Raises exceptions on invalid configurations\n        #\n        # @param [String] network_name Name of the network\n        # @param [Hash] root_options Root networking options\n        # @param [Hash] network_options Docker scoped networking options\n        # @param [Driver] driver Docker driver\n        # @return [Boolean]\n        def validate_network_configuration!(network_name, root_options, network_options, driver)\n          if root_options[:ip] &&\n              driver.network_containing_address(root_options[:ip]) != network_name\n            raise Errors::NetworkAddressInvalid,\n              address: root_options[:ip],\n              network_name: network_name\n          end\n          if network_options[:subnet] &&\n              driver.network_containing_address(network_options[:subnet]) != network_name\n            raise Errors::NetworkSubnetInvalid,\n              subnet: network_options[:subnet],\n              network_name: network_name\n          end\n          true\n        end\n\n        # Generate configuration for private network\n        #\n        # @param [Hash] root_options Root networking options\n        # @param [Hash] net_options Docker scoped networking options\n        # @param [Hash] env Local call env\n        # @return [String, Hash] Network name and updated network_options\n        def process_private_network(root_options, network_options, env)\n          if root_options[:name] && validate_network_name!(root_options[:name], env)\n            network_name = root_options[:name]\n          end\n\n          if root_options[:type].to_s == \"dhcp\"\n            if !root_options[:ip] && !root_options[:subnet]\n              network_name = \"vagrant_network\" if !network_name\n              return [network_name, network_options]\n            end\n            if root_options[:subnet]\n              addr = IPAddr.new(root_options[:subnet])\n              root_options[:netmask] = addr.prefix\n            end\n          end\n\n          if root_options[:ip]\n            addr = IPAddr.new(root_options[:ip])\n          elsif addr.nil?\n            raise Errors::NetworkIPAddressRequired\n          end\n\n          # If address is ipv6, enable ipv6 support\n          network_options[:ipv6] = addr.ipv6?\n\n          # If no mask is provided, attempt to locate any existing\n          # network which contains the assigned IP address\n          if !root_options[:netmask] && !network_name\n            network_name = env[:machine].provider.driver.\n              network_containing_address(root_options[:ip])\n            # When no existing network is found, we are creating\n            # a new network. Since no mask was provided, default\n            # to /24 for ipv4 and /64 for ipv6\n            if !network_name\n              root_options[:netmask] = addr.ipv4? ? 24 : 64\n            end\n          end\n\n          # With no network name, process options to find or determine\n          # name for new network\n          if !network_name\n            if !root_options[:subnet]\n              # Only generate a subnet if not given one\n              subnet = IPAddr.new(\"#{addr}/#{root_options[:netmask]}\")\n              network = \"#{subnet}/#{root_options[:netmask]}\"\n            else\n              network = root_options[:subnet]\n            end\n\n            network_options[:subnet] = network\n            existing_network = env[:machine].provider.driver.\n              network_defined?(network)\n\n            if !existing_network\n              network_name = \"vagrant_network_#{network}\"\n            else\n              if !existing_network.to_s.start_with?(\"vagrant_network\")\n                env[:ui].warn(I18n.t(\"docker_provider.subnet_exists\",\n                  network_name: existing_network,\n                  subnet: network))\n              end\n              network_name = existing_network\n            end\n          end\n\n          [network_name, network_options]\n        end\n\n        # Generate configuration for public network\n        #\n        # TODO: When the Vagrant installer upgrades to Ruby 2.5.x,\n        # remove all instances of the roundabout way of determining a prefix\n        # and instead just use the built-in `.prefix` method\n        #\n        # @param [Hash] root_options Root networking options\n        # @param [Hash] net_options Docker scoped networking options\n        # @param [Hash] env Local call env\n        # @return [String, Hash] Network name and updated network_options\n        def process_public_network(root_options, net_options, env)\n          if root_options[:name] && validate_network_name!(root_options[:name], env)\n            network_name = root_options[:name]\n          end\n          if !network_name\n            valid_interfaces = list_interfaces\n            if valid_interfaces.empty?\n              raise Errors::NetworkNoInterfaces\n            elsif valid_interfaces.size == 1\n              bridge_interface = valid_interfaces.first\n            elsif idx = valid_interfaces.detect{|i| Array(root_options[:bridge]).include?(i.name) }\n              bridge_interface = idx\n            end\n            if !bridge_interface\n              env[:ui].info(I18n.t(\"vagrant.actions.vm.bridged_networking.available\"),\n                prefix: false)\n              valid_interfaces.each_with_index do |int, i|\n                env[:ui].info(\"#{i + 1}) #{int.name}\", prefix: false)\n              end\n              env[:ui].info(I18n.t(\n                \"vagrant.actions.vm.bridged_networking.choice_help\") + \"\\n\",\n                prefix: false\n              )\n            end\n            while !bridge_interface\n              choice = env[:ui].ask(\n                I18n.t(\"vagrant.actions.vm.bridged_networking.select_interface\") + \" \",\n                prefix: false)\n              bridge_interface = valid_interfaces[choice.to_i - 1]\n            end\n            base_opts = Vagrant::Util::HashWithIndifferentAccess.new\n            base_opts[:opt] = \"parent=#{bridge_interface.name}\"\n            subnet = IPAddr.new(bridge_interface.addr.ip_address <<\n              \"/\" << bridge_interface.netmask.ip_unpack.first)\n            netmask = bridge_interface.netmask.ip_unpack.first\n            prefix = IPAddr.new(\"255.255.255.255/#{netmask}\").to_i.to_s(2).count(\"1\")\n            base_opts[:subnet] = \"#{subnet}/#{prefix}\"\n            subnet_addr = IPAddr.new(base_opts[:subnet])\n            base_opts[:driver] = \"macvlan\"\n            base_opts[:gateway] = subnet_addr.succ.to_s\n            base_opts[:ipv6] = subnet_addr.ipv6?\n            network_options = base_opts.merge(net_options)\n\n            # Check if network already exists for this subnet\n            network_name = env[:machine].provider.driver.\n              network_containing_address(network_options[:gateway])\n            if !network_name\n              network_name = \"vagrant_network_public_#{bridge_interface.name}\"\n            end\n\n            # If the network doesn't already exist, gather available address range\n            # within subnet which docker can provide addressing\n            if !env[:machine].provider.driver.existing_named_network?(network_name)\n              if !net_options[:gateway]\n                network_options[:gateway] = request_public_gateway(\n                  network_options, bridge_interface.name, env)\n              end\n              network_options[:ip_range] = request_public_iprange(\n                network_options, bridge_interface, env)\n            end\n          end\n          [network_name, network_options]\n        end\n\n        # Request the gateway address for the public network\n        #\n        # @param [Hash] network_options Docker scoped networking options\n        # @param [String] interface The bridge interface used\n        # @param [Hash] env Local call env\n        # @return [String] Gateway address\n        def request_public_gateway(network_options, interface, env)\n          subnet = IPAddr.new(network_options[:subnet])\n          gateway = nil\n          while !gateway\n            gateway = env[:ui].ask(I18n.t(\n              \"docker_provider.network_bridge_gateway_request\",\n              interface: interface,\n              default_gateway: network_options[:gateway]) + \" \",\n              prefix: false\n            ).strip\n            if gateway.empty?\n              gateway = network_options[:gateway]\n            end\n            begin\n              gateway = IPAddr.new(gateway)\n              if !subnet.include?(gateway)\n                env[:ui].warn(I18n.t(\"docker_provider.network_bridge_gateway_outofbounds\",\n                  gateway: gateway,\n                  subnet: network_options[:subnet]) + \"\\n\", prefix: false)\n              end\n            rescue IPAddr::InvalidAddressError\n              env[:ui].warn(I18n.t(\"docker_provider.network_bridge_gateway_invalid\",\n                gateway: gateway) + \"\\n\", prefix: false)\n              gateway = nil\n            end\n          end\n          gateway.to_s\n        end\n\n        # Request the IP range allowed for use by docker when creating a new\n        # public network\n        #\n        # TODO: When the Vagrant installer upgrades to Ruby 2.5.x,\n        # remove all instances of the roundabout way of determining a prefix\n        # and instead just use the built-in `.prefix` method\n        #\n        # @param [Hash] network_options Docker scoped networking options\n        # @param [Socket::Ifaddr] interface The bridge interface used\n        # @param [Hash] env Local call env\n        # @return [String] Address range\n        def request_public_iprange(network_options, interface, env)\n          return network_options[:ip_range] if network_options[:ip_range]\n          subnet = IPAddr.new(network_options[:subnet])\n          env[:ui].info(I18n.t(\n            \"docker_provider.network_bridge_iprange_info\") + \"\\n\",\n            prefix: false\n          )\n          range = nil\n          while !range\n            range = env[:ui].ask(I18n.t(\n              \"docker_provider.network_bridge_iprange_request\",\n              interface: interface.name,\n              default_range: network_options[:subnet]) + \" \",\n              prefix: false\n            ).strip\n            if range.empty?\n              range = network_options[:subnet]\n            end\n            begin\n              range = IPAddr.new(range)\n              if !subnet.include?(range)\n                netmask = interface.netmask.ip_unpack.first\n                prefix = IPAddr.new(\"255.255.255.255/#{netmask}\").to_i.to_s(2).count(\"1\")\n                env[:ui].warn(I18n.t(\n                  \"docker_provider.network_bridge_iprange_outofbounds\",\n                  subnet: network_options[:subnet],\n                  range: \"#{range}/#{prefix}\"\n                ) + \"\\n\", prefix: false)\n                range = nil\n              end\n            rescue IPAddr::InvalidAddressError\n              env[:ui].warn(I18n.t(\n                \"docker_provider.network_bridge_iprange_invalid\",\n                range: range) + \"\\n\", prefix: false)\n              range = nil\n            end\n          end\n\n          netmask = interface.netmask.ip_unpack.first\n          prefix = IPAddr.new(\"255.255.255.255/#{netmask}\").to_i.to_s(2).count(\"1\")\n          \"#{range}/#{prefix}\"\n        end\n\n        # Execute the action\n        def call(env)\n          # If we are using a host VM, then don't worry about it\n          machine = env[:machine]\n          if machine.provider.host_vm?\n            @logger.debug(\"Not setting up networks because docker host_vm is in use\")\n            return @app.call(env)\n          end\n\n          connections = {}\n          @@lock.synchronize do\n            machine.env.lock(\"docker-network-create\", retry: true) do\n              env[:ui].info(I18n.t(\"docker_provider.network_create\"))\n              machine.config.vm.networks.each_with_index do |net_info, net_idx|\n                type, options = net_info\n                network_options = scoped_hash_override(options, :docker_network)\n                network_options.delete_if{|k,_| options.key?(k)}\n\n                case type\n                when :public_network\n                  network_name, network_options = process_public_network(\n                    options, network_options, env)\n                when :private_network\n                  network_name, network_options = process_private_network(\n                    options, network_options, env)\n                else\n                  next # unsupported type so ignore\n                end\n\n                if !network_name\n                  raise Errors::NetworkInvalidOption, container: machine.name\n                end\n\n                if !machine.provider.driver.existing_named_network?(network_name)\n                  @logger.debug(\"Creating network #{network_name}\")\n                  cli_opts = generate_create_cli_arguments(network_options)\n                  machine.provider.driver.create_network(network_name, cli_opts)\n                else\n                  @logger.debug(\"Network #{network_name} already created\")\n                  validate_network_configuration!(network_name, options, network_options, machine.provider.driver)\n                end\n                connections[net_idx] = network_name\n              end\n            end\n          end\n\n          env[:docker_connects] = connections\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/prepare_nfs_settings.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class PrepareNFSSettings\n        include Vagrant::Util::Retryable\n\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::action::vm::nfs\")\n        end\n\n        def call(env)\n          @machine = env[:machine]\n\n          @app.call(env)\n\n          if using_nfs? && !privileged_container?\n            raise Errors::NfsWithoutPrivilegedError\n          end\n\n          if using_nfs?\n            @logger.info(\"Using NFS, preparing NFS settings by reading host IP and machine IP\")\n            add_ips_to_env!(env)\n          end\n        end\n\n        # We're using NFS if we have any synced folder with NFS configured. If\n        # we are not using NFS we don't need to do the extra work to\n        # populate these fields in the environment.\n        def using_nfs?\n          @machine.config.vm.synced_folders.any? { |_, opts| opts[:type] == :nfs }\n        end\n\n        def privileged_container?\n          @machine.provider.driver.privileged?(@machine.id)\n        end\n\n        # Extracts the proper host and guest IPs for NFS mounts and stores them\n        # in the environment for the SyncedFolder action to use them in\n        # mounting.\n        #\n        # The ! indicates that this method modifies its argument.\n        def add_ips_to_env!(env)\n          provider = env[:machine].provider\n\n          host_ip    = provider.driver.docker_bridge_ip\n          machine_ip = provider.ssh_info[:host]\n\n          raise Vagrant::Errors::NFSNoHostonlyNetwork if !host_ip || !machine_ip\n\n          env[:nfs_host_ip]    = host_ip\n          env[:nfs_machine_ip] = machine_ip\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/prepare_nfs_valid_ids.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class PrepareNFSValidIds\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::action::vm::nfs\")\n        end\n\n        def call(env)\n          machine = env[:machine]\n          env[:nfs_valid_ids] = machine.provider.driver.all_containers\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/prepare_ssh.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class PrepareSSH\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          # If we aren't using a host VM, then don't worry about it\n          return @app.call(env) if !env[:machine].provider.host_vm?\n\n          env[:machine].ui.output(I18n.t(\n            \"docker_provider.ssh_through_host_vm\"))\n\n          # Modify the SSH info to be the host VM's info\n          env[:ssh_info] = env[:machine].provider.host_vm.ssh_info\n\n          # Modify the SSH options for when we `vagrant ssh`...\n          ssh_opts = env[:ssh_opts] || {}\n\n          # Build the command we'll execute within the Docker host machine:\n          ssh_command = env[:machine].communicate.container_ssh_command\n          if !Array(ssh_opts[:extra_args]).empty?\n            ssh_command << \" #{Array(ssh_opts[:extra_args]).join(\" \")}\"\n          end\n\n          # Modify the SSH options for the original command:\n          # Append \"-t\" to force a TTY allocation\n          ssh_opts[:extra_args] = [\"-t\"]\n          # Enable Agent forwarding when requested for the target VM\n          if env[:machine].ssh_info[:forward_agent]\n            ssh_opts[:extra_args] << \"-o ForwardAgent=yes\"\n          end\n          ssh_opts[:extra_args] << ssh_command\n\n          # Set the opts\n          env[:ssh_opts] = ssh_opts\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/pull.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class Pull\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          @env             = env\n          @machine         = env[:machine]\n          @provider_config = @machine.provider_config\n          @driver          = @machine.provider.driver\n\n          # Skip pulling if the image is built\n          return @app.call(env) if @env[:create_image] || !@provider_config.pull\n\n          image = @provider_config.image\n          env[:ui].output(I18n.t(\"docker_provider.pull\", image: image))\n          @driver.pull(image)\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/start.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class Start\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          machine = env[:machine]\n          driver  = machine.provider.driver\n\n          machine.ui.output(I18n.t(\"docker_provider.messages.starting\"))\n          driver.start(machine.id)\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/stop.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class Stop\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          machine = env[:machine]\n          driver  = machine.provider.driver\n          if driver.running?(machine.id)\n            env[:ui].info I18n.t(\"docker_provider.messages.stopping\")\n            driver.stop(machine.id, machine.provider_config.stop_timeout)\n          end\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action/wait_for_running.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"thread\"\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      class WaitForRunning\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::docker::waitforrunning\")\n        end\n\n        def call(env)\n          machine = env[:machine]\n\n          wait = true\n          if !machine.provider_config.remains_running\n            @logger.debug(\"remains_running is false\")\n            wait = false\n          elsif machine.state.id == :running\n            @logger.debug(\"container is already running\")\n            wait = false\n          end\n\n          # If we're not waiting, just return\n          return @app.call(env) if !wait\n\n          machine.ui.output(I18n.t(\"docker_provider.waiting_for_running\"))\n\n          # First, make sure it leaves the stopped state if its supposed to.\n          after = sleeper(5)\n          while machine.state.id == :stopped\n            if after[:done]\n              raise Errors::StateStopped\n            end\n            sleep 0.2\n          end\n\n          # Then, wait for it to become running\n          after = sleeper(30)\n          while true\n            state = machine.state\n            break if state.id == :running\n            @logger.info(\"Waiting for container to run. State: #{state.id}\")\n\n            if after[:done]\n              raise Errors::StateNotRunning\n            end\n\n            sleep 0.2\n          end\n\n          @app.call(env)\n        end\n\n        protected\n\n        def sleeper(duration)\n          Thread.new(duration) do |d|\n            sleep(d)\n            Thread.current[:done] = true\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/action.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Action\n      # Include the built-in modules so we can use them as top-level things.\n      include Vagrant::Action::Builtin\n\n      # This action starts another container just like the real one running\n      # but only for the purpose of running a single command rather than\n      # to exist long-running.\n      def self.action_run_command\n        Vagrant::Action::Builder.new.tap do |b|\n          # We just call the \"up\" action. We create a separate action\n          # to hold this though in case we modify it in the future, and\n          # so that we can switch on the \"machine_action\" env var.\n          b.use action_up\n        end\n      end\n\n      # This action brings the \"machine\" up from nothing, including creating the\n      # container, configuring metadata, and booting.\n      def self.action_up\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              b2.use HandleBox\n            end\n          end\n\n          b.use ConfigValidate\n          b.use HostMachine\n\n          # Yeah, this is supposed to be here twice (once more above). This\n          # catches the case when the container was supposed to be created,\n          # but the host state was unknown, and now we know its not actually\n          # created.\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              b2.use HandleBox\n              b2.use DestroyBuildImage\n            end\n          end\n\n          b.use action_start\n        end\n      end\n\n      def self.action_package\n        lambda do |env|\n          raise Errors::PackageNotSupported\n        end\n      end\n\n      # This action just runs the provisioners on the machine.\n      def self.action_provision\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ConfigValidate\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              b2.use Message, I18n.t(\"docker_provider.messages.not_created\")\n              next\n            end\n\n            b2.use Call, IsState, :running do |env2, b3|\n              if !env2[:result]\n                b3.use Message, I18n.t(\"docker_provider.messages.not_running\")\n                next\n              end\n\n              b3.use Call, HasProvisioner do |env3, b4|\n                b4.use Provision\n              end\n            end\n          end\n        end\n      end\n\n      # This is the action that is primarily responsible for halting\n      # the virtual machine, gracefully or by force.\n      def self.action_halt\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use Call, IsState, :host_state_unknown do |env, b2|\n            if env[:result]\n              b2.use HostMachine\n            end\n          end\n\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              b2.use Message, I18n.t(\"docker_provider.messages.not_created\")\n              next\n            end\n\n            b2.use Stop\n          end\n        end\n      end\n\n      # This action is responsible for reloading the machine, which\n      # brings it down, sucks in new configuration, and brings the\n      # machine back up with the new configuration.\n      def self.action_reload\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ConfigValidate\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              b2.use Message, I18n.t(\"docker_provider.messages.not_created\")\n              next\n            end\n\n            b2.use action_halt\n\n            b2.use Call, IsBuild do |env2, b3|\n              if env2[:result]\n                b3.use EnvSet, force_halt: true\n                b3.use action_halt\n                b3.use HostMachineSyncFoldersDisable\n                b3.use Destroy\n                b3.use ProvisionerCleanup\n              end\n            end\n\n            b2.use action_start\n          end\n        end\n      end\n\n      # This is the action that is primarily responsible for completely\n      # freeing the resources of the underlying virtual machine.\n      def self.action_destroy\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use Call, IsHostMachineCreated do |env, b2|\n            if !env[:result]\n              b2.use Message, I18n.t(\"docker_provider.messages.not_created\")\n              next\n            end\n\n            b2.use Call, IsState, :host_state_unknown do |env2, b3|\n              if env2[:result]\n                b3.use HostMachine\n              end\n            end\n\n            b2.use Call, IsState, :not_created do |env2, b3|\n              if env2[:result]\n                b3.use Message,\n                  I18n.t(\"docker_provider.messages.not_created\")\n                next\n              end\n\n              b3.use Call, DestroyConfirm do |env3, b4|\n                if env3[:result]\n                  b4.use ConfigValidate\n                  b4.use ProvisionerCleanup, :before\n                  b4.use EnvSet, force_halt: true\n                  b4.use action_halt\n                  b4.use HostMachineSyncFoldersDisable\n                  b4.use Destroy\n                  b4.use DestroyNetwork\n                  b4.use DestroyBuildImage\n                else\n                  b4.use Message,\n                    I18n.t(\"docker_provider.messages.will_not_destroy\")\n                end\n              end\n            end\n          end\n        end\n      end\n\n      # This is the action that will exec into an SSH shell.\n      def self.action_ssh\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              raise Errors::ContainerNotCreatedError\n            end\n\n            b2.use Call, IsState, :running do |env2, b3|\n              if !env2[:result]\n                raise Errors::ContainerNotRunningError\n              end\n\n              b3.use PrepareSSH\n              b3.use SSHExec\n            end\n          end\n        end\n      end\n\n      # This is the action that will run a single SSH command.\n      def self.action_ssh_run\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              raise Errors::ContainerNotCreatedError\n            end\n\n            b2.use Call, IsState, :running do |env2, b3|\n              if !env2[:result]\n                raise Errors::ContainerNotRunningError\n              end\n\n              b3.use SSHRun\n            end\n          end\n        end\n      end\n\n      def self.action_start\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use Call, IsState, :running do |env, b2|\n            if env[:machine_action] != :run_command\n              b2.use Call, HasProvisioner do |env2, b3|\n                b3.use Provision\n              end\n            end\n\n            # If the container is running and we're doing a run, we're done\n            next if env[:result] && env[:machine_action] != :run_command\n\n            b2.use Call, IsState, :not_created do |env2, b3|\n              if env2[:result]\n                # First time making this thing, set to the \"preparing\" state\n                b3.use InitState\n              else\n                b3.use EnvSet, host_machine_sync_folders: false\n              end\n            end\n\n            b2.use HostMachineBuildDir\n            b2.use HostMachineSyncFolders\n            b2.use PrepareNFSValidIds\n            b2.use SyncedFolderCleanup\n            b2.use SyncedFolders\n            b2.use PrepareNFSSettings\n            b2.use PrepareNetworks\n            b2.use Login\n            b2.use Build\n\n            if env[:machine_action] != :run_command\n              # If the container is NOT created yet, then do some setup steps\n              # necessary for creating it.\n\n              b2.use Call, IsState, :preparing do |env2, b3|\n                if env2[:result]\n                  b3.use EnvSet, port_collision_repair: true\n                  b3.use HostMachinePortWarning\n                  b3.use HostMachinePortChecker\n                  b3.use ForwardedPorts # This action converts the `ports` param into proper network configs\n                  b3.use PrepareForwardedPortCollisionParams\n                  b3.use HandleForwardedPortCollisions\n                  b3.use Pull\n                  b3.use Create\n                  b3.use WaitForRunning\n                else\n                  b3.use CompareSyncedFolders\n                end\n              end\n\n              b2.use ConnectNetworks\n              b2.use Start\n              b2.use WaitForRunning\n\n              b2.use Call, HasSSH do |env2, b3|\n                if env2[:result]\n                  b3.use WaitForCommunicator\n                end\n              end\n            else\n              # We're in a run command, so we do things a bit differently.\n              b2.use Create\n            end\n          end\n        end\n      end\n\n      def self.action_suspend\n        lambda do |env|\n          raise Errors::SuspendNotSupported\n        end\n      end\n\n      # The autoload farm\n      action_root = Pathname.new(File.expand_path(\"../action\", __FILE__))\n      autoload :Build, action_root.join(\"build\")\n      autoload :CompareSyncedFolders, action_root.join(\"compare_synced_folders\")\n      autoload :ConnectNetworks, action_root.join(\"connect_networks\")\n      autoload :Create, action_root.join(\"create\")\n      autoload :Destroy, action_root.join(\"destroy\")\n      autoload :DestroyBuildImage, action_root.join(\"destroy_build_image\")\n      autoload :DestroyNetwork, action_root.join(\"destroy_network\")\n      autoload :ForwardedPorts, action_root.join(\"forwarded_ports\")\n      autoload :HasSSH, action_root.join(\"has_ssh\")\n      autoload :HostMachine, action_root.join(\"host_machine\")\n      autoload :HostMachineBuildDir, action_root.join(\"host_machine_build_dir\")\n      autoload :HostMachinePortChecker, action_root.join(\"host_machine_port_checker\")\n      autoload :HostMachinePortWarning, action_root.join(\"host_machine_port_warning\")\n      autoload :HostMachineRequired, action_root.join(\"host_machine_required\")\n      autoload :HostMachineSyncFolders, action_root.join(\"host_machine_sync_folders\")\n      autoload :HostMachineSyncFoldersDisable, action_root.join(\"host_machine_sync_folders_disable\")\n      autoload :InitState, action_root.join(\"init_state\")\n      autoload :IsBuild, action_root.join(\"is_build\")\n      autoload :IsHostMachineCreated, action_root.join(\"is_host_machine_created\")\n      autoload :Login, action_root.join(\"login\")\n      autoload :PrepareForwardedPortCollisionParams, action_root.join(\"prepare_forwarded_port_collision_params\")\n      autoload :PrepareNetworks, action_root.join(\"prepare_networks\")\n      autoload :PrepareNFSValidIds, action_root.join(\"prepare_nfs_valid_ids\")\n      autoload :PrepareNFSSettings, action_root.join(\"prepare_nfs_settings\")\n      autoload :PrepareSSH, action_root.join(\"prepare_ssh\")\n      autoload :Pull, action_root.join(\"pull\")\n      autoload :Start, action_root.join(\"start\")\n      autoload :Stop, action_root.join(\"stop\")\n      autoload :WaitForRunning, action_root.join(\"wait_for_running\")\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/cap/has_communicator.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Cap\n      module HasCommunicator\n        def self.has_communicator(machine)\n          return machine.provider_config.has_ssh\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/cap/proxy_machine.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Cap\n      module ProxyMachine\n        def self.proxy_machine(machine)\n          return nil if !machine.provider.host_vm?\n          machine.provider.host_vm\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/cap/public_address.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Cap\n      module PublicAddress\n        def self.public_address(machine)\n          return nil if machine.state.id != :running\n\n          # If we're using a host VM, then return the IP of that\n          # rather than of our own machine.\n          if machine.provider.host_vm?\n            host_machine = machine.provider.host_vm\n            return nil if !host_machine.provider.capability?(:public_address)\n            return host_machine.provider.capability(:public_address)\n          end\n\n          ssh_info = machine.ssh_info\n          return nil if !ssh_info\n          ssh_info[:host]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/command/exec.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/safe_exec'\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Command\n      class Exec < Vagrant.plugin(\"2\", :command)\n        def self.synopsis\n          \"attach to an already-running docker container\"\n        end\n\n        def execute\n          options = {}\n          options[:detach] = false\n          options[:pty] = false\n          options[:interactive] = false\n          options[:prefix] = true\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant docker-exec [options] [name] -- <command> [args]\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"--[no-]detach\", \"Run in the background\") do |d|\n              options[:detach] = d\n            end\n\n            o.on(\"-i\", \"--[no-]interactive\", \"Keep STDIN open even if not attached\") do |i|\n              options[:interactive] = i\n            end\n\n            o.on(\"-t\", \"--[no-]tty\", \"Allocate a pty\") do |t|\n              options[:pty] = t\n            end\n\n            o.on(\"-u\", \"--user USER\", \"User or UID\") do |u|\n              options[:user] = u\n            end\n\n            o.on(\"--[no-]prefix\", \"Prefix output with machine names\") do |p|\n              options[:prefix] = p\n            end\n          end\n\n          # Parse out the extra args to send to SSH, which is everything\n          # after the \"--\"\n          command     = nil\n          split_index = @argv.index(\"--\")\n          if split_index\n            command = @argv.drop(split_index + 1)\n            @argv   = @argv.take(split_index)\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n\n          # Show the error if we don't have \"--\" _after_ parse_options\n          # so that \"-h\" and \"--help\" work properly.\n          if !split_index\n            raise Errors::ExecCommandRequired\n          end\n\n          target_opts = { provider: :docker }\n          target_opts[:single_target] = options[:pty]\n\n          with_target_vms(argv, target_opts) do |machine|\n            if machine.state.id != :running\n              @env.ui.info(\"#{machine.id} is not running.\")\n              next\n            end\n            exec_command(machine, command, options)\n          end\n\n          return 0\n        end\n\n        def exec_command(machine, command, options)\n          exec_cmd = %w(docker exec)\n          exec_cmd << \"-i\" if options[:interactive]\n          exec_cmd << \"-t\" if options[:pty]\n          exec_cmd << \"-u\" << options[:user] if options[:user]\n          exec_cmd << machine.id\n          exec_cmd += options[:extra_args] if options[:extra_args]\n          exec_cmd += command\n\n          # Run this interactively if asked.\n          exec_options = options\n\n          if options[:pty]\n            Vagrant::Util::SafeExec.exec(exec_cmd[0], *exec_cmd[1..-1])\n          else\n            output = \"\"\n            machine.provider.driver.execute(*exec_cmd, **exec_options) do |type, data|\n              output += data\n            end\n\n            output_options = {}\n            output_options[:prefix] = false if !options[:prefix]\n\n            if !output.empty?\n              machine.ui.output(output.chomp, **output_options)\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/command/logs.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Command\n      class Logs < Vagrant.plugin(\"2\", :command)\n        def self.synopsis\n          \"outputs the logs from the Docker container\"\n        end\n\n        def execute\n          options = {}\n          options[:follow] = false\n          options[:prefix] = true\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant docker-logs [options]\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"--[no-]follow\", \"Continue streaming in log output\") do |f|\n              options[:follow] = f\n            end\n\n            o.on(\"--[no-]prefix\", \"Prefix output with machine names\") do |p|\n              options[:prefix] = p\n            end\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n\n          # This keeps track of if we ran our action on any machines...\n          any_success = false\n\n          # Start a batch action that sends all the logs to stdout. This\n          # will parallelize, if enabled, across all containers that are\n          # chosen.\n          @env.batch do |batch|\n            with_target_vms(argv) do |machine|\n              if machine.provider_name != :docker\n                machine.ui.output(I18n.t(\"docker_provider.not_docker_provider\"))\n                next\n              end\n\n              state = machine.state.id\n              if state == :host_state_unknown\n                machine.ui.output(I18n.t(\"docker_provider.logs_host_state_unknown\"))\n                next\n              elsif state == :not_created\n                machine.ui.output(I18n.t(\"docker_provider.not_created_skip\"))\n                next\n              end\n\n              # At least one was run!\n              any_success = true\n\n              batch.custom(machine) do |m|\n                execute_single(m, options)\n              end\n            end\n          end\n\n          # If we didn't run on any machines, then exit status 1\n          return any_success ? 0 : 1\n        end\n\n        protected\n\n        # Executes the \"docker logs\" command on a single machine and proxies\n        # the output to our UI.\n        def execute_single(machine, options)\n          command = [\"docker\", \"logs\"]\n          command << \"--follow\" if options[:follow]\n          command << machine.id\n\n          output_options = {}\n          output_options[:prefix] = false if !options[:prefix]\n\n          data_acc = \"\"\n          machine.provider.driver.execute(*command) do |type, data|\n            # Accumulate the data so we only output lines at a time\n            data_acc << data\n\n            # If we have a newline, then output all the lines we have so far\n            if data_acc.include?(\"\\n\")\n              lines    = data_acc.split(\"\\n\")\n\n              if !data_acc.end_with?(\"\\n\")\n                data_acc = lines.pop.chomp\n              else\n                data_acc = \"\"\n              end\n\n              lines.each do |line|\n                line = \" \" if line == \"\"\n                machine.ui.output(line, **output_options)\n              end\n            end\n          end\n\n          # Output any remaining data\n          machine.ui.output(data_acc, **output_options) if !data_acc.empty?\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/command/run.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Command\n      class Run < Vagrant.plugin(\"2\", :command)\n        def self.synopsis\n          \"run a one-off command in the context of a container\"\n        end\n\n        def execute\n          options = {}\n          options[:detach] = false\n          options[:pty] = false\n          options[:rm] = true\n\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant docker-run [command...]\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"--[no-]detach\", \"Run in the background\") do |d|\n              options[:detach] = d\n            end\n\n            o.on(\"-t\", \"--[no-]tty\", \"Allocate a pty\") do |t|\n              options[:pty] = t\n            end\n\n            o.on(\"-r,\", \"--[no-]rm\", \"Remove container after execution\") do |r|\n              options[:rm] = r\n            end\n          end\n\n          # Parse out the extra args to send to SSH, which is everything\n          # after the \"--\"\n          command     = nil\n          split_index = @argv.index(\"--\")\n          if split_index\n            command = @argv.drop(split_index + 1)\n            @argv   = @argv.take(split_index)\n          end\n\n          # Parse the options\n          argv = parse_options(opts)\n          return if !argv\n\n          # Show the error if we don't have \"--\" _after_ parse_options\n          # so that \"-h\" and \"--help\" work properly.\n          if !split_index\n            @env.ui.error(I18n.t(\"docker_provider.run_command_required\"))\n            return 1\n          end\n\n          target_opts = { provider: :docker }\n          target_opts[:single_target] = options[:pty]\n\n          with_target_vms(argv, target_opts) do |machine|\n            # Run it!\n            machine.action(\n              :run_command,\n              run_command: command,\n              run_detach: options[:detach],\n              run_pty: options[:pty],\n              run_rm: options[:rm]\n            )\n          end\n\n          0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/communicator.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"digest/md5\"\nrequire \"tempfile\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    # This communicator uses the host VM as proxy to communicate to the\n    # actual Docker container via SSH.\n    class Communicator < Vagrant.plugin(\"2\", :communicator)\n      def initialize(machine)\n        @machine = machine\n        @host_vm = machine.provider.host_vm\n\n        # We only work on the Docker provider\n        if machine.provider_name != :docker\n          raise Errors::CommunicatorNotDocker\n        end\n      end\n\n      #-------------------------------------------------------------------\n      # Communicator Methods\n      #-------------------------------------------------------------------\n\n      def ready?\n        # We can't be ready if we can't talk to the host VM\n        return false if !@host_vm.communicate.ready?\n\n        # We're ready if we can establish an SSH connection to the container\n        command = container_ssh_command\n        return false if !command\n        @host_vm.communicate.test(\"#{command} exit\")\n      end\n\n      def download(from, to)\n        # Same process as upload, but in reverse\n\n        # First, we use `cat` to copy that file from the Docker container.\n        temp = \"/tmp/docker_d#{Time.now.to_i}_#{rand(100000)}\"\n        @host_vm.communicate.execute(\"#{container_ssh_command} 'cat #{from}' >#{temp}\")\n\n        # Then, we download this from the host VM.\n        @host_vm.communicate.download(temp, to)\n\n        # Remove the temporary file\n        @host_vm.communicate.execute(\"rm -f #{temp}\", error_check: false)\n      end\n\n      def execute(command, **opts, &block)\n        fence = {}\n        fence[:stderr] = \"VAGRANT FENCE: #{Time.now.to_i} #{rand(100000)}\"\n        fence[:stdout] = \"VAGRANT FENCE: #{Time.now.to_i} #{rand(100000)}\"\n\n        # We want to emulate how the SSH communicator actually executes\n        # things, so we build up the list of commands to execute in a\n        # giant shell script.\n        tf = Tempfile.new(\"vagrant\")\n        tf.binmode\n        tf.write(\"export TERM=vt100\\n\")\n        tf.write(\"echo #{fence[:stdout]}\\n\")\n        tf.write(\"echo #{fence[:stderr]} >&2\\n\")\n        tf.write(\"#{command}\\n\")\n        tf.write(\"exit\\n\")\n        tf.close\n\n        # Upload the temp file to the remote machine\n        remote_temp = \"/tmp/docker_#{Time.now.to_i}_#{rand(100000)}\"\n        @host_vm.communicate.upload(tf.path, remote_temp)\n\n        # Determine the shell to execute. Prefer the explicitly passed in shell\n        # over the default configured shell. If we are using `sudo` then we\n        # need to wrap the shell in a `sudo` call.\n        shell_cmd = @machine.config.ssh.shell\n        shell_cmd = opts[:shell] if opts[:shell]\n        shell_cmd = \"sudo -E -H #{shell_cmd}\" if opts[:sudo]\n\n        acc    = {}\n        fenced = {}\n        result = @host_vm.communicate.execute(\n          \"#{container_ssh_command} '#{shell_cmd}' <#{remote_temp}\",\n          opts) do |type, data|\n          # If we don't have a block, we don't care about the data\n          next if !block\n\n          # We only care about stdout and stderr output\n          next if ![:stdout, :stderr].include?(type)\n\n          # If we reached our fence, then just output\n          if fenced[type]\n            block.call(type, data)\n            next\n          end\n\n          # Otherwise, accumulate\n          acc[type] = data\n\n          # Look for the fence\n          index = acc[type].index(fence[type])\n          next if !index\n\n          fenced[type] = true\n          index += fence[type].length\n          data  = acc[type][index..-1].chomp\n          acc[type] = \"\"\n          block.call(type, data)\n        end\n\n        @host_vm.communicate.execute(\"rm -f #{remote_temp}\", error_check: false)\n\n        return result\n      end\n\n      def sudo(command, **opts, &block)\n        opts = { sudo: true }.merge(opts)\n        execute(command, opts, &block)\n      end\n\n      def test(command, **opts)\n        opts = { error_check: false }.merge(opts)\n        execute(command, opts) == 0\n      end\n\n      def upload(from, to)\n        # First, we upload this to the host VM to some temporary directory.\n        to_temp = \"/tmp/docker_#{Time.now.to_i}_#{rand(100000)}\"\n        @host_vm.communicate.upload(from, to_temp)\n\n        # Then, we use `cat` to get that file into the Docker container.\n        @host_vm.communicate.execute(\n          \"#{container_ssh_command} 'cat >#{to}' <#{to_temp}\")\n\n        # Remove the temporary file\n        @host_vm.communicate.execute(\"rm -f #{to_temp}\", error_check: false)\n      end\n\n      #-------------------------------------------------------------------\n      # Other Methods\n      #-------------------------------------------------------------------\n\n      # This returns the raw SSH command string that can be used to\n      # connect via SSH to the container if you're on the same machine\n      # as the container.\n      #\n      # @return [String]\n      def container_ssh_command\n        # Get the container's SSH info\n        info = @machine.ssh_info\n        return nil if !info\n        info[:port] ||= 22\n\n        # Make sure our private keys are synced over to the host VM\n        ssh_args = sync_private_keys(info).map do |path|\n          \"-i #{path}\"\n        end\n\n        # Use ad-hoc SSH options for the hop on the docker proxy\n        if info[:forward_agent]\n          ssh_args << \"-o ForwardAgent=yes\"\n        end\n        ssh_args.concat([\"-o Compression=yes\",\n                         \"-o ConnectTimeout=5\",\n                         \"-o StrictHostKeyChecking=no\",\n                         \"-o UserKnownHostsFile=/dev/null\"])\n\n        # Build the SSH command\n        \"ssh #{info[:username]}@#{info[:host]} -p#{info[:port]} #{ssh_args.join(\" \")}\"\n      end\n\n      protected\n\n      def sync_private_keys(info)\n        @keys ||= {}\n\n        id = Digest::MD5.hexdigest(\n          @machine.env.root_path.to_s + @machine.name.to_s)\n\n        result = []\n        info[:private_key_path].each do |path|\n          if !@keys[path.to_s]\n            # We haven't seen this before, upload it!\n            guest_path = \"/tmp/key_#{id}_#{Digest::MD5.hexdigest(path.to_s)}\"\n            @host_vm.communicate.upload(path.to_s, guest_path)\n\n            # Make sure it has the proper chmod\n            @host_vm.communicate.execute(\"chmod 0600 #{guest_path}\")\n\n            # Set it\n            @keys[path.to_s] = guest_path\n          end\n\n          result << @keys[path.to_s]\n        end\n\n        result\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nrequire_relative \"../../../lib/vagrant/util/platform\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    class Config < Vagrant.plugin(\"2\", :config)\n      attr_accessor :image, :cmd, :ports, :volumes, :privileged\n\n      # Additional arguments to pass to `docker build` when creating\n      # an image using the build dir setting.\n      #\n      # @return [Array<String>]\n      attr_accessor :build_args\n\n      # The directory with a Dockerfile to build and use as the basis\n      # for this container. If this is set, neither \"image\" nor \"git_repo\"\n      # should be set.\n      #\n      # @return [String]\n      attr_accessor :build_dir\n\n      # The URL for a git repository with a Dockerfile to build and use\n      # as the basis for this container. If this is set, neither \"image\"\n      # nor \"build_dir\" should be set.\n      #\n      # @return [String]\n      attr_accessor :git_repo\n\n      # Use docker-compose to manage the lifecycle and environment for\n      # containers instead of using docker directly.\n      #\n      # @return [Boolean]\n      attr_accessor :compose\n\n      # Configuration Hash used for build the docker-compose composition\n      # file. This can be used for adding networks or volumes.\n      #\n      # @return [Hash]\n      attr_accessor :compose_configuration\n\n      # An optional file name of a Dockerfile to be used when building\n      # the image. This requires Docker >1.5.0.\n      #\n      # @return [String]\n      attr_accessor :dockerfile\n\n      # Additional arguments to pass to `docker run` when creating\n      # the container for the first time. This is an array of args.\n      #\n      # @return [Array<String>]\n      attr_accessor :create_args\n\n      # Environmental variables to set in the container.\n      #\n      # @return [Hash]\n      attr_accessor :env\n\n      # Ports to expose from the container but not to the host machine.\n      # This is useful for links.\n      #\n      # @return [Array<Integer>]\n      attr_accessor :expose\n\n      # Force using a proxy VM, even on Linux hosts.\n      #\n      # @return [Boolean]\n      attr_accessor :force_host_vm\n\n      # True if the Docker container exposes SSH access. If this is true,\n      # then Vagrant can do a bunch more things like setting the hostname,\n      # provisioning, etc.\n      attr_accessor :has_ssh\n\n      # Options for the build dir synced folder if a host VM is in use.\n      #\n      # @return [Hash]\n      attr_accessor :host_vm_build_dir_options\n\n      # The name for the container. This must be unique for all containers\n      # on the proxy machine if it is made.\n      #\n      # @return [String]\n      attr_accessor :name\n\n      # If true, the image will be pulled on every `up` and `reload`\n      # to ensure the latest image.\n      #\n      # @return [Bool]\n      attr_accessor :pull\n\n      # True if the docker container is meant to stay in the \"running\"\n      # state (is a long running process). By default this is true.\n      #\n      # @return [Boolean]\n      attr_accessor :remains_running\n\n      # The time to wait before sending a SIGTERM to the container\n      # when it is stopped.\n      #\n      # @return [Integer]\n      attr_accessor :stop_timeout\n\n      # The name of the machine in the Vagrantfile set with\n      # \"vagrant_vagrantfile\" that will be the docker host. Defaults\n      # to \"default\"\n      #\n      # See the \"vagrant_vagrantfile\" docs for more info.\n      #\n      # @return [String]\n      attr_accessor :vagrant_machine\n\n      # The path to the Vagrantfile that contains a VM that will be\n      # started as the Docker host if needed (Windows, OS X, Linux\n      # without container support).\n      #\n      # Defaults to a built-in Vagrantfile that will load boot2docker.\n      #\n      # NOTE: This only has an effect if Vagrant needs a Docker host.\n      # Vagrant determines this automatically based on the environment\n      # it is running in.\n      #\n      # @return [String]\n      attr_accessor :vagrant_vagrantfile\n\n      #--------------------------------------------------------------\n      # Auth Settings\n      #--------------------------------------------------------------\n\n      # Server to authenticate to. If blank, will use the default\n      # Docker authentication endpoint (which is the Docker Hub at the\n      # time of this comment).\n      #\n      # @return [String]\n      attr_accessor :auth_server\n\n      # Email for logging in to a remote Docker server.\n      #\n      # @return [String]\n      attr_accessor :email\n\n      # Email for logging in to a remote Docker server.\n      #\n      # @return [String]\n      attr_accessor :username\n\n      # Password for logging in to a remote Docker server. If this is\n      # not blank, then Vagrant will run `docker login` prior to any\n      # Docker runs.\n      #\n      # The presence of auth will also force the Docker environments to\n      # serialize on `up` so that different users/passwords don't overlap.\n      #\n      # @return [String]\n      attr_accessor :password\n\n      def initialize\n        @build_args = []\n        @build_dir  = UNSET_VALUE\n        @git_repo   = UNSET_VALUE\n        @cmd        = UNSET_VALUE\n        @compose    = UNSET_VALUE\n        @compose_configuration = {}\n        @create_args = UNSET_VALUE\n        @dockerfile = UNSET_VALUE\n        @env        = {}\n        @expose     = []\n        @force_host_vm = UNSET_VALUE\n        @has_ssh    = UNSET_VALUE\n        @host_vm_build_dir_options = UNSET_VALUE\n        @image      = UNSET_VALUE\n        @name       = UNSET_VALUE\n        @links      = []\n        @pull       = UNSET_VALUE\n        @ports      = UNSET_VALUE\n        @privileged = UNSET_VALUE\n        @remains_running = UNSET_VALUE\n        @stop_timeout = UNSET_VALUE\n        @volumes    = []\n        @vagrant_machine = UNSET_VALUE\n        @vagrant_vagrantfile = UNSET_VALUE\n\n        @auth_server = UNSET_VALUE\n        @email    = UNSET_VALUE\n        @username = UNSET_VALUE\n        @password = UNSET_VALUE\n      end\n\n      def link(name)\n        @links << name\n      end\n\n      def merge(other)\n        super.tap do |result|\n          # This is a bit confusing. The tests explain the purpose of this\n          # better than the code lets on, I believe.\n          has_image     = (other.image != UNSET_VALUE)\n          has_build_dir = (other.build_dir != UNSET_VALUE)\n          has_git_repo  = (other.git_repo != UNSET_VALUE)\n\n          if (has_image ^ has_build_dir ^ has_git_repo) && !(has_image && has_build_dir && has_git_repo)\n            # image\n            if has_image\n              if @build_dir != UNSET_VALUE\n                result.build_dir = nil\n              end\n              if @git_repo != UNSET_VALUE\n                result.git_repo = nil\n              end\n            end\n\n            # build_dir\n            if has_build_dir\n              if @image != UNSET_VALUE\n                result.image = nil\n              end\n              if @git_repo != UNSET_VALUE\n                result.git_repo = nil\n              end\n            end\n\n            # git_repo\n            if has_git_repo\n              if @build_dir != UNSET_VALUE\n                result.build_dir = nil\n              end\n              if @image != UNSET_VALUE\n                result.image = nil\n              end\n            end\n          end\n\n          env = {}\n          env.merge!(@env) if @env\n          env.merge!(other.env) if other.env\n          result.env = env\n\n          expose = self.expose.dup\n          expose += other.expose\n          result.instance_variable_set(:@expose, expose)\n\n          links = _links.dup\n          links += other._links\n          result.instance_variable_set(:@links, links)\n        end\n      end\n\n      def finalize!\n        @build_args = [] if @build_args == UNSET_VALUE\n        @build_dir  = nil if @build_dir == UNSET_VALUE\n        @git_repo   = nil if @git_repo == UNSET_VALUE\n        @cmd        = [] if @cmd == UNSET_VALUE\n        @compose    = false if @compose == UNSET_VALUE\n        @create_args = [] if @create_args == UNSET_VALUE\n        @dockerfile = nil if @dockerfile == UNSET_VALUE\n        @env       ||= {}\n        @has_ssh    = false if @has_ssh == UNSET_VALUE\n        @image      = nil if @image == UNSET_VALUE\n        @name       = nil if @name == UNSET_VALUE\n        @pull       = false if @pull == UNSET_VALUE\n        @ports      = [] if @ports == UNSET_VALUE\n        @privileged = false if @privileged == UNSET_VALUE\n        @remains_running = true if @remains_running == UNSET_VALUE\n        @stop_timeout = 1 if @stop_timeout == UNSET_VALUE\n        @vagrant_machine = nil if @vagrant_machine == UNSET_VALUE\n        @vagrant_vagrantfile = nil if @vagrant_vagrantfile == UNSET_VALUE\n\n        @auth_server = nil if @auth_server == UNSET_VALUE\n        @email = \"\" if @email == UNSET_VALUE\n        @username = \"\" if @username == UNSET_VALUE\n        @password = \"\" if @password == UNSET_VALUE\n\n        if @host_vm_build_dir_options == UNSET_VALUE\n          @host_vm_build_dir_options = nil\n        end\n\n        # On non-linux platforms (where there is no native docker), force the\n        # host VM. Other users can optionally disable this by setting the\n        # value explicitly to false in their Vagrantfile.\n        if @force_host_vm == UNSET_VALUE\n          @force_host_vm = !Vagrant::Util::Platform.linux? &&\n            !Vagrant::Util::Platform.darwin? &&\n            !Vagrant::Util::Platform.windows?\n        end\n\n        # The machine name must be a symbol\n        @vagrant_machine = @vagrant_machine.to_sym if @vagrant_machine\n\n        @expose.uniq!\n\n        if @compose_configuration.is_a?(Hash)\n          # Ensures configuration is using basic types\n          @compose_configuration = JSON.parse(@compose_configuration.to_json)\n        end\n      end\n\n      def validate(machine)\n        errors = _detected_errors\n\n        if [@build_dir, @git_repo, @image].compact.size > 1\n          errors << I18n.t(\"docker_provider.errors.config.both_build_and_image_and_git\")\n        end\n\n        if !@build_dir && !@git_repo && !@image\n          errors << I18n.t(\"docker_provider.errors.config.build_dir_or_image\")\n        end\n\n        if @build_dir\n          build_dir_pn = Pathname.new(@build_dir)\n          if !build_dir_pn.directory?\n            errors << I18n.t(\"docker_provider.errors.config.build_dir_invalid\")\n          end\n        end\n\n        # Comparison logic taken directly from docker's urlutil.go\n        if @git_repo && !( @git_repo =~ /^http(?:s)?:\\/\\/.*.git(?:#.+)?$/ || @git_repo =~ /^git(?:hub\\.com|@|:\\/\\/)/)\n          errors << I18n.t(\"docker_provider.errors.config.git_repo_invalid\")\n        end\n\n        if !@compose_configuration.is_a?(Hash)\n          errors << I18n.t(\"docker_provider.errors.config.compose_configuration_hash\")\n        end\n\n        if @compose && @force_host_vm\n          errors << I18n.t(\"docker_provider.errors.config.compose_force_vm\")\n        end\n\n        if !@create_args.is_a?(Array)\n          errors << I18n.t(\"docker_provider.errors.config.create_args_array\")\n        end\n\n        @links.each do |link|\n          parts = link.split(\":\")\n          if parts.length != 2 || parts[0] == \"\" || parts[1] == \"\"\n            errors << I18n.t(\n              \"docker_provider.errors.config.invalid_link\", link: link)\n          end\n        end\n\n        if @vagrant_vagrantfile\n          vf_pn = Pathname.new(@vagrant_vagrantfile)\n          if !vf_pn.file?\n            errors << I18n.t(\"docker_provider.errors.config.invalid_vagrantfile\")\n          end\n        end\n\n        { \"docker provider\" => errors }\n      end\n\n      #--------------------------------------------------------------\n      # Functions below should not be called by config files\n      #--------------------------------------------------------------\n\n      def _links\n        @links\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/driver/compose.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    class Driver\n      class Compose < Driver\n\n        # @return [Integer] Maximum number of seconds to wait for lock\n        LOCK_TIMEOUT = 60\n        # @return [String] Compose file format version\n        COMPOSE_VERSION = \"2\".freeze\n\n        # @return [Pathname] data directory to store composition\n        attr_reader :data_directory\n        # @return [Vagrant::Machine]\n        attr_reader :machine\n\n        # Create a new driver instance\n        #\n        # @param [Vagrant::Machine] machine Machine instance for this driver\n        def initialize(machine)\n          if !Vagrant::Util::Which.which(\"docker-compose\")\n            raise Errors::DockerComposeNotInstalledError\n          end\n          super()\n          @machine = machine\n          @data_directory = Pathname.new(machine.env.local_data_path).\n            join(\"docker-compose\")\n          @data_directory.mkpath\n          @logger = Log4r::Logger.new(\"vagrant::docker::driver::compose\")\n          @compose_lock = Mutex.new\n          @logger.debug(\"Docker compose driver initialize for machine `#{@machine.name}` (`#{@machine.id}`)\")\n          @logger.debug(\"Data directory for composition file `#{@data_directory}`\")\n        end\n\n        # Updates the docker compose config file with the given arguments\n        #\n        # @param [String] dir - local directory or git repo URL\n        # @param [Hash] opts - valid key: extra_args\n        # @param [Block] block\n        # @return [Nil]\n        def build(dir, **opts, &block)\n          name = machine.name.to_s\n          @logger.debug(\"Applying build for `#{name}` using `#{dir}` directory.\")\n          begin\n            update_composition do |composition|\n              services = composition[\"services\"] ||= {}\n              services[name] ||= {}\n              services[name][\"build\"] = {\"context\" => dir}\n              # Extract custom dockerfile location if set\n              if opts[:extra_args] && opts[:extra_args].include?(\"--file\")\n                services[name][\"build\"][\"dockerfile\"] = opts[:extra_args][opts[:extra_args].index(\"--file\") + 1]\n              end\n              # Extract any build args that can be found\n              case opts[:extra_args]\n              when Array\n                if opts[:extra_args].include?(\"--build-arg\")\n                  idx = 0\n                  extra_args = {}\n                  while(idx < opts[:extra_args].size)\n                    arg_value = opts[:extra_args][idx]\n                    idx += 1\n                    if arg_value.start_with?(\"--build-arg\")\n                      if !arg_value.include?(\"=\")\n                        arg_value = opts[:extra_args][idx]\n                        idx += 1\n                      end\n                      key, val = arg_value.to_s.split(\"=\", 2).to_s.split(\"=\")\n                      extra_args[key] = val\n                    end\n                  end\n                end\n              when Hash\n                services[name][\"build\"][\"args\"] = opts[:extra_args]\n              end\n            end\n          rescue => error\n            @logger.error(\"Failed to apply build using `#{dir}` directory: #{error.class} - #{error}\")\n            update_composition do |composition|\n              composition[\"services\"].delete(name)\n            end\n            raise\n          end\n        end\n\n        def create(params, **opts, &block)\n          # NOTE: Use the direct machine name as we don't\n          # need to worry about uniqueness with compose\n          name    = machine.name.to_s\n          image   = params.fetch(:image)\n          links   = Array(params.fetch(:links, [])).map do |link|\n            case link\n            when Array\n              link\n            else\n              link.to_s.split(\":\")\n            end\n          end\n          ports   = Array(params[:ports])\n          volumes = Array(params[:volumes]).map do |v|\n            v = v.to_s\n            host, guest = v.split(\":\", 2)\n            if v.include?(\":\") && (Vagrant::Util::Platform.windows? || Vagrant::Util::Platform.wsl?)\n              host = Vagrant::Util::Platform.windows_path(host)\n              # NOTE: Docker does not support UNC style paths (which also\n              # means that there's no long path support). Hopefully this\n              # will be fixed someday and the gsub below can be removed.\n              host.gsub!(/^[^A-Za-z]+/, \"\")\n            end\n            # if host path is a volume key, don't expand it.\n            # if both exist (a path and a key) show warning and move on\n            # otherwise assume it's a realative path and expand the host path\n            compose_config = get_composition\n            if compose_config[\"volumes\"] && compose_config[\"volumes\"].keys.include?(host)\n              if File.directory?(@machine.env.cwd.join(host).to_s)\n                @machine.env.ui.warn(I18n.t(\"docker_provider.volume_path_not_expanded\",\n                                           host: host))\n              end\n            else\n              @logger.debug(\"Path expanding #{host} to current Vagrant working dir instead of docker-compose config file directory\")\n              host = @machine.env.cwd.join(host).to_s\n            end\n            \"#{host}:#{guest}\"\n          end\n          cmd     = Array(params.fetch(:cmd))\n          env     = Hash[*params.fetch(:env).flatten.map(&:to_s)]\n          expose  = Array(params[:expose])\n          @logger.debug(\"Creating container `#{name}`\")\n          begin\n            update_args = [:apply]\n            update_args.push(:detach) if params[:detach]\n            update_args << block\n            update_composition(*update_args) do |composition|\n              services = composition[\"services\"] ||= {}\n              services[name] ||= {}\n              if params[:extra_args].is_a?(Hash)\n                services[name].merge!(\n                  Hash[\n                    params[:extra_args].map{ |k, v|\n                      [k.to_s, v]\n                    }\n                  ]\n                )\n              end\n              services[name].merge!(\n                \"environment\" => env,\n                \"expose\" => expose,\n                \"ports\" => ports,\n                \"volumes\" => volumes,\n                \"links\" => links,\n                \"command\" => cmd\n              )\n              services[name][\"image\"] = image if image\n              services[name][\"hostname\"] = params[:hostname] if params[:hostname]\n              services[name][\"privileged\"] = true if params[:privileged]\n              services[name][\"pty\"] = true if params[:pty]\n            end\n          rescue => error\n            @logger.error(\"Failed to create container `#{name}`: #{error.class} - #{error}\")\n            update_composition do |composition|\n              composition[\"services\"].delete(name)\n            end\n            raise\n          end\n          get_container_id(name)\n        end\n\n        def rm(cid)\n          if created?(cid)\n            destroy = false\n            synchronized do\n              compose_execute(\"rm\", \"-f\", machine.name.to_s)\n              update_composition do |composition|\n                if composition[\"services\"] && composition[\"services\"].key?(machine.name.to_s)\n                  @logger.info(\"Removing container `#{machine.name}`\")\n                  if composition[\"services\"].size > 1\n                    composition[\"services\"].delete(machine.name.to_s)\n                  else\n                    destroy = true\n                  end\n                end\n              end\n              if destroy\n                @logger.info(\"No containers remain. Destroying full environment.\")\n                compose_execute(\"down\", \"--volumes\", \"--rmi\", \"local\")\n                @logger.info(\"Deleting composition path `#{composition_path}`\")\n                composition_path.delete\n              end\n            end\n          end\n        end\n\n        def rmi(*_)\n          true\n        end\n\n        def created?(cid)\n          result = super\n          if !result\n            composition = get_composition\n            if composition[\"services\"] && composition[\"services\"].has_key?(machine.name.to_s)\n              result = true\n            end\n          end\n          result\n        end\n\n        private\n\n        # Lookup the ID for the container with the given name\n        #\n        # @param [String] name Name of container\n        # @return [String] Container ID\n        def get_container_id(name)\n          compose_execute(\"ps\", \"-q\", name).chomp\n        end\n\n        # Execute a `docker-compose` command\n        def compose_execute(*cmd, **opts, &block)\n          synchronized do\n            execute(\"docker-compose\", \"-f\", composition_path.to_s,\n              \"-p\", machine.env.cwd.basename.to_s, *cmd, **opts, &block)\n          end\n        end\n\n        # Apply any changes made to the composition\n        def apply_composition!(*args)\n          block = args.detect{|arg| arg.is_a?(Proc) }\n          execute_args = [\"up\", \"--remove-orphans\"]\n          if args.include?(:detach)\n            execute_args << \"-d\"\n          end\n          machine.env.lock(\"compose\", retry: true) do\n            if block\n              compose_execute(*execute_args, &block)\n            else\n              compose_execute(*execute_args)\n            end\n          end\n        end\n\n        # Update the composition and apply changes if requested\n        #\n        # @param [Boolean] apply Apply composition changes\n        def update_composition(*args)\n          synchronized do\n            machine.env.lock(\"compose\", retry: true) do\n              composition = get_composition\n              result = yield composition\n              write_composition(composition)\n              if args.include?(:apply) || (args.include?(:conditional) && result)\n                apply_composition!(*args)\n              end\n            end\n          end\n        end\n\n        # @return [Hash] current composition contents\n        def get_composition\n          composition = {\"version\" => COMPOSE_VERSION.dup}\n          if composition_path.exist?\n            composition = Vagrant::Util::DeepMerge.deep_merge(composition, YAML.load(composition_path.read))\n          end\n          composition = Vagrant::Util::DeepMerge.deep_merge(composition, machine.provider_config.compose_configuration.dup)\n          @logger.debug(\"Fetched composition with provider configuration applied: #{composition}\")\n          composition\n        end\n\n        # Save the composition\n        #\n        # @param [Hash] composition New composition\n        def write_composition(composition)\n          @logger.debug(\"Saving composition to `#{composition_path}`: #{composition}\")\n          tmp_file = Tempfile.new(\"vagrant-docker-compose\")\n          tmp_file.write(composition.to_yaml)\n          tmp_file.close\n          synchronized do\n            FileUtils.mv(tmp_file.path, composition_path.to_s)\n          end\n        end\n\n        # @return [Pathname] path to the docker-compose.yml file\n        def composition_path\n          data_directory.join(\"docker-compose.yml\")\n        end\n\n        def synchronized\n          if !@compose_lock.owned?\n            timeout = LOCK_TIMEOUT.to_f\n            until @compose_lock.owned?\n              if @compose_lock.try_lock\n                if timeout > 0\n                  timeout -= sleep(1)\n                else\n                  raise Errors::ComposeLockTimeoutError\n                end\n              end\n            end\n            got_lock = true\n          end\n          begin\n            result = yield\n          ensure\n            @compose_lock.unlock if got_lock\n          end\n          result\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/driver.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\nrequire \"log4r\"\n\nrequire_relative \"./driver/compose\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    class Driver\n      # The executor is responsible for actually executing Docker commands.\n      # This is set by the provider, but defaults to local execution.\n      attr_accessor :executor\n\n      def initialize\n        @logger   = Log4r::Logger.new(\"vagrant::docker::driver\")\n        @executor = Executor::Local.new\n      end\n\n      # Returns the id for a new container built from `docker build`. Raises\n      # an exception if the id was unable to be captured from the output\n      #\n      # @return [String] id - ID matched from the docker build output.\n      def build(dir, **opts, &block)\n        args = Array(opts[:extra_args])\n        args << dir\n        opts = {with_stderr: true}\n        result = execute('docker', 'build', *args, **opts, &block)\n        # Check for the new output format 'writing image sha256...'\n        # In this case, docker buildkit is enabled. Its format is different\n        # from standard docker\n        matches = result.scan(/writing image .+:([^\\s]+)/i).last\n        if !matches\n          # Check for output of docker using containerd backend store\n          matches = result.scan(/exporting manifest list .+:([^\\s]+)/i).last\n        end\n        if !matches\n          if podman?\n            # Check for podman format when it is emulating docker CLI.\n            # Podman outputs the full hash of the container on\n            # the last line after a successful build.\n            match = result.split.select { |str| str.match?(/^[0-9a-z]{64}/) }.last\n            return match[0..7] unless match.nil?\n          else\n            matches = result.scan(/Successfully built (.+)$/i).last\n          end\n\n          if !matches\n            # This will cause a stack trace in Vagrant, but it is a bug\n            # if this happens anyways.\n            raise Errors::BuildError, result: result\n          end\n        end\n\n        # Return the matched group `id`\n        matches[0].strip\n      end\n\n      # Check if podman emulating docker CLI is enabled.\n      #\n      # @return [Bool]\n      def podman?\n        execute('docker', '--version').include?(\"podman\")\n      end\n\n      def create(params, **opts, &block)\n        image   = params.fetch(:image)\n        links   = params.fetch(:links)\n        ports   = Array(params[:ports])\n        volumes = Array(params[:volumes])\n        name    = params.fetch(:name)\n        cmd     = Array(params.fetch(:cmd))\n        env     = params.fetch(:env)\n        expose  = Array(params[:expose])\n\n        run_cmd = %W(docker run --name #{name})\n        run_cmd << \"-d\" if params[:detach]\n        run_cmd += env.map { |k,v| ['-e', \"#{k}=#{v}\"] }\n        run_cmd += expose.map { |p| ['--expose', \"#{p}\"] }\n        run_cmd += links.map { |k, v| ['--link', \"#{k}:#{v}\"] }\n        run_cmd += ports.map { |p| ['-p', p.to_s] }\n        run_cmd += volumes.map { |v|\n          v = v.to_s\n          if v.include?(\":\") && @executor.windows?\n            if v.index(\":\") != v.rindex(\":\")\n              # If we have 2 colons, the host path is an absolute Windows URL\n              # and we need to remove the colon from it\n              host, _, guest = v.rpartition(\":\")\n              host = \"//\" + host[0].downcase + host[2..-1]\n              v = [host, guest].join(\":\")\n            else\n              host, guest = v.split(\":\", 2)\n              host = Vagrant::Util::Platform.windows_path(host)\n              # NOTE: Docker does not support UNC style paths (which also\n              # means that there's no long path support). Hopefully this\n              # will be fixed someday and the gsub below can be removed.\n              host.gsub!(/^[^A-Za-z]+/, \"\")\n              v = [host, guest].join(\":\")\n            end\n          end\n\n          ['-v', v.to_s]\n        }\n        run_cmd += %W(--privileged) if params[:privileged]\n        run_cmd += %W(-h #{params[:hostname]}) if params[:hostname]\n        run_cmd << \"-t\" if params[:pty]\n        run_cmd << \"--rm=true\" if params[:rm]\n        run_cmd += params[:extra_args] if params[:extra_args]\n        run_cmd += [image, cmd]\n\n        execute(*run_cmd.flatten, **opts, &block).chomp.lines.last\n      end\n\n      def state(cid)\n        case\n        when running?(cid)\n          :running\n        when created?(cid)\n          :stopped\n        else\n          :not_created\n        end\n      end\n\n      def created?(cid)\n        result = execute('docker', 'ps', '-a', '-q', '--no-trunc').to_s\n        result =~ /^#{Regexp.escape cid}$/\n      end\n\n      def image?(id)\n        result = execute('docker', 'images', '-q', '--no-trunc').to_s\n        result =~ /\\b#{Regexp.escape(id)}\\b/\n      end\n\n      # Reads all current docker containers and determines what ports\n      # are currently registered to be forwarded\n      # {2222=>#<Set: {\"127.0.0.1\"}>, 8080=>#<Set: {\"*\"}>, 9090=>#<Set: {\"*\"}>}\n      #\n      # Note: This is this format because of what the builtin action for resolving colliding\n      # port forwards expects.\n      #\n      # @return [Hash[Set]] used_ports - {forward_port: #<Set: {\"host ip address\"}>}\n      def read_used_ports\n        used_ports = Hash.new{|hash,key| hash[key] = Set.new}\n\n        all_containers.each do |c|\n          container_info = inspect_container(c)\n\n          active = container_info[\"State\"][\"Running\"]\n          next unless active # Ignore used ports on inactive containers\n\n          if container_info[\"HostConfig\"][\"PortBindings\"]\n            port_bindings = container_info[\"HostConfig\"][\"PortBindings\"]\n            next if port_bindings.empty? # Nothing defined, but not nil either\n\n            port_bindings.each do |guest_port,host_mapping|\n              host_mapping.each do |h|\n                if h[\"HostIp\"] == \"\"\n                  hostip = \"*\"\n                else\n                  hostip = h[\"HostIp\"]\n                end\n                hostport = h[\"HostPort\"]\n                used_ports[hostport].add(hostip)\n              end\n            end\n          end\n        end\n\n        used_ports\n      end\n\n      def running?(cid)\n        result = execute('docker', 'ps', '-q', '--no-trunc')\n        result =~ /^#{Regexp.escape cid}$/m\n      end\n\n      def privileged?(cid)\n        inspect_container(cid)['HostConfig']['Privileged']\n      end\n\n      def login(email, username, password, server)\n        cmd = %W(docker login)\n        cmd += [\"-e\", email] if email != \"\"\n        cmd += [\"-u\", username] if username != \"\"\n        cmd += [\"-p\", password] if password != \"\"\n        cmd << server if server && server != \"\"\n\n        execute(*cmd.flatten)\n      end\n\n      def logout(server)\n        cmd = %W(docker logout)\n        cmd << server if server && server != \"\"\n        execute(*cmd.flatten)\n      end\n\n      def pull(image)\n        execute('docker', 'pull', image)\n      end\n\n      def start(cid)\n        if !running?(cid)\n          execute('docker', 'start', cid)\n          # This resets the cached information we have around, allowing `vagrant reload`s\n          # to work properly\n          @data = nil\n        end\n      end\n\n      def stop(cid, timeout)\n        if running?(cid)\n          execute('docker', 'stop', '-t', timeout.to_s, cid)\n        end\n      end\n\n      def rm(cid)\n        if created?(cid)\n          execute('docker', 'rm', '-f', '-v', cid)\n        end\n      end\n\n      def rmi(id)\n        execute('docker', 'rmi', id)\n        return true\n      rescue => e\n        return false if e.to_s.include?(\"is using it\") or\n                        e.to_s.include?(\"is being used\") or\n                        e.to_s.include?(\"is in use\")\n        raise if !e.to_s.include?(\"No such image\")\n      end\n\n      # Inspect the provided container\n      #\n      # @param [String] cid ID or name of container\n      # @return [Hash]\n      def inspect_container(cid)\n        JSON.parse(execute('docker', 'inspect', cid)).first\n      end\n\n      # @return [Array<String>] list of all container IDs\n      def all_containers\n        execute('docker', 'ps', '-a', '-q', '--no-trunc').to_s.split\n      end\n\n      # Attempts to first use the docker-cli tool to inspect the default bridge subnet\n      # Falls back to using /sbin/ip if that fails\n      #\n      # @return [String] IP address of the docker bridge\n      def docker_bridge_ip\n        bridge = inspect_network(\"bridge\")&.first\n        if bridge \n          bridge_ip = bridge.dig(\"IPAM\", \"Config\", 0, \"Gateway\")\n        end\n        return bridge_ip if bridge_ip\n        @logger.debug(\"Failed to get bridge ip from docker, falling back to `ip`\")\n        docker_bridge_ip_fallback\n      end\n\n      def docker_bridge_ip_fallback\n        output = execute('ip', '-4', 'addr', 'show', 'scope', 'global', 'docker0')\n        if output =~ /^\\s+inet ([0-9.]+)\\/[0-9]+\\s+/\n          return $1.to_s\n        else\n          # TODO: Raise a user-friendly message\n          raise 'Unable to fetch docker bridge IP!'\n        end\n      end\n\n      # @param [String] network - name of network to connect container to\n      # @param [String] cid - container id\n      # @param [Array]  opts - An array of flags used for listing networks\n      def connect_network(network, cid, opts=nil)\n        command = ['docker', 'network', 'connect', network, cid].push(*opts)\n        output = execute(*command)\n        output\n      end\n\n      # @param [String] network - name of network to create\n      # @param [Array]  opts - An array of flags used for listing networks\n      def create_network(network, opts=nil)\n        command = ['docker', 'network', 'create', network].push(*opts)\n        output = execute(*command)\n        output\n      end\n\n      # @param [String] network - name of network to disconnect container from\n      # @param [String] cid - container id\n      def disconnect_network(network, cid)\n        command = ['docker', 'network', 'disconnect', network, cid, \"--force\"]\n        output = execute(*command)\n        output\n      end\n\n      # @param [Array]  networks - list of networks to inspect\n      # @param [Array]  opts - An array of flags used for listing networks\n      def inspect_network(network, opts=nil)\n        command = ['docker', 'network', 'inspect'] + Array(network)\n        command = command.push(*opts)\n        output = execute(*command)\n        begin\n          JSON.load(output)\n        rescue JSON::ParserError\n          @logger.warn(\"Failed to parse network inspection of network: #{network}\")\n          @logger.debug(\"Failed network output content: `#{output.inspect}`\")\n          nil\n        end\n      end\n\n      # @param [String] opts - Flags used for listing networks\n      def list_network(*opts)\n        command = ['docker', 'network', 'ls', *opts]\n        output = execute(*command)\n        output\n      end\n\n      # Will delete _all_ defined but unused networks in the docker engine. Even\n      # networks not created by Vagrant.\n      #\n      # @param [Array] opts - An array of flags used for listing networks\n      def prune_network(opts=nil)\n        command = ['docker', 'network', 'prune', '--force'].push(*opts)\n        output = execute(*command)\n        output\n      end\n\n      # Delete network(s)\n      #\n      # @param [String] network - name of network to remove\n      def rm_network(*network)\n        command = ['docker', 'network', 'rm', *network]\n        output = execute(*command)\n        output\n      end\n\n      # @param [Array] opts - An array of flags used for listing networks\n      def execute(*cmd, **opts, &block)\n        @executor.execute(*cmd, **opts, &block)\n      end\n\n      # ######################\n      # Docker network helpers\n      # ######################\n\n      # Determines if a given network has been defined through vagrant with a given\n      # subnet string\n      #\n      # @param [String] subnet_string - Subnet to look for\n      # @return [String] network name - Name of network with requested subnet.`nil` if not found\n      def network_defined?(subnet_string)\n        all_networks = list_network_names\n\n        network_info = inspect_network(all_networks)\n        network_info.each do |network|\n          config = Array(network.dig(\"IPAM\", \"Config\"))\n          next if config.empty? || !config.first.is_a?(Hash)\n          if (config.first[\"Subnet\"] == subnet_string)\n            @logger.debug(\"Found existing network #{network[\"Name\"]} already configured with #{subnet_string}\")\n            return network[\"Name\"]\n          end\n        end\n        return nil\n      end\n\n      # Locate network which contains given address\n      #\n      # @param [String] address IP address\n      # @return [String] network name\n      def network_containing_address(address)\n        names = list_network_names\n        networks = inspect_network(names)\n        return if !networks\n        networks.each do |net|\n          next if !net[\"IPAM\"]\n          config = net[\"IPAM\"][\"Config\"]\n          next if !config || config.size < 1\n          config.each do |opts|\n            subnet = IPAddr.new(opts[\"Subnet\"])\n            if subnet.include?(address)\n              return net[\"Name\"]\n            end\n          end\n        end\n        nil\n      end\n\n      # Looks to see if a docker network has already been defined\n      # with the given name\n      #\n      # @param [String] network_name - name of network to look for\n      # @return [Bool]\n      def existing_named_network?(network_name)\n        result = list_network_names\n        result.any?{|net_name| net_name == network_name}\n      end\n\n      # @return [Array<String>] list of all docker networks\n      def list_network_names\n        list_network(\"--format={{.Name}}\").split(\"\\n\").map(&:strip)\n      end\n\n      # Returns true or false if network is in use or not.\n      # Nil if Vagrant fails to receive proper JSON from `docker network inspect`\n      #\n      # @param [String] network - name of network to look for\n      # @return [Bool,nil]\n      def network_used?(network)\n        result = inspect_network(network)\n        return nil if !result\n        return result.first[\"Containers\"].size > 0\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Errors\n      class DockerError < Vagrant::Errors::VagrantError\n        error_namespace(\"docker_provider.errors\")\n      end\n\n      class BuildError < DockerError\n        error_key(:build_error)\n      end\n\n      class CommunicatorNonDocker < DockerError\n        error_key(:communicator_non_docker)\n      end\n\n      class ComposeLockTimeoutError < DockerError\n        error_key(:compose_lock_timeout)\n      end\n\n      class ContainerNotRunningError < DockerError\n        error_key(:not_running)\n      end\n\n      class ContainerNotCreatedError < DockerError\n        error_key(:not_created)\n      end\n\n      class DockerComposeNotInstalledError < DockerError\n        error_key(:docker_compose_not_installed)\n      end\n\n      class ExecuteError < DockerError\n        error_key(:execute_error)\n      end\n\n      class ExecCommandRequired < DockerError\n        error_key(:exec_command_required)\n      end\n\n      class HostVMCommunicatorNotReady < DockerError\n        error_key(:host_vm_communicator_not_ready)\n      end\n\n      class ImageNotConfiguredError < DockerError\n        error_key(:docker_provider_image_not_configured)\n      end\n\n      class NfsWithoutPrivilegedError < DockerError\n        error_key(:docker_provider_nfs_without_privileged)\n      end\n\n      class NetworkAddressInvalid < DockerError\n        error_key(:network_address_invalid)\n      end\n\n      class NetworkIPAddressRequired < DockerError\n        error_key(:network_address_required)\n      end\n\n      class NetworkSubnetInvalid < DockerError\n        error_key(:network_subnet_invalid)\n      end\n\n      class NetworkInvalidOption < DockerError\n        error_key(:network_invalid_option)\n      end\n\n      class NetworkNameMissing < DockerError\n        error_key(:network_name_missing)\n      end\n\n      class NetworkNameUndefined < DockerError\n        error_key(:network_name_undefined)\n      end\n\n      class NetworkNoInterfaces < DockerError\n        error_key(:network_no_interfaces)\n      end\n\n      class PackageNotSupported < DockerError\n        error_key(:package_not_supported)\n      end\n\n      class StateNotRunning < DockerError\n        error_key(:state_not_running)\n      end\n\n      class StateStopped < DockerError\n        error_key(:state_stopped)\n      end\n\n      class SuspendNotSupported < DockerError\n        error_key(:suspend_not_supported)\n      end\n\n      class SyncedFolderNonDocker < DockerError\n        error_key(:synced_folder_non_docker)\n      end\n\n      class VagrantfileNotFound < DockerError\n        error_key(:vagrantfile_not_found)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/executor/local.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/busy\"\nrequire \"vagrant/util/subprocess\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Executor\n      # The Local executor executes a Docker client that is running\n      # locally.\n      class Local\n        def execute(*cmd, **opts, &block)\n          # Append in the options for subprocess\n          cmd << { notify: [:stdout, :stderr] }\n\n          interrupted  = false\n          int_callback = ->{ interrupted = true }\n          result = ::Vagrant::Util::Busy.busy(int_callback) do\n            ::Vagrant::Util::Subprocess.execute(*cmd, &block)\n          end\n\n          result.stderr.gsub!(\"\\r\\n\", \"\\n\")\n          result.stdout.gsub!(\"\\r\\n\", \"\\n\")\n\n          if result.exit_code != 0 && !interrupted\n            raise Errors::ExecuteError,\n              command: cmd.inspect,\n              stderr: result.stderr,\n              stdout: result.stdout\n          end\n\n          if opts\n            if opts[:with_stderr]\n              return result.stdout + \" \" + result.stderr\n            else\n              return result.stdout\n            end\n          end\n        end\n\n        def windows?\n          ::Vagrant::Util::Platform.windows? || ::Vagrant::Util::Platform.wsl?\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/executor/vagrant.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/shell_quote\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    module Executor\n      # The Vagrant executor runs Docker over SSH against the given\n      # Vagrant-managed machine.\n      class Vagrant\n        def initialize(host_machine)\n          @host_machine = host_machine\n        end\n\n        def execute(*cmd, **opts, &block)\n          quote = '\"'\n          cmd   = cmd.map do |a|\n            \"#{quote}#{::Vagrant::Util::ShellQuote.escape(a, quote)}#{quote}\"\n          end.join(\" \")\n\n          # If we want stdin, we just run in a full subprocess\n          return ssh_run(cmd) if opts[:stdin]\n\n          # Add a start fence so we know when to start reading output.\n          # We have to do this because boot2docker outputs a login shell\n          # boot2docker version that we get otherwise and messes up output.\n          start_fence = \"========== VAGRANT DOCKER BEGIN ==========\"\n          ssh_cmd     = \"echo -n \\\"#{start_fence}\\\"; #{cmd}\"\n\n          stderr = \"\"\n          stdout = \"\"\n          fenced = false\n          comm   = @host_machine.communicate\n          code   = comm.execute(ssh_cmd, error_check: false) do |type, data|\n            next if ![:stdout, :stderr].include?(type)\n            stderr << data if type == :stderr\n            stdout << data if type == :stdout\n\n            if !fenced\n              index = stdout.index(start_fence)\n              if index\n                fenced = true\n\n                index += start_fence.length\n                stdout = stdout[index..-1]\n                stdout.chomp!\n\n                # We're now fenced, send all the data through\n                if block\n                  block.call(:stdout, stdout) if stdout != \"\"\n                  block.call(:stderr, stderr) if stderr != \"\"\n                end\n              end\n            else\n              # If we're already fenced, just send the data through.\n              block.call(type, data) if block && fenced\n            end\n          end\n\n          if code != 0\n            raise Errors::ExecuteError,\n              command: cmd,\n              stderr: stderr.chomp,\n              stdout: stdout.chomp\n          end\n\n          stdout.chomp\n        end\n\n        def windows?\n          false\n        end\n\n        protected\n\n        def ssh_run(cmd)\n          @host_machine.action(\n            :ssh_run,\n            ssh_run_command: cmd,\n          )\n\n          \"\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/hostmachine/Vagrantfile",
    "content": "Vagrant.configure(\"2\") do |config|\n  config.vm.box = \"hashicorp/boot2docker\"\nend\n"
  },
  {
    "path": "plugins/providers/docker/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    autoload :Action, File.expand_path(\"../action\", __FILE__)\n    autoload :Driver, File.expand_path(\"../driver\", __FILE__)\n    autoload :Errors, File.expand_path(\"../errors\", __FILE__)\n\n    module Executor\n      autoload :Local, File.expand_path(\"../executor/local\", __FILE__)\n      autoload :Vagrant, File.expand_path(\"../executor/vagrant\", __FILE__)\n    end\n\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"docker-provider\"\n      description <<-EOF\n      The Docker provider allows Vagrant to manage and control\n      Docker containers.\n      EOF\n\n      provider(:docker, box_optional: true, parallel: true, defaultable: false) do\n        require_relative 'provider'\n        init!\n        Provider\n      end\n\n      command(\"docker-exec\", primary: false) do\n        require_relative \"command/exec\"\n        init!\n        Command::Exec\n      end\n\n      command(\"docker-logs\", primary: false) do\n        require_relative \"command/logs\"\n        init!\n        Command::Logs\n      end\n\n      command(\"docker-run\", primary: false) do\n        require_relative \"command/run\"\n        init!\n        Command::Run\n      end\n\n      communicator(:docker_hostvm) do\n        require_relative \"communicator\"\n        init!\n        Communicator\n      end\n\n      config(:docker, :provider) do\n        require_relative 'config'\n        init!\n        Config\n      end\n\n      synced_folder(:docker) do\n        require File.expand_path(\"../synced_folder\", __FILE__)\n        SyncedFolder\n      end\n\n      provider_capability(\"docker\", \"public_address\") do\n        require_relative \"cap/public_address\"\n        Cap::PublicAddress\n      end\n\n      provider_capability(\"docker\", \"proxy_machine\") do\n        require_relative \"cap/proxy_machine\"\n        Cap::ProxyMachine\n      end\n\n      provider_capability(\"docker\", \"has_communicator\") do\n        require_relative \"cap/has_communicator\"\n        Cap::HasCommunicator\n      end\n\n      protected\n\n      def self.init!\n        return if defined?(@_init)\n        I18n.load_path << File.expand_path(\n          \"templates/locales/providers_docker.yml\", Vagrant.source_root)\n        I18n.reload!\n        @_init = true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/provider.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"digest/md5\"\nrequire \"fileutils\"\nrequire \"thread\"\nrequire \"log4r\"\n\nrequire \"vagrant/util/silence_warnings\"\n\nmodule VagrantPlugins\n  module DockerProvider\n    class Provider < Vagrant.plugin(\"2\", :provider)\n      @@host_vm_mutex = Mutex.new\n\n      def self.usable?(raise_error=false)\n        Driver.new.execute(\"docker\", \"version\")\n        true\n      rescue Vagrant::Errors::CommandUnavailable, Errors::ExecuteError\n        raise if raise_error\n        return false\n      end\n\n      def initialize(machine)\n        @logger  = Log4r::Logger.new(\"vagrant::provider::docker\")\n        @machine = machine\n\n        if host_vm?\n          # We need to use a special communicator that proxies our\n          # SSH requests over our host VM to the container itself.\n          @machine.config.vm.communicator = :docker_hostvm\n        end\n      end\n\n      # @see Vagrant::Plugin::V2::Provider#action\n      def action(name)\n        action_method = \"action_#{name}\"\n        return Action.send(action_method) if Action.respond_to?(action_method)\n        nil\n      end\n\n      # Returns the driver instance for this provider.\n      def driver\n        if !@driver\n          if @machine.provider_config.compose\n            @driver = Driver::Compose.new(@machine)\n          else\n            @driver = Driver.new\n          end\n        end\n        if host_vm?\n          @driver.executor = Executor::Vagrant.new(host_vm)\n        end\n\n        @driver\n      end\n\n      # This returns the {Vagrant::Machine} that is our host machine.\n      # It does not perform any action on the machine or verify it is\n      # running.\n      #\n      # @return [Vagrant::Machine]\n      def host_vm\n        return @host_vm if @host_vm\n\n        vf_path           = @machine.provider_config.vagrant_vagrantfile\n        host_machine_name = @machine.provider_config.vagrant_machine || :default\n        if !vf_path\n          # We don't have a Vagrantfile path set, so we're going to use\n          # the default but we need to copy it into the data dir so that\n          # we don't write into our installation dir (we can't).\n          default_path = File.expand_path(\"../hostmachine/Vagrantfile\", __FILE__)\n          vf_path      = @machine.env.data_dir.join(\"docker-host\", \"Vagrantfile\")\n          begin\n            @machine.env.lock(\"docker-provider-hostvm\") do\n              vf_path.dirname.mkpath\n              FileUtils.cp(default_path, vf_path)\n            end\n          rescue Vagrant::Errors::EnvironmentLockedError\n            # Lock contention, just retry\n            retry\n          end\n\n          # Set the machine name since we hardcode that for the default\n          host_machine_name = :default\n        end\n\n        # Expand it so that the home directories and so on get processed\n        # properly.\n        vf_path = File.expand_path(vf_path, @machine.env.root_path)\n\n        vf_file = File.basename(vf_path)\n        vf_path = File.dirname(vf_path)\n\n        # Create the env to manage this machine\n        @host_vm = Vagrant::Util::SilenceWarnings.silence! do\n          host_env = Vagrant::Environment.new(\n            cwd: vf_path,\n            home_path: @machine.env.home_path,\n            ui_class: @machine.env.ui_class,\n            vagrantfile_name: vf_file,\n          )\n\n          # If there is no root path, then the Vagrantfile wasn't found\n          # and it is an error...\n          raise Errors::VagrantfileNotFound if !host_env.root_path\n\n          host_env.machine(\n            host_machine_name,\n            host_env.default_provider(\n              exclude: [:docker],\n              force_default: false,\n            ))\n        end\n\n        @host_vm\n      end\n\n      # This acquires a lock on the host VM.\n      def host_vm_lock\n        hash = Digest::MD5.hexdigest(host_vm.data_dir.to_s)\n\n        # We do a process-level mutex on the outside, since we can\n        # wait for that a short amount of time. Then, we do a process lock\n        # on the inside, which will raise an exception if locked.\n        host_vm_mutex.synchronize do\n          @machine.env.lock(hash) do\n            return yield\n          end\n        end\n      end\n\n      # This is a process-local mutex that can be used by parallel\n      # providers to lock the host VM access.\n      def host_vm_mutex\n        @@host_vm_mutex\n      end\n\n      # This says whether or not Docker will be running within a VM\n      # rather than directly on our system. Docker needs to run in a VM\n      # when we're not on Linux, or not on a Linux that supports Docker.\n      def host_vm?\n        @machine.provider_config.force_host_vm\n      end\n\n      # Returns the SSH info for accessing the Container.\n      def ssh_info\n        # If the container isn't running, we can't SSH into it\n        return nil if state.id != :running\n\n        port_name = \"#{@machine.config.ssh.guest_port}/tcp\"\n        network   = driver.inspect_container(@machine.id)['NetworkSettings']\n\n        if network[\"Ports\"][port_name].respond_to?(:first)\n          port_info = network[\"Ports\"][port_name].first\n        else\n          ip = network[\"IPAddress\"]\n          port = @machine.config.ssh.guest_port\n          if !ip.to_s.empty?\n            port_info = {\n              \"HostIp\" => ip,\n              \"HostPort\" => port\n            }\n          end\n        end\n\n        # If we were not able to identify the container's IP, we return nil\n        # here and we let Vagrant core deal with it ;)\n        return nil if port_info.nil? || port_info.empty?\n\n        {\n          host: port_info['HostIp'],\n          port: port_info['HostPort']\n        }\n      end\n\n      def state\n        state_id = nil\n        state_id = :not_created if !@machine.id\n\n        begin\n          state_id = :host_state_unknown if !state_id && \\\n            host_vm? && !host_vm.communicate.ready?\n        rescue Errors::VagrantfileNotFound\n          state_id = :host_state_unknown\n        end\n\n        state_id = :not_created if !state_id && \\\n          (!@machine.id || !driver.created?(@machine.id))\n        state_id = driver.state(@machine.id) if @machine.id && !state_id\n        state_id = :unknown if !state_id\n\n        # This is a special pseudo-state so that we don't set the\n        # NOT_CREATED_ID while we're setting up the machine. This avoids\n        # clearing the data dir.\n        state_id = :preparing if @machine.id == \"preparing\"\n\n        short = state_id.to_s.gsub(\"_\", \" \")\n        long  = I18n.t(\"docker_provider.status.#{state_id}\")\n\n        # If we're not created, then specify the special ID flag\n        if state_id == :not_created\n          state_id = Vagrant::MachineState::NOT_CREATED_ID\n        end\n\n        Vagrant::MachineState.new(state_id, short, long)\n      end\n\n      def to_s\n        id = @machine.id ? @machine.id : \"new container\"\n        \"Docker (#{id})\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/docker/synced_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvider\n    class SyncedFolder < Vagrant.plugin(\"2\", :synced_folder)\n      def usable?(machine, raise_error=false)\n        # These synced folders only work if the provider is Docker\n        if machine.provider_name != :docker\n          if raise_error\n            raise Errors::SyncedFolderNonDocker,\n              provider: machine.provider_name.to_s\n          end\n\n          return false\n        end\n\n        true\n      end\n\n      def prepare(machine, folders, _opts)\n        folders.each do |id, data|\n          next if data[:ignore]\n\n          host_path  = data[:hostpath]\n          guest_path = data[:guestpath]\n          # Append consistency option if it exists, otherwise let it nil out\n          consistency = data[:docker_consistency]\n          consistency &&= \":\" + consistency\n          machine.provider_config.volumes << \"#{host_path}:#{guest_path}#{consistency}\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/check_access.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class CheckAccess\n        def initialize(app, env)\n          @app    = app\n        end\n\n        def call(env)\n          env[:ui].output(\"Verifying Hyper-V is accessible...\")\n          result = env[:machine].provider.driver.execute(:check_hyperv_access,\n            \"Path\" => Vagrant::Util::Platform.wsl_to_windows_path(env[:machine].data_dir).gsub(\"/\", \"\\\\\")\n          )\n          if !result[\"result\"]\n            raise Errors::SystemAccessRequired,\n              root_dir: result[\"root_dir\"]\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/check_enabled.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class CheckEnabled\n        def initialize(app, env)\n          @app    = app\n        end\n\n        def call(env)\n          env[:ui].output(\"Verifying Hyper-V is enabled...\")\n          result = env[:machine].provider.driver.execute(\"check_hyperv.ps1\", {})\n          raise Errors::PowerShellFeaturesDisabled if !result[\"result\"]\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/configure.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class Configure\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::hyperv::configure\")\n        end\n\n        def call(env)\n          switches = env[:machine].provider.driver.execute(:get_switches)\n          if switches.empty?\n            raise Errors::NoSwitches\n          end\n\n          switch = nil\n          env[:machine].config.vm.networks.each do |type, opts|\n            next if type != :public_network && type != :private_network\n\n            if opts[:bridge]\n              @logger.debug(\"Looking for switch with name or ID: #{opts[:bridge]}\")\n              switch = switches.find{ |s|\n                s[\"Name\"].downcase == opts[:bridge].to_s.downcase ||\n                  s[\"Id\"].downcase == opts[:bridge].to_s.downcase\n              }\n              if switch\n                @logger.debug(\"Found switch - Name: #{switch[\"Name\"]} ID: #{switch[\"Id\"]}\")\n                switch = switch[\"Id\"]\n                break\n              end\n            end\n          end\n\n          # If we already configured previously don't prompt for switch\n          sentinel = env[:machine].data_dir.join(\"action_configure\")\n\n          if !switch && !sentinel.file?\n            if switches.length > 1\n              env[:ui].detail(I18n.t(\"vagrant_hyperv.choose_switch\") + \"\\n \")\n              switches.each_index do |i|\n                switch = switches[i]\n                env[:ui].detail(\"#{i+1}) #{switch[\"Name\"]}\")\n              end\n              env[:ui].detail(\" \")\n\n              switch = nil\n              while !switch\n                switch = env[:ui].ask(\"What switch would you like to use? \")\n                next if !switch\n                switch = switch.to_i - 1\n                switch = nil if switch < 0 || switch >= switches.length\n              end\n              switch = switches[switch][\"Id\"]\n            else\n              switch = switches.first[\"Id\"]\n              @logger.debug(\"Only single switch available so using that.\")\n            end\n          end\n\n          options = {\n            \"VMID\" => env[:machine].id,\n            \"SwitchID\" => switch,\n            \"Memory\" => env[:machine].provider_config.memory,\n            \"MaxMemory\" => env[:machine].provider_config.maxmemory,\n            \"Processors\" => env[:machine].provider_config.cpus,\n            \"AutoStartAction\" => env[:machine].provider_config.auto_start_action,\n            \"AutoStopAction\" => env[:machine].provider_config.auto_stop_action,\n            \"EnableCheckpoints\" => env[:machine].provider_config.enable_checkpoints,\n            \"EnableAutomaticCheckpoints\" => env[:machine].provider_config.enable_automatic_checkpoints,\n            \"VirtualizationExtensions\" => !!env[:machine].provider_config.enable_virtualization_extensions,\n          }\n          options.delete_if{|_,v| v.nil? }\n\n          env[:ui].detail(\"Configuring the VM...\")\n          env[:machine].provider.driver.execute(:configure_vm, options)\n\n          # Create the sentinel\n          if !sentinel.file?\n            sentinel.open(\"w\") do |f|\n              f.write(Time.now.to_i.to_s)\n            end\n          end\n\n          if !env[:machine].provider_config.vm_integration_services.empty?\n            env[:ui].detail(\"Setting VM Integration Services\")\n\n            env[:machine].provider_config.vm_integration_services.each do |key, value|\n              state = value ? \"enabled\" : \"disabled\"\n              env[:ui].output(\"#{key} is #{state}\")\n            end\n\n            env[:machine].provider.driver.set_vm_integration_services(\n              env[:machine].provider_config.vm_integration_services)\n          end\n\n          if env[:machine].provider_config.enable_enhanced_session_mode\n            env[:ui].detail(I18n.t(\"vagrant.hyperv_enable_enhanced_session\"))\n            env[:machine].provider.driver.set_enhanced_session_transport_type(\"HvSocket\")\n          else\n            env[:ui].detail(I18n.t(\"vagrant.hyperv_disable_enhanced_session\"))\n            env[:machine].provider.driver.set_enhanced_session_transport_type(\"VMBus\")\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/delete_vm.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class DeleteVM\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[:ui].info(\"Deleting the machine...\")\n          env[:machine].provider.driver.delete_vm\n          # NOTE: We remove the data directory and recreate it\n          #       to overcome an issue seen when running within\n          #       the WSL. Hyper-V will successfully remove the\n          #       VM and the files will appear to be gone, but\n          #       on a subsequent up, they will cause collisions.\n          #       This forces them to be gone for real.\n          FileUtils.rm_rf(env[:machine].data_dir)\n          FileUtils.mkdir_p(env[:machine].data_dir)\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/export.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class Export\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          @env = env\n\n          @env[:ui].info @env[:machine].state.id.to_s\n\n          raise Vagrant::Errors::VMPowerOffToPackage if\n            @env[:machine].state.id != :off\n\n          export\n\n          @app.call(env)\n        end\n\n        def export\n          @env[:ui].info I18n.t(\"vagrant.actions.vm.export.exporting\")\n          export_tmp_dir = Vagrant::Util::Platform.wsl_to_windows_path(@env[\"export.temp_dir\"])\n          @env[:machine].provider.driver.export(export_tmp_dir) do |progress|\n            @env[:ui].rewriting do |ui|\n              ui.clear_line\n              ui.report_progress(progress.percent, 100, false)\n            end\n          end\n\n          # Clear the line a final time so the next data can appear\n          # alone on the line.\n          @env[:ui].clear_line\n        end\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/import.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class Import\n\n        VALID_HD_EXTENSIONS = [\".vhd\".freeze, \".vhdx\".freeze].freeze\n\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::hyperv::import\")\n        end\n\n        def call(env)\n          vm_dir = env[:machine].box.directory.join(\"Virtual Machines\")\n          hd_dir = env[:machine].box.directory.join(\"Virtual Hard Disks\")\n\n          if !vm_dir.directory? || !hd_dir.directory?\n            @logger.error(\"Required virtual machine directory not found!\")\n            raise Errors::BoxInvalid, name: env[:machine].name\n          end\n\n          valid_config_ext = [\".xml\"]\n          if env[:machine].provider.driver.has_vmcx_support?\n            valid_config_ext << \".vmcx\"\n          end\n\n          config_path = nil\n          vm_dir.each_child do |file|\n            if valid_config_ext.include?(file.extname.downcase)\n              config_path = file\n              break\n            end\n          end\n\n          if !config_path\n            @logger.error(\"Failed to locate box configuration path\")\n            raise Errors::BoxInvalid, name: env[:machine].name\n          else\n            @logger.info(\"Found box configuration path: #{config_path}\")\n          end\n\n          image_path = nil\n          hd_dir.each_child do |file|\n            if VALID_HD_EXTENSIONS.include?(file.extname.downcase)\n              image_path = file\n              break\n            end\n          end\n\n          if !image_path\n            @logger.error(\"Failed to locate box image path\")\n            raise Errors::BoxInvalid, name: env[:machine].name\n          else\n            @logger.info(\"Found box image path: #{image_path}\")\n          end\n\n          env[:ui].output(\"Importing a Hyper-V instance\")\n          dest_path = env[:machine].data_dir.join(\"Virtual Hard Disks\").join(image_path.basename).to_s\n\n          options = {\n            \"VMConfigFile\" => Vagrant::Util::Platform.wsl_to_windows_path(config_path).gsub(\"/\", \"\\\\\"),\n            \"DestinationPath\" => Vagrant::Util::Platform.wsl_to_windows_path(dest_path).gsub(\"/\", \"\\\\\"),\n            \"DataPath\" => Vagrant::Util::Platform.wsl_to_windows_path(env[:machine].data_dir).gsub(\"/\", \"\\\\\"),\n            \"LinkedClone\" => !!env[:machine].provider_config.linked_clone,\n            \"SourcePath\" => Vagrant::Util::Platform.wsl_to_windows_path(image_path).gsub(\"/\", \"\\\\\"),\n            \"VMName\" => env[:machine].provider_config.vmname,\n            \"Memory\" => env[:machine].provider_config.memory,\n            \"MaxMemory\" => env[:machine].provider_config.maxmemory,\n            \"Processors\" => env[:machine].provider_config.cpus,\n          }\n\n          env[:ui].detail(\"Creating and registering the VM...\")\n          server = env[:machine].provider.driver.import(options)\n\n          @logger.debug(\"import result value: #{server.inspect}\")\n\n          sid = case server[\"id\"]\n                when String\n                  server[\"id\"]\n                when Array\n                  server[\"id\"].first\n                else\n                  raise TypeError,\n                    \"Expected String or Array value, received: #{server[\"id\"].class}\"\n                end\n\n          env[:ui].detail(\"Successfully imported VM\")\n          env[:machine].id = sid\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/is_windows.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class IsWindows\n        def initialize(app, env)\n          @app    = app\n        end\n\n        def call(env)\n          env[:result] = env[:machine].config.vm.guest == :windows\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/message_will_not_destroy.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class MessageWillNotDestroy\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[:ui].info I18n.t(\"vagrant.commands.destroy.will_not_destroy\",\n                              name: env[:machine].name)\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/net_set_mac.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class NetSetMac\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          mac = env[:machine].provider_config.mac\n          if mac\n            env[:ui].info(\"[Settings] [Network Adapter] Setting MAC address to: #{mac}\")\n            env[:machine].provider.driver.net_set_mac(mac)\n          end  \n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/net_set_vlan.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class NetSetVLan\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          vlan_id = env[:machine].provider_config.vlan_id\n          if  vlan_id\n            env[:ui].info(\"[Settings] [Network Adapter] Setting Vlan ID to: #{vlan_id}\")\n            env[:machine].provider.driver.net_set_vlan(vlan_id)\n          end  \n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/package.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../lib/vagrant/action/general/package\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class Package < Vagrant::Action::General::Package\n        # Doing this so that we can test that the parent is properly\n        # called in the unit tests.\n        alias_method :general_call, :call\n        def call(env)\n          general_call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/package_metadata_json.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\n\n#require 'vagrant/util/template_renderer'\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class PackageMetadataJson\n        # For TemplateRenderer\n        include Vagrant::Util\n\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          @env = env\n          create_metadata\n          @app.call(env)\n        end\n\n        # This method creates a metadata.json file to tell vagrant this is a\n        # Hyper V box\n        def create_metadata\n          File.open(File.join(@env[\"export.temp_dir\"], \"metadata.json\"), \"w\") do |f|\n            f.write(JSON.generate({\n              provider: \"hyperv\"\n            }))\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/package_setup_files.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../lib/vagrant/action/general/package_setup_files\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class PackageSetupFiles < Vagrant::Action::General::PackageSetupFiles\n        # Doing this so that we can test that the parent is properly\n        # called in the unit tests.\n        alias_method :general_call, :call\n        def call(env)\n          general_call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/package_setup_folders.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\n\nrequire_relative \"../../../../lib/vagrant/action/general/package_setup_folders\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class PackageSetupFolders < Vagrant::Action::General::PackageSetupFolders\n        # Doing this so that we can test that the parent is properly\n        # called in the unit tests.\n        alias_method :general_call, :call\n        def call(env)\n          general_call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/package_vagrantfile.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/template_renderer'\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class PackageVagrantfile\n        # For TemplateRenderer\n        include Vagrant::Util\n\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          @env = env\n          create_vagrantfile\n          @app.call(env)\n        end\n\n        # This method creates the auto-generated Vagrantfile at the root of the\n        # box. This Vagrantfile contains the MAC address so that the user doesn't\n        # have to worry about it.\n        def create_vagrantfile\n          File.open(File.join(@env[\"export.temp_dir\"], \"Vagrantfile\"), \"w\") do |f|\n            mac_address = @env[:machine].provider.driver.read_mac_address\n            f.write(TemplateRenderer.render(\"package_Vagrantfile\", {\n              base_mac: mac_address[\"mac\"]\n            }))\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/read_guest_ip.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"timeout\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      # This action reads the SSH info for the machine and puts it into the\n      # `:machine_ssh_info` key in the environment.\n      class ReadGuestIP\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::hyperv::connection\")\n        end\n\n        def call(env)\n          env[:machine_ssh_info] = read_host_ip(env)\n          @app.call(env)\n        end\n\n        def read_host_ip(env)\n          return nil if env[:machine].id.nil?\n\n          # Get Network details from WMI Provider\n          # Wait for 120 sec By then the machine should be ready\n          host_ip = nil\n          begin\n            Timeout.timeout(120) do\n            begin\n              network_info  = env[:machine].provider.driver.read_guest_ip\n              host_ip = network_info[\"ip\"]\n              sleep 10 if host_ip.empty?\n              end while host_ip.empty?\n            end\n          rescue Timeout::Error\n            @logger.info(\"Cannot find the IP address of the virtual machine\")\n          end\n          return { host: host_ip } unless host_ip.nil?\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/read_state.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class ReadState\n        def initialize(app, env)\n          @app    = app\n          @logger = Log4r::Logger.new(\"vagrant::hyperv::connection\")\n        end\n\n        def call(env)\n          if env[:machine].id\n            response = env[:machine].provider.driver.get_current_state\n            env[:machine_state_id] = response[\"state\"].downcase.to_sym\n\n            # If the machine isn't created, then our ID is stale, so just\n            # mark it as not created.\n            if env[:machine_state_id] == :not_created\n              env[:machine].id = nil\n            end\n          else\n            env[:machine_state_id] = :not_created\n          end\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/resume_vm.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\r\n  module HyperV\r\n    module Action\r\n      class ResumeVM\r\n        def initialize(app, env)\r\n          @app = app\r\n        end\r\n\r\n        def call(env)\r\n          env[:ui].info(\"Resuming the machine...\")\r\n          env[:machine].provider.driver.resume\r\n          @app.call(env)\r\n        end\r\n      end\r\n    end\r\n  end\r\nend\r\n"
  },
  {
    "path": "plugins/providers/hyperv/action/set_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class SetName\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::hyperv::set_name\")\n        end\n\n        def call(env)\n          name = env[:machine].provider_config.vmname\n\n          # If we already set the name before, then don't do anything\n          sentinel = env[:machine].data_dir.join(\"action_set_name\")\n          if !name && sentinel.file?\n            @logger.info(\"Default name was already set before, not doing it again.\")\n            return @app.call(env)\n          end\n\n          # If no name was manually set, then use a default\n          if !name\n            prefix = \"#{env[:root_path].basename.to_s}_#{env[:machine].name}\"\n            prefix.gsub!(/[^-a-z0-9_]/i, \"\")\n\n            # milliseconds + random number suffix to allow for simultaneous\n            # `vagrant up` of the same box in different dirs\n            name = prefix + \"_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}\"\n          end\n\n          env[:machine].provider.driver.set_name(name)\n\n          # Create the sentinel\n          sentinel.open(\"w\") do |f|\n            f.write(Time.now.to_i.to_s)\n          end\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/snapshot_delete.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class SnapshotDelete\n        def initialize(app, env)\n          @app    = app\n        end\n\n        def call(env)\n\n          env[:ui].info(I18n.t(\n            \"vagrant.actions.vm.snapshot.deleting\",\n            name: env[:snapshot_name]))\n\n          env[:machine].provider.driver.delete_snapshot(env[:snapshot_name])\n\n          env[:ui].success(I18n.t(\n            \"vagrant.actions.vm.snapshot.deleted\",\n            name: env[:snapshot_name]))\n\n          @app.call(env)\n\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/snapshot_restore.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class SnapshotRestore\n        def initialize(app, env)\n          @app    = app\n        end\n\n        def call(env)\n\n          env[:ui].info(I18n.t(\n            \"vagrant.actions.vm.snapshot.restoring\",\n            name: env[:snapshot_name]))\n\n          env[:machine].provider.driver.restore_snapshot(env[:snapshot_name])\n\n          @app.call(env)\n\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/snapshot_save.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class SnapshotSave\n        def initialize(app, env)\n          @app    = app\n        end\n\n        def call(env)\n\n          env[:ui].info(I18n.t(\n            \"vagrant.actions.vm.snapshot.saving\",\n            name: env[:snapshot_name]))\n\n          env[:machine].provider.driver.create_snapshot(env[:snapshot_name])\n\n          env[:ui].success(I18n.t(\n            \"vagrant.actions.vm.snapshot.saved\",\n            name: env[:snapshot_name]))\n\n          @app.call(env)\n\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/start_instance.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class StartInstance\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[:ui].output('Starting the machine...')\n          env[:machine].provider.driver.start\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/stop_instance.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class StopInstance\n        def initialize(app, env)\n          @app    = app\n        end\n\n        def call(env)\n          env[:ui].info(\"Stopping the machine...\")\n          env[:machine].provider.driver.stop\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action/suspend_vm.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\r\n  module HyperV\r\n    module Action\r\n      class SuspendVM\r\n        def initialize(app, env)\r\n          @app = app\r\n        end\r\n\r\n        def call(env)\r\n          env[:ui].info(\"Suspending the machine...\")\r\n          env[:machine].provider.driver.suspend\r\n          @app.call(env)\r\n        end\r\n      end\r\n    end\r\n  end\r\nend\r\n"
  },
  {
    "path": "plugins/providers/hyperv/action/wait_for_ip_address.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"ipaddr\"\nrequire \"timeout\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      class WaitForIPAddress\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::hyperv::wait_for_ip_addr\")\n        end\n\n        def call(env)\n          timeout = env[:machine].provider_config.ip_address_timeout\n\n          env[:ui].output(\"Waiting for the machine to report its IP address...\")\n          env[:ui].detail(\"Timeout: #{timeout} seconds\")\n\n          guest_ip = nil\n          Timeout.timeout(timeout) do\n            while true\n              # If a ctrl-c came through, break out\n              return if env[:interrupted]\n\n              # Try to get the IP\n              begin\n                network_info = env[:machine].provider.driver.read_guest_ip\n                guest_ip = network_info[\"ip\"]\n\n                if guest_ip\n                  begin\n                    IPAddr.new(guest_ip)\n                    break\n                  rescue IPAddr::InvalidAddressError\n                    # Ignore, continue looking.\n                    @logger.warn(\"Invalid IP address returned: #{guest_ip}\")\n                  end\n                end\n              rescue Errors::PowerShellError\n                # Ignore, continue looking.\n                @logger.warn(\"Failed to read guest IP.\")\n              end\n              sleep 1\n            end\n          end\n\n          # If we were interrupted then return now\n          return if env[:interrupted]\n\n          env[:ui].detail(\"IP: #{guest_ip}\")\n\n          @app.call(env)\n        rescue Timeout::Error\n          raise Errors::IPAddrTimeout\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/action.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nrequire \"vagrant/action/builder\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Action\n      # Include the built-in modules so we can use them as top-level things.\n      include Vagrant::Action::Builtin\n\n      def self.action_reload\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ConfigValidate\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              b2.use Message, I18n.t(\"vagrant_hyperv.message_not_created\")\n              next\n            end\n\n            b2.use action_halt\n            b2.use action_start\n          end\n        end\n      end\n\n      def self.action_destroy\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use Call, IsState, :not_created do |env1, b1|\n            if env1[:result]\n              b1.use Message, I18n.t(\"vagrant_hyperv.message_not_created\")\n              next\n            end\n\n            b1.use Call, DestroyConfirm do |env2, b2|\n              if !env2[:result]\n                b2.use MessageWillNotDestroy\n                next\n              end\n\n              b2.use ConfigValidate\n              b2.use ProvisionerCleanup, :before\n              b2.use StopInstance\n              b2.use DeleteVM\n              b2.use SyncedFolderCleanup\n            end\n          end\n        end\n      end\n\n      def self.action_halt\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ConfigValidate\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              b2.use Message, I18n.t(\"vagrant_hyperv.message_not_created\")\n              next\n            end\n\n            b2.use Call, GracefulHalt, :off, :running do |env2, b3|\n              if !env2[:result]\n                b3.use StopInstance\n              end\n            end\n          end\n        end\n      end\n\n      # This action packages the virtual machine into a single box file.\n      def self.action_package\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckEnabled\n          b.use Call, IsState, :not_created do |env1, b2|\n            if env1[:result]\n              b2.use Message, I18n.t(\"vagrant_hyperv.message_not_created\")\n              next\n            end\n\n            b2.use PackageSetupFolders\n            b2.use PackageSetupFiles\n            b2.use action_halt\n            b2.use SyncedFolderCleanup\n            b2.use Package\n            b2.use PackageVagrantfile\n            b2.use PackageMetadataJson\n            b2.use Export\n          end\n        end\n      end\n\n      def self.action_provision\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ConfigValidate\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              b2.use Message, I18n.t(\"vagrant_hyperv.message_not_created\")\n              next\n            end\n\n            b2.use Call, IsState, :running do |env1, b3|\n              if !env1[:result]\n                b3.use Message, I18n.t(\"vagrant_hyperv.message_not_running\")\n                next\n              end\n\n              b3.use Provision\n            end\n          end\n        end\n      end\n\n      def self.action_resume\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use HandleBox\n          b.use ConfigValidate\n          b.use Call, IsState, :not_created do |env, b1|\n            if env[:result]\n              b1.use Message, I18n.t(\"vagrant_hyperv.message_not_created\")\n              next\n            end\n\n            b1.use ResumeVM\n            b1.use WaitForIPAddress\n            b1.use WaitForCommunicator, [:running]\n          end\n        end\n      end\n\n      def self.action_start\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use Call, IsState, :running do |env1, b1|\n            if env1[:result]\n              b1.use action_provision\n              next\n            end\n\n            b1.use Call, IsState, :paused do |env2, b2|\n              if env2[:result]\n                b2.use action_resume\n                next\n              end\n\n              b2.use Call, IsState, :saved do |env3, b3|\n                # When state is `:saved` it is a snapshot being restored\n                if !env3[:result]\n                  b3.use Provision\n                  b3.use Configure\n                  b3.use SetName\n                  b3.use NetSetVLan\n                  b3.use NetSetMac\n                end\n\n                b3.use CloudInitSetup\n                b3.use CleanupDisks\n                b3.use Disk\n                b3.use SyncedFolderCleanup\n                b3.use StartInstance\n                b3.use WaitForIPAddress\n                b3.use WaitForCommunicator, [:running]\n                b3.use CloudInitWait\n                b3.use SyncedFolders\n                b3.use SetHostname\n              end\n            end\n          end\n        end\n      end\n\n      def self.action_up\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckEnabled\n          b.use CheckAccess\n          b.use HandleBox\n          b.use ConfigValidate\n          b.use Call, IsState, :not_created do |env1, b1|\n            if env1[:result]\n              b1.use Import\n            end\n\n            b1.use action_start\n          end\n        end\n      end\n\n      def self.action_read_state\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ConfigValidate\n          b.use ReadState\n        end\n      end\n\n      def self.action_ssh\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ConfigValidate\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              raise Vagrant::Errors::VMNotCreatedError\n            end\n\n            b2.use Call, IsState, :running do |env1, b3|\n              if !env1[:result]\n                raise Vagrant::Errors::VMNotRunningError\n              end\n\n              b3.use SSHExec\n            end\n          end\n        end\n      end\n\n      def self.action_ssh_run\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ConfigValidate\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              raise Vagrant::Errors::VMNotCreatedError\n            end\n\n            b2.use Call, IsState, :running do |env1, b3|\n              if !env1[:result]\n                raise Vagrant::Errors::VMNotRunningError\n              end\n\n              b3.use SSHRun\n            end\n          end\n        end\n      end\n\n      def self.action_suspend\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ConfigValidate\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              b2.use Message, I18n.t(\"vagrant_hyperv.message_not_created\")\n              next\n            end\n\n            b2.use SuspendVM\n          end\n        end\n      end\n\n      def self.action_snapshot_delete\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ConfigValidate\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              b2.use Message, I18n.t(\"vagrant_hyperv.message_not_created\")\n              next\n            end\n\n            b2.use SnapshotDelete\n\n          end\n        end\n      end\n\n      def self.action_snapshot_restore\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ConfigValidate\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              b2.use Message, I18n.t(\"vagrant_hyperv.message_not_created\")\n              next\n            end\n\n            b2.use action_halt\n            b2.use SnapshotRestore\n\n            b2.use Call, IsEnvSet, :snapshot_delete do |env2, b3|\n              if env2[:result]\n                b3.use action_snapshot_delete\n              end\n            end\n\n            b2.use action_start\n\n          end\n        end\n      end\n\n      def self.action_snapshot_save\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use ConfigValidate\n          b.use Call, IsState, :not_created do |env, b2|\n            if env[:result]\n              b2.use Message, I18n.t(\"vagrant_hyperv.message_not_created\")\n              next\n            end\n            b2.use SnapshotSave\n          end\n        end\n      end\n\n      # The autoload farm\n      action_root = Pathname.new(File.expand_path(\"../action\", __FILE__))\n      autoload :PackageSetupFolders, action_root.join(\"package_setup_folders\")\n      autoload :PackageSetupFiles, action_root.join(\"package_setup_files\")\n      autoload :PackageVagrantfile, action_root.join(\"package_vagrantfile\")\n      autoload :PackageMetadataJson, action_root.join(\"package_metadata_json\")\n      autoload :Export, action_root.join(\"export\")\n\n      autoload :CheckEnabled, action_root.join(\"check_enabled\")\n      autoload :CheckAccess, action_root.join(\"check_access\")\n      autoload :Configure, action_root.join(\"configure\")\n      autoload :DeleteVM, action_root.join(\"delete_vm\")\n      autoload :Import, action_root.join(\"import\")\n      autoload :Package, action_root.join(\"package\")\n      autoload :IsWindows, action_root.join(\"is_windows\")\n      autoload :ReadState, action_root.join(\"read_state\")\n      autoload :ResumeVM, action_root.join(\"resume_vm\")\n      autoload :StartInstance, action_root.join('start_instance')\n      autoload :StopInstance, action_root.join('stop_instance')\n      autoload :SuspendVM, action_root.join(\"suspend_vm\")\n      autoload :WaitForIPAddress, action_root.join(\"wait_for_ip_address\")\n      autoload :NetSetVLan, action_root.join(\"net_set_vlan\")\n      autoload :NetSetMac, action_root.join(\"net_set_mac\")\n      autoload :MessageWillNotDestroy, action_root.join(\"message_will_not_destroy\")\n      autoload :SnapshotDelete, action_root.join(\"snapshot_delete\")\n      autoload :SnapshotRestore, action_root.join(\"snapshot_restore\")\n      autoload :SnapshotSave, action_root.join(\"snapshot_save\")\n      autoload :SetName, action_root.join(\"set_name\")\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/cap/cleanup_disks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"vagrant/util/experimental\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Cap\n      module CleanupDisks\n        LOGGER = Log4r::Logger.new(\"vagrant::plugins::hyperv::cleanup_disks\")\n\n        # @param [Vagrant::Machine] machine\n        # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks\n        # @param [Hash] disk_meta_file - A hash of all the previously defined disks from the last configure_disk action\n        def self.cleanup_disks(machine, defined_disks, disk_meta_file)\n          return if disk_meta_file.values.flatten.empty?\n\n          handle_cleanup_disk(machine, defined_disks, disk_meta_file[\"disk\"])\n          handle_cleanup_dvd(machine, defined_disks, disk_meta_file[\"dvd\"])\n          # TODO: Floppy disks\n        end\n\n        protected\n\n        # @param [Vagrant::Machine] machine\n        # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks\n        # @param [Hash] disk_meta - A hash of all the previously defined disks from the last configure_disk action\n        def self.handle_cleanup_disk(machine, defined_disks, disk_meta)\n          all_disks = machine.provider.driver.list_hdds\n\n          disk_meta.each do |d|\n            # look at Path instead of Name or UUID\n            disk_name  = File.basename(d[\"Path\"], '.*')\n            dsk = defined_disks.select { |dk| dk.name == disk_name }\n\n            if !dsk.empty? || d[\"primary\"] == true\n              next\n            else\n              LOGGER.warn(\"Found disk not in Vagrantfile config: '#{d[\"Name\"]}'. Removing disk from guest #{machine.name}\")\n\n              machine.ui.warn(I18n.t(\"vagrant.cap.cleanup_disks.disk_cleanup\", name: d[\"Name\"]), prefix: true)\n\n              disk_actual = all_disks.select { |a| File.realdirpath(a[\"Path\"]) == File.realdirpath(d[\"Path\"]) }.first\n              if !disk_actual\n                machine.ui.warn(I18n.t(\"vagrant.cap.cleanup_disks.disk_not_found\", name: d[\"Name\"]), prefix: true)\n              else\n                machine.provider.driver.remove_disk(disk_actual[\"ControllerType\"], disk_actual[\"ControllerNumber\"], disk_actual[\"ControllerLocation\"], disk_actual[\"Path\"])\n              end\n            end\n          end\n        end\n\n        def self.handle_cleanup_dvd(machine, defined_disks, disk_meta)\n          # Get a list of all attached DVD drives\n          attached = machine.provider.driver.read_scsi_controllers.map do |controller|\n            controller[\"Drives\"].map do |drive|\n              drive if drive[\"DvdMediaType\"].to_i == 1\n            end.compact\n          end.flatten.compact\n\n          # Generate list of dvd disks that previously\n          # existed but are no longer defined\n          orphan_attachments = disk_meta.find_all do |mdisk|\n            defined_disks.none? do |defined_disk|\n              defined_disk.type == :dvd &&\n                File.expand_path(mdisk[\"Path\"]) == File.expand_path(defined_disk.file)\n            end\n          end\n\n          # Remove any entries that are not currently\n          # attached\n          orphan_attachments.delete_if do |mdisk|\n            attached.any? do |attachment|\n              File.expand_path(attachment[\"Path\"]) == mdisk[\"Path\"]\n            end\n          end\n\n          # Now remove any orphan attachments that remain\n          orphan_attachments.each do |mdisk|\n            LOGGER.debug(\"removing dvd attachment: #{mdisk}\")\n\n            machine.provider.driver.detach_dvd(\n              mdisk[\"ControllerLocation\"],\n              mdisk[\"ControllerNumber\"]\n            )\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/cap/configure_disks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"fileutils\"\nrequire \"vagrant/util/numeric\"\nrequire \"vagrant/util/experimental\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Cap\n      module ConfigureDisks\n        LOGGER = Log4r::Logger.new(\"vagrant::plugins::hyperv::configure_disks\")\n\n        # @param [Vagrant::Machine] machine\n        # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks\n        # @return [Hash] configured_disks - A hash of all the current configured disks\n        def self.configure_disks(machine, defined_disks)\n          return {} if defined_disks.empty?\n\n          machine.ui.info(I18n.t(\"vagrant.cap.configure_disks.start\"))\n\n          current_disks = machine.provider.driver.list_hdds\n\n          configured_disks = {disk: [], floppy: [], dvd: []}\n\n          defined_disks.each do |disk|\n            if disk.type == :disk\n              disk_data = handle_configure_disk(machine, disk, current_disks)\n              configured_disks[:disk] << disk_data if !disk_data.empty?\n            elsif disk.type == :floppy\n              # TODO: Write me\n              machine.ui.info(I18n.t(\"vagrant.cap.configure_disks.floppy_not_supported\", name: disk.name))\n            elsif disk.type == :dvd\n              disk_data = handle_configure_dvd(machine, disk)\n              configured_disks[:dvd] << disk_data if !disk_data.empty?\n            end\n          end\n\n          configured_disks\n        end\n\n        protected\n\n        # @param [Vagrant::Machine] machine - the current machine\n        # @param [Config::Disk] disk - the current disk to configure\n        # @param [Array] all_disks - A list of all currently defined disks in VirtualBox\n        # @return [Hash] current_disk - Returns the current disk. Returns nil if it doesn't exist\n        def self.get_current_disk(machine, disk, all_disks)\n          current_disk = nil\n          if disk.primary\n            # Ensure we grab the proper primary disk\n            # We can't rely on the order of `all_disks`, as they will not\n            # always come in port order, but primary should always be the\n            # first disk.\n\n            sorted_disks = all_disks.sort_by { |d|\n              [d[\"ControllerNumber\"].to_i, d[\"ControllerLocation\"].to_i]\n            }\n\n            LOGGER.debug(\"sorted disks for primary detection: #{sorted_disks}\")\n\n            current_disk = sorted_disks.first\n\n            # Need to get actual disk info to obtain UUID instead of what's returned\n            #\n            # This is not required for newly created disks, as its metadata is\n            # set when creating and attaching the disk. This is only for the primary\n            # disk, since it already exists.\n            current_disk = machine.provider.driver.get_disk(current_disk[\"Path\"])\n          else\n            # Hyper-V disk names aren't the actual names of the disk, so we have\n            # to grab the name from the file path instead\n            current_disk = all_disks.detect { |d| File.basename(d[\"Path\"], '.*') == disk.name}\n          end\n\n          current_disk\n        end\n\n        # Handles all disk configs of type `:disk`\n        #\n        # @param [Vagrant::Machine] machine - the current machine\n        # @param [Config::Disk] disk - the current disk to configure\n        # @param [Array] all_disks - A list of all currently defined disks in VirtualBox\n        # @return [Hash] - disk_metadata\n        def self.handle_configure_disk(machine, disk, all_disks)\n          disk_metadata = {}\n\n          # Grab the existing configured disk, if it exists\n          current_disk = get_current_disk(machine, disk, all_disks)\n\n          # Configure current disk\n          if !current_disk\n            # create new disk and attach\n            disk_metadata = create_disk(machine, disk)\n          elsif compare_disk_size(machine, disk, current_disk)\n            disk_metadata = resize_disk(machine, disk, current_disk)\n          else\n            disk_metadata = {UUID: current_disk[\"DiskIdentifier\"], Name: disk.name, Path: current_disk[\"Path\"]}\n            if disk.primary\n              disk_metadata[:primary] = true\n            end\n          end\n\n          disk_metadata\n        end\n\n        def self.handle_configure_dvd(machine, dvd)\n          dvd_location = File.expand_path(dvd.file)\n\n          find_dvd_disk = proc {\n            catch(:found) do\n              machine.provider.driver.read_scsi_controllers.each do |controller|\n                controller[\"Drives\"].each do |disk|\n                  throw :found, disk if File.expand_path(disk[\"Path\"]) == dvd_location\n                end\n              end\n\n              nil\n            end\n          }\n\n          generate_dvd_metadata = proc { |disk|\n            disk.slice(\n              \"Name\", \"Id\", \"Path\",\n              \"ControllerLocation\", \"ControllerNumber\",\n              \"ControllerType\"\n            )\n          }\n\n          # If the disk is already attached just\n          # return the information\n          if dvd_attached = find_dvd_disk.call\n            return generate_dvd_metadata.call(dvd_attached)\n          end\n\n          # Attach the disk\n          machine.provider.driver.attach_dvd(dvd_location)\n\n          # Find disk and return information\n          disk = find_dvd_disk.call\n          return generate_dvd_metadata.call(disk) if disk\n\n          LOGGER.warn(\"failed to locate dvd on controller, stubbing\")\n          {\"Path\" => dvd_location}\n        end\n\n        # Check to see if current disk is configured based on defined_disks\n        #\n        # @param [Kernel_V2::VagrantConfigDisk] disk_config\n        # @param [Hash] defined_disk\n        # @return [Boolean]\n        def self.compare_disk_size(machine, disk_config, defined_disk)\n          # Hyper-V returns disk size in bytes\n          requested_disk_size = disk_config.size\n          disk_actual = machine.provider.driver.get_disk(defined_disk[\"Path\"])\n          defined_disk_size = disk_actual[\"Size\"]\n\n          if defined_disk_size > requested_disk_size\n            if File.extname(disk_actual[\"Path\"]) == \".vhdx\"\n              # VHDX formats can be shrunk\n              return true\n            else\n              machine.ui.warn(I18n.t(\"vagrant.cap.configure_disks.shrink_size_not_supported\", name: disk_config.name))\n              return false\n            end\n          elsif defined_disk_size < requested_disk_size\n            return true\n          else\n            return false\n          end\n        end\n\n        # Creates and attaches a disk to a machine\n        #\n        # @param [Vagrant::Machine] machine\n        # @param [Kernel_V2::VagrantConfigDisk] disk_config\n        def self.create_disk(machine, disk_config)\n          machine.ui.detail(I18n.t(\"vagrant.cap.configure_disks.create_disk\", name: disk_config.name))\n          disk_provider_config = {}\n\n          if disk_config.provider_config && disk_config.provider_config.key?(:hyperv)\n            disk_provider_config = disk_config.provider_config[:hyperv]\n          end\n\n          if !disk_provider_config.empty?\n            disk_provider_config = convert_size_vars!(disk_provider_config)\n          end\n\n          # Get the machines data dir, that will now be the path for the new disk\n          guest_disk_folder = machine.data_dir.join(\"Virtual Hard Disks\")\n\n          if disk_config.file\n            disk_file = disk_config.file\n            LOGGER.info(\"Disk already defined by user at '#{disk_file}'. Using this disk instead of creating a new one...\")\n          else\n            # Set the extension\n            disk_ext = disk_config.disk_ext\n            disk_file = File.join(guest_disk_folder, disk_config.name) + \".#{disk_ext}\"\n\n            LOGGER.info(\"Attempting to create a new disk file '#{disk_file}' of size '#{disk_config.size}' bytes\")\n\n            machine.provider.driver.create_disk(disk_file, disk_config.size, **disk_provider_config)\n          end\n\n          disk_info = machine.provider.driver.get_disk(disk_file)\n          disk_metadata = {UUID: disk_info[\"DiskIdentifier\"], Name: disk_config.name, Path: disk_info[\"Path\"]}\n\n          machine.provider.driver.attach_disk(disk_file, **disk_provider_config)\n\n          disk_metadata\n        end\n\n        # Converts any \"shortcut\" options such as \"123MB\" into its byte form. This\n        # is due to what parameter type is expected when calling the `New-VHD`\n        # powershell command\n        #\n        # @param [Hash] disk_provider_config\n        # @return [Hash] disk_provider_config\n        def self.convert_size_vars!(disk_provider_config)\n          if disk_provider_config.key?(:BlockSizeBytes)\n            bytes = Vagrant::Util::Numeric.string_to_bytes(disk_provider_config[:BlockSizeBytes])\n            disk_provider_config[:BlockSizeBytes] = bytes\n          end\n\n          disk_provider_config\n        end\n\n        # @param [Vagrant::Machine] machine\n        # @param [Config::Disk] disk_config - the current disk to configure\n        # @param [Hash] defined_disk - current disk as represented by VirtualBox\n        # @return [Hash] - disk_metadata\n        def self.resize_disk(machine, disk_config, defined_disk)\n          machine.ui.detail(I18n.t(\"vagrant.cap.configure_disks.resize_disk\", name: disk_config.name), prefix: true)\n\n          machine.provider.driver.resize_disk(defined_disk[\"Path\"], disk_config.size.to_i)\n\n          disk_info = machine.provider.driver.get_disk(defined_disk[\"Path\"])\n\n          # Store updated metadata\n          disk_metadata = {UUID: disk_info[\"DiskIdentifier\"], Name: disk_config.name, Path: disk_info[\"Path\"]}\n          if disk_config.primary\n            disk_metadata[:primary] = true\n          end\n\n          disk_metadata\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/cap/public_address.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Cap\n      module PublicAddress\n        def self.public_address(machine)\n          return nil if machine.state.id != :running\n\n          ssh_info = machine.ssh_info\n          return nil if !ssh_info\n          ssh_info[:host]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/cap/snapshot_list.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Cap\n      module SnapshotList\n        def self.snapshot_list(machine)\n          machine.provider.driver.list_snapshots\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/cap/validate_disk_ext.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module HyperV\n    module Cap\n      module ValidateDiskExt\n        LOGGER = Log4r::Logger.new(\"vagrant::plugins::hyperv::validate_disk_ext\")\n\n        # The default set of disk formats that Hyper-V supports\n        DEFAULT_DISK_EXT_LIST = [\"vhd\", \"vhdx\"].map(&:freeze).freeze\n        DEFAULT_DISK_EXT = \"vhdx\".freeze\n\n        # @param [Vagrant::Machine] machine\n        # @param [String] disk_ext\n        # @return [Bool]\n        def self.validate_disk_ext(machine, disk_ext)\n          DEFAULT_DISK_EXT_LIST.include?(disk_ext)\n        end\n\n        # @param [Vagrant::Machine] machine\n        # @return [Array]\n        def self.default_disk_exts(machine)\n          DEFAULT_DISK_EXT_LIST\n        end\n\n        # @param [Vagrant::Machine] machine\n        # @return [String]\n        def self.set_default_disk_ext(machine)\n          DEFAULT_DISK_EXT\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HyperV\n    class Config < Vagrant.plugin(\"2\", :config)\n      # Allowed automatic start actions for VM\n      ALLOWED_AUTO_START_ACTIONS = [\n        \"Nothing\".freeze,\n        \"StartIfRunning\".freeze,\n        \"Start\".freeze\n      ].freeze\n\n      # Allowed automatic stop actions for VM\n      ALLOWED_AUTO_STOP_ACTIONS = [\n        \"ShutDown\".freeze,\n        \"TurnOff\".freeze,\n        \"Save\".freeze\n      ].freeze\n\n      # @return [Integer] Seconds to wait for an IP address when booting\n      attr_accessor :ip_address_timeout\n      # @return [Integer] Memory size in MB\n      attr_accessor :memory\n      # @return [Integer] Maximum memory size in MB. Enables dynamic memory.\n      attr_accessor :maxmemory\n      # @return [Integer] Number of CPUs\n      attr_accessor :cpus\n      # @return [String] Name of the VM (Shown in the Hyper-V Manager)\n      attr_accessor :vmname\n      # @return [Integer] VLAN ID for network interface\n      attr_accessor :vlan_id\n      # @return [String] MAC address for network interface\n      attr_accessor :mac\n      # @return [Boolean] Create linked clone instead of full clone\n      # @note **DEPRECATED** use #linked_clone instead\n      attr_accessor :differencing_disk\n      # @return [Boolean] Create linked clone instead of full clone\n      attr_accessor :linked_clone\n      # @return [String] Automatic action on start of host. Default: Nothing (Nothing, StartIfRunning, Start)\n      attr_accessor :auto_start_action\n      # @return [String] Automatic action on stop of host. Default: ShutDown (ShutDown, TurnOff, Save)\n      attr_accessor :auto_stop_action\n      # @return [Boolean] Enable checkpoints. Default: true\n      attr_accessor :enable_checkpoints\n      # @return [Boolean] Enable automatic checkpoints. Default: false\n      attr_accessor :enable_automatic_checkpoints\n      # @return [Boolean] Enable virtualization extensions\n      attr_accessor :enable_virtualization_extensions\n      # @return [Hash] Options for VMServiceIntegration\n      attr_accessor :vm_integration_services\n      # @return [Boolean] Enable Enhanced session mode\n      attr_accessor :enable_enhanced_session_mode\n\n      def initialize\n        @ip_address_timeout = UNSET_VALUE\n        @memory = UNSET_VALUE\n        @maxmemory = UNSET_VALUE\n        @cpus = UNSET_VALUE\n        @vmname = UNSET_VALUE\n        @vlan_id = UNSET_VALUE\n        @mac = UNSET_VALUE\n        @linked_clone = UNSET_VALUE\n        @differencing_disk = UNSET_VALUE\n        @auto_start_action = UNSET_VALUE\n        @auto_stop_action = UNSET_VALUE\n        @enable_virtualization_extensions = UNSET_VALUE\n        @enable_automatic_checkpoints = UNSET_VALUE\n        @enable_checkpoints = UNSET_VALUE\n        @vm_integration_services = {}\n        @enable_enhanced_session_mode = UNSET_VALUE\n      end\n\n      def finalize!\n        if @differencing_disk != UNSET_VALUE\n          @_differencing_disk_deprecation = true\n        end\n        @linked_clone = false if @linked_clone == UNSET_VALUE\n        @differencing_disk = false if @differencing_disk == UNSET_VALUE\n        @linked_clone ||= @differencing_disk\n        @differencing_disk ||= @linked_clone\n        if @ip_address_timeout == UNSET_VALUE\n          @ip_address_timeout = 120\n        end\n        @memory = nil if @memory == UNSET_VALUE\n        @maxmemory = nil if @maxmemory == UNSET_VALUE\n        @cpus = nil if @cpus == UNSET_VALUE\n        @vmname = nil if @vmname == UNSET_VALUE\n        @vlan_id = nil if @vlan_id == UNSET_VALUE\n        @mac = nil if @mac == UNSET_VALUE\n\n        @auto_start_action = \"Nothing\" if @auto_start_action == UNSET_VALUE\n        @auto_stop_action = \"ShutDown\" if @auto_stop_action == UNSET_VALUE\n        @enable_virtualization_extensions = false if @enable_virtualization_extensions == UNSET_VALUE\n\n        if @enable_automatic_checkpoints == UNSET_VALUE\n          @enable_automatic_checkpoints = false\n        else\n          @enable_automatic_checkpoints = !!@enable_automatic_checkpoints\n        end\n        if @enable_checkpoints == UNSET_VALUE\n          @enable_checkpoints = true\n        else\n          @enable_checkpoints = !!@enable_checkpoints\n        end\n\n        # If automatic checkpoints are enabled, checkpoints will automatically be enabled\n        @enable_checkpoints ||= @enable_automatic_checkpoints\n\n        @enable_enhanced_session_mode = false if @enable_enhanced_session_mode == UNSET_VALUE\n      end\n\n      def validate(machine)\n        errors = _detected_errors\n\n        if @_differencing_disk_deprecation && machine\n          machine.ui.warn I18n.t(\"vagrant_hyperv.config.differencing_disk_deprecation\")\n        end\n\n        if !vm_integration_services.is_a?(Hash)\n          errors << I18n.t(\"vagrant_hyperv.config.invalid_integration_services_type\",\n            received: vm_integration_services.class)\n        else\n          vm_integration_services.each do |key, value|\n            if ![true, false].include?(value)\n              errors << I18n.t(\"vagrant_hyperv.config.invalid_integration_services_entry\",\n                entry_name: name, entry_value: value)\n            end\n          end\n        end\n\n        if !ALLOWED_AUTO_START_ACTIONS.include?(auto_start_action)\n          errors << I18n.t(\"vagrant_hyperv.config.invalid_auto_start_action\", action: auto_start_action,\n            allowed_actions: ALLOWED_AUTO_START_ACTIONS.join(\", \"))\n        end\n\n        if !ALLOWED_AUTO_STOP_ACTIONS.include?(auto_stop_action)\n          errors << I18n.t(\"vagrant_hyperv.config.invalid_auto_stop_action\", action: auto_stop_action,\n            allowed_actions: ALLOWED_AUTO_STOP_ACTIONS.join(\", \"))\n        end\n\n        {\"Hyper-V\" => errors}\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/driver.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\nrequire \"log4r\"\n\nrequire \"vagrant/util/powershell\"\n\nrequire_relative \"plugin\"\n\nmodule VagrantPlugins\n  module HyperV\n    class Driver\n      ERROR_REGEXP  = /===Begin-Error===(.+?)===End-Error===/m\n      OUTPUT_REGEXP = /===Begin-Output===(.+?)===End-Output===/m\n\n      # Name mapping for integration services for id\n      # https://social.technet.microsoft.com/Forums/de-DE/154917de-f3ca-4b1e-b3f8-23dd4b4f0f06/getvmintegrationservice-sprachabhngig?forum=powershell_de\n      INTEGRATION_SERVICES_MAP = {\n        guest_service_interface: \"6C09BB55-D683-4DA0-8931-C9BF705F6480\".freeze,\n        heartbeat: \"84EAAE65-2F2E-45F5-9BB5-0E857DC8EB47\".freeze,\n        key_value_pair_exchange: \"2A34B1C2-FD73-4043-8A5B-DD2159BC743F\".freeze,\n        shutdown: \"9F8233AC-BE49-4C79-8EE3-E7E1985B2077\".freeze,\n        time_synchronization: \"2497F4DE-E9FA-4204-80E4-4B75C46419C0\".freeze,\n        vss: \"5CED1297-4598-4915-A5FC-AD21BB4D02A4\".freeze,\n      }.freeze\n\n      # @return [String] VM ID\n      attr_reader :vm_id\n\n      def initialize(id)\n        @vm_id = id\n        @logger = Log4r::Logger.new(\"vagrant::hyperv::driver\")\n      end\n\n      # @return [Boolean] Supports VMCX\n      def has_vmcx_support?\n        !!execute(:has_vmcx_support)[\"result\"]\n      end\n\n      # Execute a PowerShell command and process the results\n      #\n      # @param [String] path Path to PowerShell script\n      # @param [Hash] options Options to pass to command\n      #\n      # @return [Object, nil] If the command returned JSON content\n      #                       it will be parsed and returned, otherwise\n      #                       nil will be returned\n      def execute(path, options={})\n        if path.is_a?(Symbol)\n          path = \"#{path}.ps1\"\n        end\n        r = execute_powershell(path, options)\n\n        # We only want unix-style line endings within Vagrant\n        r.stdout.gsub!(\"\\r\\n\", \"\\n\")\n        r.stderr.gsub!(\"\\r\\n\", \"\\n\")\n\n        error_match  = ERROR_REGEXP.match(r.stdout)\n        output_match = OUTPUT_REGEXP.match(r.stdout)\n\n        if error_match\n          data = JSON.parse(error_match[1])\n\n          # We have some error data.\n          raise Errors::PowerShellError,\n            script: path,\n            stderr: data[\"error\"]\n        end\n\n        if r.exit_code != 0\n          raise Errors::PowerShellError,\n            script: path,\n            stderr: r.stderr\n        end\n\n        # Nothing\n        return nil if !output_match\n        return JSON.parse(output_match[1])\n      end\n\n      # Fetch current state of the VM\n      #\n      # @return [Hash<state, status>]\n      def get_current_state\n        execute(:get_vm_status, VmId: vm_id)\n      end\n\n      # Delete the VM\n      #\n      # @return [nil]\n      def delete_vm\n        execute(:delete_vm, VmId: vm_id)\n      end\n\n      # Export the VM to the given path\n      #\n      # @param [String] path Path for export\n      # @return [nil]\n      def export(path)\n        execute(:export_vm, VmId: vm_id, Path: path)\n      end\n\n      # Get the IP address of the VM\n      #\n      # @return [Hash<ip>]\n      def read_guest_ip\n        execute(:get_network_config, VmId: vm_id)\n      end\n\n      # Get the MAC address of the VM\n      #\n      # @return [Hash<mac>]\n      def read_mac_address\n        execute(:get_network_mac, VmId: vm_id)\n      end\n\n      # Resume the VM from suspension\n      #\n      # @return [nil]\n      def resume\n        execute(:resume_vm, VmId: vm_id)\n      end\n\n      # Start the VM\n      #\n      # @return [nil]\n      def start\n        execute(:start_vm, VmId: vm_id )\n      end\n\n      # Stop the VM\n      #\n      # @return [nil]\n      def stop\n        execute(:stop_vm, VmId: vm_id)\n      end\n\n      # Suspend the VM\n      #\n      # @return [nil]\n      def suspend\n        execute(:suspend_vm, VmId: vm_id)\n      end\n\n      # Import a new VM\n      #\n      # @param [Hash] options Configuration options\n      # @return [Hash<id>] New VM ID\n      def import(options)\n        execute(:import_vm, options)\n      end\n\n      # Set the VLAN ID\n      #\n      # @param [String] vlan_id VLAN ID\n      # @return [nil]\n      def net_set_vlan(vlan_id)\n        execute(:set_network_vlan, VmId: vm_id, VlanId: vlan_id)\n      end\n\n      # Set the VM adapter MAC address\n      #\n      # @param [String] mac_addr MAC address\n      # @return [nil]\n      def net_set_mac(mac_addr)\n        execute(:set_network_mac, VmId: vm_id, Mac: mac_addr)\n      end\n\n      # Create a new snapshot with the given name\n      #\n      # @param [String] snapshot_name Name of the new snapshot\n      # @return [nil]\n      def create_snapshot(snapshot_name)\n        execute(:create_snapshot, VmId: vm_id, SnapName: snapshot_name)\n      end\n\n      # Restore the given snapshot\n      #\n      # @param [String] snapshot_name Name of snapshot to restore\n      # @return [nil]\n      def restore_snapshot(snapshot_name)\n        execute(:restore_snapshot, VmId: vm_id,  SnapName: snapshot_name)\n      end\n\n      # Get list of current snapshots\n      #\n      # @return [Array<String>] snapshot names\n      def list_snapshots\n        snaps = execute(:list_snapshots, VmID: vm_id)\n        snaps.map { |s| s['Name'] }\n      end\n\n      # Delete snapshot with the given name\n      #\n      # @param [String] snapshot_name Name of snapshot to delete\n      # @return [nil]\n      def delete_snapshot(snapshot_name)\n        execute(:delete_snapshot, VmID: vm_id, SnapName: snapshot_name)\n      end\n\n      # Enable or disable VM integration services\n      #\n      # @param [Hash] config Integration services to enable or disable\n      # @return [nil]\n      # @note Keys in the config hash will be remapped if found in the\n      #       INTEGRATION_SERVICES_MAP. If they are not, the name will\n      #       be passed directly. This allows new integration services\n      #       to configurable even if Vagrant is not aware of them.\n      def set_vm_integration_services(config)\n        config.each_pair do |srv_name, srv_enable|\n          args = {VMID: vm_id, Id: INTEGRATION_SERVICES_MAP.fetch(srv_name.to_sym, srv_name).to_s}\n          args[:Enable] = true if srv_enable\n          execute(:set_vm_integration_services, args)\n        end\n      end\n\n      # Set the name of the VM\n      #\n      # @param [String] vmname Name of the VM\n      # @return [nil]\n      def set_name(vmname)\n        execute(:set_name, VMID: vm_id, VMName: vmname)\n      end\n\n      #\n      # Disk Driver methods\n      #\n\n      # @param [String] controller_type\n      # @param [String] controller_number\n      # @param [String] controller_location\n      # @param [Hash] opts\n      # @option opts [String] :ControllerType\n      # @option opts [String] :ControllerNumber\n      # @option opts [String] :ControllerLocation\n      def attach_disk(disk_file_path,  **opts)\n        execute(:attach_disk_drive, VmId: @vm_id, Path: disk_file_path, ControllerType: opts[:ControllerType],\n                ControllerNumber: opts[:ControllerNumber], ControllerLocation: opts[:ControllerLocation])\n      end\n\n      # @param [String] path\n      # @param [Int] size_bytes\n      # @param [Hash] opts\n      # @option opts [Bool] :Fixed\n      # @option opts [String] :BlockSizeBytes\n      # @option opts [String] :LogicalSectorSizeBytes\n      # @option opts [String] :PhysicalSectorSizeBytes\n      # @option opts [String] :SourceDisk\n      # @option opts [Bool] :Differencing\n      # @option opts [String] :ParentPath\n      def create_disk(path, size_bytes, **opts)\n        execute(:new_vhd, Path: path, SizeBytes: size_bytes, Fixed: opts[:Fixed],\n               BlockSizeBytes: opts[:BlockSizeBytes], LogicalSectorSizeBytes: opts[:LogicalSectorSizeBytes],\n               PhysicalSectorSizeBytes: opts[:PhysicalSectorSizeBytes],\n               SourceDisk: opts[:SourceDisk], Differencing: opts[:Differencing],\n               ParentPath: opts[:ParentPath])\n      end\n\n      # @param [String] disk_file_path\n      def dismount_disk(disk_file_path)\n        execute(:dismount_vhd, DiskFilePath: disk_file_path)\n      end\n\n      # @param [String] disk_file_path\n      def get_disk(disk_file_path)\n        execute(:get_vhd, DiskFilePath: disk_file_path)\n      end\n\n      # Add a DVD drive to VM\n      #\n      # @param [String] iso_path\n      # @return [nil]\n      def attach_dvd(iso_path)\n        execute(:add_dvd, VmId: vm_id, ISOPath: iso_path)\n      end\n\n      # Remove a DVD drive from VM\n      #\n      # @param [Integer] controller_location\n      # @param [Integer] controller_number\n      # @return [nil]\n      def detach_dvd(controller_location, controller_number)\n        execute(:remove_dvd,\n          VmId: vm_id,\n          ControllerLocation: controller_location,\n          ControllerNumber: controller_number\n        )\n      end\n\n      # @return [Array[Hash]]\n      def list_hdds\n        execute(:list_hdds, VmId: @vm_id)\n      end\n\n      # @param [String] controller_type\n      # @param [String] controller_number\n      # @param [String] controller_location\n      # @param [String] disk_file_path\n      # @param [Hash] opts\n      # @option opts [String] :ControllerType\n      # @option opts [String] :ControllerNumber\n      # @option opts [String] :ControllerLocation\n      def remove_disk(controller_type, controller_number, controller_location, disk_file_path, **opts)\n        execute(:remove_disk_drive, VmId: @vm_id, ControllerType: controller_type,\n                ControllerNumber: controller_number, ControllerLocation: controller_location,\n                DiskFilePath: disk_file_path)\n      end\n\n      # @param [String] path\n      # @param [Int] size_bytes\n      # @param [Hash] opts\n      def resize_disk(disk_file_path, size_bytes, **opts)\n        execute(:resize_disk_drive, VmId: @vm_id, DiskFilePath: disk_file_path,\n                DiskSize: size_bytes)\n      end\n\n      # Set enhanced session transport type of the VM\n      #\n      # @param [String] enhanced session transport type of the VM\n      # @return [nil]\n      def set_enhanced_session_transport_type(transport_type)\n        result = execute(:set_enhanced_session_transport_type, VmID: vm_id, type: transport_type)\n        if !result.nil?\n          @logger.debug(\"EnhancedSessionTransportType is not supported by this version of hyperv, ignoring\")\n        end\n      end\n\n      # Get SCSI controllers attached to VM\n      #\n      # @return [Array<Hash>]\n      def read_scsi_controllers\n        result = execute(:get_scsi_controller, VmId: vm_id)\n        return result if result.is_a?(Array)\n        [result]\n      end\n\n      protected\n\n      def execute_powershell(path, options, &block)\n        lib_path = Pathname.new(File.expand_path(\"../scripts\", __FILE__))\n        mod_path = Vagrant::Util::Platform.wsl_to_windows_path(lib_path.join(\"utils\")).to_s.gsub(\"/\", \"\\\\\")\n        path = Vagrant::Util::Platform.wsl_to_windows_path(lib_path.join(path)).to_s.gsub(\"/\", \"\\\\\")\n        options = options || {}\n        ps_options = []\n        options.each do |key, value|\n          next if !value || value.to_s.empty?\n          next if value == false\n          ps_options << \"-#{key}\"\n          # If the value is a TrueClass assume switch\n          next if value == true\n          ps_options << \"'#{value}'\"\n        end\n\n        # Always have a stop error action for failures\n        ps_options << \"-ErrorAction\" << \"Stop\"\n\n        # Include our module path so we can nicely load helper modules\n        opts = {\n          notify: [:stdout, :stderr, :stdin],\n          module_path: mod_path\n        }\n\n        Vagrant::Util::PowerShell.execute(path, *ps_options, **opts, &block)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    module Errors\n      # A convenient superclass for all our errors.\n      class HyperVError < Vagrant::Errors::VagrantError\n        error_namespace(\"vagrant_hyperv.errors\")\n      end\n\n      class AdminRequired < HyperVError\n        error_key(:admin_required)\n      end\n\n      class BoxInvalid < HyperVError\n        error_key(:box_invalid)\n      end\n\n      class IPAddrTimeout < HyperVError\n        error_key(:ip_addr_timeout)\n      end\n\n      class NoSwitches < HyperVError\n        error_key(:no_switches)\n      end\n\n      class PowerShellFeaturesDisabled < HyperVError\n        error_key(:powershell_features_disabled)\n      end\n\n      class PowerShellError < HyperVError\n        error_key(:powershell_error)\n      end\n\n      class PowerShellRequired < HyperVError\n        error_key(:powershell_required)\n      end\n\n      class WindowsRequired < HyperVError\n        error_key(:windows_required)\n      end\n\n      class SystemAccessRequired < HyperVError\n        error_key(:system_access_required)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HyperV\n    autoload :Action, File.expand_path(\"../action\", __FILE__)\n    autoload :Errors, File.expand_path(\"../errors\", __FILE__)\n\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"Hyper-V provider\"\n      description <<-DESC\n      This plugin installs a provider that allows Vagrant to manage\n      machines in Hyper-V.\n      DESC\n\n      provider(:hyperv, priority: 4) do\n        require_relative \"provider\"\n        init!\n        Provider\n      end\n\n      config(:hyperv, :provider) do\n        require_relative \"config\"\n        init!\n        Config\n      end\n\n      provider_capability(\"hyperv\", \"public_address\") do\n        require_relative \"cap/public_address\"\n        Cap::PublicAddress\n      end\n\n      provider_capability(\"hyperv\", \"snapshot_list\") do\n        require_relative \"cap/snapshot_list\"\n        Cap::SnapshotList\n      end\n\n      provider_capability(:hyperv, :configure_disks) do\n        require_relative \"cap/configure_disks\"\n        Cap::ConfigureDisks\n      end\n\n      provider_capability(:hyperv, :cleanup_disks) do\n        require_relative \"cap/cleanup_disks\"\n        Cap::CleanupDisks\n      end\n\n      provider_capability(:hyperv, :validate_disk_ext) do\n        require_relative \"cap/validate_disk_ext\"\n        Cap::ValidateDiskExt\n      end\n\n      provider_capability(:hyperv, :default_disk_exts) do\n        require_relative \"cap/validate_disk_ext\"\n        Cap::ValidateDiskExt\n      end\n\n      provider_capability(:hyperv, :set_default_disk_ext) do\n        require_relative \"cap/validate_disk_ext\"\n        Cap::ValidateDiskExt\n      end\n\n      protected\n\n      def self.init!\n        return if defined?(@_init)\n        I18n.load_path << File.expand_path(\n          \"templates/locales/providers_hyperv.yml\", Vagrant.source_root)\n        I18n.reload!\n        @_init = true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/provider.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nrequire_relative \"driver\"\nrequire_relative \"plugin\"\n\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/powershell\"\n\nmodule VagrantPlugins\n  module HyperV\n    class Provider < Vagrant.plugin(\"2\", :provider)\n      attr_reader :driver\n\n      def self.usable?(raise_error=false)\n        if !Vagrant::Util::Platform.windows? &&\n            !Vagrant::Util::Platform.wsl?\n          raise Errors::WindowsRequired\n        end\n\n        if !Vagrant::Util::Platform.windows_admin? &&\n           !Vagrant::Util::Platform.windows_hyperv_admin?\n            raise Errors::AdminRequired\n        end\n\n        if !Vagrant::Util::PowerShell.available?\n          raise Errors::PowerShellRequired\n        end\n\n        true\n      rescue Errors::HyperVError\n        raise if raise_error\n        return false\n      end\n\n      def initialize(machine)\n        @machine = machine\n\n        # This method will load in our driver, so we call it now to\n        # initialize it.\n        machine_id_changed\n        @logger = Log4r::Logger.new(\"vagrant::hyperv::provider\")\n      end\n\n      def action(name)\n        # Attempt to get the action method from the Action class if it\n        # exists, otherwise return nil to show that we don't support the\n        # given action.\n        action_method = \"action_#{name}\"\n        return Action.send(action_method) if Action.respond_to?(action_method)\n        nil\n      end\n\n      def machine_id_changed\n        @driver = Driver.new(@machine.id)\n      end\n\n      def state\n        state_id = nil\n        state_id = :not_created if !@machine.id\n\n        if !state_id\n          # Run a custom action we define called \"read_state\" which does\n          # what it says. It puts the state in the `:machine_state_id`\n          # key in the environment.\n          env = @machine.action(:read_state)\n          state_id = env[:machine_state_id]\n        end\n\n        # Get the short and long description\n        short = state_id.to_s\n        long  = \"\"\n\n        # If we're not created, then specify the special ID flag\n        if state_id == :not_created\n          state_id = Vagrant::MachineState::NOT_CREATED_ID\n        end\n\n        # Return the MachineState object\n        Vagrant::MachineState.new(state_id, short, long)\n      end\n\n      def to_s\n        id = @machine.id.nil? ? \"new\" : @machine.id\n        \"Hyper-V (#{id})\"\n      end\n\n      # @return [Hash]\n      def ssh_info\n        # We can only SSH into a running machine\n        return nil if state.id != :running\n\n        # Read the IP of the machine using Hyper-V APIs\n        guest_ip = nil\n\n        begin\n          network_info = @driver.read_guest_ip\n          guest_ip = network_info[\"ip\"]\n        rescue Errors::PowerShellError\n          @logger.warn(\"Failed to read guest IP.\")\n        end\n\n        return nil if !guest_ip\n\n        @logger.debug(\"IP: #{guest_ip}\")\n\n        {\n          host: guest_ip,\n          port: 22,\n        }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/add_dvd.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId,\n    [Parameter(Mandatory=$true)]\n    [string]$ISOPath\n)\n\ntry {\n    Hyper-V\\Get-VM -ID $VmId | Hyper-V\\Add-VMDvdDrive -Path $ISOPath\n} catch {\n    Write-ErrorMessage \"Failed to add DVD drive for path - ${ISOPath}: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/attach_disk_drive.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId,\n    [Parameter(Mandatory=$true)]\n    [string]$Path,\n    [string]$ControllerType,\n    [string]$ControllerNumber,\n    [string]$ControllerLocation\n)\n\n$Params = @{}\n\nforeach ($key in $MyInvocation.BoundParameters.keys) {\n  $value = (Get-Variable -Exclude \"ErrorAction\" $key).Value\n  if (($key -ne \"VmId\") -and ($key -ne \"ErrorAction\")) {\n    $Params.Add($key, $value)\n  }\n}\n\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VmId\n    Hyper-V\\Add-VMHardDiskDrive -VMName $VM.Name @Params\n} catch {\n    Write-ErrorMessage \"Failed to attach disk ${DiskFilePath} to VM ${VM}: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/check_hyperv.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\r\n\r\n$check = $(-Not (-Not (Get-Command \"Hyper-V\\Get-VMSwitch\" -ErrorAction SilentlyContinue)))\r\n$result = @{\r\n    result = $check\r\n}\r\n\r\nWrite-OutputMessage $(ConvertTo-Json $result)\r\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/check_hyperv_access.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages, VagrantVM\n\nparam(\n    [parameter (Mandatory=$true)]\n    [string] $Path\n)\n\n$check = Check-VagrantHyperVAccess -Path $Path\n$result = @{\n    root_dir = ($Path -split '\\\\')[0,1] -join '\\';\n    result = $check\n}\n\nWrite-OutputMessage $(ConvertTo-Json $result)\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/clone_vhd.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$Source,\n\n    [Parameter(Mandatory=$true)]\n    [string]$Destination\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    Hyper-V\\New-VHD -Path $Destination -ParentPath $Source\n} catch {\n    Write-ErrorMessage \"Failed to clone drive: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/configure_vm.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantVM, VagrantMessages\n\nparam(\n    [parameter (Mandatory=$true)]\n    [Guid] $VMID,\n    [parameter (Mandatory=$false)]\n    [string] $SwitchID=$null,\n    [parameter (Mandatory=$false)]\n    [string] $Memory=$null,\n    [parameter (Mandatory=$false)]\n    [string] $MaxMemory=$null,\n    [parameter (Mandatory=$false)]\n    [string] $Processors=$null,\n    [parameter (Mandatory=$false)]\n    [string] $AutoStartAction=$null,\n    [parameter (Mandatory=$false)]\n    [string] $AutoStopAction=$null,\n    [parameter (Mandatory=$false)]\n    [switch] $VirtualizationExtensions,\n    [parameter (Mandatory=$false)]\n    [switch] $EnableCheckpoints,\n    [parameter (Mandatory=$false)]\n    [switch] $EnableAutomaticCheckpoints\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VMID\n} catch {\n    Write-ErrorMessage \"Failed to locate VM: ${PSItem}\"\n    exit 1\n}\n\nif($Processors) {\n    try {\n        Set-VagrantVMCPUS -VM $VM -CPUCount ($Processors -as [int])\n    } catch {\n        Write-ErrorMessage \"Failed to configure CPUs: ${PSItem}\"\n        exit 1\n    }\n}\n\nif($Memory -or $MaxMemory) {\n    try {\n        Set-VagrantVMMemory -VM $VM -Memory $Memory -MaxMemory $MaxMemory\n    } catch {\n        Write-ErrorMessage \"Failed to configure memory: ${PSItem}\"\n        exit 1\n    }\n}\n\nif($AutoStartAction -or $AutoStopAction) {\n    try {\n        Set-VagrantVMAutoActions -VM $VM -AutoStartAction $AutoStartAction -AutoStopAction $AutoStopAction\n    } catch {\n        Write-ErrorMessage \"Failed to configure automatic actions: ${PSItem}\"\n        exit 1\n    }\n}\n\nif($VirtualizationExtensions) {\n    $virtex = $true\n} else {\n    $virtex = $false\n}\n\ntry {\n    Set-VagrantVMVirtExtensions -VM $VM -Enabled $virtex\n} catch {\n    Write-ErrorMessage \"Failed to configure virtualization extensions: ${PSItem}\"\n    exit 1\n}\n\nif($SwitchID) {\n    try {\n        $SwitchName = Get-VagrantVMSwitch -NameOrID $SwitchID\n        Set-VagrantVMSwitch -VM $VM -SwitchName $SwitchName\n    } catch {\n        Write-ErrorMessage \"Failed to configure network adapter: ${PSItem}\"\n        exit 1\n    }\n}\n\nif($EnableCheckpoints) {\n    $checkpoints = \"Standard\"\n    $CheckpointAction = \"enable\"\n} else {\n    $checkpoints = \"Disabled\"\n    $CheckpointAction = \"disable\"\n}\n\ntry {\n    if((Get-Command Hyper-V\\Set-VM).Parameters[\"CheckpointType\"] -eq $null) {\n        if($CheckpointAction -eq \"enable\") {\n            Write-ErrorMessage \"CheckpointType is not available. Cannot enable checkpoints.\"\n            exit 1\n        }\n    } else {\n        Hyper-V\\Set-VM -VM $VM -CheckpointType $checkpoints\n    }\n} catch {\n    Write-ErrorMessage \"Failed to ${CheckpointAction} checkpoints on VM: ${PSItem}\"\n    exit 1\n}\n\nif($EnableAutomaticCheckpoints) {\n    $autochecks = 1\n    $AutoAction = \"enabled\"\n} else {\n    $autochecks = 0\n    $AutoAction = \"disable\"\n}\n\ntry {\n    if((Get-Command Hyper-V\\Set-VM).Parameters[\"AutomaticCheckpointsEnabled\"] -eq $null) {\n        if($autochecks -eq 1) {\n            Write-ErrorMessage \"AutomaticCheckpointsEnabled is not available\"\n            exit 1\n        }\n    } else {\n        Hyper-V\\Set-VM -VM $VM -AutomaticCheckpointsEnabled $autochecks\n    }\n} catch {\n    Write-ErrorMessage \"Failed to ${AutoAction} automatic checkpoints on VM: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/create_snapshot.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId,\n    [string]$SnapName\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VmId\n    $ChkPnt = $VM.CheckpointType\n    if($ChkPnt -eq \"Disabled\") {\n        Hyper-V\\Set-VM -VM $VM -CheckpointType \"Standard\"\n    }\n    Hyper-V\\Checkpoint-VM $VM -SnapshotName $SnapName\n    if($ChkPnt -eq \"Disabled\") {\n        Hyper-V\\Set-VM -VM $VM -CheckpointType \"Disabled\"\n    }\n} catch {\n    Write-ErrorMessage \"Failed to create snapshot: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/delete_snapshot.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId,\n    [string]$SnapName\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VmId\n    Hyper-V\\Remove-VMSnapshot $VM -Name $SnapName\n} catch {\n    Write-ErrorMessage \"Failed to delete snapshot: ${PSItem}\"\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/delete_vm.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VmId\n    if((Get-Command Hyper-V\\Set-VM).Parameters[\"AutomaticCheckpointsEnabled\"] -ne $null) {\n        Hyper-V\\Set-VM -VM $VM -AutomaticCheckpointsEnabled $false -ErrorAction SilentlyContinue\n    }\n    Hyper-V\\Remove-VM $VM -Force\n} catch {\n    Write-ErrorMessage \"Failed to delete VM: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/dismount_vhd.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$DiskFilePath\n)\n\ntry {\n    Hyper-V\\Dismount-VHD -path $DiskFilePath\n} catch {\n    Write-ErrorMessage \"Failed to dismount disk info from disk file path ${DiskFilePath}: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/export_vm.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId,\n    [Parameter(Mandatory=$true)]\n    [string]$Path\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $vm = Hyper-V\\Get-VM -Id $VmId\n    $vm | Hyper-V\\Export-VM -Path $Path\n} catch {\n    Write-ErrorMessage \"Failed to export VM: ${PSItem}\"\n    exit 1\n}\n\n# Prepare directory structure for box import\ntry {\n    $name = $vm.Name\n    Move-Item $Path/$name/* $Path\n    Remove-Item -Path $Path/Snapshots -Force -Recurse\n    Remove-Item -Path $Path/$name -Force\n} catch {\n    Write-ErrorMessage \"Failed to format exported box: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/file_sync.ps1",
    "content": "#Requires -Modules VagrantMessages\n#-------------------------------------------------------------------------\n# Copyright (c) Microsoft Open Technologies, Inc.\n# All Rights Reserved. Licensed under the MIT License.\n#--------------------------------------------------------------------------\n\nparam (\n    [parameter (Mandatory=$true)]\n    [string]$vm_id,\n    [parameter (Mandatory=$true)]\n    [string]$guest_ip,\n    [parameter (Mandatory=$true)]\n    [string]$username,\n    [parameter (Mandatory=$true)]\n    [string]$password,\n    [parameter (Mandatory=$true)]\n    [string]$host_path,\n    [parameter (Mandatory=$true)]\n    [string]$guest_path\n)\n\nfunction Get-file-hash($source_path, $delimiter) {\n    $source_files = @()\n    (Get-ChildItem $source_path -rec | ForEach-Object -Process {\n      Get-FileHash -Path $_.FullName -Algorithm MD5 } ) |\n        ForEach-Object -Process {\n          $source_files += $_.Path.Replace($source_path, \"\") + $delimiter + $_.Hash\n        }\n    $source_files\n}\n\nfunction Get-Remote-Session($guest_ip, $username, $password) {\n    $secstr = convertto-securestring -AsPlainText -Force -String $password\n    $cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr\n    New-PSSession -ComputerName $guest_ip -Credential $cred\n}\n\nfunction Get-remote-file-hash($source_path, $delimiter, $session) {\n    Invoke-Command -Session $session -ScriptBlock ${function:Get-file-hash} -ArgumentList $source_path, $delimiter\n    # TODO:\n    # Check if remote PS Scripting errors\n}\n\nfunction Sync-Remote-Machine($machine, $remove_files, $copy_files, $host_path, $guest_path) {\n    ForEach ($item in $copy_files) {\n      $from = $host_path + $item\n      $to = $guest_path + $item\n      # Copy VM can also take a VM object\n      Hyper-V\\Copy-VMFile  -VM $machine -SourcePath $from -DestinationPath $to -CreateFullPath -FileSource Host -Force\n    }\n}\n\nfunction Create-Remote-Folders($empty_source_folders, $guest_path) {\n    ForEach ($item in $empty_source_folders) {\n        $new_name =  $guest_path + $item\n        New-Item \"$new_name\" -type directory -Force\n    }\n}\n\nfunction Get-Empty-folders-From-Source($host_path) {\n  Get-ChildItem $host_path -recurse |\n        Where-Object {$_.PSIsContainer -eq $True} |\n            Where-Object {$_.GetFiles().Count -eq 0} |\n                Select-Object FullName | ForEach-Object -Process {\n                    $empty_source_folders += ($_.FullName.Replace($host_path, \"\"))\n                }\n}\n\n$delimiter = \" || \"\n\n$machine = Hyper-V\\Get-VM -Id $vm_id\n\n# FIXME: PowerShell guys please fix this.\n# The below script checks for all VMIntegrationService which are not enabled\n# and will enable this.\n# When when all the services are enabled this throws an error.\n# Enable VMIntegrationService to true\ntry {\n  Hyper-V\\Get-VM -Id $vm_id | Hyper-V\\Get-VMIntegrationService -Name \"Guest Service Interface\" | Hyper-V\\Enable-VMIntegrationService -Passthru\n  }\n  catch { }\n\n$session = Get-Remote-Session $guest_ip $username $password\n\n$source_files = Get-file-hash $host_path $delimiter\n$destination_files = Get-remote-file-hash $guest_path $delimiter $session\n\nif (!$destination_files) {\n  $destination_files = @()\n}\nif (!$source_files) {\n  $source_files = @()\n}\n\n# Compare source and destination files\n$remove_files = @()\n$copy_files = @()\n\n\nCompare-Object -ReferenceObject $source_files -DifferenceObject $destination_files | ForEach-Object {\n  if ($_.SideIndicator -eq '=>') {\n      $remove_files += $_.InputObject.Split($delimiter)[0]\n  } else {\n      $copy_files += $_.InputObject.Split($delimiter)[0]\n  }\n}\n\n# Update the files to remote machine\nSync-Remote-Machine $machine $remove_files $copy_files $host_path $guest_path\n\n# Create any empty folders which missed to sync to remote machine\n$empty_source_folders = @()\n$directories = Get-Empty-folders-From-Source $host_path\n\n$result = Invoke-Command -Session $session -ScriptBlock ${function:Create-Remote-Folders} -ArgumentList $empty_source_folders, $guest_path\n# Always remove the connection after Use\nRemove-PSSession -Id $session.Id\n\n$resultHash = @{\n  message = \"OK\"\n}\n$result = ConvertTo-Json $resultHash\nWrite-OutputMessage $result\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/get_network_config.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $vm = Hyper-V\\Get-VM -Id $VmId\n} catch {\n    Write-ErrorMessage \"Failed to locate VM: ${PSItem}\"\n    exit 1\n}\n\ntry {\n    $netdev = Hyper-V\\Get-VMNetworkAdapter -VM $vm | Select-Object -Index 0\n    if ($netdev -ne $null) {\n        if ($netdev.IpAddresses.Length -gt 0) {\n            foreach ($ip_address in $netdev.IpAddresses) {\n                if ($ip_address.Contains(\".\") -And [string]::IsNullOrEmpty($ip4_address)) {\n                    $ip4_address = $ip_address\n                } elseif ($ip_address.Contains(\":\") -And [string]::IsNullOrEmpty($ip6_address)) {\n                    $ip6_address = $ip_address\n                }\n            }\n        }\n\n        # If no address was found in the network settings, check for\n        # neighbor with mac address and see if an IP exists\n        if (([string]::IsNullOrEmpty($ip4_address)) -And ([string]::IsNullOrEmpty($ip6_address))) {\n            $macaddress = $netdev.MacAddress -replace '(.{2})(?!$)', '${1}-'\n            $addr = Get-NetNeighbor -LinkLayerAddress $macaddress -ErrorAction SilentlyContinue | select IPAddress\n            if ($addr) {\n                $ip_address = $addr.IPAddress\n                if ($ip_address.Contains(\".\")) {\n                    $ip4_address = $ip_address\n                } elseif ($ip_address.Contains(\":\")) {\n                    $ip6_address = $ip_address\n                }\n            }\n        }\n    }\n\n    if (-Not ([string]::IsNullOrEmpty($ip4_address))) {\n        $guest_ipaddress = $ip4_address\n    } elseif (-Not ([string]::IsNullOrEmpty($ip6_address))) {\n        $guest_ipaddress = $ip6_address\n    }\n\n    if (-Not ([string]::IsNullOrEmpty($guest_ipaddress))) {\n        $resultHash = @{\n            ip = $guest_ipaddress\n        }\n        $result = ConvertTo-Json $resultHash\n        Write-OutputMessage $result\n    } else {\n        Write-ErrorMessage \"Failed to determine IP address\"\n        exit 1\n    }\n} catch {\n    Write-ErrorMessage \"Unexpected error while detecting network configuration: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/get_network_mac.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $ip_address = \"\"\n    $vm = Hyper-V\\Get-VM -Id $VmId\n    $networks = Hyper-V\\Get-VMNetworkAdapter -VM $vm\n    foreach ($network in $networks) {\n        if ($network.MacAddress -gt 0) {\n            $mac_address = $network.MacAddress\n            if (-Not ([string]::IsNullOrEmpty($mac_address))) {\n                # We found our mac address!\n                break\n            }\n        }\n    }\n\n    $resultHash = @{\n        mac = \"$mac_address\"\n    }\n    $result = ConvertTo-Json $resultHash\n    Write-OutputMessage $result\n} catch {\n    Write-ErrorMessage \"Unexpected error while fetching MAC: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/get_scsi_controller.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId\n)\n\ntry {\n    $Controller = Hyper-V\\Get-VM -ID $VmId | Hyper-V\\Get-VMScsiController\n} catch {\n    Write-ErrorMessage \"Failed to retrieve scsi controller info for ${VmId}: ${PSItem}\"\n    exit 1\n}\n\n$result = ConvertTo-json $Controller -Depth 20\nWrite-OutputMessage $result\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/get_switches.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\r\n# This will have a SwitchType property. As far as I know the values are:\r\n#\r\n#   0 - Private\r\n#   1 - Internal\r\n#\r\n\r\n$Switches = @(Hyper-V\\Get-VMSwitch `\r\n    | Select-Object Name,SwitchType,NetAdapterInterfaceDescription,Id)\r\nWrite-OutputMessage $(ConvertTo-JSON $Switches)\r\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/get_vhd.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$DiskFilePath\n)\n\ntry {\n    $Disk = Hyper-V\\Get-VHD -path $DiskFilePath\n} catch {\n    Write-ErrorMessage \"Failed to retrieve disk info from disk file path ${DiskFilePath}: ${PSItem}\"\n    exit 1\n}\n\n$result = ConvertTo-json $Disk\nWrite-OutputMessage $result\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/get_vm_status.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId\n)\n\n# Make sure the exception type is loaded\ntry\n{\n    # Microsoft.HyperV.PowerShell is present on all versions of Windows with HyperV\n    [void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.HyperV.PowerShell, Culture=neutral, PublicKeyToken=31bf3856ad364e35')\n    # Microsoft.HyperV.PowerShell.Objects is only present on Windows >= 10.0, so this will fail, and we ignore it since the needed exception\n    # type was loaded in Microsoft.HyperV.PowerShell\n    [void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.HyperV.PowerShell.Objects, Culture=neutral, PublicKeyToken=31bf3856ad364e35')\n} catch {\n  # Empty catch ok, since if we didn't load the types, we will fail in the next block\n}\n\n$VmmsPath = if ([environment]::Is64BitProcess) { \"$($env:SystemRoot)\\System32\\vmms.exe\" } else { \"$($env:SystemRoot)\\Sysnative\\vmms.exe\" }\n$HyperVVersion = [version](Get-Item $VmmsPath).VersionInfo.ProductVersion\n\nif($HyperVVersion -lt ([version]'10.0')) {\n  $ExceptionType = [Microsoft.HyperV.PowerShell.VirtualizationOperationFailedException]\n} else {\n  $ExceptionType = [Microsoft.HyperV.PowerShell.VirtualizationException]\n}\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VmId -ErrorAction \"Stop\"\n    $State = $VM.state\n    $Status = $VM.status\n} catch [Exception] {\n    if($_.Exception.GetType() -eq $ExceptionType)\n    {\n        $State = \"not_created\"\n        $Status = $State\n    }\n    else\n    {\n        throw;\n    }\n}\n\n$resultHash = @{\n    state = \"$State\"\n    status = \"$Status\"\n}\n$result = ConvertTo-Json $resultHash\nWrite-OutputMessage $result\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/has_vmcx_support.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\n# Windows version 10 and up have support for binary format\n$check = [System.Environment]::OSVersion.Version.Major -ge 10\n$result = @{\n    result = $check\n}\n\nWrite-OutputMessage $(ConvertTo-Json $result)\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/import_vm.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantVM, VagrantMessages\n\nparam(\n    [parameter (Mandatory=$true)]\n    [string] $VMConfigFile,\n    [parameter (Mandatory=$true)]\n    [string] $DestinationPath,\n    [parameter (Mandatory=$true)]\n    [string] $DataPath,\n    [parameter (Mandatory=$true)]\n    [string] $SourcePath,\n    [parameter (Mandatory=$false)]\n    [switch] $LinkedClone,\n    [parameter (Mandatory=$false)]\n    [int] $Memory = $null,\n    [parameter (Mandatory=$false)]\n    [int] $MaxMemory = $null,\n    [parameter (Mandatory=$false)]\n    [int] $Processors = $null,\n    [parameter (Mandatory=$false)]\n    [string] $VMName=$null\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    if($LinkedClone) {\n        $linked = $true\n    } else {\n        $linked = $false\n    }\n\n    $VM = New-VagrantVM -VMConfigFile $VMConfigFile -DestinationPath $DestinationPath `\n      -DataPath $DataPath -SourcePath $SourcePath -LinkedClone $linked -Memory $Memory `\n      -MaxMemory $MaxMemory -CPUCount $Processors -VMName $VMName\n\n    $Result = @{\n        id = $VM.Id.Guid;\n    }\n    Write-OutputMessage (ConvertTo-Json $Result)\n} catch {\n    Write-ErrorMessage \"${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/list_hdds.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId\n)\n\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VmId\n    $Disks = @(Hyper-V\\Get-VMHardDiskDrive -VMName $VM.Name)\n} catch {\n    Write-ErrorMessage \"Failed to retrieve all disk info from ${VM}: ${PSItem}\"\n    exit 1\n}\n\n$result = ConvertTo-json $Disks\nWrite-OutputMessage $result\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/list_snapshots.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VmId\n    $Snapshots = @(Hyper-V\\Get-VMSnapshot $VM | Select-Object Name)\n} catch {\n    Write-ErrorMessage \"Failed to get snapshot list: ${PSItem}\"\n    exit 1\n}\n\n$result = ConvertTo-json $Snapshots\nWrite-OutputMessage $result\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/new_vhd.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$Path,\n    [Parameter(Mandatory=$true)]\n    [UInt64]$SizeBytes,\n    [switch]$Fixed,\n    [switch]$Differencing,\n    [string]$ParentPath,\n    [Uint32]$BlockSizeBytes,\n    [UInt32]$LogicalSectorSizeBytes,\n    [UInt32]$PhysicalSectorSizeBytes,\n    [UInt32]$SourceDisk\n)\n\n$Params = @{}\n\nforeach ($key in $MyInvocation.BoundParameters.keys) {\n  $value = (Get-Variable -Exclude \"ErrorAction\" $key).Value\n  if ($key -ne \"ErrorAction\") {\n    $Params.Add($key, $value)\n  }\n}\n\ntry {\n    Hyper-V\\New-VHD @Params\n} catch {\n    Write-ErrorMessage \"Failed to create disk ${DiskFilePath}: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/remove_disk_drive.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId,\n    [Parameter(Mandatory=$true)]\n    [string]$ControllerType,\n    [Parameter(Mandatory=$true)]\n    [string]$ControllerNumber,\n    [Parameter(Mandatory=$true)]\n    [string]$ControllerLocation,\n    [Parameter(Mandatory=$true)]\n    [string]$DiskFilePath\n)\n\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VmId\n\n    Hyper-v\\Remove-VMHardDiskDrive -VMName $VM.Name -ControllerType $ControllerType -ControllerNumber $ControllerNumber -ControllerLocation $ControllerLocation\n\n    Remove-Item -Path $DiskFilePath\n} catch {\n    Write-ErrorMessage \"Failed to remove disk ${DiskFilePath} to VM ${VM}: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/remove_dvd.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId,\n    [Parameter(Mandatory=$true)]\n    [Int32]$ControllerNumber,\n    [Parameter(Mandatory=$true)]\n    [Int32]$ControllerLocation\n)\n\ntry {\n    $vm = Hyper-V\\Get-VM -ID $VmId\n    Hyper-V\\Remove-VMDvdDrive -ControllerNumber $ControllerNumber -ControllerLocation $ControllerLocation -VMName $vm.Name\n} catch {\n    Write-ErrorMessage \"Failed to remove DVD drive (Location: '${ControllerLocation}' Number '${ControllerNumber}'): ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/resize_disk_drive.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId,\n    [Parameter(Mandatory=$true)]\n    [string]$DiskFilePath,\n    [Parameter(Mandatory=$true)]\n    [UInt64]$DiskSize\n)\n\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VmId\n    Hyper-V\\Resize-VHD -Path $DiskFilePath -SizeBytes $DiskSize\n} catch {\n    Write-ErrorMessage \"Failed to resize disk ${DiskFilePath} for VM ${VM}: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/restore_snapshot.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId,\n    [string]$SnapName\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VmId\n    Hyper-V\\Restore-VMSnapshot $VM -Name $SnapName -Confirm:$false\n} catch {\n    Write-ErrorMessage \"Failed to restore snapshot: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/resume_vm.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\r\n\r\nparam(\r\n    [Parameter(Mandatory=$true)]\r\n    [string]$VmId\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n\r\ntry {\r\n    $VM = Hyper-V\\Get-VM -Id $VmId\r\n    Hyper-V\\Resume-VM $VM\r\n} catch {\r\n    Write-ErrorMessage \"Failed to resume VM: ${PSItem}\"\r\n    exit 1\r\n}\r\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/set_enhanced_session_transport_type.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam (\n    [parameter (Mandatory=$true)]\n    [Guid] $VMID,\n    [parameter (Mandatory=$true)]\n    [string] $Type\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VMID\n} catch {\n    Write-ErrorMessage \"Failed to locate VM: ${PSItem}\"\n    exit 1\n}\n\ntry {\n    # HyperV 1.1 (Windows Server 2012R2) crashes on this call. Vagrantfiles before 2.2.10 do break without skipping this.\n    $present = Get-Command Hyper-V\\Set-VM -ParameterName EnhancedSessionTransportType -ErrorAction SilentlyContinue\n    if($present) {\n        Hyper-V\\Set-VM -VM $VM -EnhancedSessionTransportType $Type\n    }else{\n        $message = @{\n            \"EnhancedSessionTransportTypeSupportPresent\"=$false;\n            } | ConvertTo-Json\n        Write-OutputMessage $message\n    }\n} catch {\n    Write-ErrorMessage \"Failed to assign EnhancedSessionTransportType to ${Type}:${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/set_name.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam (\n    [parameter (Mandatory=$true)]\n    [Guid] $VMID,\n    [parameter (Mandatory=$true)]\n    [string] $VMName\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VMID\n} catch {\n    Write-ErrorMessage \"Failed to locate VM: ${PSItem}\"\n    exit 1\n}\n\ntry {\n    Hyper-V\\Set-VM -VM $VM -NewVMName $VMName\n} catch {\n    Write-ErrorMessage \"Failed to assign new VM name ${VMName}: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/set_network_mac.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam (\n    [parameter (Mandatory=$true)]\n    [string]$VmId,\n    [parameter (Mandatory=$true)]\n    [string]$Mac\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $vm = Hyper-V\\Get-VM -Id $VmId\n    Hyper-V\\Set-VMNetworkAdapter $vm -StaticMacAddress $Mac\n} catch {\n    Write-ErrorMessage \"Failed to set VM MAC address: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/set_network_vlan.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam (\n    [parameter (Mandatory=$true)]\n    [string]$VmId,\n    [parameter (Mandatory=$true)]\n    [int]$VlanId\n)\n\ntry {\n  $vm = Hyper-V\\Get-VM -Id $VmId -ErrorAction \"stop\"\n  Hyper-V\\Set-VMNetworkAdapterVlan $vm -Access -Vlanid $VlanId\n}\ncatch {\n  Write-ErrorMessage \"Failed to set VM's Vlan ID $_\"\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/set_vm_integration_services.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantVM, VagrantMessages\n\nparam (\n    [parameter (Mandatory=$true)]\n    [string] $VMID,\n    [parameter (Mandatory=$true)]\n    [string] $Id,\n    [parameter (Mandatory=$false)]\n    [switch] $Enable\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $VM = Hyper-V\\Get-VM -Id $VMID\n} catch {\n    Write-ErrorMessage \"Failed to locate VM: ${PSItem}\"\n    exit 1\n}\n\ntry {\n    Set-VagrantVMService -VM $VM -Id $Id -Enable $Enable\n} catch {\n    if($Enable){ $action = \"enable\" } else { $action = \"disable\" }\n    Write-ErrorMessage \"Failed to ${action} VM integration service id ${Id}: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/start_vm.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nparam (\n    [parameter (Mandatory=$true)]\n    [string]$VmId\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry {\n    $vm = Hyper-V\\Get-VM -Id $VmId\n    Hyper-V\\Start-VM $vm\n    $state = $vm.state\n    $status = $vm.status\n    $name = $vm.name\n    $resultHash = @{\n        state = \"$state\"\n        status = \"$status\"\n        name = \"$name\"\n    }\n    $result = ConvertTo-Json $resultHash\n    Write-OutputMessage $result\n} catch {\n    Write-ErrorMessage \"Failed to start VM ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/stop_vm.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\n\nParam(\n    [Parameter(Mandatory=$true)]\n    [string]$VmId\n)\n\n$ErrorActionPreference = \"Stop\"\n\ntry{\n    # Shuts down virtual machine regardless of any unsaved application data\n    $VM = Hyper-V\\Get-VM -Id $VmId\n    Hyper-V\\Stop-VM $VM -Force\n} catch {\n    Write-ErrorMessage \"Failed to stop VM: ${PSItem}\"\n    exit 1\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/suspend_vm.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n#Requires -Modules VagrantMessages\r\n\r\nparam(\r\n    [Parameter(Mandatory=$true)]\r\n    [string]$VmId\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n\r\ntry{\r\n    $VM = Hyper-V\\Get-VM -Id $VmId\r\n    Hyper-V\\Suspend-VM $VM\r\n} catch {\r\n    Write-ErrorMessage \"Failed to suspend VM: ${PSItem}\"\r\n    exit 1\r\n}\r\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/utils/VagrantMessages/VagrantMessages.psm1",
    "content": "#-------------------------------------------------------------------------\n# Copyright (c) Microsoft Open Technologies, Inc.\n# All Rights Reserved. Licensed under the MIT License.\n#--------------------------------------------------------------------------\n\nfunction Write-ErrorMessage {\n    param (\n        [parameter (Mandatory=$true,Position=0)]\n        [string] $Message\n    )\n    $error_message = @{\n        error = $Message\n    }\n    Write-Host \"===Begin-Error===\"\n    Write-Host (ConvertTo-Json $error_message)\n    Write-Host \"===End-Error===\"\n}\n\nfunction Write-OutputMessage {\n    param (\n        [parameter (Mandatory=$true,Position=0)]\n        [string] $Message\n    )\n    Write-Host \"===Begin-Output===\"\n    Write-Host $Message\n    Write-Host \"===End-Output===\"\n}\n"
  },
  {
    "path": "plugins/providers/hyperv/scripts/utils/VagrantVM/VagrantVM.psm1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# Always stop when errors are encountered unless instructed not to\n$ErrorActionPreference = \"Stop\"\n\n# Check the version of Powershell currently in use. If it's\n# under 7.3.0 we need to restrict the maximum version of the\n# security module to prevent errors.\n# Source: https://github.com/PowerShell/PowerShell/issues/18530\n$checkVersion = $PSVersionTable.PSVersion\nif($checkVersion -eq \"\") {\n    $checkVersion = $(Get-Host).Version\n}\n\nif([System.Version]$checkVersion -lt [System.Version]\"7.3.0\") {\n    Import-Module Microsoft.Powershell.Security -MaximumVersion 3.0.0.0\n} else {\n    Import-Module Microsoft.Powershell.Security\n}\n\n# Vagrant VM creation functions\n\nfunction New-VagrantVM {\n    param (\n        [parameter(Mandatory=$true)]\n        [string] $VMConfigFile,\n        [parameter(Mandatory=$true)]\n        [string] $DestinationPath,\n        [parameter (Mandatory=$true)]\n        [string] $DataPath,\n        [parameter (Mandatory=$true)]\n        [string] $SourcePath,\n        [parameter (Mandatory=$false)]\n        [bool] $LinkedClone = $false,\n        [parameter (Mandatory=$false)]\n        [int] $Memory = $null,\n        [parameter (Mandatory=$false)]\n        [int] $MaxMemory = $null,\n        [parameter (Mandatory=$false)]\n        [int] $CPUCount = $null,\n        [parameter(Mandatory=$false)]\n        [string] $VMName\n    )\n    if([IO.Path]::GetExtension($VMConfigFile).ToLower() -eq \".xml\") {\n        return New-VagrantVMXML @PSBoundParameters\n    } else {\n        return New-VagrantVMVMCX @PSBoundParameters\n    }\n<#\n.SYNOPSIS\n\nCreate a new Vagrant Hyper-V VM by cloning original. This\nis the general use function with will call the specialized\nfunction based on the extension of the configuration file.\n\n.DESCRIPTION\n\nUsing an existing Hyper-V VM a new Hyper-V VM is created\nby cloning the original.\n\n.PARAMETER VMConfigFile\nPath to the original Hyper-V VM configuration file.\n\n.PARAMETER DestinationPath\nPath to new Hyper-V VM hard drive.\n\n.PARAMETER DataPath\nDirectory path of the original Hyper-V VM to be cloned.\n\n.PARAMETER SourcePath\nPath to the original Hyper-V VM hard drive.\n\n.PARAMETER LinkedClone\nNew Hyper-V VM should be linked clone instead of complete copy.\n\n.PARAMETER VMName\nName of the new Hyper-V VM.\n\n.INPUTS\n\nNone.\n\n.OUTPUTS\n\nVirtualMachine. The cloned Hyper-V VM.\n#>\n}\n\nfunction New-VagrantVMVMCX {\n    param (\n        [parameter(Mandatory=$true)]\n        [string] $VMConfigFile,\n        [parameter(Mandatory=$true)]\n        [string] $DestinationPath,\n        [parameter (Mandatory=$true)]\n        [string] $DataPath,\n        [parameter (Mandatory=$true)]\n        [string] $SourcePath,\n        [parameter (Mandatory=$false)]\n        [bool] $LinkedClone = $false,\n        [parameter (Mandatory=$false)]\n        [int] $Memory = $null,\n        [parameter (Mandatory=$false)]\n        [int] $MaxMemory = $null,\n        [parameter (Mandatory=$false)]\n        [int] $CPUCount = $null,\n        [parameter(Mandatory=$false)]\n        [string] $VMName\n    )\n\n    $NewVMConfig = @{\n        Path = $VMConfigFile;\n        SnapshotFilePath = Join-Path $DataPath \"Snapshots\";\n        VhdDestinationPath = Join-Path $DataPath \"Virtual Hard Disks\";\n        VirtualMachinePath = $DataPath;\n    }\n    $VMConfig = (Hyper-V\\Compare-VM -Copy -GenerateNewID @NewVMConfig -ErrorAction SilentlyContinue)\n\n    # If the config is empty it means the import failed. Attempt to provide\n    # context for failure\n    if($VMConfig -eq $null) {\n        Report-ErrorVagrantVMImport -VMConfigFile $VMConfigFile\n    }\n\n    $VM = $VMConfig.VM\n    $Gen = $VM.Generation\n\n    # Set VM name if name has been provided\n    if($VMName) {\n        Hyper-V\\Set-VM -VM $VM -NewVMName $VMName\n    }\n\n    # Set EFI secure boot on machines after Gen 1\n    if($Gen -gt 1) {\n        Hyper-V\\Set-VMFirmware -VM $VM -EnableSecureBoot (Hyper-V\\Get-VMFirmware -VM $VM).SecureBoot\n    }\n\n    # Disconnect adapters from switches\n    Hyper-V\\Get-VMNetworkAdapter -VM $VM | Hyper-V\\Disconnect-VMNetworkAdapter\n\n    # If we have a memory value provided, set it here\n    if($Memory -ne $null) {\n        Set-VagrantVMMemory -VM $VM -Memory $Memory -MaxMemory $MaxMemory\n    }\n\n    # If we have a CPU count provided, set it here\n    if($CPUCount -ne $null) {\n        Set-VagrantVMCPUS -VM $VM -CPUCount $CPUCount\n    }\n\n    # Verify new VM\n    $Report = Hyper-V\\Compare-VM -CompatibilityReport $VMConfig\n    if($Report.Incompatibilities.Length -gt 0){\n        throw $(ConvertTo-Json $($Report.Incompatibilities | Select -ExpandProperty Message))\n    }\n\n    if($LinkedClone) {\n        $Controllers = Hyper-V\\Get-VMScsiController -VM $VM\n        if($Gen -eq 1){\n            $Controllers = @($Controllers) + @(Hyper-V\\Get-VMIdeController -VM $VM)\n        }\n        foreach($Controller in $Controllers) {\n            foreach($Drive in $Controller.Drives) {\n                if([System.IO.Path]::GetFileName($Drive.Path) -eq [System.IO.Path]::GetFileName($SourcePath)) {\n                    $Path = $Drive.Path\n                    Hyper-V\\Remove-VMHardDiskDrive $Drive\n                    Hyper-V\\New-VHD -Path $DestinationPath -ParentPath $SourcePath -Differencing\n                    Hyper-V\\Add-VMHardDiskDrive -VM $VM -Path $DestinationPath\n                    break\n                }\n            }\n        }\n\n    }\n    return Hyper-V\\Import-VM -CompatibilityReport $VMConfig\n<#\n.SYNOPSIS\n\nCreate a new Vagrant Hyper-V VM by cloning original (VMCX based).\n\n.DESCRIPTION\n\nUsing an existing Hyper-V VM a new Hyper-V VM is created\nby cloning the original.\n\n.PARAMETER VMConfigFile\nPath to the original Hyper-V VM configuration file.\n\n.PARAMETER DestinationPath\nPath to new Hyper-V VM hard drive.\n\n.PARAMETER DataPath\nDirectory path of the original Hyper-V VM to be cloned.\n\n.PARAMETER SourcePath\nPath to the original Hyper-V VM hard drive.\n\n.PARAMETER LinkedClone\nNew Hyper-V VM should be linked clone instead of complete copy.\n\n.PARAMETER VMName\nName of the new Hyper-V VM.\n\n.INPUTS\n\nNone.\n\n.OUTPUTS\n\nVirtualMachine. The cloned Hyper-V VM.\n#>\n}\n\nfunction New-VagrantVMXML {\n    param (\n        [parameter(Mandatory=$true)]\n        [string] $VMConfigFile,\n        [parameter(Mandatory=$true)]\n        [string] $DestinationPath,\n        [parameter (Mandatory=$true)]\n        [string] $DataPath,\n        [parameter (Mandatory=$true)]\n        [string] $SourcePath,\n        [parameter (Mandatory=$false)]\n        [bool] $LinkedClone = $false,\n        [parameter (Mandatory=$false)]\n        [int] $Memory = $null,\n        [parameter (Mandatory=$false)]\n        [int] $MaxMemory = $null,\n        [parameter (Mandatory=$false)]\n        [int] $CPUCount = $null,\n        [parameter(Mandatory=$false)]\n        [string] $VMName\n    )\n\n    $DestinationDirectory = [System.IO.Path]::GetDirectoryName($DestinationPath)\n    New-Item -ItemType Directory -Force -Path $DestinationDirectory\n\n    if($LinkedClone){\n        Hyper-V\\New-VHD -Path $DestinationPath -ParentPath $SourcePath -ErrorAction Stop\n    } else {\n        Copy-Item $SourcePath -Destination $DestinationPath -ErrorAction Stop\n    }\n\n    [xml]$VMConfig = Get-Content -Path $VMConfigFile\n    $Gen = [int]($VMConfig.configuration.properties.subtype.\"#text\") + 1\n    if(!$VMName) {\n        $VMName = $VMConfig.configuration.properties.name.\"#text\"\n    }\n\n    # Determine boot device\n    if($Gen -eq 1) {\n        Switch ((Select-Xml -xml $VMConfig -XPath \"//boot\").node.device0.\"#text\") {\n            \"Floppy\"    { $BootDevice = \"Floppy\" }\n            \"HardDrive\" { $BootDevice = \"IDE\" }\n            \"Optical\"   { $BootDevice = \"CD\" }\n            \"Network\"   { $BootDevice = \"LegacyNetworkAdapter\" }\n            \"Default\"   { $BootDevice = \"IDE\" }\n        }\n    } else {\n        Switch ((Select-Xml -xml $VMConfig -XPath \"//boot\").node.device0.\"#text\") {\n            \"HardDrive\" { $BootDevice = \"VHD\" }\n            \"Optical\"   { $BootDevice = \"CD\" }\n            \"Network\"   { $BootDevice = \"NetworkAdapter\" }\n            \"Default\"   { $BootDevice = \"VHD\" }\n        }\n    }\n\n    # Determine if secure boot is enabled\n    $SecureBoot = (Select-Xml -XML $VMConfig -XPath \"//secure_boot_enabled\").Node.\"#text\"\n    $SecureBootTemplate = (Select-Xml -XML $VMConfig -XPath \"//secure_boot_template\").Node.\"#text\"\n\n    $NewVMConfig = @{\n        Name = $VMName;\n        NoVHD = $true;\n        BootDevice = $BootDevice;\n    }\n\n    # Generation parameter in PS4 so validate before using\n    if((Get-Command Hyper-V\\New-VM).Parameters.Keys.Contains(\"generation\")) {\n        $NewVMConfig.Generation = $Gen\n    }\n\n    # Create new VM instance\n    $VM = Hyper-V\\New-VM @NewVMConfig\n\n    # Configure secure boot\n    if($Gen -gt 1) {\n        if($SecureBoot -eq \"True\") {\n            Hyper-V\\Set-VMFirmware -VM $VM -EnableSecureBoot On\n            if ( \n                    ( ![System.String]::IsNullOrEmpty($SecureBootTemplate) )`\n                     -and`\n                    ( (Get-Command Hyper-V\\Set-VMFirmware).Parameters.Keys.Contains(\"secureboottemplate\") ) \n                ) {\n                    Hyper-V\\Set-VMFirmware -VM $VM -SecureBootTemplate $SecureBootTemplate\n                }\n        } else {\n            Hyper-V\\Set-VMFirmware -VM $VM -EnableSecureBoot Off\n        }\n    }\n\n    # Configure drives\n    [regex]$DriveNumberMatcher = \"\\d\"\n    $Controllers = Select-Xml -XML $VMConfig -XPath \"//*[starts-with(name(.),'controller')]\"\n\n    foreach($Controller in $Controllers) {\n        $Node = $Controller.Node\n        if($Node.ParentNode.ChannelInstanceGuid) {\n            $ControllerType = \"SCSI\"\n        } else {\n            $ControllerType = \"IDE\"\n        }\n        $Drives = $Node.ChildNodes | where {$_.pathname.\"#text\"}\n        foreach($Drive in $Drives) {\n            $DriveType = $Drive.type.\"#text\"\n            if($DriveType -ne \"VHD\") {\n                continue\n            }\n\n            $NewDriveConfig = @{\n                ControllerNumber = $DriveNumberMatcher.Match($Controller.node.name).value;\n                Path = $DestinationPath;\n                ControllerType = $ControllerType;\n            }\n            if($Drive.pool_id.\"#text\") {\n                $NewDriveConfig.ResourcePoolname = $Drive.pool_id.\"#text\"\n            }\n            $VM | Hyper-V\\Add-VMHardDiskDrive @NewDriveConfig\n        }\n    }\n\n    # Apply original VM configuration to new VM instance\n\n    if($CPUCount -ne $null -and $CPUCount -gt 0) {\n        $processors = $CPUCount\n    } else {\n        $processors = $VMConfig.configuration.settings.processors.count.\"#text\"\n    }\n    $notes = (Select-Xml -XML $VMConfig -XPath \"//notes\").node.\"#text\"\n    $memoryNode = (Select-Xml -XML $VMConfig -XPath \"//memory\").node.bank\n    if ($memoryNode.dynamic_memory_enabled.\"#text\" -eq \"True\") {\n        $dynamicmemory = $True\n    }\n    else {\n        $dynamicmemory = $False\n    }\n\n\n    if($Memory -ne $null -and $Memory -gt 0) {\n        $MemoryMaximumBytes = $Memory * 1MB\n        $MemoryStartupBytes = $Memory * 1MB\n        $MemoryMinimumBytes = $Memory * 1MB\n    } else {\n        $MemoryMaximumBytes = ($memoryNode.limit.\"#text\" -as [int]) * 1MB\n        $MemoryStartupBytes = ($memoryNode.size.\"#text\" -as [int]) * 1MB\n        $MemoryMinimumBytes = ($memoryNode.reservation.\"#text\" -as [int]) * 1MB\n    }\n\n    if($MaxMemory -ne $null -and $MaxMemory -gt 0) {\n        $dynamicmemory = $true\n        $MemoryMaximumBytes = $MaxMemory * 1MB\n    }\n\n    $Config = @{\n        ProcessorCount = $processors;\n        MemoryStartupBytes = $MemoryStartupBytes\n    }\n    if($dynamicmemory) {\n        $Config.DynamicMemory = $true\n        $Config.MemoryMinimumBytes = $MemoryMinimumBytes\n        $Config.MemoryMaximumBytes = $MemoryMaximumBytes\n    } else {\n        $Config.StaticMemory = $true\n    }\n    if($notes) {\n        $Config.Notes = $notes\n    }\n    Hyper-V\\Set-VM -VM $VM @Config\n\n    return $VM\n<#\n.SYNOPSIS\n\nCreate a new Vagrant Hyper-V VM by cloning original (XML based).\n\n.DESCRIPTION\n\nUsing an existing Hyper-V VM a new Hyper-V VM is created\nby cloning the original.\n\n.PARAMETER VMConfigFile\nPath to the original Hyper-V VM configuration file.\n\n.PARAMETER DestinationPath\nPath to new Hyper-V VM hard drive.\n\n.PARAMETER DataPath\nDirectory path of the original Hyper-V VM to be cloned.\n\n.PARAMETER SourcePath\nPath to the original Hyper-V VM hard drive.\n\n.PARAMETER LinkedClone\nNew Hyper-V VM should be linked clone instead of complete copy.\n\n.PARAMETER VMName\nName of the new Hyper-V VM.\n\n.INPUTS\n\nNone.\n\n.OUTPUTS\n\nVirtualMachine. The cloned Hyper-V VM.\n#>\n}\n\nfunction Report-ErrorVagrantVMImport {\n    param (\n        [parameter(Mandatory=$true)]\n        [string] $VMConfigFile\n    )\n\n    $ManagementService = Get-WmiObject -Namespace 'root\\virtualization\\v2' -Class 'Msvm_VirtualSystemManagementService'\n    if($null -eq $ManagementService) {\n        throw 'The Hyper-V Virtual Machine Management Service (VMMS) is not running.'\n    }\n\n    # Relative path names will fail when attempting to import a system\n    # definition so always ensure we are using the full path to the\n    # configuration file.\n    $FullPathFile = (Resolve-Path $VMConfigFile).Path\n\n    $Result = $ManagementService.ImportSystemDefinition($FullPathFile, $null, $true)\n    if($Result.ReturnValue -eq 0) {\n        throw \"Unknown error encountered while importing VM\"\n    } elseif($Result.ReturnValue -eq 4096) {\n        $job = Get-WmiObject -Namespace 'root\\virtualization\\v2' -Query 'select * from Msvm_ConcreteJob' | Where {$_.__PATH -eq $Result.Job}\n        while($job.JobState -eq 3 -or $job.JobState -eq 4) {\n            start-sleep 1\n            $job = Get-WmiObject -Namespace 'root\\virtualization\\v2' -Query 'select * from Msvm_ConcreteJob' | Where {$_.__PATH -eq $Result.Job}\n        }\n        $ErrorMsg = $job.ErrorDescription + \"`n`n\"\n        $ErrorMsg = $ErrorMsg + \"Error Code: \" + $job.ErrorCode + \"`n\"\n        $cause = \"Unknown\"\n        switch($job.ErrorCode) {\n            32768 { $cause = \"Failed\" }\n            32769 { $cause = \"Access Denied\" }\n            32770 { $cause = \"Not Supported\" }\n            32771 { $cause = \"Status is unknown\" }\n            32772 { $cause = \"Timeout\" }\n            32773 { $cause = \"Invalid parameter\" }\n            32774 { $cause = \"System is in use\" }\n            32775 { $cause = \"Invalid state for this operation\" }\n            32776 { $cause = \"Incorrect data type\" }\n            32777 { $cause = \"System is not available\" }\n            32778 { $cause = \"Out of memory\" }\n            32779 { $cause = \"File in Use\" }\n            32784 { $cause = \"VM version is unsupported\" }\n        }\n        $ErrorMsg = $ErrorMsg + \"Cause: ${cause}\"\n        throw $ErrorMsg\n    } else {\n        throw \"Failed to run VM import job. Error value: ${Result.ReturnValue}\"\n    }\n<#\n.SYNOPSIS\n\nDetermines cause of error for VM import.\n\n.DESCRIPTION\n\nRuns a local import of the VM configuration and attempts to determine\nthe underlying cause of the import failure.\n\n.PARAMETER VMConfigFile\nPath to the Hyper-V VM configuration file.\n\n.INPUTS\n\nNone.\n\n.OUTPUTS\n\nNone.\n#>\n}\n\n# Vagrant VM configuration functions\n\nfunction Set-VagrantVMMemory {\n    param (\n        [parameter (Mandatory=$true)]\n        [Microsoft.HyperV.PowerShell.VirtualMachine] $VM,\n        [parameter (Mandatory=$false)]\n        [int] $Memory,\n        [parameter (Mandatory=$false)]\n        [int] $MaxMemory\n    )\n\n    $ConfigMemory = Hyper-V\\Get-VMMemory -VM $VM\n\n    if(!$Memory) {\n        $MemoryStartupBytes = ($ConfigMemory.Startup)\n        $MemoryMinimumBytes = ($ConfigMemory.Minimum)\n        $MemoryMaximumBytes = ($ConfigMemory.Maximum)\n    } else {\n        $MemoryStartupBytes = $Memory * 1MB\n        $MemoryMinimumBytes = $Memory * 1MB\n        $MemoryMaximumBytes = $Memory * 1MB\n    }\n\n    if($MaxMemory) {\n        $DynamicMemory = $true\n        $MemoryMaximumBytes = $MaxMemory * 1MB\n    }\n\n    if($DynamicMemory) {\n        if($MemoryMaximumBytes -lt $MemoryMinimumBytes) {\n            throw \"Maximum memory value is less than required minimum memory value.\"\n        }\n        if ($MemoryMaximumBytes -lt $MemoryStartupBytes) {\n            throw \"Maximum memory value is less than configured startup memory value.\"\n        }\n\n        Hyper-V\\Set-VM -VM $VM -DynamicMemory\n        Hyper-V\\Set-VM -VM $VM -MemoryMinimumBytes $MemoryMinimumBytes -MemoryMaximumBytes `\n          $MemoryMaximumBytes -MemoryStartupBytes $MemoryStartupBytes\n    } else {\n        Hyper-V\\Set-VM -VM $VM -StaticMemory\n        Hyper-V\\Set-VM -VM $VM -MemoryStartupBytes $MemoryStartupBytes\n    }\n    return $VM\n<#\n.SYNOPSIS\n\nConfigure VM memory settings.\n\n.DESCRIPTION\n\nAdjusts the VM memory settings. If MaxMemory is defined, dynamic memory\nis enabled on the VM.\n\n.PARAMETER VM\n\nHyper-V VM for modification.\n\n.Parameter Memory\n\nMemory to allocate to the given VM in MB.\n\n.Parameter MaxMemory\n\nMaximum memory to allocate to the given VM in MB. When this value is\nprovided dynamic memory is enabled for the VM. The Memory value or\nthe currently configured memory of the VM will be used as the minimum\nand startup memory value.\n\n.Output\n\nVirtualMachine.\n#>\n}\n\nfunction Set-VagrantVMCPUS {\n    param (\n        [parameter (Mandatory=$true)]\n        [Microsoft.HyperV.PowerShell.VirtualMachine] $VM,\n        [parameter (Mandatory=$false)]\n        [int] $CPUCount\n    )\n\n    if($CPUCount) {\n        Hyper-V\\Set-VM -VM $VM -ProcessorCount $CPUCount\n    }\n    return $VM\n<#\n.SYNOPSIS\n\nConfigure VM CPU count.\n\n.DESCRIPTION\n\nConfigure the number of CPUs on the given VM.\n\n.PARAMETER VM\n\nHyper-V VM for modification.\n\n.PARAMETER CPUCount\n\nNumber of CPUs.\n\n.Output\n\nVirtualMachine.\n#>\n}\n\nfunction Set-VagrantVMVirtExtensions {\n    param (\n        [parameter (Mandatory=$true)]\n        [Microsoft.HyperV.PowerShell.VirtualMachine] $VM,\n        [parameter (Mandatory=$false)]\n        [bool] $Enabled=$false\n    )\n\n    # Check that this option is available\n    if((Get-Command Hyper-V\\Set-VMProcessor).Parameters[\"ExposeVirtualizationExtensions\"] -eq $null) {\n        if($Enabled) {\n            throw \"ExposeVirtualizationExtensions is not available\"\n        } else {\n            return $VM\n        }\n    }\n\n    Hyper-V\\Set-VMProcessor -VM $VM -ExposeVirtualizationExtensions $Enabled\n    return $VM\n<#\n.SYNOPSIS\n\nEnable virtualization extensions on VM.\n\n.PARAMETER VM\n\nHyper-V VM for modification.\n\n.PARAMETER Enabled\n\nEnable virtualization extensions on given VM.\n\n.OUTPUT\n\nVirtualMachine.\n#>\n}\n\nfunction Set-VagrantVMAutoActions {\n    param (\n        [parameter (Mandatory=$true)]\n        [Microsoft.HyperV.PowerShell.VirtualMachine] $VM,\n        [parameter (Mandatory=$false)]\n        [string] $AutoStartAction=\"Nothing\",\n        [parameter (Mandatory=$false)]\n        [string] $AutoStopAction=\"ShutDown\"\n    )\n\n    Hyper-V\\Set-VM -VM $VM -AutomaticStartAction $AutoStartAction\n    Hyper-V\\Set-VM -VM $VM -AutomaticStopAction $AutoStopAction\n    return $VM\n<#\n.SYNOPSIS\n\nConfigure automatic start and stop actions for VM\n\n.DESCRIPTION\n\nConfigures the automatic start and automatic stop actions for\nthe given VM.\n\n.PARAMETER VM\n\nHyper-V VM for modification.\n\n.PARAMETER AutoStartAction\n\nAction the VM should automatically take when the host is started.\n\n.PARAMETER AutoStopAction\n\nAction the VM should automatically take when the host is stopped.\n\n.OUTPUT\n\nVirtualMachine.\n#>\n}\n\nfunction Set-VagrantVMService {\n    param (\n        [parameter (Mandatory=$true)]\n        [Microsoft.HyperV.PowerShell.VirtualMachine] $VM,\n        [parameter (Mandatory=$true)]\n        [string] $Id,\n        [parameter (Mandatory=$true)]\n        [bool] $Enable\n    )\n\n    if($Enable) {\n        Hyper-V\\Get-VMIntegrationService -VM $VM | ?{$_.Id -match $Id} | Hyper-V\\Enable-VMIntegrationService\n    } else {\n        Hyper-V\\Get-VMIntegrationService -VM $VM | ?{$_.Id -match $Id} | Hyper-V\\Disable-VMIntegrationService\n    }\n    return $VM\n<#\n.SYNOPSIS\n\nEnable or disable Hyper-V VM integration services.\n\n.PARAMETER VM\n\nHyper-V VM for modification.\n\n.PARAMETER Id\n\nId of the integration service.\n\n.PARAMETER Enable\n\nEnable or disable the service.\n\n.OUTPUT\n\nVirtualMachine.\n#>\n}\n\n# Vagrant networking functions\n\nfunction Get-VagrantVMSwitch {\n    param (\n        [parameter (Mandatory=$true)]\n        [string] $NameOrID\n    )\n    $SwitchName = $(Hyper-V\\Get-VMSwitch -Id $NameOrID).Name\n    if(!$SwitchName) {\n        $SwitchName = $(Hyper-V\\Get-VMSwitch -Name $NameOrID).Name\n    }\n    if(!$SwitchName) {\n        throw \"Failed to locate switch with name or ID: ${NameOrID}\"\n    }\n    return $SwitchName\n<#\n.SYNOPSIS\n\nGet name of VMSwitch.\n\n.DESCRIPTION\n\nFind VMSwitch by name or ID and return name.\n\n.PARAMETER NameOrID\n\nName or ID of VMSwitch.\n\n.OUTPUT\n\nName of VMSwitch.\n#>\n}\n\nfunction Set-VagrantVMSwitch {\n    param (\n        [parameter (Mandatory=$true)]\n        [Microsoft.HyperV.PowerShell.VirtualMachine] $VM,\n        [parameter (Mandatory=$true)]\n        [String] $SwitchName\n    )\n    $Adapter = Hyper-V\\Get-VMNetworkAdapter -VM $VM\n    Hyper-V\\Connect-VMNetworkAdapter -VMNetworkAdapter $Adapter -SwitchName $SwitchName\n    return $VM\n<#\n.SYNOPSIS\n\nConfigure VM to use given switch.\n\n.DESCRIPTION\n\nConfigures VM adapter to use the the VMSwitch with the given name.\n\n.PARAMETER VM\n\nHyper-V VM for modification.\n\n.PARAMETER SwitchName\n\nName of the VMSwitch.\n\n.OUTPUT\n\nVirtualMachine.\n#>\n}\n\nfunction Check-VagrantHyperVAccess {\n    param (\n        [parameter (Mandatory=$true)]\n        [string] $Path\n    )\n    $acl = Get-ACL -Path $Path\n    $systemACL = $acl.Access | where {\n        try { return $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value -eq \"S-1-5-18\" } catch { return $false } -and\n        $_.FileSystemRights -eq \"FullControl\" -and\n        $_.AccessControlType -eq \"Allow\" -and\n        $_.IsInherited -eq $true}\n    if($systemACL) {\n        return $true\n    }\n    return $false\n<#\n.SYNOPSIS\n\nCheck Hyper-V access at given path.\n\n.DESCRIPTION\n\nChecks that the given path has the correct access rules for Hyper-V\n\n.PARAMETER PATH\n\nPath to check\n\n.OUTPUT\n\nBoolean\n#>\n}\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/boot.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class Boot\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          @env = env\n\n          boot_mode = @env[:machine].provider_config.gui ? \"gui\" : \"headless\"\n\n          # Start up the VM and wait for it to boot.\n          env[:ui].info I18n.t(\"vagrant.actions.vm.boot.booting\")\n          env[:machine].provider.driver.start(boot_mode)\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/check_accessible.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class CheckAccessible\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          if env[:machine].state.id == :inaccessible\n            # The VM we are attempting to manipulate is inaccessible. This\n            # is a very bad situation and can only be fixed by the user. It\n            # also prohibits us from actually doing anything with the virtual\n            # machine, so we raise an error.\n            raise Vagrant::Errors::VMInaccessible\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/check_created.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      # This middleware checks that the VM is created, and raises an exception\n      # if it is not, notifying the user that creation is required.\n      class CheckCreated\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          if env[:machine].state.id == :not_created\n            raise Vagrant::Errors::VMNotCreatedError\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/check_guest_additions.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class CheckGuestAdditions\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::plugins::virtualbox::check_guest_additions\")\n        end\n\n        def call(env)\n          if !env[:machine].provider_config.check_guest_additions\n            @logger.info(\"Not checking guest additions because configuration\")\n            return @app.call(env)\n          end\n\n          env[:ui].output(I18n.t(\"vagrant.virtualbox.checking_guest_additions\"))\n\n          # Use the raw interface for now, while the virtualbox gem\n          # doesn't support guest properties (due to cross platform issues)\n          version = env[:machine].provider.driver.read_guest_additions_version\n          if !version\n            env[:ui].detail(I18n.t(\"vagrant.actions.vm.check_guest_additions.not_detected\"))\n          else\n            # Read the versions\n            versions = [version, env[:machine].provider.driver.version]\n\n            # Strip of any -OSE or _OSE and read only the first two parts\n            # of the version such as \"4.2\" in \"4.2.0\"\n            versions.map! do |v|\n              v     = v.gsub(/[-_]ose/i, '')\n              match = /^(\\d+\\.\\d+)\\.(\\d+)/.match(v)\n              v     = match[1] if match\n              v\n            end\n\n            guest_version = versions[0]\n            vb_version    = versions[1]\n\n            if guest_version != vb_version\n              env[:ui].detail(I18n.t(\"vagrant.actions.vm.check_guest_additions.version_mismatch\",\n                                   guest_version: version,\n                                   virtualbox_version: vb_version))\n            end\n          end\n\n          # Continue\n          @app.call(env)\n        end\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/check_running.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      # This middleware checks that the VM is running, and raises an exception\n      # if it is not, notifying the user that the VM must be running.\n      class CheckRunning\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          if env[:machine].state.id != :running\n            raise Vagrant::Errors::VMNotRunningError\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/check_virtualbox.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/platform'\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      # Checks that VirtualBox is installed and ready to be used.\n      class CheckVirtualbox\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox\")\n        end\n\n        def call(env)\n          # This verifies that VirtualBox is installed and the driver is\n          # ready to function. If not, then an exception will be raised\n          # which will break us out of execution of the middleware sequence.\n          Driver::Meta.new.verify!\n\n          # Carry on.\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/clean_machine_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      # Cleans up the VirtualBox machine folder for any \".xml-prev\"\n      # files which VirtualBox may have left over. This is a bug in\n      # VirtualBox. As soon as this is fixed, this middleware can and\n      # will be removed.\n      class CleanMachineFolder\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          machine_folder = env[:machine].provider.driver.read_machine_folder\n\n          begin\n            clean_machine_folder(machine_folder)\n          rescue Errno::EPERM\n            raise Vagrant::Errors::MachineFolderNotAccessible,\n              name: env[:machine].name,\n              path: machine_folder\n          end\n\n          @app.call(env)\n        end\n\n        def clean_machine_folder(machine_folder)\n          folder = File.join(machine_folder, \"*\")\n\n          # Small safeguard against potentially unwanted rm-rf, since the default\n          # machine folder will typically always be greater than 10 characters long.\n          # For users with it < 10, out of luck?\n          return if folder.length < 10\n\n          Dir[folder].each do |f|\n            next unless File.directory?(f)\n\n            keep = Dir[\"#{f}/**/*\"].find do |d|\n              # Find a file that doesn't have \".xml-prev\" as the suffix,\n              # which signals that we want to keep this folder\n              File.file?(d) && !(File.basename(d) =~ /\\.vbox-prev$/)\n            end\n\n            FileUtils.rm_rf(f) if !keep\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/clear_forwarded_ports.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class ClearForwardedPorts\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          if !env[:machine].provider.driver.read_forwarded_ports.empty?\n            env[:ui].info I18n.t(\"vagrant.actions.vm.clear_forward_ports.deleting\")\n            env[:machine].provider.driver.clear_forwarded_ports\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/clear_network_interfaces.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class ClearNetworkInterfaces\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          # Create the adapters array to make all adapters nothing.\n          # We do adapters 2 to 8 because that is every built-in adapter\n          # excluding the NAT adapter on port 1 which Vagrant always\n          # expects to exist.\n          adapters = []\n          2.upto(env[:machine].provider.driver.max_network_adapters).each do |i|\n            adapters << {\n              adapter: i,\n              type:    :none\n            }\n          end\n\n          # \"Enable\" all the adapters we setup.\n          env[:ui].info I18n.t(\"vagrant.actions.vm.clear_network_interfaces.deleting\")\n          env[:machine].provider.driver.enable_adapters(adapters)\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/created.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class Created\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          # Set the result to be true if the machine is created.\n          env[:result] = env[:machine].state.id != :not_created\n\n          # Call the next if we have one (but we shouldn't, since this\n          # middleware is built to run with the Call-type middlewares)\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/customize.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class Customize\n        def initialize(app, env, event)\n          @app = app\n          @event = event\n        end\n\n        def call(env)\n          customizations = []\n          env[:machine].provider_config.customizations.each do |event, command|\n            if event == @event\n              customizations << command\n            end\n          end\n\n          if !customizations.empty?\n            env[:ui].info I18n.t(\"vagrant.actions.vm.customize.running\", event: @event)\n\n            # Execute each customization command.\n            customizations.each do |command|\n              processed_command = command.collect do |arg|\n                arg = env[:machine].id if arg == :id\n                arg.to_s\n              end\n\n              begin\n                env[:machine].provider.driver.execute_command(\n                  processed_command + [retryable: true])\n              rescue Vagrant::Errors::VBoxManageError => e\n                raise Vagrant::Errors::VMCustomizationFailed, {\n                  command: command,\n                  error:   e.inspect\n                }\n              end\n            end\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/destroy.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class Destroy\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[:ui].info I18n.t(\"vagrant.actions.vm.destroy.destroying\")\n          env[:machine].provider.driver.delete\n          env[:machine].id = nil\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/destroy_unused_network_interfaces.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class DestroyUnusedNetworkInterfaces\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::plugins::virtualbox::destroy_unused_netifs\")\n        end\n\n        def call(env)\n          if env[:machine].provider_config.destroy_unused_network_interfaces\n            @logger.info(\"Destroying unused network interfaces...\")\n            env[:machine].provider.driver.delete_unused_host_only_networks\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/discard_state.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class DiscardState\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          if env[:machine].state.id == :saved\n            env[:ui].info I18n.t(\"vagrant.actions.vm.discard_state.discarding\")\n            env[:machine].provider.driver.discard_saved_state\n          end\n\n           @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/export.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\nrequire 'vagrant/util/platform'\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class Export\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          @env = env\n\n          raise Vagrant::Errors::VMPowerOffToPackage if \\\n            @env[:machine].state.id != :poweroff\n\n          export\n\n          @app.call(env)\n        end\n\n        def export\n          @env[:ui].info I18n.t(\"vagrant.actions.vm.export.exporting\")\n          @env[:machine].provider.driver.export(ovf_path) do |progress|\n            @env[:ui].rewriting do |ui|\n              ui.clear_line\n              ui.report_progress(progress.percent, 100, false)\n            end\n          end\n\n          # Clear the line a final time so the next data can appear\n          # alone on the line.\n          @env[:ui].clear_line\n        end\n\n        def ovf_path\n          path = File.join(@env[\"export.temp_dir\"], \"box.ovf\")\n\n          # If we're within WSL, we should use the correct path rather than\n          # the mnt path. GH-9059\n          if Vagrant::Util::Platform.wsl?\n            path = Vagrant::Util::Platform.wsl_to_windows_path(path)\n          end\n\n          return path\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/forced_halt.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class ForcedHalt\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          current_state = env[:machine].state.id\n          if current_state == :running || current_state == :gurumeditation\n            env[:ui].info I18n.t(\"vagrant.actions.vm.halt.force\")\n            env[:machine].provider.driver.halt\n          end\n\n          # Sleep for a second to verify that the VM properly\n          # cleans itself up. Silly VirtualBox.\n          sleep 1 if !env[\"vagrant.test\"]\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/forward_ports.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class ForwardPorts\n        include Util::CompileForwardedPorts\n\n        def initialize(app, env)\n          @app = app\n        end\n\n        #--------------------------------------------------------------\n        # Execution\n        #--------------------------------------------------------------\n        def call(env)\n          @env = env\n\n          # Get the ports we're forwarding\n          env[:forwarded_ports] ||= compile_forwarded_ports(env[:machine].config)\n\n          # Warn if we're port forwarding to any privileged ports...\n          env[:forwarded_ports].each do |fp|\n            if fp.host_port <= 1024\n              env[:ui].warn I18n.t(\"vagrant.actions.vm.forward_ports.privileged_ports\")\n              break\n            end\n          end\n\n          env[:ui].output(I18n.t(\"vagrant.actions.vm.forward_ports.forwarding\"))\n          forward_ports\n\n          @app.call(env)\n        end\n\n        def forward_ports\n          ports = []\n\n          interfaces = @env[:machine].provider.driver.read_network_interfaces\n\n          @env[:forwarded_ports].each do |fp|\n            message_attributes = {\n              adapter: fp.adapter,\n              guest_port: fp.guest_port,\n              host_port: fp.host_port\n            }\n\n            # Assuming the only reason to establish port forwarding is\n            # because the VM is using Virtualbox NAT networking. Host-only\n            # bridged networking don't require port-forwarding and establishing\n            # forwarded ports on these attachment types has uncertain behaviour.\n            @env[:ui].detail(I18n.t(\"vagrant.actions.vm.forward_ports.forwarding_entry\",\n                                    **message_attributes))\n\n            # Verify we have the network interface to attach to\n            if !interfaces[fp.adapter]\n              raise Vagrant::Errors::ForwardPortAdapterNotFound,\n                adapter: fp.adapter.to_s,\n                guest: fp.guest_port.to_s,\n                host: fp.host_port.to_s\n            end\n\n            # Port forwarding requires the network interface to be a NAT interface,\n            # so verify that that is the case.\n            if interfaces[fp.adapter][:type] != :nat\n              @env[:ui].detail(I18n.t(\"vagrant.actions.vm.forward_ports.non_nat\",\n                                    **message_attributes))\n              next\n            end\n\n            # Add the options to the ports array to send to the driver later\n            ports << {\n              adapter:   fp.adapter,\n              guestip:   fp.guest_ip,\n              guestport: fp.guest_port,\n              hostip:    fp.host_ip,\n              hostport:  fp.host_port,\n              name:      fp.id,\n              protocol:  fp.protocol\n            }\n          end\n\n          if !ports.empty?\n            # We only need to forward ports if there are any to forward\n            @env[:machine].provider.driver.forward_ports(ports)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/import.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class Import\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          if env[:clone_id]\n            clone(env)\n          else\n            import(env)\n          end\n        end\n\n        def clone(env)\n          # Do the actual clone\n          env[:ui].info I18n.t(\"vagrant.actions.vm.clone.creating\")\n          env[:machine].id = env[:machine].provider.driver.clonevm(\n            env[:clone_id], env[:clone_snapshot]) do |progress|\n            env[:ui].rewriting do |ui|\n              ui.clear_line\n              ui.report_progress(progress, 100, false)\n            end\n          end\n\n          # Clear the line one last time since the progress meter doesn't\n          # disappear immediately.\n          env[:ui].clear_line\n\n          # Flag as erroneous and return if clone failed\n          raise Vagrant::Errors::VMCloneFailure if !env[:machine].id\n\n          # Copy the SSH key from the clone machine if we can\n          if env[:clone_machine]\n            key_path = env[:clone_machine].data_dir.join(\"private_key\")\n            if key_path.file?\n              FileUtils.cp(\n                key_path,\n                env[:machine].data_dir.join(\"private_key\"))\n            end\n          end\n\n          # Continue\n          @app.call(env)\n        end\n\n        def import(env)\n          env[:ui].info I18n.t(\"vagrant.actions.vm.import.importing\",\n                               name: env[:machine].box.name)\n\n          # Import the virtual machine\n          ovf_file = env[:machine].box.directory.join(\"box.ovf\").to_s\n          id = env[:machine].provider.driver.import(ovf_file) do |progress|\n            env[:ui].rewriting do |ui|\n              ui.clear_line\n              ui.report_progress(progress, 100, false)\n            end\n          end\n\n          # Set the machine ID\n          env[:machine_id] = id\n          env[:machine].id = id if !env[:skip_machine]\n\n          # Clear the line one last time since the progress meter doesn't disappear\n          # immediately.\n          env[:ui].clear_line\n\n          # If we got interrupted, then the import could have been\n          # interrupted and its not a big deal. Just return out.\n          return if env[:interrupted]\n\n          # Flag as erroneous and return if import failed\n          raise Vagrant::Errors::VMImportFailure if !id\n\n          # Import completed successfully. Continue the chain\n          @app.call(env)\n        end\n\n        def recover(env)\n          if env[:machine] && env[:machine].state.id != Vagrant::MachineState::NOT_CREATED_ID\n            return if env[\"vagrant.error\"].is_a?(Vagrant::Errors::VagrantError)\n\n            # If we're not supposed to destroy on error then just return\n            return if !env[:destroy_on_error]\n\n            # Interrupted, destroy the VM. We note that we don't want to\n            # validate the configuration here, and we don't want to confirm\n            # we want to destroy.\n            destroy_env = env.clone\n            destroy_env[:config_validate] = false\n            destroy_env[:force_confirm_destroy] = true\n\n            # We don't want to double-execute any hooks attached to\n            # machine_action_up. Instead we should be honoring destroy hooks.\n            # Changing the action name here should make the Builder do the\n            # right thing.\n            destroy_env[:raw_action_name] = :destroy\n            destroy_env[:action_name] = :machine_action_destroy\n            env[:action_runner].run(Action.action_destroy, destroy_env)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/import_master.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"digest/md5\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class ImportMaster\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::action::vm::create_master\")\n        end\n\n        def call(env)\n          # If we don't have a box, nothing to do\n          if !env[:machine].box\n            return @app.call(env)\n          end\n\n          # Do the import while locked so that nobody else imports\n          # a master at the same time. This is a no-op if we already\n          # have a master that exists.\n          lock_key = Digest::MD5.hexdigest(env[:machine].box.name)\n          env[:machine].env.lock(lock_key, retry: true) do\n            import_master(env)\n          end\n\n          # If we got interrupted, then the import could have been\n          # interrupted and its not a big deal. Just return out.\n          if env[:interrupted]\n            @logger.info(\"Import of master VM was interrupted -> exiting.\")\n            return\n          end\n\n          # Import completed successfully. Continue the chain\n          @app.call(env)\n        end\n\n        protected\n\n        def import_master(env)\n          master_id_file = env[:machine].box.directory.join(\"master_id\")\n\n          # Read the master ID if we have it in the file.\n          env[:clone_id] = master_id_file.read.chomp if master_id_file.file?\n\n          # If we have the ID and the VM exists already, then we\n          # have nothing to do. Success!\n          if env[:clone_id] && env[:machine].provider.driver.vm_exists?(env[:clone_id])\n            @logger.info(\n              \"Master VM for '#{env[:machine].box.name}' already exists \" +\n              \" (id=#{env[:clone_id]}) - skipping import step.\")\n            return\n          else\n            if env.delete(:clone_id)\n              @logger.info(\"Deleting previous reference for master VM\" +\n                \"'#{env[:machine].box.name}' (id=#{env[:clone_id]}) - \" +\n                \"maybe it was removed manually\")\n            end\n          end\n\n          env[:ui].info(I18n.t(\"vagrant.actions.vm.clone.setup_master\"))\n          env[:ui].detail(I18n.t(\"vagrant.actions.vm.clone.setup_master_detail\"))\n\n          # Import the virtual machine\n          import_env = env[:action_runner].run(Import, env.dup.merge(skip_machine: true))\n          env[:clone_id] = import_env[:machine_id]\n\n          @logger.info(\n            \"Imported box #{env[:machine].box.name} as master vm \" +\n            \"with id #{env[:clone_id]}\")\n\n          @logger.debug(\"Writing id of master VM '#{env[:clone_id]}' to #{master_id_file}\")\n          master_id_file.open(\"w+\") do |f|\n            f.write(env[:clone_id])\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/is_paused.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class IsPaused\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          # Set the result to be true if the machine is paused.\n          env[:result] = env[:machine].state.id == :paused\n\n          # Call the next if we have one (but we shouldn't, since this\n          # middleware is built to run with the Call-type middlewares)\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/is_running.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class IsRunning\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          # Set the result to be true if the machine is running.\n          env[:result] = env[:machine].state.id == :running\n\n          # Call the next if we have one (but we shouldn't, since this\n          # middleware is built to run with the Call-type middlewares)\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/is_saved.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class IsSaved\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          # Set the result to be true if the machine is saved.\n          env[:result] = env[:machine].state.id == :saved\n\n          # Call the next if we have one (but we shouldn't, since this\n          # middleware is built to run with the Call-type middlewares)\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/match_mac_address.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class MatchMACAddress\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          # If we cloned, we don't need a base mac, it is already set!\n          return @app.call(env) if env[:machine].config.vm.clone\n\n          if env[:machine].config.vm.base_mac\n            # Create the proc which we want to use to modify the virtual machine\n            env[:ui].info I18n.t(\"vagrant.actions.vm.match_mac.matching\")\n            env[:machine].provider.driver.set_mac_address(env[:machine].config.vm.base_mac)\n          else\n            env[:ui].info I18n.t(\"vagrant.actions.vm.match_mac.generating\")\n            env[:machine].provider.driver.set_mac_address(nil)\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/message_already_running.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class MessageAlreadyRunning\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[:ui].info I18n.t(\"vagrant.commands.common.vm_already_running\")\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/message_not_created.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class MessageNotCreated\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[:ui].info I18n.t(\"vagrant.commands.common.vm_not_created\")\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/message_not_running.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class MessageNotRunning\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[:ui].info I18n.t(\"vagrant.commands.common.vm_not_running\")\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/message_will_not_destroy.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class MessageWillNotDestroy\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[:ui].info I18n.t(\"vagrant.commands.destroy.will_not_destroy\",\n                              name: env[:machine].name)\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/network.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"ipaddr\"\nrequire \"resolv\"\nrequire \"set\"\n\nrequire \"log4r\"\n\nrequire \"vagrant/util/network_ip\"\nrequire \"vagrant/util/scoped_hash_override\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      # This middleware class sets up all networking for the VirtualBox\n      # instance. This includes host only networks, bridged networking,\n      # forwarded ports, etc.\n      #\n      # This handles all the `config.vm.network` configurations.\n      class Network\n\n        # Location of the VirtualBox networks configuration file\n        VBOX_NET_CONF = \"/etc/vbox/networks.conf\".freeze\n        # Version of VirtualBox that introduced hostonly network range restrictions\n        HOSTONLY_VALIDATE_VERSION = Gem::Version.new(\"6.1.28\")\n        # Version of VirtualBox on darwin platform that ignores restrictions\n        DARWIN_IGNORE_HOSTONLY_VALIDATE_VERSION = Gem::Version.new(\"7.0.0\")\n        # Default valid range for hostonly networks\n        HOSTONLY_DEFAULT_RANGE = [\n          IPAddr.new(\"192.168.56.0/21\").freeze,\n          IPAddr.new(\"fe80::/10\").freeze\n        ].freeze\n\n        include Vagrant::Util::NetworkIP\n        include Vagrant::Util::ScopedHashOverride\n\n        def initialize(app, env)\n          @logger = Log4r::Logger.new(\"vagrant::plugins::virtualbox::network\")\n          @app    = app\n        end\n\n        def call(env)\n          # TODO: Validate network configuration prior to anything below\n          @env = env\n\n          # Get the list of network adapters from the configuration\n          network_adapters_config = env[:machine].provider_config.network_adapters.dup\n\n          # Assign the adapter slot for each high-level network\n          available_slots = Set.new(1..36)\n          network_adapters_config.each do |slot, _data|\n            available_slots.delete(slot)\n          end\n\n          @logger.debug(\"Available slots for high-level adapters: #{available_slots.inspect}\")\n          @logger.info(\"Determining network adapters required for high-level configuration...\")\n          available_slots = available_slots.to_a.sort\n          env[:machine].config.vm.networks.each do |type, options|\n            # We only handle private and public networks\n            next if type != :private_network && type != :public_network\n\n            options = scoped_hash_override(options, :virtualbox)\n\n            # Figure out the slot that this adapter will go into\n            slot = options[:adapter]\n            if !slot\n              if available_slots.empty?\n                raise Vagrant::Errors::VirtualBoxNoRoomForHighLevelNetwork\n              end\n\n              slot = available_slots.shift\n            end\n\n            # Internal network is a special type\n            if type == :private_network && options[:intnet]\n              type = :internal_network\n            end\n\n            if !options.key?(:type) && options.key?(:ip)\n              begin\n                addr = IPAddr.new(options[:ip])\n                options[:type] = if addr.ipv4?\n                                   :static\n                                 else\n                                   :static6\n                                 end\n              rescue IPAddr::Error => err\n                raise Vagrant::Errors::NetworkAddressInvalid,\n                      address: options[:ip], mask: options[:netmask],\n                      error: err.message\n              end\n            end\n\n            # Configure it\n            data = nil\n            if type == :private_network\n              # private_network = hostonly\n              data = [:hostonly, options]\n            elsif type == :public_network\n              # public_network = bridged\n              data = [:bridged, options]\n            elsif type == :internal_network\n              data = [:intnet, options]\n            end\n\n            # Store it!\n            @logger.info(\" -- Slot #{slot}: #{data[0]}\")\n            network_adapters_config[slot] = data\n          end\n\n          @logger.info(\"Determining adapters and compiling network configuration...\")\n          adapters = []\n          networks = []\n          network_adapters_config.each do |slot, data|\n            type    = data[0]\n            options = data[1]\n\n            @logger.info(\"Network slot #{slot}. Type: #{type}.\")\n\n            # Get the normalized configuration for this type\n            config = send(\"#{type}_config\", options)\n            config[:adapter] = slot\n            @logger.debug(\"Normalized configuration: #{config.inspect}\")\n\n            # Get the VirtualBox adapter configuration\n            adapter = send(\"#{type}_adapter\", config)\n            adapters << adapter\n            @logger.debug(\"Adapter configuration: #{adapter.inspect}\")\n\n            # Get the network configuration\n            network = send(\"#{type}_network_config\", config)\n            network[:auto_config] = config[:auto_config]\n            networks << network\n          end\n\n          if !adapters.empty?\n            # Enable the adapters\n            @logger.info(\"Enabling adapters...\")\n            env[:ui].output(I18n.t(\"vagrant.actions.vm.network.preparing\"))\n            adapters.each do |adapter|\n              env[:ui].detail(I18n.t(\n                \"vagrant.virtualbox.network_adapter\",\n                adapter: adapter[:adapter].to_s,\n                type: adapter[:type].to_s,\n                extra: \"\",\n              ))\n            end\n\n            env[:machine].provider.driver.enable_adapters(adapters)\n          end\n\n          # Continue the middleware chain.\n          @app.call(env)\n\n          # If we have networks to configure, then we configure it now, since\n          # that requires the machine to be up and running.\n          if !adapters.empty? && !networks.empty?\n            assign_interface_numbers(networks, adapters)\n\n            # Only configure the networks the user requested us to configure\n            networks_to_configure = networks.select { |n| n[:auto_config] }\n            if !networks_to_configure.empty?\n              env[:ui].info I18n.t(\"vagrant.actions.vm.network.configuring\")\n              env[:machine].guest.capability(:configure_networks, networks_to_configure)\n            end\n          end\n        end\n\n        def bridged_config(options)\n          return {\n            auto_config:                     true,\n            bridge:                          nil,\n            mac:                             nil,\n            nic_type:                        nil,\n            use_dhcp_assigned_default_route: false\n          }.merge(options || {})\n        end\n\n        def bridged_adapter(config)\n          # Find the bridged interfaces that are available\n          bridgedifs = @env[:machine].provider.driver.read_bridged_interfaces\n          bridgedifs.delete_if { |interface| interface[:status] == \"Down\" || interface[:status] == \"Unknown\" }\n\n          # The name of the chosen bridge interface will be assigned to this\n          # variable.\n          chosen_bridge = nil\n\n          if config[:bridge]\n            @logger.debug(\"Bridge was directly specified in config, searching for: #{config[:bridge]}\")\n\n            # Search for a matching bridged interface\n            Array(config[:bridge]).each do |bridge|\n              bridge = bridge.downcase if bridge.respond_to?(:downcase)\n              bridgedifs.each do |interface|\n                if bridge === interface[:name].downcase\n                  @logger.debug(\"Specific bridge found as configured in the Vagrantfile. Using it.\")\n                  chosen_bridge = interface[:name]\n                  break\n                end\n              end\n              break if chosen_bridge\n            end\n\n            # If one wasn't found, then we notify the user here.\n            if !chosen_bridge\n              @env[:ui].info I18n.t(\"vagrant.actions.vm.bridged_networking.specific_not_found\",\n                                    bridge: config[:bridge])\n            end\n          end\n\n          # If we still don't have a bridge chosen (this means that one wasn't\n          # specified in the Vagrantfile, or the bridge specified in the Vagrantfile\n          # wasn't found), then we fall back to the normal means of searching for a\n          # bridged network.\n          if !chosen_bridge\n            if bridgedifs.length == 1\n              # One bridgeable interface? Just use it.\n              chosen_bridge = bridgedifs[0][:name]\n              @logger.debug(\"Only one bridged interface available. Using it by default.\")\n            else\n              # More than one bridgeable interface requires a user decision, so\n              # show options to choose from.\n              @env[:ui].info I18n.t(\n                \"vagrant.actions.vm.bridged_networking.available\",\n                prefix: false)\n              bridgedifs.each_index do |index|\n                interface = bridgedifs[index]\n                @env[:ui].info(\"#{index + 1}) #{interface[:name]}\", prefix: false)\n              end\n              @env[:ui].info(I18n.t(\n                \"vagrant.actions.vm.bridged_networking.choice_help\")+\"\\n\")\n\n              # The range of valid choices\n              valid = Range.new(1, bridgedifs.length)\n\n              # The choice that the user has chosen as the bridging interface\n              choice = nil\n              while !valid.include?(choice)\n                choice = @env[:ui].ask(\n                  \"Which interface should the network bridge to? \")\n                choice = choice.to_i\n              end\n\n              chosen_bridge = bridgedifs[choice - 1][:name]\n            end\n          end\n\n          @logger.info(\"Bridging adapter #{config[:adapter]} to #{chosen_bridge}\")\n\n          # Given the choice we can now define the adapter we're using\n          return {\n            adapter:     config[:adapter],\n            type:        :bridged,\n            bridge:      chosen_bridge,\n            mac_address: config[:mac],\n            nic_type:    config[:nic_type]\n          }\n        end\n\n        def bridged_network_config(config)\n          if config[:ip]\n            options = {\n                auto_config: true,\n                mac:         nil,\n                netmask:     \"255.255.255.0\",\n                type:        :static\n            }.merge(config)\n            options[:type] = options[:type].to_sym\n            return options\n          end\n\n          return {\n            type: :dhcp,\n            use_dhcp_assigned_default_route: config[:use_dhcp_assigned_default_route]\n          }\n        end\n\n        def hostonly_config(options)\n          options = {\n            auto_config: true,\n            mac:         nil,\n            nic_type:    nil,\n            type:        :static,\n          }.merge(options)\n\n          # Make sure the type is a symbol\n          options[:type] = options[:type].to_sym\n\n          if options[:type] == :dhcp && !options[:ip]\n            # Try to find a matching device to set the config ip to\n            matching_device = hostonly_find_matching_network(options)\n            if matching_device\n              options[:ip] = matching_device[:ip]\n            else\n              # Default IP is in the 20-bit private network block for DHCP based networks\n              options[:ip] = \"192.168.56.1\"\n            end\n          end\n\n          begin\n            ip = IPAddr.new(options[:ip])\n            if ip.ipv4?\n              options[:netmask] ||= \"255.255.255.0\"\n            elsif ip.ipv6?\n              options[:netmask] ||= 64\n\n              # Append a 6 to the end of the type if it is not already set\n              options[:type] = \"#{options[:type]}6\".to_sym if\n                !options[:type].to_s.end_with?(\"6\")\n            else\n              raise IPAddr::AddressFamilyError, 'unknown address family'\n            end\n\n            # Calculate our network address for the given IP/netmask\n            netaddr = IPAddr.new(\"#{options[:ip]}/#{options[:netmask]}\")\n          rescue IPAddr::Error => e\n            raise Vagrant::Errors::NetworkAddressInvalid,\n              address: options[:ip], mask: options[:netmask],\n              error: e.message\n          end\n\n          validate_hostonly_ip!(options[:ip], @env[:machine].provider.driver)\n\n          if ip.ipv4?\n            # Verify that a host-only network subnet would not collide\n            # with a bridged networking interface.\n            #\n            # If the subnets overlap in any way then the host only network\n            # will not work because the routing tables will force the\n            # traffic onto the real interface rather than the VirtualBox\n            # interface.\n            @env[:machine].provider.driver.read_bridged_interfaces.each do |interface|\n              that_netaddr = network_address(interface[:ip], interface[:netmask])\n              if netaddr == that_netaddr && interface[:status] != \"Down\"\n                raise Vagrant::Errors::NetworkCollision,\n                  netaddr: netaddr,\n                  that_netaddr: that_netaddr,\n                  interface_name: interface[:name]\n              end\n            end\n          end\n\n          # Calculate the adapter IP which is the network address with\n          # the final bit + 1. Usually it is \"x.x.x.1\" for IPv4 and\n          # \"<prefix>::1\" for IPv6\n          options[:adapter_ip] ||= (netaddr | 1).to_s\n\n          dhcp_options = {}\n          if options[:type] == :dhcp\n            # Calculate the DHCP server IP and lower & upper bound\n            # Example: for \"192.168.22.64/26\" network range those are:\n            # dhcp_ip: \"192.168.22.66\",\n            # dhcp_lower: \"192.168.22.67\"\n            # dhcp_upper: \"192.168.22.126\"\n            ip_range = netaddr.to_range\n            dhcp_options[:dhcp_ip] = options[:dhcp_ip] || (ip_range.first | 2).to_s\n            dhcp_options[:dhcp_lower] = options[:dhcp_lower] || (ip_range.first | 3).to_s\n            dhcp_options[:dhcp_upper] = options[:dhcp_upper] || (ip_range.last(2).first).to_s\n          end\n\n          # Find the hostonly interface name if display name was\n          # provided\n          if options[:name]\n            hostif = @env[:machine].provider.driver.read_host_only_interfaces.detect { |interface|\n              interface[:name] == options[:name] ||\n                interface[:display_name] == options[:name]\n            }\n            options[:name] = hostif[:name] if hostif\n          end\n\n          return {\n            adapter_ip:  options[:adapter_ip],\n            auto_config: options[:auto_config],\n            ip:          options[:ip],\n            mac:         options[:mac],\n            name:        options[:name],\n            netmask:     options[:netmask],\n            nic_type:    options[:nic_type],\n            type:        options[:type]\n          }.merge(dhcp_options)\n        end\n\n        def hostonly_adapter(config)\n          @logger.info(\"Searching for matching hostonly network: #{config[:ip]}\")\n          interface = hostonly_find_matching_network(config)\n\n          if !interface\n            @logger.info(\"Network not found. Creating if we can.\")\n\n            # It is an error if a specific host only network name was specified\n            # but the network wasn't found.\n            if config[:name]\n              raise Vagrant::Errors::NetworkNotFound, name: config[:name]\n            end\n\n            # Create a new network\n            interface = hostonly_create_network(config)\n            @logger.info(\"Created network: #{interface[:name]}\")\n          end\n\n          if config[:type] == :dhcp\n            create_dhcp_server_if_necessary(interface, config)\n          end\n\n          return {\n            adapter:     config[:adapter],\n            hostonly:    interface[:name],\n            mac_address: config[:mac],\n            nic_type:    config[:nic_type],\n            type:        :hostonly\n          }\n        end\n\n        def hostonly_network_config(config)\n          return {\n            type:       config[:type],\n            adapter_ip: config[:adapter_ip],\n            ip:         config[:ip],\n            netmask:    config[:netmask]\n          }\n        end\n\n        def intnet_config(options)\n          return {\n            type: \"static\",\n            ip: nil,\n            netmask: \"255.255.255.0\",\n            adapter: nil,\n            mac: nil,\n            intnet: nil,\n            auto_config: true\n          }.merge(options || {})\n        end\n\n        def intnet_adapter(config)\n          intnet_name = config[:intnet]\n          intnet_name = \"intnet\" if intnet_name == true\n\n          return {\n            adapter: config[:adapter],\n            type: :intnet,\n            mac_address: config[:mac],\n            nic_type: config[:nic_type],\n            intnet: intnet_name,\n          }\n        end\n\n        def intnet_network_config(config)\n          return {\n            type: config[:type],\n            ip: config[:ip],\n            netmask: config[:netmask]\n          }\n        end\n\n        def nat_config(options)\n          return options.merge(\n            auto_config: false\n          )\n        end\n\n        def nat_adapter(config)\n          return {\n            adapter: config[:adapter],\n            type:    :nat,\n            nic_type: config[:nic_type],\n          }\n        end\n\n        def nat_network_config(config)\n          return {}\n        end\n\n        #-----------------------------------------------------------------\n        # Misc. helpers\n        #-----------------------------------------------------------------\n        # Assigns the actual interface number of a network based on the\n        # enabled NICs on the virtual machine.\n        #\n        # This interface number is used by the guest to configure the\n        # NIC on the guest VM.\n        #\n        # The networks are modified in place by adding an \":interface\"\n        # field to each.\n        def assign_interface_numbers(networks, adapters)\n          current = 0\n          adapter_to_interface = {}\n\n          # Make a first pass to assign interface numbers by adapter location\n          vm_adapters = @env[:machine].provider.driver.read_network_interfaces\n          vm_adapters.sort.each do |number, adapter|\n            if adapter[:type] != :none\n              # Not used, so assign the interface number and increment\n              adapter_to_interface[number] = current\n              current += 1\n            end\n          end\n\n          # Make a pass through the adapters to assign the :interface\n          # key to each network configuration.\n          adapters.each_index do |i|\n            adapter = adapters[i]\n            network = networks[i]\n\n            # Figure out the interface number by simple lookup\n            network[:interface] = adapter_to_interface[adapter[:adapter]]\n          end\n        end\n\n        #-----------------------------------------------------------------\n        # Hostonly Helper Functions\n        #-----------------------------------------------------------------\n        # This creates a host only network for the given configuration.\n        def hostonly_create_network(config)\n          @env[:machine].provider.driver.create_host_only_network(config)\n        end\n\n        # This finds a matching host only network for the given configuration.\n        def hostonly_find_matching_network(config)\n          this_netaddr = network_address(config[:ip], config[:netmask])  if config[:ip]\n\n          @env[:machine].provider.driver.read_host_only_interfaces.each do |interface|\n            return interface if config[:name] && config[:name] == interface[:name]\n\n            #if a config name is specified, we should only look for that.\n            if config[:name].to_s != \"\"\n              next\n            end\n\n            if interface[:ip] != \"\"\n              return interface if this_netaddr == \\\n                network_address(interface[:ip], interface[:netmask])\n            end\n\n            if interface[:ipv6] != \"\"\n              return interface if this_netaddr == \\\n                network_address(interface[:ipv6], interface[:ipv6_prefix])\n            end\n          end\n\n          nil\n        end\n\n        # Validates the IP used to configure the network is within the allowed\n        # ranges. It only validates if the network configuration file exists.\n        # This was introduced in 6.1.28 so previous version won't have restrictions\n        # placed on the valid ranges\n        def validate_hostonly_ip!(ip, driver)\n          return if Gem::Version.new(driver.version) < HOSTONLY_VALIDATE_VERSION ||\n                    (\n                      Vagrant::Util::Platform.darwin? &&\n                      Gem::Version.new(driver.version) >= DARWIN_IGNORE_HOSTONLY_VALIDATE_VERSION\n                    ) ||\n                    Vagrant::Util::Platform.windows?\n\n          ip = IPAddr.new(ip.to_s) if !ip.is_a?(IPAddr)\n          valid_ranges = load_net_conf\n          return if valid_ranges.any?{ |range| range.include?(ip) }\n          raise Vagrant::Errors::VirtualBoxInvalidHostSubnet,\n            address: ip,\n            ranges: valid_ranges.map{ |r| \"#{r}/#{r.prefix}\" }.join(\", \")\n        end\n\n        def load_net_conf\n          return HOSTONLY_DEFAULT_RANGE if !File.exist?(VBOX_NET_CONF)\n          File.readlines(VBOX_NET_CONF).map do |line|\n            line = line.strip\n            next if !line.start_with?(\"*\")\n            line[1,line.length].strip.split(\" \").map do |entry|\n              IPAddr.new(entry)\n            end\n          end.flatten.compact\n        end\n\n        #-----------------------------------------------------------------\n        # DHCP Server Helper Functions\n        #-----------------------------------------------------------------\n\n        DEFAULT_DHCP_SERVER_FROM_VBOX_INSTALL = {\n          network_name: 'HostInterfaceNetworking-vboxnet0',\n          network:      'vboxnet0',\n          ip:           '192.168.56.100',\n          netmask:      '255.255.255.0',\n          lower:        '192.168.56.101',\n          upper:        '192.168.56.254'\n        }.freeze\n\n        #\n        # When a host-only network of type: :dhcp is configured,\n        # this handles the potential creation of a vbox dhcpserver to manage\n        # it.\n        #\n        # @param [Hash<String>] interface hash as returned from read_host_only_interfaces\n        # @param [Hash<String>] config hash as returned from hostonly_config\n        def create_dhcp_server_if_necessary(interface, config)\n          existing_dhcp_server = find_matching_dhcp_server(interface)\n          if existing_dhcp_server\n            if dhcp_server_matches_config?(existing_dhcp_server, config)\n              @logger.debug(\"DHCP server already properly configured\")\n              return\n            elsif existing_dhcp_server == DEFAULT_DHCP_SERVER_FROM_VBOX_INSTALL\n              @env[:ui].info I18n.t(\"vagrant.actions.vm.network.cleanup_vbox_default_dhcp\")\n              @env[:machine].provider.driver.remove_dhcp_server(existing_dhcp_server[:network_name])\n            else\n              # We have an invalid DHCP server that we're not able to\n              # automatically clean up, so we need to give up and tell the user\n              # to sort out their own vbox dhcpservers and hostonlyifs\n              raise Vagrant::Errors::NetworkDHCPAlreadyAttached\n            end\n          end\n\n          @logger.debug(\"Creating a DHCP server...\")\n          @env[:machine].provider.driver.create_dhcp_server(interface[:name], config)\n        end\n\n        # Detect when an existing DHCP server matches precisely the\n        # requested config for a hostonly interface.\n        #\n        # @param [Hash<String>] dhcp_server as found by read_dhcp_servers\n        # @param [Hash<String>] config as returned from hostonly_config\n        # @return [Boolean]\n        def dhcp_server_matches_config?(dhcp_server, config)\n          dhcp_server[:ip]    == config[:dhcp_ip]    &&\n          dhcp_server[:lower] == config[:dhcp_lower] &&\n          dhcp_server[:upper] == config[:dhcp_upper]\n        end\n\n        # Returns the existing dhcp server, if any, that is attached to the\n        # specified interface.\n        #\n        # @return [Hash<String>] dhcp_server or nil if not found\n        def find_matching_dhcp_server(interface)\n          @env[:machine].provider.driver.read_dhcp_servers.detect do |dhcp_server|\n            interface[:name] && interface[:name] == dhcp_server[:network]\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/network_fix_ipv6.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"ipaddr\"\nrequire \"socket\"\n\nrequire \"log4r\"\n\nrequire \"vagrant/util/presence\"\nrequire \"vagrant/util/scoped_hash_override\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      # This middleware works around a bug in VirtualBox where booting\n      # a VM with an IPv6 host-only network will sometimes lose the\n      # route to that machine.\n      class NetworkFixIPv6\n        include Vagrant::Util::Presence\n        include Vagrant::Util::ScopedHashOverride\n\n        def initialize(app, env)\n          @logger = Log4r::Logger.new(\"vagrant::plugins::virtualbox::network\")\n          @app    = app\n        end\n\n        def call(env)\n          @env = env\n\n          # Determine if we have an IPv6 network\n          has_v6 = false\n          env[:machine].config.vm.networks.each do |type, options|\n            next if type != :private_network\n            options = scoped_hash_override(options, :virtualbox)\n            next if options[:ip].to_s.strip == \"\"\n\n            if IPAddr.new(options[:ip]).ipv6?\n              has_v6 = true\n              break\n            end\n          end\n\n          # Call up\n          @app.call(env)\n\n          # If we have no IPv6, forget it\n          return if !has_v6\n\n          link_local_range = IPAddr.new(\"fe80::/10\")\n          host_only_interfaces(env).each do |interface|\n            next if !present?(interface[:ipv6])\n            next if interface[:status] != \"Up\"\n\n            ip = IPAddr.new(interface[:ipv6])\n            ip |= (\"1\" * (128 - interface[:ipv6_prefix].to_i)).to_i(2)\n\n            next if link_local_range.include?(ip)\n\n            @logger.info(\"testing IPv6: #{ip}\")\n\n            begin\n              UDPSocket.new(Socket::AF_INET6).connect(ip.to_s, 80)\n            rescue Errno::EHOSTUNREACH\n              @logger.info(\"IPv6 host unreachable. Fixing: #{ip}\")\n              env[:machine].provider.driver.reconfig_host_only(interface)\n            end\n          end\n        end\n\n        # The list of interface names for host-only adapters.\n        # @return [Array<String>]\n        def host_only_interface_names(env)\n          env[:machine].provider.driver.read_network_interfaces\n            .map { |_, i| i[:hostonly] if i[:type] == :hostonly }.compact\n        end\n\n        # The list of host_only_interfaces that are tied to a host-only adapter.\n        # @return [Array]\n        def host_only_interfaces(env)\n          iface_names = self.host_only_interface_names(env)\n          env[:machine].provider.driver.read_host_only_interfaces\n            .select { |interface| iface_names.include?(interface[:name]) }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/package.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../lib/vagrant/action/general/package\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class Package < Vagrant::Action::General::Package\n        # Doing this so that we can test that the parent is properly\n        # called in the unit tests.\n        alias_method :general_call, :call\n        def call(env)\n          general_call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/package_setup_files.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../lib/vagrant/action/general/package_setup_files\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class PackageSetupFiles < Vagrant::Action::General::PackageSetupFiles\n        # Doing this so that we can test that the parent is properly\n        # called in the unit tests.\n        alias_method :general_call, :call\n        def call(env)\n          general_call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/package_setup_folders.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\n\nrequire_relative \"../../../../lib/vagrant/action/general/package_setup_folders\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class PackageSetupFolders < Vagrant::Action::General::PackageSetupFolders\n        # Doing this so that we can test that the parent is properly\n        # called in the unit tests.\n        alias_method :general_call, :call\n        def call(env)\n          general_call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/package_vagrantfile.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'vagrant/util/template_renderer'\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class PackageVagrantfile\n        # For TemplateRenderer\n        include Vagrant::Util\n\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          @env = env\n          create_vagrantfile\n          @app.call(env)\n        end\n\n        # This method creates the auto-generated Vagrantfile at the root of the\n        # box. This Vagrantfile contains the MAC address so that the user doesn't\n        # have to worry about it.\n        def create_vagrantfile\n          File.open(File.join(@env[\"export.temp_dir\"], \"Vagrantfile\"), \"w\") do |f|\n            f.write(TemplateRenderer.render(\"package_Vagrantfile\", {\n              base_mac: @env[:machine].provider.driver.read_mac_address\n            }))\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/prepare_clone_snapshot.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"digest/md5\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class PrepareCloneSnapshot\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::action::vm::prepare_clone\")\n        end\n\n        def call(env)\n          if !env[:clone_id]\n            @logger.info(\"no clone master, not preparing clone snapshot\")\n            return @app.call(env)\n          end\n\n          # If we're not doing a linked clone, snapshots don't matter\n          if !env[:machine].provider_config.linked_clone\n            return @app.call(env)\n          end\n\n          # We lock so that we don't snapshot in parallel\n          lock_key = Digest::MD5.hexdigest(\"#{env[:clone_id]}-snapshot\")\n          env[:machine].env.lock(lock_key, retry: true) do\n            prepare_snapshot(env)\n          end\n\n          # Continue\n          @app.call(env)\n        end\n\n        protected\n\n        def prepare_snapshot(env)\n          name = env[:machine].provider_config.linked_clone_snapshot\n          name_set = !!name\n          name = \"base\" if !name\n          env[:clone_snapshot] = name\n\n          # Get the snapshots. We're done if it already exists\n          snapshots = env[:machine].provider.driver.list_snapshots(env[:clone_id])\n          if snapshots.include?(name)\n            @logger.info(\"clone snapshot already exists, doing nothing\")\n            return\n          end\n\n          # If they asked for a specific snapshot, it is an error\n          if name_set\n            # TODO: Error\n          end\n\n          @logger.info(\"Creating base snapshot for master VM.\")\n          env[:machine].provider.driver.create_snapshot(\n            env[:clone_id], name) do |progress|\n              env[:ui].rewriting do |ui|\n                ui.clear_line\n                ui.report_progress(progress, 100, false)\n              end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/prepare_forwarded_port_collision_params.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class PrepareForwardedPortCollisionParams\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          # Get the forwarded ports used by other virtual machines and\n          # consider those in use as well.\n          env[:port_collision_extra_in_use] = env[:machine].provider.driver.read_used_ports\n\n          # Build the remap for any existing collision detections\n          remap = {}\n          env[:port_collision_remap] = remap\n          env[:machine].provider.driver.read_forwarded_ports.each do |_nic, name, hostport, _guestport|\n            env[:machine].config.vm.networks.each do |type, options|\n              next if type != :forwarded_port\n\n              # If the ID matches the name of the forwarded port, then\n              # remap.\n              if options[:id] == name\n                remap[options[:host]] = hostport\n                break\n              end\n            end\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/prepare_nfs_settings.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"ipaddr\"\nrequire \"vagrant/action/builtin/mixin_synced_folders\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class PrepareNFSSettings\n        include Vagrant::Action::Builtin::MixinSyncedFolders\n        include Vagrant::Util::Retryable\n\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::action::vm::nfs\")\n        end\n\n        def call(env)\n          @machine = env[:machine]\n\n          @app.call(env)\n\n          opts = {\n            cached: !!env[:synced_folders_cached],\n            config: env[:synced_folders_config],\n            disable_usable_check: !!env[:test],\n          }\n          folders = synced_folders(env[:machine], **opts)\n\n          if folders.key?(:nfs)\n            @logger.info(\"Using NFS, preparing NFS settings by reading host IP and machine IP\")\n            add_ips_to_env!(env)\n          end\n        end\n\n        # Extracts the proper host and guest IPs for NFS mounts and stores them\n        # in the environment for the SyncedFolder action to use them in\n        # mounting.\n        #\n        # The ! indicates that this method modifies its argument.\n        def add_ips_to_env!(env)\n          adapter, host_ip = find_host_only_adapter\n          machine_ip       = read_static_machine_ips\n\n          if !machine_ip\n            # No static IP, attempt to use the dynamic IP.\n            machine_ip = read_dynamic_machine_ip(adapter)\n          else\n            # We have static IPs, also attempt to read any dynamic IPs.\n            # If there is no dynamic IP on the adapter, it doesn't matter. We\n            # already have a static IP.\n            begin\n              dynamic_ip = read_dynamic_machine_ip(adapter)\n            rescue Vagrant::Errors::NFSNoGuestIP\n              dynamic_ip = nil\n            end\n\n            # If we found a dynamic IP and we didn't include it in the\n            # machine_ip array yet, do so.\n            if dynamic_ip && !machine_ip.include?(dynamic_ip)\n              machine_ip.push(dynamic_ip)\n            end\n          end\n\n          if host_ip && !machine_ip.empty?\n            interface = @machine.provider.driver.read_host_only_interfaces.detect do |iface|\n              iface[:ip] == host_ip\n            end\n            host_ipaddr = IPAddr.new(\"#{host_ip}/#{interface.fetch(:netmask, \"0.0.0.0\")}\")\n\n            case machine_ip\n            when String\n              machine_ip = nil if !host_ipaddr.include?(machine_ip)\n            when Array\n              machine_ip.delete_if do |m_ip|\n                !host_ipaddr.include?(m_ip)\n              end\n              machine_ip = nil if machine_ip.empty?\n            end\n          end\n\n          raise Vagrant::Errors::NFSNoHostonlyNetwork if !host_ip || !machine_ip\n\n          env[:nfs_host_ip]    = host_ip\n          env[:nfs_machine_ip] = machine_ip\n        end\n\n        # Finds first host only network adapter and returns its adapter number\n        # and IP address\n        #\n        # @return [Integer, String] adapter number, ip address of found host-only adapter\n        def find_host_only_adapter\n          @machine.provider.driver.read_network_interfaces.each do |adapter, opts|\n            if opts[:type] == :hostonly\n              @machine.provider.driver.read_host_only_interfaces.each do |interface|\n                if interface[:name] == opts[:hostonly]\n                  return adapter, interface[:ip]\n                end\n              end\n            end\n          end\n\n          nil\n        end\n\n        # Returns the IP address(es) of the guest by looking for static IPs\n        # given to host only adapters in the Vagrantfile\n        #\n        # @return [Array]<String> Configured static IPs\n        def read_static_machine_ips\n          ips = []\n          @machine.config.vm.networks.each do |type, options|\n            options = scoped_hash_override(options, :virtualbox)\n\n            if type == :private_network && options[:type] != :dhcp && options[:ip].is_a?(String)\n              ips << options[:ip]\n            end\n          end\n\n          if ips.empty?\n            return nil\n          end\n\n          ips\n        end\n\n        # Returns the IP address of the guest by looking at vbox guest property\n        # for the appropriate guest adapter.\n        #\n        # For DHCP interfaces, the guest property will not be present until the\n        # guest completes\n        #\n        # @param [Integer] adapter number to read IP for\n        # @return [String] ip address of adapter\n        def read_dynamic_machine_ip(adapter)\n          return nil unless adapter\n\n          # vbox guest properties are 0-indexed, while showvminfo network\n          # interfaces are 1-indexed. go figure.\n          guestproperty_adapter = adapter - 1\n\n          # we need to wait for the guest's IP to show up as a guest property.\n          # retry thresholds are relatively high since we might need to wait\n          # for DHCP, but even static IPs can take a second or two to appear.\n          retryable(retry_options.merge(on: Vagrant::Errors::VirtualBoxGuestPropertyNotFound)) do\n            @machine.provider.driver.read_guest_ip(guestproperty_adapter)\n          end\n        rescue Vagrant::Errors::VirtualBoxGuestPropertyNotFound\n          # this error is more specific with a better error message directing\n          # the user towards the fact that it's probably a reportable bug\n          raise Vagrant::Errors::NFSNoGuestIP\n        end\n\n        # Separating these out so we can stub out the sleep in tests\n        def retry_options\n          {tries: 15, sleep: 1}\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/prepare_nfs_valid_ids.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class PrepareNFSValidIds\n        def initialize(app, env)\n          @app = app\n          @logger = Log4r::Logger.new(\"vagrant::action::vm::nfs\")\n        end\n\n        def call(env)\n          env[:nfs_valid_ids] = env[:machine].provider.driver.read_vms.values\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/resume.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class Resume\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          current_state = env[:machine].state.id\n\n          if current_state == :paused\n            env[:ui].info I18n.t(\"vagrant.actions.vm.resume.unpausing\")\n            env[:machine].provider.driver.resume\n          elsif current_state == :saved\n            env[:ui].info I18n.t(\"vagrant.actions.vm.resume.resuming\")\n            env[:action_runner].run(Boot, env)\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/sane_defaults.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class SaneDefaults\n        def initialize(app, env)\n          @logger = Log4r::Logger.new(\"vagrant::action::vm::sanedefaults\")\n          @app = app\n        end\n\n        def call(env)\n          # Set the env on an instance variable so we can access it in\n          # helpers.\n          @env = env\n\n          # Use rtcuseutc so that the VM sees UTC time.\n          command = [\"modifyvm\", env[:machine].id, \"--rtcuseutc\", \"on\"]\n          attempt_and_log(command, \"Enabling rtcuseutc...\")\n\n          if env[:machine].provider_config.auto_nat_dns_proxy\n            @logger.info(\"Automatically figuring out whether to enable/disable NAT DNS proxy...\")\n\n            # Enable/disable the NAT DNS proxy as necessary\n            if enable_dns_proxy?\n              command = [\"modifyvm\", env[:machine].id, \"--natdnsproxy1\", \"on\"]\n              attempt_and_log(command, \"Enable the NAT DNS proxy on adapter 1...\")\n            else\n              command = [\"modifyvm\", env[:machine].id, \"--natdnsproxy1\", \"off\" ]\n              attempt_and_log(command, \"Disable the NAT DNS proxy on adapter 1...\")\n              command = [\"modifyvm\", env[:machine].id, \"--natdnshostresolver1\", \"off\" ]\n              attempt_and_log(command, \"Disable the NAT DNS resolver on adapter 1...\")\n            end\n          else\n            @logger.info(\"NOT trying to automatically manage NAT DNS proxy.\")\n          end\n\n          @app.call(env)\n        end\n\n        protected\n\n        # This is just a helper method that executes a single command, logs\n        # the given string to the log, and also includes the exit status in\n        # the log message.\n        #\n        # We assume every command is idempotent and pass along the `retryable`\n        # flag. This is because VBoxManage is janky about running simultaneously\n        # on the same box, and if we up multiple boxes at the same time, a bunch\n        # of modifyvm commands get fired\n        #\n        # @param [Array] command Command to run\n        # @param [String] log Log message to write.\n        def attempt_and_log(command, log)\n          begin\n            @env[:machine].provider.driver.execute_command(\n              command + [retryable: true])\n          rescue Vagrant::Errors::VBoxManageError => e\n            @logger.info(\"#{log} (error = #{e.inspect})\")\n          end\n        end\n\n        # This uses some heuristics to determine if the NAT DNS proxy should\n        # be enabled or disabled. See the comments within the function body\n        # itself to see the checks it does.\n        #\n        # @return [Boolean]\n        def enable_dns_proxy?\n          begin\n            contents = File.read(\"/etc/resolv.conf\")\n\n            if contents =~ /^nameserver 127\\.0\\.(0|1)\\.1$/\n              # The use of both natdnsproxy and natdnshostresolver break on\n              # Ubuntu 12.04 and 12.10 that uses resolvconf with localhost. When used\n              # VirtualBox will give the client dns server 10.0.2.3, while\n              # not binding to that address itself. Therefore disable this\n              # feature if host uses the resolvconf server 127.0.0.1 or\n              # 127.0.1.1\n              @logger.info(\"Disabling DNS proxy since resolv.conf contains 127.0.0.1 or 127.0.1.1\")\n              return false\n            end\n          rescue Errno::ENOENT; end\n\n          return true\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/set_default_nic_type.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      # This sets the default NIC type used for network adapters created\n      # on the guest. Also includes a check of NIC types in use and VirtualBox\n      # version to determine if E1000 NIC types are vulnerable.\n      #\n      # NOTE: Vulnerability was fixed here: https://www.virtualbox.org/changeset/75330/vbox\n      class SetDefaultNICType\n        # Defines versions of VirtualBox with susceptible implementation\n        # of the E1000 devices.\n        E1000_SUSCEPTIBLE = Gem::Requirement.new(\"< 5.2.22\").freeze\n\n        def initialize(app, env)\n          @logger = Log4r::Logger.new(\"vagrant::plugins::virtualbox::set_default_nic_type\")\n          @app    = app\n        end\n\n        def call(env)\n          default_nic_type = env[:machine].provider_config.default_nic_type\n\n          e1000_in_use = [\n            # simple check on default_nic_type\n            ->{ default_nic_type.nil? || default_nic_type.to_s.start_with?(\"8254\") },\n            # check provider defined adapters\n            ->{ env[:machine].provider_config.network_adapters.values.detect{ |_, opts|\n                opts[:nic_type].to_s.start_with?(\"8254\") } },\n            # finish with inspecting configured networks\n            ->{ env[:machine].config.vm.networks.detect{ |_, opts|\n                opts.fetch(:virtualbox__nic_type, opts[:nic_type]).to_s.start_with?(\"8254\") } }\n          ]\n\n          # Check if VirtualBox E1000 implementation is vulnerable\n          if E1000_SUSCEPTIBLE.satisfied_by?(Gem::Version.new(env[:machine].provider.driver.version))\n            @logger.info(\"Detected VirtualBox version with susceptible E1000 implementation (`#{E1000_SUSCEPTIBLE}`)\")\n            if e1000_in_use.any?(&:call)\n              env[:ui].warn I18n.t(\"vagrant.actions.vm.set_default_nic_type.e1000_warning\")\n            end\n          end\n\n          if default_nic_type\n            @logger.info(\"Default NIC type for VirtualBox interfaces `#{default_nic_type}`\")\n            # Update network adapters defined in provider configuration\n            env[:machine].provider_config.network_adapters.each do |slot, args|\n              _, opts = args\n              if opts && !opts.key?(:nic_type)\n                @logger.info(\"Setting default NIC type (`#{default_nic_type}`) adapter `#{slot}` - `#{args}`\")\n                opts[:nic_type] = default_nic_type\n              end\n            end\n\n            # Update generally defined networks\n            env[:machine].config.vm.networks.each do |type, options|\n              next if !type.to_s.end_with?(\"_network\")\n              if !options.key?(:nic_type) && !options.key?(:virtualbox__nic_type)\n                @logger.info(\"Setting default NIC type (`#{default_nic_type}`) for `#{type}` - `#{options}`\")\n                options[:virtualbox__nic_type] = default_nic_type\n              end\n            end\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/set_name.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class SetName\n        def initialize(app, env)\n          @logger = Log4r::Logger.new(\"vagrant::action::vm::setname\")\n          @app = app\n        end\n\n        def call(env)\n          name = env[:machine].provider_config.name\n\n          # If we already set the name before, then don't do anything\n          sentinel = env[:machine].data_dir.join(\"action_set_name\")\n          if !name && sentinel.file?\n            @logger.info(\"Default name was already set before, not doing it again.\")\n            return @app.call(env)\n          end\n\n          # If no name was manually set, then use a default\n          if !name\n            prefix = \"#{env[:root_path].basename.to_s}_#{env[:machine].name}\"\n            prefix.gsub!(/[^-a-z0-9_]/i, \"\")\n\n            # milliseconds + random number suffix to allow for simultaneous\n            # `vagrant up` of the same box in different dirs\n            name = prefix + \"_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}\"\n          end\n\n          # Verify the name is not taken\n          vms = env[:machine].provider.driver.read_vms\n          raise Vagrant::Errors::VMNameExists, name: name if \\\n            vms.key?(name) && vms[name] != env[:machine].id\n\n          if vms.key?(name)\n            @logger.info(\"Not setting the name because our name is already set.\")\n          else\n            env[:ui].info(I18n.t(\n              \"vagrant.actions.vm.set_name.setting_name\", name: name))\n            env[:machine].provider.driver.set_name(name)\n          end\n\n          # Create the sentinel\n          sentinel.open(\"w\") do |f|\n            f.write(Time.now.to_i.to_s)\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/setup_package_files.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nrequire_relative \"package_setup_files\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class SetupPackageFiles < PackageSetupFiles\n        def initialize(*)\n          @logger = Log4r::Logger.new(\"vagrant::plugins::virtualbox::setup_package_files\")\n          @logger.warn { \"SetupPackageFiles has been renamed to PackageSetupFiles\" }\n          super\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/snapshot_delete.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class SnapshotDelete\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[:ui].info(I18n.t(\n            \"vagrant.actions.vm.snapshot.deleting\",\n            name: env[:snapshot_name]))\n          env[:machine].provider.driver.delete_snapshot(\n            env[:machine].id, env[:snapshot_name]) do |progress|\n            env[:ui].rewriting do |ui|\n              ui.clear_line\n              ui.report_progress(progress, 100, false)\n            end\n          end\n\n          # Clear the line one last time since the progress meter doesn't disappear\n          # immediately.\n          env[:ui].clear_line\n\n          env[:ui].success(I18n.t(\n            \"vagrant.actions.vm.snapshot.deleted\",\n            name: env[:snapshot_name]))\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/snapshot_restore.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class SnapshotRestore\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[:ui].info(I18n.t(\n            \"vagrant.actions.vm.snapshot.restoring\",\n            name: env[:snapshot_name]))\n          env[:machine].provider.driver.restore_snapshot(\n            env[:machine].id, env[:snapshot_name]) do |progress|\n            env[:ui].rewriting do |ui|\n              ui.clear_line\n              ui.report_progress(progress, 100, false)\n            end\n          end\n\n          # Clear the line one last time since the progress meter doesn't disappear\n          # immediately.\n          env[:ui].clear_line\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/snapshot_save.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class SnapshotSave\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          env[:ui].info(I18n.t(\n            \"vagrant.actions.vm.snapshot.saving\",\n            name: env[:snapshot_name]))\n          env[:machine].provider.driver.create_snapshot(\n            env[:machine].id, env[:snapshot_name])\n\n          env[:ui].success(I18n.t(\n            \"vagrant.actions.vm.snapshot.saved\",\n            name: env[:snapshot_name]))\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action/suspend.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      class Suspend\n        def initialize(app, env)\n          @app = app\n        end\n\n        def call(env)\n          if env[:machine].state.id == :running\n            env[:ui].info I18n.t(\"vagrant.actions.vm.suspend.suspending\")\n            env[:machine].provider.driver.suspend\n          end\n\n          @app.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/action.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/action/builder\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Action\n      autoload :Boot, File.expand_path(\"../action/boot\", __FILE__)\n      autoload :CheckAccessible, File.expand_path(\"../action/check_accessible\", __FILE__)\n      autoload :CheckCreated, File.expand_path(\"../action/check_created\", __FILE__)\n      autoload :CheckGuestAdditions, File.expand_path(\"../action/check_guest_additions\", __FILE__)\n      autoload :CheckRunning, File.expand_path(\"../action/check_running\", __FILE__)\n      autoload :CheckVirtualbox, File.expand_path(\"../action/check_virtualbox\", __FILE__)\n      autoload :CleanMachineFolder, File.expand_path(\"../action/clean_machine_folder\", __FILE__)\n      autoload :ClearForwardedPorts, File.expand_path(\"../action/clear_forwarded_ports\", __FILE__)\n      autoload :ClearNetworkInterfaces, File.expand_path(\"../action/clear_network_interfaces\", __FILE__)\n      autoload :Created, File.expand_path(\"../action/created\", __FILE__)\n      autoload :Customize, File.expand_path(\"../action/customize\", __FILE__)\n      autoload :Destroy, File.expand_path(\"../action/destroy\", __FILE__)\n      autoload :DestroyUnusedNetworkInterfaces, File.expand_path(\"../action/destroy_unused_network_interfaces\", __FILE__)\n      autoload :DiscardState, File.expand_path(\"../action/discard_state\", __FILE__)\n      autoload :Export, File.expand_path(\"../action/export\", __FILE__)\n      autoload :ForcedHalt, File.expand_path(\"../action/forced_halt\", __FILE__)\n      autoload :ForwardPorts, File.expand_path(\"../action/forward_ports\", __FILE__)\n      autoload :Import, File.expand_path(\"../action/import\", __FILE__)\n      autoload :ImportMaster, File.expand_path(\"../action/import_master\", __FILE__)\n      autoload :IsPaused, File.expand_path(\"../action/is_paused\", __FILE__)\n      autoload :IsRunning, File.expand_path(\"../action/is_running\", __FILE__)\n      autoload :IsSaved, File.expand_path(\"../action/is_saved\", __FILE__)\n      autoload :MatchMACAddress, File.expand_path(\"../action/match_mac_address\", __FILE__)\n      autoload :MessageAlreadyRunning, File.expand_path(\"../action/message_already_running\", __FILE__)\n      autoload :MessageNotCreated, File.expand_path(\"../action/message_not_created\", __FILE__)\n      autoload :MessageNotRunning, File.expand_path(\"../action/message_not_running\", __FILE__)\n      autoload :MessageWillNotDestroy, File.expand_path(\"../action/message_will_not_destroy\", __FILE__)\n      autoload :Network, File.expand_path(\"../action/network\", __FILE__)\n      autoload :NetworkFixIPv6, File.expand_path(\"../action/network_fix_ipv6\", __FILE__)\n      autoload :Package, File.expand_path(\"../action/package\", __FILE__)\n      autoload :PackageSetupFiles, File.expand_path(\"../action/package_setup_files\", __FILE__)\n      autoload :PackageSetupFolders, File.expand_path(\"../action/package_setup_folders\", __FILE__)\n      autoload :PackageVagrantfile, File.expand_path(\"../action/package_vagrantfile\", __FILE__)\n      autoload :PrepareCloneSnapshot, File.expand_path(\"../action/prepare_clone_snapshot\", __FILE__)\n      autoload :PrepareNFSSettings, File.expand_path(\"../action/prepare_nfs_settings\", __FILE__)\n      autoload :PrepareNFSValidIds, File.expand_path(\"../action/prepare_nfs_valid_ids\", __FILE__)\n      autoload :PrepareForwardedPortCollisionParams, File.expand_path(\"../action/prepare_forwarded_port_collision_params\", __FILE__)\n      autoload :Resume, File.expand_path(\"../action/resume\", __FILE__)\n      autoload :SaneDefaults, File.expand_path(\"../action/sane_defaults\", __FILE__)\n      autoload :SetDefaultNICType, File.expand_path(\"../action/set_default_nic_type\", __FILE__)\n      autoload :SetName, File.expand_path(\"../action/set_name\", __FILE__)\n      autoload :SnapshotDelete, File.expand_path(\"../action/snapshot_delete\", __FILE__)\n      autoload :SnapshotRestore, File.expand_path(\"../action/snapshot_restore\", __FILE__)\n      autoload :SnapshotSave, File.expand_path(\"../action/snapshot_save\", __FILE__)\n      autoload :Suspend, File.expand_path(\"../action/suspend\", __FILE__)\n\n      # @deprecated use {PackageSetupFiles} instead\n      autoload :SetupPackageFiles, File.expand_path(\"../action/setup_package_files\", __FILE__)\n\n      # Include the built-in modules so that we can use them as top-level\n      # things.\n      include Vagrant::Action::Builtin\n\n      # This action boots the VM, assuming the VM is in a state that requires\n      # a bootup (i.e. not saved).\n      def self.action_boot\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckAccessible\n          b.use CleanMachineFolder\n          b.use SetName\n          b.use ClearForwardedPorts\n          b.use Provision\n          b.use EnvSet, port_collision_repair: true\n          b.use PrepareForwardedPortCollisionParams\n          b.use HandleForwardedPortCollisions\n          b.use PrepareNFSValidIds\n          b.use SyncedFolderCleanup\n          b.use SyncedFolders\n          b.use PrepareNFSSettings\n          b.use SetDefaultNICType\n          b.use ClearNetworkInterfaces\n          b.use Network\n          b.use NetworkFixIPv6\n          b.use ForwardPorts\n          b.use SetHostname\n          b.use SaneDefaults\n          b.use CloudInitSetup\n          b.use CleanupDisks\n          b.use Disk\n          b.use Customize, \"pre-boot\"\n          b.use Boot\n          b.use Customize, \"post-boot\"\n          b.use WaitForCommunicator, [:starting, :running, :paused]\n          b.use CloudInitWait\n          b.use Customize, \"post-comm\"\n          b.use CheckGuestAdditions\n        end\n      end\n\n      # This is the action that is primarily responsible for completely\n      # freeing the resources of the underlying virtual machine.\n      def self.action_destroy\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n          b.use Call, Created do |env1, b2|\n            if !env1[:result]\n              b2.use MessageNotCreated\n              next\n            end\n\n            b2.use Call, DestroyConfirm do |env2, b3|\n              if env2[:result]\n                b3.use ConfigValidate\n                b3.use ProvisionerCleanup, :before\n                b3.use CheckAccessible\n                b3.use EnvSet, force_halt: env2[:force_halt]\n                b3.use action_halt\n                b3.use Destroy\n                b3.use CleanMachineFolder\n                b3.use DestroyUnusedNetworkInterfaces\n                b3.use PrepareNFSValidIds\n                b3.use SyncedFolderCleanup\n              else\n                b3.use MessageWillNotDestroy\n              end\n            end\n          end\n        end\n      end\n\n      # This is the action that is primarily responsible for halting\n      # the virtual machine, gracefully or by force.\n      def self.action_halt\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n          b.use Call, Created do |env, b2|\n            if env[:result]\n              b2.use CheckAccessible\n              b2.use DiscardState\n\n              b2.use Call, IsPaused do |env2, b3|\n                next if !env2[:result]\n                b3.use Resume\n              end\n\n              b2.use Call, GracefulHalt, :poweroff, :running do |env2, b3|\n                if !env2[:result]\n                  b3.use ForcedHalt\n                end\n              end\n            else\n              b2.use MessageNotCreated\n            end\n          end\n        end\n      end\n\n      # This action packages the virtual machine into a single box file.\n      def self.action_package\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n          b.use Call, Created do |env1, b2|\n            if !env1[:result]\n              b2.use MessageNotCreated\n              next\n            end\n\n            b2.use PackageSetupFolders\n            b2.use PackageSetupFiles\n            b2.use CheckAccessible\n            b2.use action_halt\n            b2.use ClearForwardedPorts\n            b2.use PrepareNFSValidIds\n            b2.use SyncedFolderCleanup\n            b2.use Package\n            b2.use Export\n            b2.use PackageVagrantfile\n          end\n        end\n      end\n\n      # This action just runs the provisioners on the machine.\n      def self.action_provision\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n          b.use ConfigValidate\n          b.use Call, Created do |env1, b2|\n            if !env1[:result]\n              b2.use MessageNotCreated\n              next\n            end\n\n            b2.use Call, IsRunning do |env2, b3|\n              if !env2[:result]\n                b3.use MessageNotRunning\n                next\n              end\n\n              b3.use CheckAccessible\n              b3.use Provision\n            end\n          end\n        end\n      end\n\n      # This action is responsible for reloading the machine, which\n      # brings it down, sucks in new configuration, and brings the\n      # machine back up with the new configuration.\n      def self.action_reload\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n          b.use Call, Created do |env1, b2|\n            if !env1[:result]\n              b2.use MessageNotCreated\n              next\n            end\n\n            b2.use ConfigValidate\n            b2.use action_halt\n            b2.use action_start\n          end\n        end\n      end\n\n      # This is the action that is primarily responsible for resuming\n      # suspended machines.\n      def self.action_resume\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n          b.use Call, Created do |env, b2|\n            if env[:result]\n              b2.use CheckAccessible\n              b2.use EnvSet, port_collision_repair: false\n              b2.use PrepareForwardedPortCollisionParams\n              b2.use HandleForwardedPortCollisions\n              b2.use Resume\n              b2.use Provision\n              b2.use WaitForCommunicator, [:restoring, :running]\n            else\n              b2.use MessageNotCreated\n            end\n          end\n        end\n      end\n\n      def self.action_snapshot_delete\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n          b.use Call, Created do |env, b2|\n            if env[:result]\n              b2.use SnapshotDelete\n            else\n              b2.use MessageNotCreated\n            end\n          end\n        end\n      end\n\n      # This is the action that is primarily responsible for restoring a snapshot\n      def self.action_snapshot_restore\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n          b.use Call, Created do |env, b2|\n            if !env[:result]\n              raise Vagrant::Errors::VMNotCreatedError\n            end\n\n            b2.use CheckAccessible\n            b2.use EnvSet, force_halt: true\n            b2.use action_halt\n            b2.use SnapshotRestore\n\n            b2.use Call, IsEnvSet, :snapshot_delete do |env2, b3|\n              if env2[:result]\n                b3.use action_snapshot_delete\n              end\n            end\n\n            b2.use Call, IsEnvSet, :snapshot_start do |env2, b3|\n              if env2[:result]\n                b3.use action_start\n              end\n            end\n          end\n        end\n      end\n\n      # This is the action that is primarily responsible for saving a snapshot\n      def self.action_snapshot_save\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n          b.use Call, Created do |env, b2|\n            if env[:result]\n              b2.use SnapshotSave\n            else\n              b2.use MessageNotCreated\n            end\n          end\n        end\n      end\n\n      # This is the action that will exec into an SSH shell.\n      def self.action_ssh\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n          b.use CheckCreated\n          b.use CheckAccessible\n          b.use CheckRunning\n          b.use SSHExec\n        end\n      end\n\n      # This is the action that will run a single SSH command.\n      def self.action_ssh_run\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n          b.use CheckCreated\n          b.use CheckAccessible\n          b.use CheckRunning\n          b.use SSHRun\n        end\n      end\n\n      # This action starts a VM, assuming it is already imported and exists.\n      # A precondition of this action is that the VM exists.\n      def self.action_start\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n          b.use ConfigValidate\n          b.use BoxCheckOutdated\n          b.use Call, IsRunning do |env, b2|\n            # If the VM is running, run the necessary provisioners\n            if env[:result]\n              b2.use action_provision\n              next\n            end\n\n            b2.use Call, IsSaved do |env2, b3|\n              if env2[:result]\n                # The VM is saved, so just resume it\n                b3.use action_resume\n                next\n              end\n\n              b3.use Call, IsPaused do |env3, b4|\n                if env3[:result]\n                  b4.use Resume\n                  next\n                end\n\n                # The VM is not saved, so we must have to boot it up\n                # like normal. Boot!\n                b4.use action_boot\n              end\n            end\n          end\n        end\n      end\n\n      # This is the action that is primarily responsible for suspending\n      # the virtual machine.\n      def self.action_suspend\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n          b.use Call, Created do |env, b2|\n            if env[:result]\n              b2.use CheckAccessible\n              b2.use Suspend\n            else\n              b2.use MessageNotCreated\n            end\n          end\n        end\n      end\n\n      # This is the action that is called to sync folders to a running\n      # machine without a reboot.\n      def self.action_sync_folders\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use PrepareNFSValidIds\n          b.use SyncedFolders\n          b.use PrepareNFSSettings\n        end\n      end\n\n      # This action brings the machine up from nothing, including importing\n      # the box, configuring metadata, and booting.\n      def self.action_up\n        Vagrant::Action::Builder.new.tap do |b|\n          b.use CheckVirtualbox\n\n          # Handle box_url downloading early so that if the Vagrantfile\n          # references any files in the box or something it all just\n          # works fine.\n          b.use Call, Created do |env, b2|\n            if !env[:result]\n              b2.use HandleBox\n            end\n          end\n\n          b.use ConfigValidate\n          b.use Call, Created do |env, b2|\n            # If the VM is NOT created yet, then do the setup steps\n            if !env[:result]\n              b2.use CheckAccessible\n              b2.use Customize, \"pre-import\"\n\n              if env[:machine].provider_config.linked_clone\n                # We are cloning from the box\n                b2.use ImportMaster\n              end\n\n              b2.use PrepareClone\n              b2.use PrepareCloneSnapshot\n              b2.use Import\n              b2.use DiscardState\n              b2.use MatchMACAddress\n            end\n          end\n\n          b.use action_start\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/cap/cleanup_disks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"vagrant/util/experimental\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Cap\n      module CleanupDisks\n        LOGGER = Log4r::Logger.new(\"vagrant::plugins::virtualbox::cleanup_disks\")\n\n        # @param [Vagrant::Machine] machine\n        # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks\n        # @param [Hash] disk_meta_file - A hash of all the previously defined disks from the last configure_disk action\n        def self.cleanup_disks(machine, defined_disks, disk_meta_file)\n          return if disk_meta_file.values.flatten.empty?\n\n          handle_cleanup_disk(machine, defined_disks, disk_meta_file[\"disk\"])\n          handle_cleanup_dvd(machine, defined_disks, disk_meta_file[\"dvd\"])\n          # TODO: Floppy disks\n        end\n\n        protected\n\n        # @param [Vagrant::Machine] machine\n        # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks\n        # @param [Array<Hash>] disk_meta - An array of all the previously defined disks from the last configure_disk action\n        def self.handle_cleanup_disk(machine, defined_disks, disk_meta)\n          raise TypeError, \"Expected `Array` but received `#{disk_meta.class}`\" if !disk_meta.is_a?(Array)\n          storage_controllers = machine.provider.driver.read_storage_controllers\n\n          primary = storage_controllers.get_primary_attachment\n          primary_uuid = primary[:uuid]\n\n          disk_meta.each do |d|\n            dsk = defined_disks.select { |dk| dk.name == d[\"name\"] }\n            if !dsk.empty? || d[\"uuid\"] == primary_uuid\n              next\n            else\n              LOGGER.warn(\"Found disk not in Vagrantfile config: '#{d[\"name\"]}'. Removing disk from guest #{machine.name}\")\n              machine.ui.warn(I18n.t(\"vagrant.cap.cleanup_disks.disk_cleanup\", name: d[\"name\"]), prefix: true)\n\n              controller = storage_controllers.get_controller(d[\"controller\"])\n              attachment = controller.get_attachment(uuid: d[\"uuid\"])\n\n              if !attachment\n                LOGGER.warn(\"Disk '#{d[\"name\"]}' not attached to guest, but still exists.\")\n              else\n                machine.provider.driver.remove_disk(controller.name, attachment[:port], attachment[:device])\n              end\n\n              machine.provider.driver.close_medium(d[\"uuid\"])\n            end\n          end\n        end\n\n        # @param [Vagrant::Machine] machine\n        # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_dvds\n        # @param [Array<Hash>] dvd_meta - An array of all the previously defined dvds from the last configure_disk action\n        def self.handle_cleanup_dvd(machine, defined_dvds, dvd_meta)\n          raise TypeError, \"Expected `Array` but received `#{dvd_meta.class}`\" if !dvd_meta.is_a?(Array)\n          dvd_meta.each do |d|\n            dsk = defined_dvds.select { |dk| dk.name == d[\"name\"] }\n            if !dsk.empty?\n              next\n            else\n              LOGGER.warn(\"Found dvd not in Vagrantfile config: '#{d[\"name\"]}'. Removing dvd from guest #{machine.name}\")\n              machine.ui.warn(\"DVD '#{d[\"name\"]}' no longer exists in Vagrant config. Removing medium from guest...\", prefix: true)\n\n              storage_controllers = machine.provider.driver.read_storage_controllers\n              controller = storage_controllers.get_controller(d[\"controller\"])\n              attachment = controller.get_attachment(uuid: d[\"uuid\"])\n\n              if !attachment\n                LOGGER.warn(\"DVD '#{d[\"name\"]}' not attached to guest, but still exists.\")\n              else\n                machine.provider.driver.remove_disk(controller.name, attachment[:port], attachment[:device])\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/cap/configure_disks.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire \"fileutils\"\nrequire \"vagrant/util/numeric\"\nrequire \"vagrant/util/experimental\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Cap\n      module ConfigureDisks\n        LOGGER = Log4r::Logger.new(\"vagrant::plugins::virtualbox::configure_disks\")\n\n        # @param [Vagrant::Machine] machine\n        # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks\n        # @return [Hash] configured_disks - A hash of all the current configured disks\n        def self.configure_disks(machine, defined_disks)\n          return {} if defined_disks.empty?\n\n          machine.ui.info(I18n.t(\"vagrant.cap.configure_disks.start\"))\n\n          storage_controllers = machine.provider.driver.read_storage_controllers\n\n          # Check to determine which controller we should attach disks to.\n          # If there is only one storage controller attached to the VM, use\n          # it. If there are multiple controllers (e.g. IDE/SATA), attach DVDs\n          # to the IDE controller and disks to the SATA controller.\n          if storage_controllers.size == 1\n            controller = storage_controllers.first\n\n            # The only way you can define up to the controller limit is if\n            # exactly one disk is a primary disk, otherwise we need to reserve\n            # a slot for the primary\n            if (defined_disks.any? { |d| d.primary } && defined_disks.size > controller.limit) ||\n               defined_disks.size > controller.limit - 1\n              raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit,\n                limit: controller.limit,\n                name: controller.name\n            else\n              disk_controller = controller\n              dvd_controller = controller\n            end\n          else\n            disks_defined = defined_disks.select { |d| d.type == :disk }\n            if disks_defined.any?\n              disk_controller = storage_controllers.get_primary_controller\n\n              if (disks_defined.any? { |d| d.primary } && disks_defined.size > disk_controller.limit) ||\n                 disks_defined.size > disk_controller.limit - 1\n                raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit,\n                  limit: disk_controller.limit,\n                  name: disk_controller.name\n              end\n            end\n\n            dvds_defined = defined_disks.select { |d| d.type == :dvd }\n            if dvds_defined.any?\n              dvd_controller = storage_controllers.get_dvd_controller\n\n              if dvds_defined.size > dvd_controller.limit\n                raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit,\n                  limit: dvd_controller.limit,\n                  name: dvd_controller.name\n              end\n            end\n          end\n\n          configured_disks = { disk: [], floppy: [], dvd: [] }\n\n          defined_disks.each do |disk|\n            if disk.type == :disk\n              disk_data = handle_configure_disk(machine, disk, disk_controller.name)\n              configured_disks[:disk] << disk_data unless disk_data.empty?\n            elsif disk.type == :floppy\n              # TODO: Write me\n              machine.ui.info(I18n.t(\"vagrant.cap.configure_disks.floppy_not_supported\", name: disk.name))\n            elsif disk.type == :dvd\n              dvd_data = handle_configure_dvd(machine, disk, dvd_controller.name)\n              configured_disks[:dvd] << dvd_data unless dvd_data.empty?\n            end\n          end\n\n          configured_disks\n        end\n\n        protected\n\n        # @param [Vagrant::Machine] machine - the current machine\n        # @param [Config::Disk] disk - the current disk to configure\n        # @param [Array] all_disks - A list of all currently defined disks in VirtualBox\n        # @return [Hash] current_disk - Returns the current disk. Returns nil if it doesn't exist\n        def self.get_current_disk(machine, disk, all_disks)\n          current_disk = nil\n          if disk.primary\n            storage_controllers = machine.provider.driver.read_storage_controllers\n            current_disk = storage_controllers.get_primary_attachment\n          else\n            current_disk = all_disks.detect { |d| d[:disk_name] == disk.name }\n          end\n\n          current_disk\n        end\n\n        # Handles all disk configs of type `:disk`\n        #\n        # @param [Vagrant::Machine] machine - the current machine\n        # @param [Config::Disk] disk - the current disk to configure\n        # @param [String] controller_name - the name of the storage controller to use\n        # @return [Hash] - disk_metadata\n        def self.handle_configure_disk(machine, disk, controller_name)\n          storage_controllers = machine.provider.driver.read_storage_controllers\n          controller = storage_controllers.get_controller(controller_name)\n          all_disks = controller.attachments\n\n          disk_metadata = {}\n\n          # Grab the existing configured disk attached to guest, if it exists\n          current_disk = get_current_disk(machine, disk, all_disks)\n\n          if !current_disk\n            # Look for an existing disk that's not been attached but exists\n            # inside VirtualBox\n            #\n            # NOTE: This assumes that if that disk exists and was created by\n            # Vagrant, it exists in the same location as the primary disk file.\n            # Otherwise Vagrant has no good way to determining if the disk was\n            # associated with the guest, since disk names are not unique\n            # globally to VirtualBox.\n            primary = storage_controllers.get_primary_attachment\n            existing_disk = machine.provider.driver.list_hdds.detect do |d|\n              File.dirname(d[\"Location\"]) == File.dirname(primary[:location]) &&\n                d[\"Disk Name\"] == disk.name\n            end\n\n            if !existing_disk\n              # create new disk and attach to guest\n              disk_metadata = create_disk(machine, disk, controller)\n            else\n              # Disk has been created but failed to be attached to guest, so\n              # this method recovers that disk from previous failure\n              # and attaches it onto the guest\n              LOGGER.warn(\"Disk '#{disk.name}' is not connected to guest '#{machine.name}', Vagrant will attempt to connect disk to guest\")\n              dsk_info = get_next_port(machine, controller)\n              machine.provider.driver.attach_disk(controller.name,\n                                                  dsk_info[:port],\n                                                  dsk_info[:device],\n                                                  \"hdd\",\n                                                  existing_disk[\"Location\"])\n\n              disk_metadata[:uuid] = existing_disk[\"UUID\"]\n              disk_metadata[:port] = dsk_info[:port]\n              disk_metadata[:device] = dsk_info[:device]\n              disk_metadata[:name] = disk.name\n              disk_metadata[:controller] = controller.name\n            end\n          elsif compare_disk_size(machine, disk, current_disk)\n            disk_metadata = resize_disk(machine, disk, current_disk, controller)\n          else\n            LOGGER.info(\"No further configuration required for disk '#{disk.name}'\")\n            disk_metadata[:uuid] = current_disk[:uuid]\n            disk_metadata[:port] = current_disk[:port]\n            disk_metadata[:device] = current_disk[:device]\n\n            disk_metadata[:name] = disk.name\n            disk_metadata[:controller] = controller.name\n          end\n\n          disk_metadata\n        end\n\n        # Handles all disk configs of type `:dvd`\n        #\n        # @param [Vagrant::Machine] machine - the current machine\n        # @param [Config::Disk] dvd - the current disk to configure\n        # @param [String] controller_name - the name of the storage controller to use\n        # @return [Hash] - dvd_metadata\n        def self.handle_configure_dvd(machine, dvd, controller_name)\n          storage_controllers = machine.provider.driver.read_storage_controllers\n          controller = storage_controllers.get_controller(controller_name)\n\n          dvd_metadata = {}\n\n          dvd_location = File.expand_path(dvd.file)\n          dvd_attached = controller.attachments.detect { |a| a[:location] == dvd_location }\n\n          if dvd_attached\n            LOGGER.info(\"No further configuration required for dvd '#{dvd.name}'\")\n            dvd_metadata[:name] = dvd.name\n            dvd_metadata[:port] = dvd_attached[:port]\n            dvd_metadata[:device] = dvd_attached[:device]\n            dvd_metadata[:uuid] = dvd_attached[:uuid]\n            dvd_metadata[:controller] = controller.name\n          else\n            LOGGER.warn(\"DVD '#{dvd.name}' is not connected to guest '#{machine.name}', Vagrant will attempt to connect dvd to guest\")\n            dsk_info = get_next_port(machine, controller)\n            machine.provider.driver.attach_disk(controller.name,\n                                                dsk_info[:port],\n                                                dsk_info[:device],\n                                                \"dvddrive\",\n                                                dvd.file)\n\n            # Refresh the controller information\n            storage_controllers = machine.provider.driver.read_storage_controllers\n            controller = storage_controllers.get_controller(controller_name)\n\n            attachment = controller.attachments.detect { |a| a[:port] == dsk_info[:port] &&\n                                                             a[:device] == dsk_info[:device] }\n\n            dvd_metadata[:name] = dvd.name\n            dvd_metadata[:port] = dsk_info[:port]\n            dvd_metadata[:device] = dsk_info[:device]\n            dvd_metadata[:uuid] = attachment[:uuid]\n            dvd_metadata[:controller] = controller.name\n          end\n\n          dvd_metadata\n        end\n\n        # Check to see if current disk is configured based on defined_disks\n        #\n        # @param [Kernel_V2::VagrantConfigDisk] disk_config\n        # @param [Hash] defined_disk\n        # @return [Boolean]\n        def self.compare_disk_size(machine, disk_config, defined_disk)\n          requested_disk_size = Vagrant::Util::Numeric.bytes_to_megabytes(disk_config.size)\n          defined_disk_size = defined_disk[:capacity].split(\" \").first.to_f\n\n          if defined_disk_size > requested_disk_size\n            machine.ui.warn(I18n.t(\"vagrant.cap.configure_disks.shrink_size_not_supported\", name: disk_config.name))\n            return false\n          elsif defined_disk_size < requested_disk_size\n            return true\n          else\n            return false\n          end\n        end\n\n        # Creates and attaches a disk to a machine\n        #\n        # @param [Vagrant::Machine] machine\n        # @param [Kernel_V2::VagrantConfigDisk] disk_config\n        # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller -\n        # the storage controller to use\n        def self.create_disk(machine, disk_config, controller)\n          machine.ui.detail(I18n.t(\"vagrant.cap.configure_disks.create_disk\", name: disk_config.name))\n          # NOTE: At the moment, there are no provider specific configs for VirtualBox\n          # but we grab it anyway for future use.\n          disk_provider_config = disk_config.provider_config[:virtualbox] if disk_config.provider_config\n\n          guest_info = machine.provider.driver.show_vm_info\n          guest_folder = File.dirname(guest_info[\"CfgFile\"])\n\n          disk_ext = disk_config.disk_ext\n          disk_file = File.join(guest_folder, disk_config.name) + \".#{disk_ext}\"\n\n          LOGGER.info(\"Attempting to create a new disk file '#{disk_file}' of size '#{disk_config.size}' bytes\")\n\n          disk_var = machine.provider.driver.create_disk(disk_file, disk_config.size, disk_ext.upcase)\n          dsk_controller_info = get_next_port(machine, controller)\n          machine.provider.driver.attach_disk(controller.name,\n                                              dsk_controller_info[:port],\n                                              dsk_controller_info[:device],\n                                              \"hdd\",\n                                              disk_file)\n\n          disk_metadata = { uuid: disk_var.split(\":\").last.strip, name: disk_config.name,\n                            controller: controller.name, port: dsk_controller_info[:port],\n                            device: dsk_controller_info[:device] }\n\n          disk_metadata\n        end\n\n        # Finds the next available port\n        #\n        # SATA Controller-ImageUUID-0-0 (sub out ImageUUID)\n        # - Controller: SATA Controller\n        # - Port: 0\n        # - Device: 0\n        #\n        # Note: Virtualbox returns the string above with the port and device info\n        #  disk_info = key.split(\"-\")\n        #  port = disk_info[2]\n        #  device = disk_info[3]\n        #\n        # @param [Vagrant::Machine] machine\n        # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller -\n        # the storage controller to use\n        # @return [Hash] dsk_info - The next available port and device on a given controller\n        def self.get_next_port(machine, controller)\n          dsk_info = {}\n\n          if controller.devices_per_port == 1\n            used_ports = controller.attachments.map { |a| a[:port].to_i }\n            next_available_port = ((0..(controller.maxportcount - 1)).to_a - used_ports).first\n\n            dsk_info[:port] = next_available_port.to_s\n            dsk_info[:device] = \"0\"\n          elsif controller.devices_per_port == 2\n            # IDE Controllers have primary/secondary devices, so find the first port\n            # with an empty device\n            (0..(controller.maxportcount - 1)).each do |port|\n              # Skip this port if it's full\n              port_attachments = controller.attachments.select { |a| a[:port] == port.to_s }\n              next if port_attachments.count == controller.devices_per_port\n\n              dsk_info[:port] = port.to_s\n\n              # Check for a free device\n              if port_attachments.any? { |a| a[:device] == \"0\" }\n                dsk_info[:device] = \"1\"\n              else\n                dsk_info[:device] = \"0\"\n              end\n\n              break if dsk_info[:port]\n            end\n          else\n            raise Vagrant::Errors::VirtualBoxDisksUnsupportedController, controller_name: controller.name\n          end\n\n          if dsk_info[:port].to_s.empty?\n            # This likely only occurs if additional disks have been added outside of Vagrant configuration\n            LOGGER.warn(\"There is no more available space to attach disks to for the controller '#{controller}'. Clear up some space on the controller '#{controller}' to attach new disks.\")\n            raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit,\n              limit: controller.limit,\n              name: controller.name\n          end\n\n          dsk_info\n        end\n\n        # @param [Vagrant::Machine] machine\n        # @param [Config::Disk] disk_config - the current disk to configure\n        # @param [Hash] defined_disk - current disk as represented by VirtualBox\n        # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller -\n        # the storage controller to use\n        # @return [Hash] - disk_metadata\n        def self.resize_disk(machine, disk_config, defined_disk, controller)\n          machine.ui.detail(I18n.t(\"vagrant.cap.configure_disks.resize_disk\", name: disk_config.name), prefix: true)\n\n          if defined_disk[:storage_format] == \"VMDK\"\n            LOGGER.warn(\"Disk type VMDK cannot be resized in VirtualBox. Vagrant will convert disk to VDI format to resize first, and then convert resized disk back to VMDK format\")\n\n            # original disk information in case anything goes wrong during clone/resize\n            original_disk = defined_disk\n            backup_disk_location = \"#{original_disk[:location]}.backup\"\n\n            # clone disk to vdi formatted disk\n            vdi_disk_file = machine.provider.driver.vmdk_to_vdi(defined_disk[:location])\n            # resize vdi\n            machine.provider.driver.resize_disk(vdi_disk_file, disk_config.size.to_i)\n\n            begin\n              # Danger Zone\n              # remove and close original volume\n              machine.provider.driver.remove_disk(controller.name, defined_disk[:port], defined_disk[:device])\n              # Create a backup of the original disk if something goes wrong\n              LOGGER.warn(\"Making a backup of the original disk at #{defined_disk[:location]}\")\n              FileUtils.mv(defined_disk[:location], backup_disk_location)\n\n              # we have to close here, otherwise we can't re-clone after\n              # resizing the vdi disk\n              machine.provider.driver.close_medium(defined_disk[:uuid])\n\n              # clone back to original vmdk format and attach resized disk\n              vmdk_disk_file = machine.provider.driver.vdi_to_vmdk(vdi_disk_file)\n              machine.provider.driver.attach_disk(controller.name,\n                                                  defined_disk[:port],\n                                                  defined_disk[:device],\n                                                  \"hdd\",\n                                                  vmdk_disk_file)\n            rescue ScriptError, SignalException, StandardError\n              LOGGER.warn(\"Vagrant encountered an error while trying to resize a disk. Vagrant will now attempt to reattach and preserve the original disk...\")\n              machine.ui.error(I18n.t(\"vagrant.cap.configure_disks.recovery_from_resize\",\n                                      location: original_disk[:location],\n                                      name: machine.name))\n              recover_from_resize(machine, defined_disk, backup_disk_location, original_disk, vdi_disk_file, controller)\n              raise\n            ensure\n              # Remove backup disk file if all goes well\n              FileUtils.remove(backup_disk_location, force: true)\n            end\n\n            # Remove cloned resized volume format\n            machine.provider.driver.close_medium(vdi_disk_file)\n\n            # Get new updated disk UUID for vagrant disk_meta file\n            storage_controllers = machine.provider.driver.read_storage_controllers\n            updated_controller = storage_controllers.get_controller(controller.name)\n            new_disk_info = updated_controller.attachments.detect { |h| h[:location] == defined_disk[:location] }\n\n            defined_disk = new_disk_info\n          else\n            machine.provider.driver.resize_disk(defined_disk[:location], disk_config.size.to_i)\n          end\n\n          disk_metadata = { uuid: defined_disk[:uuid], name: disk_config.name, controller: controller.name,\n                            port: defined_disk[:port], device: defined_disk[:device] }\n\n          disk_metadata\n        end\n\n        # Recovery method for when an exception occurs during the process of resizing disks\n        #\n        # It attempts to move back the backup disk into place, and reattach it to the guest before\n        # raising the original error\n        #\n        # @param [Vagrant::Machine] machine\n        # @param [Hash] disk_info - The disk device and port number to attach back to\n        # @param [String] backup_disk_location - The place on disk where vagrant made a backup of the original disk being resized\n        # @param [Hash] original_disk - The disk information from VirtualBox\n        # @param [String] vdi_disk_file - The place on disk where vagrant made a clone of the original disk being resized\n        # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller - the storage controller to use\n        def self.recover_from_resize(machine, disk_info, backup_disk_location, original_disk, vdi_disk_file, controller)\n          begin\n            # move backup to original name\n            FileUtils.mv(backup_disk_location, original_disk[:location], force: true)\n            # Attach disk\n            machine.provider.driver.attach_disk(controller.name,\n                                                disk_info[:port],\n                                                disk_info[:device],\n                                                \"hdd\",\n                                                original_disk[:location])\n\n            # Remove cloned disk if still hanging around\n            if vdi_disk_file\n              machine.provider.driver.close_medium(vdi_disk_file)\n            end\n\n            # We recovered!\n            machine.ui.warn(I18n.t(\"vagrant.cap.configure_disks.recovery_attached_disks\"))\n          rescue => e\n            LOGGER.error(\"Vagrant encountered an error while trying to recover. It will now show the original error and continue...\")\n            LOGGER.error(e)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/cap/mount_options.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../synced_folders/unix_mount_helpers\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Cap\n      module MountOptions\n        extend VagrantPlugins::SyncedFolder::UnixMountHelpers\n\n        VB_MOUNT_TYPE = \"vboxsf\".freeze\n\n        # Returns mount options for a virtual box synced folder\n        #\n        # @param [Machine] machine\n        # @param [String] name of mount\n        # @param [String] path of mount on guest\n        # @param [Hash] hash of mount options \n        def self.mount_options(machine, name, guest_path, options)\n          mount_options = options.fetch(:mount_options, [])\n          detected_ids = detect_owner_group_ids(machine, guest_path, mount_options, options)\n          mount_uid = detected_ids[:uid]\n          mount_gid = detected_ids[:gid]\n\n          mount_options << \"uid=#{mount_uid}\"\n          mount_options << \"gid=#{mount_gid}\"\n          mount_options << \"_netdev\"\n          mount_options = mount_options.join(',')\n          return mount_options, mount_uid, mount_gid\n        end\n\n        def self.mount_type(machine)\n          return VB_MOUNT_TYPE\n        end\n\n        def self.mount_name(machine, name, data)\n          name.gsub(/[\\s\\/\\\\]/,'_').sub(/^_/, '')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/cap/public_address.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Cap\n      module PublicAddress\n        def self.public_address(machine)\n          return nil if machine.state.id != :running\n\n          ssh_info = machine.ssh_info\n          return nil if !ssh_info\n          ssh_info[:host]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/cap/validate_disk_ext.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Cap\n      module ValidateDiskExt\n        LOGGER = Log4r::Logger.new(\"vagrant::plugins::virtualbox::validate_disk_ext\")\n\n        # The default set of disk formats that VirtualBox supports\n        DEFAULT_DISK_EXT_LIST = [\"vdi\", \"vmdk\", \"vhd\"].map(&:freeze).freeze\n        DEFAULT_DISK_EXT = \"vdi\".freeze\n\n        # @param [Vagrant::Machine] machine\n        # @param [String] disk_ext\n        # @return [Bool]\n        def self.validate_disk_ext(machine, disk_ext)\n          DEFAULT_DISK_EXT_LIST.include?(disk_ext)\n        end\n\n        # @param [Vagrant::Machine] machine\n        # @return [Array]\n        def self.default_disk_exts(machine)\n          DEFAULT_DISK_EXT_LIST\n        end\n\n        # @param [Vagrant::Machine] machine\n        # @return [String]\n        def self.set_default_disk_ext(machine)\n          DEFAULT_DISK_EXT\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/cap.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Cap\n      # Reads the forwarded ports that currently exist on the machine\n      # itself. This raises an exception if the machine isn't running.\n      #\n      # This also may not match up with configured forwarded ports, because\n      # Vagrant auto port collision fixing may have taken place.\n      #\n      # @return [Hash<Integer, Integer>] Host => Guest port mappings.\n      def self.forwarded_ports(machine)\n        return nil if machine.state.id != :running\n\n        {}.tap do |result|\n          machine.provider.driver.read_forwarded_ports.each do |_, _, h, g|\n            result[h] = g\n          end\n        end\n      end\n\n      # Reads the network interface card MAC addresses and returns them.\n      #\n      # @return [Hash<String, String>] Adapter => MAC address\n      def self.nic_mac_addresses(machine)\n        machine.provider.driver.read_mac_addresses\n      end\n\n      # Returns a list of the snapshots that are taken on this machine.\n      #\n      # @return [Array<String>] Snapshot Name\n      def self.snapshot_list(machine)\n        return [] if machine.id.nil?\n        machine.provider.driver.list_snapshots(machine.id)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    class Config < Vagrant.plugin(\"2\", :config)\n      # Vagrant by default will make \"smart\" decisions to enable/disable\n      # the NAT DNS proxy. If this is set to `true`, then the DNS proxy\n      # will not be enabled, and it is up to the end user to do it.\n      #\n      # @return [Boolean]\n      attr_accessor :auto_nat_dns_proxy\n\n      # If true, will check if guest additions are installed and up to\n      # date. By default, this is true.\n      #\n      # @return [Boolean]\n      attr_accessor :check_guest_additions\n\n      # An array of customizations to make on the VM prior to booting it.\n      #\n      # @return [Array]\n      attr_reader :customizations\n\n      # Set the default type of NIC hardware to be used for network\n      # devices. By default this is `nil` and VirtualBox's default\n      # will be used.\n      #\n      # @return [String]\n      attr_accessor :default_nic_type\n\n      # If true, unused network interfaces will automatically be deleted.\n      # This defaults to false because the detection does not work across\n      # multiple users, and because on Windows this operation requires\n      # administrative privileges.\n      #\n      # @return [Boolean]\n      attr_accessor :destroy_unused_network_interfaces\n\n      # If set to `true`, then VirtualBox will be launched with a GUI.\n      #\n      # @return [Boolean]\n      attr_accessor :gui\n\n      # If set to `true`, then a linked clone is created from a master\n      # VM generated from the specified box.\n      #\n      # @return [Boolean]\n      attr_accessor :linked_clone\n\n      # The snapshot to base the linked clone from. If this isn't set\n      # a snapshot will be made with the name of \"base\" which will be used.\n      #\n      # If this is set, then the snapshot must already exist.\n      #\n      # @return [String]\n      attr_accessor :linked_clone_snapshot\n\n      # This should be set to the name of the machine in the VirtualBox\n      # GUI.\n      #\n      # @return [String]\n      attr_accessor :name\n\n      # Whether or not this VM has a functional vboxsf filesystem module.\n      # This defaults to true. If you set this to false, then the \"virtualbox\"\n      # synced folder type won't be valid.\n      #\n      # @return [Boolean]\n      attr_accessor :functional_vboxsf\n\n      # The defined network adapters.\n      #\n      # @return [Hash]\n      attr_reader :network_adapters\n\n      def initialize\n        @auto_nat_dns_proxy = UNSET_VALUE\n        @check_guest_additions = UNSET_VALUE\n        @customizations   = []\n        @default_nic_type = UNSET_VALUE\n        @destroy_unused_network_interfaces = UNSET_VALUE\n        @functional_vboxsf = UNSET_VALUE\n        @name             = UNSET_VALUE\n        @network_adapters = {}\n        @gui              = UNSET_VALUE\n        @linked_clone = UNSET_VALUE\n        @linked_clone_snapshot = UNSET_VALUE\n\n        # We require that network adapter 1 is a NAT device.\n        network_adapter(1, :nat)\n      end\n\n      # Customize the VM by calling `VBoxManage` with the given\n      # arguments.\n      #\n      # When called multiple times, the customizations will be applied\n      # in the order given.\n      #\n      # The special `:name` parameter in the command will be replaced with\n      # the unique ID or name of the virtual machine. This is useful for\n      # parameters to `modifyvm` and the like.\n      #\n      # @param [Array] command An array of arguments to pass to\n      # VBoxManage.\n      def customize(*command)\n        event   = command.first.is_a?(String) ? command.shift : \"pre-boot\"\n        command = command[0]\n        @customizations << [event, command]\n      end\n\n      # This defines a network adapter that will be added to the VirtualBox\n      # virtual machine in the given slot.\n      #\n      # @param [Integer] slot The slot for this network adapter.\n      # @param [Symbol] type The type of adapter.\n      def network_adapter(slot, type, **opts)\n        @network_adapters[slot] = [type, opts]\n      end\n\n      # Shortcut for setting memory size for the virtual machine.\n      # Calls #customize internally.\n      #\n      # @param size [Integer, String] the memory size in MB\n      def memory=(size)\n        customize(\"pre-boot\", [\"modifyvm\", :id, \"--memory\", size.to_s])\n      end\n\n      # Shortcut for setting CPU count for the virtual machine.\n      # Calls #customize internally.\n      #\n      # @param count [Integer, String] the count of CPUs\n      def cpus=(count)\n        customize(\"pre-boot\", [\"modifyvm\", :id, \"--cpus\", count.to_i])\n      end\n\n      def merge(other)\n        super.tap do |result|\n          c = customizations.dup\n          c += other.customizations\n          result.instance_variable_set(:@customizations, c)\n        end\n      end\n\n      # This is the hook that is called to finalize the object before it\n      # is put into use.\n      def finalize!\n        # Default is to auto the DNS proxy\n        @auto_nat_dns_proxy = true if @auto_nat_dns_proxy == UNSET_VALUE\n\n        if @check_guest_additions == UNSET_VALUE\n          @check_guest_additions = true\n        end\n\n        if @destroy_unused_network_interfaces == UNSET_VALUE\n          @destroy_unused_network_interfaces = false\n        end\n\n        if @functional_vboxsf == UNSET_VALUE\n          @functional_vboxsf = true\n        end\n\n        # Default is to not show a GUI\n        @gui = false if @gui == UNSET_VALUE\n\n        # Do not create linked clone by default\n        @linked_clone = false if @linked_clone == UNSET_VALUE\n        @linked_clone_snapshot = nil if @linked_clone_snapshot == UNSET_VALUE\n\n        # The default name is just nothing, and we default it\n        @name = nil if @name == UNSET_VALUE\n\n        @default_nic_type = nil if @default_nic_type == UNSET_VALUE\n      end\n\n      def validate(machine)\n        errors = _detected_errors\n\n        valid_events = [\"pre-import\", \"pre-boot\", \"post-boot\", \"post-comm\"]\n        @customizations.each do |event, _|\n          if !valid_events.include?(event)\n            errors << I18n.t(\n              \"vagrant.virtualbox.config.invalid_event\",\n              event: event.to_s,\n              valid_events: valid_events.join(\", \"))\n          end\n        end\n\n        @customizations.each do |event, command|\n          if event == \"pre-import\" && command.index(:id)\n            errors << I18n.t(\"vagrant.virtualbox.config.id_in_pre_import\")\n          end\n        end\n\n        # Verify that internal networks are only on private networks.\n        machine.config.vm.networks.each do |type, data|\n          if data[:virtualbox__intnet] && type != :private_network\n            errors << I18n.t(\"vagrant.virtualbox.config.intnet_on_bad_type\")\n            break\n          end\n        end\n\n        { \"VirtualBox Provider\" => errors }\n      end\n\n      def to_s\n        \"VirtualBox\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/base.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'log4r'\n\nrequire 'vagrant/util/busy'\nrequire 'vagrant/util/platform'\nrequire 'vagrant/util/retryable'\nrequire 'vagrant/util/subprocess'\nrequire 'vagrant/util/which'\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      # Base class for all VirtualBox drivers.\n      #\n      # This class provides useful tools for things such as executing\n      # VBoxManage and handling SIGINTs and so on.\n      class Base\n        # Include this so we can use `Subprocess` more easily.\n        include Vagrant::Util::Retryable\n\n        def initialize\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox::base\")\n\n          # This flag is used to keep track of interrupted state (SIGINT)\n          @interrupted = false\n\n          if Vagrant::Util::Platform.windows? || Vagrant::Util::Platform.cygwin?\n            @logger.debug(\"Windows, checking for VBoxManage on PATH first\")\n            @vboxmanage_path = Vagrant::Util::Which.which(\"VBoxManage\")\n\n            # On Windows, we use the VBOX_INSTALL_PATH environmental\n            # variable to find VBoxManage.\n            if !@vboxmanage_path && (ENV.key?(\"VBOX_INSTALL_PATH\") ||\n              ENV.key?(\"VBOX_MSI_INSTALL_PATH\"))\n              @logger.debug(\"Windows. Trying VBOX_INSTALL_PATH for VBoxManage\")\n\n              # Get the path.\n              path = ENV[\"VBOX_INSTALL_PATH\"] || ENV[\"VBOX_MSI_INSTALL_PATH\"]\n              @logger.debug(\"VBOX_INSTALL_PATH value: #{path}\")\n\n              # There can actually be multiple paths in here, so we need to\n              # split by the separator \";\" and see which is a good one.\n              path.split(\";\").each do |single|\n                # Make sure it ends with a \\\n                single += \"\\\\\" if !single.end_with?(\"\\\\\")\n\n                # If the executable exists, then set it as the main path\n                # and break out\n                vboxmanage = \"#{single}VBoxManage.exe\"\n                if File.file?(vboxmanage)\n                  @vboxmanage_path = Vagrant::Util::Platform.cygwin_windows_path(vboxmanage)\n                  break\n                end\n              end\n            end\n\n            # If we still don't have one, try to find it using common locations\n            drive = ENV[\"SYSTEMDRIVE\"] || \"C:\"\n            [\n              \"#{drive}/Program Files/Oracle/VirtualBox\",\n              \"#{drive}/Program Files (x86)/Oracle/VirtualBox\",\n              \"#{ENV[\"PROGRAMFILES\"]}/Oracle/VirtualBox\"\n            ].each do |maybe|\n              path = File.join(maybe, \"VBoxManage.exe\")\n              if File.file?(path)\n                @vboxmanage_path = path\n                break\n              end\n            end\n          elsif Vagrant::Util::Platform.wsl?\n            if !Vagrant::Util::Platform.wsl_windows_access?\n              @logger.error(\"No user Windows access defined for the Windows Subsystem for Linux. This is required for VirtualBox.\")\n              raise Vagrant::Errors::WSLVirtualBoxWindowsAccessError\n            end\n            @logger.debug(\"Linux platform detected but executing within WSL. Locating VBoxManage.\")\n            @vboxmanage_path = Vagrant::Util::Which.which(\"VBoxManage\") || Vagrant::Util::Which.which(\"VBoxManage.exe\")\n            if !@vboxmanage_path\n              # If we still don't have one, try to find it using common locations\n              drive = \"/mnt/c\"\n              [\n                \"#{drive}/Program Files/Oracle/VirtualBox\",\n                \"#{drive}/Program Files (x86)/Oracle/VirtualBox\"\n              ].each do |maybe|\n                path = File.join(maybe, \"VBoxManage.exe\")\n                if File.file?(path)\n                  @vboxmanage_path = path\n                  break\n                end\n              end\n            end\n          end\n\n          # Fall back to hoping for the PATH to work out\n          @vboxmanage_path ||= \"VBoxManage\"\n          @logger.info(\"VBoxManage path: #{@vboxmanage_path}\")\n        end\n\n        # Clears the forwarded ports that have been set on the virtual machine.\n        def clear_forwarded_ports\n        end\n\n        # Clears the shared folders that have been set on the virtual machine.\n        def clear_shared_folders\n        end\n\n        # Creates a DHCP server for a host only network.\n        #\n        # @param [String] network Name of the host-only network.\n        # @param [Hash] options Options for the DHCP server.\n        def create_dhcp_server(network, options)\n        end\n\n        # Creates a host only network with the given options.\n        #\n        # @param [Hash] options Options to create the host only network.\n        # @return [Hash] The details of the host only network, including\n        #   keys `:name`, `:ip`, and `:netmask`\n        def create_host_only_network(options)\n        end\n\n        # Deletes the virtual machine references by this driver.\n        def delete\n        end\n\n        # Deletes any host only networks that aren't being used for anything.\n        def delete_unused_host_only_networks\n        end\n\n        # Discards any saved state associated with this VM.\n        def discard_saved_state\n        end\n\n        # Enables network adapters on the VM.\n        #\n        # The format of each adapter specification should be like so:\n        #\n        # {\n        #   type:     :hostonly,\n        #   hostonly: \"vboxnet0\",\n        #   mac_address: \"tubes\"\n        # }\n        #\n        # This must support setting up both host only and bridged networks.\n        #\n        # @param [Array<Hash>] adapters Array of adapters to enable.\n        def enable_adapters(adapters)\n        end\n\n        # Execute a raw command straight through to VBoxManage.\n        #\n        # Accepts a retryable: true option if the command should be retried\n        # upon failure.\n        #\n        # Raises a VBoxManage error if it fails.\n        #\n        # @param [Array] command Command to execute.\n        def execute_command(command)\n        end\n\n        # Exports the virtual machine to the given path.\n        #\n        # @param [String] path Path to the OVF file.\n        # @yield [progress] Yields the block with the progress of the export.\n        def export(path)\n        end\n\n        # Forwards a set of ports for a VM.\n        #\n        # This will not affect any previously set forwarded ports,\n        # so be sure to delete those if you need to.\n        #\n        # The format of each port hash should be the following:\n        #\n        #     {\n        #       name: \"foo\",\n        #       hostport: 8500,\n        #       guestport: 80,\n        #       adapter: 1,\n        #       protocol: \"tcp\"\n        #     }\n        #\n        # Note that \"adapter\" and \"protocol\" are optional and will default\n        # to 1 and \"tcp\" respectively.\n        #\n        # @param [Array<Hash>] ports An array of ports to set. See documentation\n        #   for more information on the format.\n        def forward_ports(ports)\n        end\n\n        # Halts the virtual machine (pulls the plug).\n        def halt\n        end\n\n        # Imports the VM from an OVF file.\n        #\n        # @param [String] ovf Path to the OVF file.\n        # @return [String] UUID of the imported VM.\n        def import(ovf)\n        end\n\n        # Returns the maximum number of network adapters.\n        def max_network_adapters\n          8\n        end\n\n        # Returns a list of forwarded ports for a VM.\n        #\n        # @param [String] uuid UUID of the VM to read from, or `nil` if this\n        #   VM.\n        # @param [Boolean] active_only If true, only VMs that are running will\n        #   be checked.\n        # @return [Array<Array>]\n        def read_forwarded_ports(uuid=nil, active_only=false)\n        end\n\n        # Returns a list of bridged interfaces.\n        #\n        # @return [Hash]\n        def read_bridged_interfaces\n        end\n\n        # Returns a list of configured DHCP servers\n        #\n        # Each DHCP server is represented as a Hash with the following details:\n        #\n        # {\n        #  :network => String, # name of the associated network interface as\n        #                      #   parsed from the NetworkName, e.g. \"vboxnet0\"\n        #  :ip      => String, # IP address of the DHCP server, e.g. \"172.28.128.2\"\n        #  :lower   => String, # lower IP address of the DHCP lease range, e.g. \"172.28.128.3\"\n        #  :upper   => String, # upper IP address of the DHCP lease range, e.g. \"172.28.128.254\"\n        # }\n        #\n        # @return [Array<Hash>] See comment above for details\n        def read_dhcp_servers\n        end\n\n        # Returns the guest additions version that is installed on this VM.\n        #\n        # @return [String]\n        def read_guest_additions_version\n        end\n\n        # Returns the value of a guest property on the current VM.\n        #\n        # @param  [String] property the name of the guest property to read\n        # @return [String] value of the guest property\n        # @raise  [VirtualBoxGuestPropertyNotFound] if the guest property does not have a value\n        def read_guest_property(property)\n        end\n\n        # Returns a list of available host only interfaces.\n        #\n        # Each interface is represented as a Hash with the following details:\n        #\n        # {\n        #  :name         => String, # interface name, e.g. \"vboxnet0\"\n        #  :ip           => String, # IP address of the interface, e.g. \"172.28.128.1\"\n        #  :netmask      => String, # netmask associated with the interface, e.g. \"255.255.255.0\"\n        #  :status       => String, # status of the interface, e.g. \"Up\", \"Down\"\n        #  :display_name => String, # user friendly display name if available\n        # }\n        #\n        # @return [Array<Hash>] See comment above for details\n        def read_host_only_interfaces\n        end\n\n        # Returns the MAC address of the first network interface.\n        #\n        # @return [String]\n        def read_mac_address\n        end\n\n        # Returns the folder where VirtualBox places it's VMs.\n        #\n        # @return [String]\n        def read_machine_folder\n        end\n\n        # Returns a list of network interfaces of the VM.\n        #\n        # @return [Hash]\n        def read_network_interfaces\n        end\n\n        # Returns the current state of this VM.\n        #\n        # @return [Symbol]\n        def read_state\n        end\n\n        # Returns a list of all forwarded ports in use by active\n        # virtual machines.\n        #\n        # @return [Array]\n        def read_used_ports\n        end\n\n        # Returns a list of all UUIDs of virtual machines currently\n        # known by VirtualBox.\n        #\n        # @return [Array<String>]\n        def read_vms\n        end\n\n        # Reconfigure the hostonly network given by interface (the result\n        # of read_host_only_networks). This is a sad function that only\n        # exists to work around VirtualBox bugs.\n        #\n        # @return nil\n        def reconfig_host_only(interface)\n        end\n\n        # Removes the DHCP server identified by the provided network name.\n        #\n        # @param [String] network_name The the full network name associated\n        #   with the DHCP server to be removed, e.g. \"HostInterfaceNetworking-vboxnet0\"\n        def remove_dhcp_server(network_name)\n        end\n\n        # Sets the MAC address of the first network adapter.\n        #\n        # @param [String] mac MAC address without any spaces/hyphens.\n        def set_mac_address(mac)\n        end\n\n        # Share a set of folders on this VM.\n        #\n        # @param [Array<Hash>] folders\n        def share_folders(folders)\n        end\n\n        # Reads the SSH port of this VM.\n        #\n        # @param [Integer] expected Expected guest port of SSH.\n        def ssh_port(expected)\n        end\n\n        # Starts the virtual machine.\n        #\n        # @param [String] mode Mode to boot the VM. Either \"headless\"\n        #   or \"gui\"\n        def start(mode)\n        end\n\n        # Suspend the virtual machine.\n        def suspend\n        end\n\n        # Unshare folders.\n        def unshare_folders(names)\n        end\n\n        # Verifies that the driver is ready to accept work.\n        #\n        # This should raise a VagrantError if things are not ready.\n        def verify!\n        end\n\n        # Verifies that an image can be imported properly.\n        #\n        # @param [String] path Path to an OVF file.\n        # @return [Boolean]\n        def verify_image(path)\n        end\n\n        # Checks if a VM with the given UUID exists.\n        #\n        # @return [Boolean]\n        def vm_exists?(uuid)\n        end\n\n        # Returns a hash of information about a given virtual machine\n        #\n        # @param [String] uuid\n        # @return [Hash] info\n        def show_vm_info\n          info = {}\n          execute('showvminfo', @uuid, '--machinereadable', retryable: true).split(\"\\n\").each do |line|\n            parts = line.partition('=')\n            key = parts.first.gsub('\"', '')\n            value = parts.last.gsub('\"', '')\n            info[key] = value\n          end\n          info\n        end\n\n        # Execute the given subcommand for VBoxManage and return the output.\n        def execute(*command, &block)\n          # Get the options hash if it exists\n          opts = {}\n          opts = command.pop if command.last.is_a?(Hash)\n\n          tries = 0\n          tries = 3 if opts[:retryable]\n\n          # Variable to store our execution result\n          r = nil\n\n          retryable(on: Vagrant::Errors::VBoxManageError, tries: tries, sleep: 1) do\n            # If there is an error with VBoxManage, this gets set to true\n            errored = false\n\n            # Execute the command\n            r = raw(*command, &block)\n\n            # If the command was a failure, then raise an exception that is\n            # nicely handled by Vagrant.\n            if r.exit_code != 0\n              if @interrupted\n                @logger.info(\"Exit code != 0, but interrupted. Ignoring.\")\n              elsif r.exit_code == 126\n                # This exit code happens if VBoxManage is on the PATH,\n                # but another executable it tries to execute is missing.\n                # This is usually indicative of a corrupted VirtualBox install.\n                raise Vagrant::Errors::VBoxManageNotFoundError\n              else\n                errored = true\n              end\n            else\n              # Sometimes, VBoxManage fails but doesn't actual return a non-zero\n              # exit code. For this we inspect the output and determine if an error\n              # occurred.\n\n              if r.stderr =~ /failed to open \\/dev\\/vboxnetctl/i\n                # This catches an error message that only shows when kernel\n                # drivers aren't properly installed.\n                @logger.error(\"Error message about unable to open vboxnetctl\")\n                raise Vagrant::Errors::VirtualBoxKernelModuleNotLoaded\n              end\n\n              if r.stderr =~ /VBoxManage([.a-z]+?): error:/\n                # This catches the generic VBoxManage error case.\n                @logger.info(\"VBoxManage error text found, assuming error.\")\n                errored = true\n              end\n            end\n\n            # If there was an error running VBoxManage, show the error and the\n            # output.\n            if errored\n              raise Vagrant::Errors::VBoxManageError,\n                command: command.inspect,\n                stderr:  r.stderr.to_s.force_encoding(\"UTF-8\"),\n                stdout:  r.stdout.to_s.force_encoding(\"UTF-8\")\n            end\n          end\n\n          # Return the output, making sure to replace any Windows-style\n          # newlines with Unix-style.\n          r.stdout.gsub(\"\\r\\n\", \"\\n\")\n        end\n\n        # Executes a command and returns the raw result object.\n        def raw(*command, &block)\n          int_callback = lambda do\n            @interrupted = true\n\n            # We have to execute this in a thread due to trap contexts\n            # and locks.\n            Thread.new { @logger.info(\"Interrupted.\") }.join\n          end\n\n          # Append in the options for subprocess\n          # NOTE: We include the LANG env var set to C to prevent command output\n          #       from being localized\n          command << { notify: [:stdout, :stderr], env: env_lang}\n\n          Vagrant::Util::Busy.busy(int_callback) do\n            Vagrant::Util::Subprocess.execute(@vboxmanage_path, *command, &block)\n          end\n        rescue Vagrant::Util::Subprocess::LaunchError => e\n          raise Vagrant::Errors::VBoxManageLaunchError,\n            message: e.to_s\n        end\n\n        private\n\n        # List of LANG values to attempt to use\n        LANG_VARIATIONS = %w(C.UTF-8 C.utf8 en_US.UTF-8 en_US.utf8 C POSIX).map(&:freeze).freeze\n\n        # By default set the LANG to C. If the host has the locale command\n        # available, check installed locales and verify C is included (or\n        # use C variant if available).\n        def env_lang\n          # If already set, just return immediately\n          return @env_lang if @env_lang\n\n          # Default the LANG to C\n          @env_lang = {LANG: \"C\"}\n\n          # If the locale command is not available, return default\n          return @env_lang if !Vagrant::Util::Which.which(\"locale\")\n\n          if defined?(@@env_lang)\n            return @env_lang = @@env_lang\n          end\n\n          @logger.debug(\"validating LANG value for virtualbox cli commands\")\n          # Get list of available locales on the system\n          result = Vagrant::Util::Subprocess.execute(\"locale\", \"-a\")\n\n          # If the command results in an error, just log the error\n          # and return the default value\n          if result.exit_code != 0\n            @logger.warn(\"locale command failed (exit code: #{result.exit_code}): #{result.stderr}\")\n            return @env_lang\n          end\n          available = result.stdout.lines.map(&:chomp).find_all { |l|\n            l == \"C\" || l == \"POSIX\" || l.start_with?(\"C.\") || l.start_with?(\"en_US.\")\n          }\n          @logger.debug(\"list of available C locales: #{available.inspect}\")\n\n          # Attempt to find a valid LANG from locale list\n          lang = LANG_VARIATIONS.detect { |l| available.include?(l) }\n\n          if lang\n            @logger.debug(\"valid variation found for LANG value: #{lang}\")\n            @env_lang[:LANG] = lang\n            @@env_lang = @env_lang\n          end\n\n          @logger.debug(\"LANG value set: #{@env_lang[:LANG].inspect}\")\n          @env_lang\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/meta.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"forwardable\"\nrequire \"thread\"\n\nrequire \"log4r\"\n\nrequire \"vagrant/util/retryable\"\n\nrequire File.expand_path(\"../base\", __FILE__)\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      class Meta < Base\n        # This is raised if the VM is not found when initializing a driver\n        # with a UUID.\n        class VMNotFound < StandardError; end\n\n        # We use forwardable to do all our driver forwarding\n        extend Forwardable\n\n        # We cache the read VirtualBox version here once we have one,\n        # since during the execution of Vagrant, it likely doesn't change.\n        @@version = nil\n        @@version_lock = Mutex.new\n\n        # The UUID of the virtual machine we represent\n        attr_reader :uuid\n\n        # The version of virtualbox that is running.\n        attr_reader :version\n\n        include Vagrant::Util::Retryable\n\n        def initialize(uuid=nil)\n          # Setup the base\n          super()\n\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox::meta\")\n          @uuid = uuid\n\n          @@version_lock.synchronize do\n            if !@@version\n              # Read and assign the version of VirtualBox we know which\n              # specific driver to instantiate.\n              begin\n                @@version = read_version\n              rescue Vagrant::Errors::CommandUnavailable,\n                Vagrant::Errors::CommandUnavailableWindows\n                # This means that VirtualBox was not found, so we raise this\n                # error here.\n                raise Vagrant::Errors::VirtualBoxNotDetected\n              end\n            end\n          end\n\n          # Instantiate the proper version driver for VirtualBox\n          @logger.debug(\"Finding driver for VirtualBox version: #{@@version}\")\n          driver_map   = {\n            \"4.0\" => Version_4_0,\n            \"4.1\" => Version_4_1,\n            \"4.2\" => Version_4_2,\n            \"4.3\" => Version_4_3,\n            \"5.0\" => Version_5_0,\n            \"5.1\" => Version_5_1,\n            \"5.2\" => Version_5_2,\n            \"6.0\" => Version_6_0,\n            \"6.1\" => Version_6_1,\n            \"7.0\" => Version_7_0,\n            \"7.1\" => Version_7_1,\n            \"7.2\" => Version_7_2,\n          }\n\n          if @@version.start_with?(\"4.2.14\")\n            # VirtualBox 4.2.14 just doesn't work with Vagrant, so show error\n            raise Vagrant::Errors::VirtualBoxBrokenVersion040214\n          end\n\n          driver_klass = nil\n          driver_map.each do |key, klass|\n            if @@version.start_with?(key)\n              driver_klass = klass\n              break\n            end\n          end\n\n          if !driver_klass\n            supported_versions = driver_map.keys.sort.join(\", \")\n            raise Vagrant::Errors::VirtualBoxInvalidVersion,\n              supported_versions: supported_versions\n          end\n\n          @logger.info(\"Using VirtualBox driver: #{driver_klass}\")\n          @driver = driver_klass.new(@uuid)\n          @version = @@version\n\n          if @uuid\n            # Verify the VM exists, and if it doesn't, then don't worry\n            # about it (mark the UUID as nil)\n            raise VMNotFound if !@driver.vm_exists?(@uuid)\n          end\n        end\n\n        def_delegators :@driver,\n          :attach_disk,\n          :clear_forwarded_ports,\n          :clear_shared_folders,\n          :clone_disk,\n          :clonevm,\n          :close_medium,\n          :create_dhcp_server,\n          :create_disk,\n          :create_host_only_network,\n          :create_snapshot,\n          :delete,\n          :delete_snapshot,\n          :delete_unused_host_only_networks,\n          :discard_saved_state,\n          :enable_adapters,\n          :execute_command,\n          :export,\n          :forward_ports,\n          :get_port_and_device,\n          :get_storage_controller,\n          :halt,\n          :import,\n          :list_snapshots,\n          :list_hdds,\n          :read_forwarded_ports,\n          :read_bridged_interfaces,\n          :read_dhcp_servers,\n          :read_guest_additions_version,\n          :read_guest_ip,\n          :read_guest_property,\n          :read_host_only_interfaces,\n          :read_host_only_networks,\n          :read_mac_address,\n          :read_mac_addresses,\n          :read_machine_folder,\n          :read_network_interfaces,\n          :read_state,\n          :read_storage_controllers,\n          :read_used_ports,\n          :read_vms,\n          :reconfig_host_only,\n          :remove_dhcp_server,\n          :remove_disk,\n          :resize_disk,\n          :restore_snapshot,\n          :resume,\n          :set_mac_address,\n          :set_name,\n          :share_folders,\n          :show_medium_info,\n          :ssh_port,\n          :start,\n          :suspend,\n          :vdi_to_vmdk,\n          :verify!,\n          :verify_image,\n          :vm_exists?,\n          :vmdk_to_vdi\n\n        protected\n\n        # This returns the version of VirtualBox that is running.\n        #\n        # @return [String]\n        def read_version\n          # The version string is usually in one of the following formats:\n          #\n          # * 4.1.8r1234\n          # * 4.1.8r1234_OSE\n          # * 4.1.8_MacPortsr1234\n          #\n          # Below accounts for all of these.\n\n          # Note: We split this into multiple lines because apparently \"\".split(\"_\")\n          # is [], so we have to check for an empty array in between.\n          output = \"\"\n          retryable(on: Vagrant::Errors::VirtualBoxVersionEmpty, tries: 3, sleep: 1) do\n            output = execute(\"--version\")\n            if output =~ /vboxdrv kernel module is not loaded/ ||\n              output =~ /VirtualBox kernel modules are not loaded/i\n              raise Vagrant::Errors::VirtualBoxKernelModuleNotLoaded\n            elsif output =~ /Please install/\n              # Check for installation incomplete warnings, for example:\n              # \"WARNING: The character device /dev/vboxdrv does not\n              # exist. Please install the virtualbox-ose-dkms package and\n              # the appropriate headers, most likely linux-headers-generic.\"\n              raise Vagrant::Errors::VirtualBoxInstallIncomplete\n            elsif output.chomp == \"\"\n              # This seems to happen on Windows for uncertain reasons.\n              # Raise an error otherwise the error is that they have an\n              # incompatible version of VirtualBox which isn't true.\n              raise Vagrant::Errors::VirtualBoxVersionEmpty,\n                vboxmanage: @vboxmanage_path.to_s\n            end\n          end\n\n          version_line = output.each_line.find do |line|\n            !line.start_with?(\"WARNING:\")\n          end\n\n          parts = version_line.to_s.split(\"_\")\n          return nil if parts.empty?\n          parts[0].split(\"r\")[0]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/version_4_0.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'log4r'\n\nrequire \"vagrant/util/platform\"\n\nrequire File.expand_path(\"../base\", __FILE__)\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      # Driver for VirtualBox 4.0.x\n      class Version_4_0 < Base\n        def initialize(uuid)\n          super()\n\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox_4_0\")\n          @uuid = uuid\n        end\n\n        def clear_forwarded_ports\n          args = []\n          read_forwarded_ports(@uuid).each do |nic, name, _, _|\n            args.concat([\"--natpf#{nic}\", \"delete\", name])\n          end\n\n          execute(\"modifyvm\", @uuid, *args) if !args.empty?\n        end\n\n        def clear_shared_folders\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if name = line[/^SharedFolderNameMachineMapping\\d+=\"(.+?)\"$/, 1]\n              execute(\"sharedfolder\", \"remove\", @uuid, \"--name\", name)\n            end\n          end\n        end\n\n        def create_dhcp_server(network, options)\n          execute(\"dhcpserver\", \"add\", \"--ifname\", network,\n                  \"--ip\", options[:dhcp_ip],\n                  \"--netmask\", options[:netmask],\n                  \"--lowerip\", options[:dhcp_lower],\n                  \"--upperip\", options[:dhcp_upper],\n                  \"--enable\")\n        end\n\n        def create_host_only_network(options)\n          # Create the interface\n          interface = execute(\"hostonlyif\", \"create\")\n          name = interface[/^Interface '(.+?)' was successfully created$/, 1]\n\n          # Get the IP so we can determine v4 vs v6\n          ip = IPAddr.new(options[:adapter_ip])\n\n          # Configure\n          if ip.ipv4?\n            execute(\"hostonlyif\", \"ipconfig\", name,\n                    \"--ip\", options[:adapter_ip],\n                    \"--netmask\", options[:netmask])\n          elsif ip.ipv6?\n            execute(\"hostonlyif\", \"ipconfig\", name,\n                    \"--ipv6\", options[:adapter_ip],\n                    \"--netmasklengthv6\", options[:netmask].to_s)\n          end\n\n          # Return the details\n          return {\n            name: name,\n            ip:   options[:adapter_ip],\n            netmask: options[:netmask],\n            dhcp: nil\n          }\n        end\n\n        def delete\n          execute(\"unregistervm\", @uuid, \"--delete\")\n        end\n\n        def delete_unused_host_only_networks\n          networks = []\n          execute(\"list\", \"hostonlyifs\").split(\"\\n\").each do |line|\n            if network_name = line[/^Name:\\s+(.+?)$/, 1]\n              networks << network_name\n            end\n          end\n\n          execute(\"list\", \"vms\").split(\"\\n\").each do |line|\n            if vm_name = line[/^\".+?\"\\s+\\{(.+?)\\}$/, 1]\n              begin\n                info = execute(\"showvminfo\", vm_name, \"--machinereadable\", retryable: true)\n                info.split(\"\\n\").each do |line|\n                  if network_name = line[/^hostonlyadapter\\d+=\"(.+?)\"$/, 1]\n                    networks.delete(network_name)\n                  end\n                end\n              rescue Vagrant::Errors::VBoxManageError => e\n                raise if !e.extra_data[:stderr].include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n                # VirtualBox could not find the vm. It may have been deleted\n                # by another process after we called 'vboxmanage list vms'? Ignore this error.\n              end\n            end\n          end\n\n          networks.each do |name|\n            # First try to remove any DHCP servers attached. We use `raw` because\n            # it is okay if this fails. It usually means that a DHCP server was\n            # never attached.\n            raw(\"dhcpserver\", \"remove\", \"--ifname\", name)\n\n            # Delete the actual host only network interface.\n            execute(\"hostonlyif\", \"remove\", name)\n          end\n        end\n\n        def discard_saved_state\n          execute(\"discardstate\", @uuid)\n        end\n\n        def enable_adapters(adapters)\n          args = []\n          adapters.each do |adapter|\n            args.concat([\"--nic#{adapter[:adapter]}\", adapter[:type].to_s])\n\n            if adapter[:bridge]\n              args.concat([\"--bridgeadapter#{adapter[:adapter]}\",\n                          adapter[:bridge], \"--cableconnected#{adapter[:adapter]}\", \"on\"])\n            end\n\n            if adapter[:hostonly]\n              args.concat([\"--hostonlyadapter#{adapter[:adapter]}\",\n                          adapter[:hostonly]])\n            end\n\n            if adapter[:mac_address]\n              args.concat([\"--macaddress#{adapter[:adapter]}\",\n                          adapter[:mac_address]])\n            end\n\n            if adapter[:nic_type]\n              args.concat([\"--nictype#{adapter[:adapter]}\", adapter[:nic_type].to_s])\n            end\n          end\n\n          execute(\"modifyvm\", @uuid, *args)\n        end\n\n        def execute_command(command)\n          execute(*command)\n        end\n\n        def export(path)\n          execute(\"export\", @uuid, \"--output\", path.to_s)\n        end\n\n        def forward_ports(ports)\n          args = []\n          ports.each do |options|\n            pf_builder = [options[:name],\n              options[:protocol] || \"tcp\",\n              options[:hostip] || \"\",\n              options[:hostport],\n              options[:guestip] || \"\",\n              options[:guestport]]\n\n            args.concat([\"--natpf#{options[:adapter] || 1}\",\n                        pf_builder.join(\",\")])\n          end\n\n          execute(\"modifyvm\", @uuid, *args) if !args.empty?\n        end\n\n        def halt\n          execute(\"controlvm\", @uuid, \"poweroff\")\n        end\n\n        def import(ovf)\n          ovf = Vagrant::Util::Platform.cygwin_windows_path(ovf)\n\n          output = \"\"\n          total = \"\"\n          last  = 0\n          execute(\"import\", ovf) do |type, data|\n            if type == :stdout\n              # Keep track of the stdout so that we can get the VM name\n              output << data\n            elsif type == :stderr\n              # Append the data so we can see the full view\n              total << data\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n              if lines.include?(\"OK.\")\n                # The progress of the import will be in the last line. Do a greedy\n                # regular expression to find what we're looking for.\n                if current = lines.last[/.+(\\d{2})%/, 1]\n                  current = current.to_i\n                  if current > last\n                    last = current\n                    yield current if block_given?\n                  end\n                end\n              end\n            end\n          end\n\n          # Find the name of the VM name\n          name = output[/Suggested VM name \"(.+?)\"/, 1]\n          if !name\n            @logger.error(\"Couldn't find VM name in the output.\")\n            return nil\n          end\n\n          output = execute(\"list\", \"vms\")\n          if existing_vm = output[/^\"#{Regexp.escape(name)}\" \\{(.+?)\\}$/, 1]\n            return existing_vm\n          end\n\n          nil\n        end\n\n        def read_forwarded_ports(uuid=nil, active_only=false)\n          uuid ||= @uuid\n\n          @logger.debug(\"read_forward_ports: uuid=#{uuid} active_only=#{active_only}\")\n\n          results = []\n          current_nic = nil\n          info = execute(\"showvminfo\", uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            # This is how we find the nic that a FP is attached to,\n            # since this comes first.\n            if nic = line[/^nic(\\d+)=\".+?\"$/, 1]\n              current_nic = nic.to_i\n            end\n\n            # If we care about active VMs only, then we check the state\n            # to verify the VM is running.\n            if active_only && (state = line[/^VMState=\"(.+?)\"$/, 1] and state != \"running\")\n              return []\n            end\n\n            # Parse out the forwarded port information\n            if matcher = /^Forwarding.+?=\"(.+?),.+?,.*?,(.+?),.*?,(.+?)\"$/.match(line)\n              result = [current_nic, matcher[1], matcher[2].to_i, matcher[3].to_i]\n              @logger.debug(\"  - #{result.inspect}\")\n              results << result\n            end\n          end\n\n          results\n        end\n\n        def read_bridged_interfaces\n          execute(\"list\", \"bridgedifs\").split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if name = line[/^Name:\\s+(.+?)$/, 1]\n                info[:name] = name\n              elsif ip = line[/^IPAddress:\\s+(.+?)$/, 1]\n                info[:ip] = ip\n              elsif netmask = line[/^NetworkMask:\\s+(.+?)$/, 1]\n                info[:netmask] = netmask\n              elsif status = line[/^Status:\\s+(.+?)$/, 1]\n                info[:status] = status\n              end\n            end\n\n            # Return the info to build up the results\n            info\n          end\n        end\n\n        def read_dhcp_servers\n          execute(\"list\", \"dhcpservers\", retryable: true).split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if network = line[/^NetworkName:\\s+HostInterfaceNetworking-(.+?)$/, 1]\n                info[:network]      = network\n                info[:network_name] = \"HostInterfaceNetworking-#{network}\"\n              elsif ip = line[/^IP:\\s+(.+?)$/, 1]\n                info[:ip] = ip\n              elsif netmask = line[/^NetworkMask:\\s+(.+?)$/, 1]\n                info[:netmask] = netmask\n              elsif lower = line[/^lowerIPAddress:\\s+(.+?)$/, 1]\n                info[:lower] = lower\n              elsif upper = line[/^upperIPAddress:\\s+(.+?)$/, 1]\n                info[:upper] = upper\n              end\n            end\n\n            info\n          end\n        end\n\n        def read_guest_additions_version\n          output = execute(\"guestproperty\", \"get\", @uuid, \"/VirtualBox/GuestAdd/Version\",\n                           retryable: true)\n          if value = output[/^Value: (.+?)$/, 1]\n            # Split the version by _ since some distro versions modify it\n            # to look like this: 4.1.2_ubuntu, and the distro part isn't\n            # too important.\n            return value.split(\"_\").first\n          end\n\n          return nil\n        end\n\n        def read_guest_ip(adapter_number)\n          ip = read_guest_property(\"/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP\")\n          if !valid_ip_address?(ip)\n            raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: \"/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP\"\n          end\n\n          return ip\n        end\n\n        def read_guest_property(property)\n          output = execute(\"guestproperty\", \"get\", @uuid, property)\n          if output =~ /^Value: (.+?)$/\n            $1.to_s\n          else\n            raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: property\n          end\n        end\n\n        def read_host_only_interfaces\n          execute(\"list\", \"hostonlyifs\", retryable: true).split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if name = line[/^Name:\\s+(.+?)$/, 1]\n                info[:name] = name\n              elsif ip = line[/^IPAddress:\\s+(.+?)$/, 1]\n                info[:ip] = ip\n              elsif netmask = line[/^NetworkMask:\\s+(.+?)$/, 1]\n                info[:netmask] = netmask\n              elsif line =~ /^IPV6Address:\\s+(.+?)$/\n                info[:ipv6] = $1.to_s.strip\n              elsif line =~ /^IPV6NetworkMaskPrefixLength:\\s+(.+?)$/\n                info[:ipv6_prefix] = $1.to_s.strip\n              elsif status = line[/^Status:\\s+(.+?)$/, 1]\n                info[:status] = status\n              elsif line =~ /^VBoxNetworkName:\\s+(.+?)$/\n                info[:display_name] = $1.to_s\n              end\n            end\n\n            info\n          end\n        end\n\n        def read_mac_address\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if mac = line[/^macaddress1=\"(.+?)\"$/, 1]\n              return mac\n            end\n          end\n\n          nil\n        end\n\n        def read_mac_addresses\n          macs = {}\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if matcher = /^macaddress(\\d+)=\"(.+?)\"$/.match(line)\n              adapter = matcher[1].to_i\n              mac = matcher[2].to_s\n              macs[adapter] = mac\n            end\n          end\n          macs\n        end\n\n        def read_machine_folder\n          execute(\"list\", \"systemproperties\", retryable: true).split(\"\\n\").each do |line|\n            if folder = line[/^Default machine folder:\\s+(.+?)$/i, 1]\n              return folder\n            end\n          end\n\n          nil\n        end\n\n        def read_network_interfaces\n          nics = {}\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if matcher = /^nic(\\d+)=\"(.+?)\"$/.match(line)\n              adapter = matcher[1].to_i\n              type    = matcher[2].to_sym\n\n              nics[adapter] ||= {}\n              nics[adapter][:type] = type\n            elsif matcher = /^hostonlyadapter(\\d+)=\"(.+?)\"$/.match(line)\n              adapter = matcher[1].to_i\n              network = matcher[2].to_s\n\n              nics[adapter] ||= {}\n              nics[adapter][:hostonly] = network\n            elsif matcher = /^bridgeadapter(\\d+)=\"(.+?)\"$/.match(line)\n              adapter = matcher[1].to_i\n              network = matcher[2].to_s\n\n              nics[adapter] ||= {}\n              nics[adapter][:bridge] = network\n            end\n          end\n\n          nics\n        end\n\n        def read_state\n          output = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          if output =~ /^name=\"<inaccessible>\"$/\n            return :inaccessible\n          elsif state = output[/^VMState=\"(.+?)\"$/, 1]\n            return state.to_sym\n          end\n\n          nil\n        end\n\n        def read_used_ports\n          ports = []\n          execute(\"list\", \"vms\", retryable: true).split(\"\\n\").each do |line|\n            if uuid = line[/^\".+?\" \\{(.+?)\\}$/, 1]\n              # Ignore our own used ports\n              next if uuid == @uuid\n\n              begin\n                read_forwarded_ports(uuid, true).each do |_, _, hostport, _|\n                  ports << hostport\n                end\n              rescue Vagrant::Errors::VBoxManageError => e\n                raise if !e.extra_data[:stderr].include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n                # VirtualBox could not find the vm. It may have been deleted\n                # by another process after we called 'vboxmanage list vms'? Ignore this error.\n              end\n            end\n          end\n\n          ports\n        end\n\n        def read_vms\n          results = {}\n          execute(\"list\", \"vms\", retryable: true).split(\"\\n\").each do |line|\n            if line =~ /^\"(.+?)\" \\{(.+?)\\}$/\n              results[$1.to_s] = $2.to_s\n            end\n          end\n\n          results\n        end\n\n        def reconfig_host_only(interface)\n          execute(\"hostonlyif\", \"ipconfig\", interface[:name],\n                  \"--ipv6\", interface[:ipv6])\n        end\n\n        def remove_dhcp_server(network_name)\n          execute(\"dhcpserver\", \"remove\", \"--netname\", network_name)\n        end\n\n        def set_mac_address(mac)\n          execute(\"modifyvm\", @uuid, \"--macaddress1\", mac)\n        end\n\n        def set_name(name)\n          execute(\"modifyvm\", @uuid, \"--name\", name)\n        end\n\n        def share_folders(folders)\n          folders.each do |folder|\n            args = [\"--name\",\n              folder[:name],\n              \"--hostpath\",\n              folder[:hostpath]]\n            args << \"--transient\" if folder.key?(:transient) && folder[:transient]\n            execute(\"sharedfolder\", \"add\", @uuid, *args)\n          end\n        end\n\n        def ssh_port(expected_port)\n          @logger.debug(\"Searching for SSH port: #{expected_port.inspect}\")\n\n          # Look for the forwarded port only by comparing the guest port\n          read_forwarded_ports.each do |_, _, hostport, guestport|\n            return hostport if guestport == expected_port\n          end\n\n          nil\n        end\n\n        def resume\n          @logger.debug(\"Resuming paused VM...\")\n          execute(\"controlvm\", @uuid, \"resume\")\n        end\n\n        def start(mode)\n          command = [\"startvm\", @uuid, \"--type\", mode.to_s]\n          r = raw(*command)\n\n          if r.exit_code == 0 || r.stdout =~ /VM \".+?\" has been successfully started/\n            # Some systems return an exit code 1 for some reason. For that\n            # we depend on the output.\n            return true\n          end\n\n          # If we reached this point then it didn't work out.\n          raise Vagrant::Errors::VBoxManageError,\n            command: command.inspect,\n            stderr: r.stderr\n        end\n\n        def suspend\n          execute(\"controlvm\", @uuid, \"savestate\")\n        end\n\n        def unshare_folders(names)\n          names.each do |name|\n            begin\n              execute(\n                \"sharedfolder\", \"remove\", @uuid,\n                \"--name\", name,\n                \"--transient\")\n\n            execute(\n              \"setextradata\", @uuid,\n              \"VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{name}\")\n            rescue Vagrant::Errors::VBoxManageError => e\n              if e.extra_data[:stderr].include?(\"VBOX_E_FILE_ERROR\")\n                # The folder doesn't exist. ignore.\n              else\n                raise\n              end\n            end\n          end\n        end\n\n        def valid_ip_address?(ip)\n          # Filter out invalid IP addresses\n          # GH-4658 VirtualBox can report an IP address of 0.0.0.0 for FreeBSD guests.\n          if ip == \"0.0.0.0\"\n            return false\n          else\n            return true\n          end\n        end\n\n        def verify!\n          # This command sometimes fails if kernel drivers aren't properly loaded\n          # so we just run the command and verify that it succeeded.\n          execute(\"list\", \"hostonlyifs\")\n        end\n\n        def verify_image(path)\n          r = raw(\"import\", path.to_s, \"--dry-run\")\n          return r.exit_code == 0\n        end\n\n        def vm_exists?(uuid)\n          raw(\"showvminfo\", uuid).exit_code == 0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/version_4_1.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'log4r'\nrequire \"vagrant/util/platform\"\n\nrequire File.expand_path(\"../base\", __FILE__)\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      # Driver for VirtualBox 4.1.x\n      class Version_4_1 < Base\n        def initialize(uuid)\n          super()\n\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox_4_1\")\n          @uuid = uuid\n        end\n\n        def clear_forwarded_ports\n          args = []\n          read_forwarded_ports(@uuid).each do |nic, name, _, _|\n            args.concat([\"--natpf#{nic}\", \"delete\", name])\n          end\n\n          execute(\"modifyvm\", @uuid, *args) if !args.empty?\n        end\n\n        def clear_shared_folders\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if folder = line[/^SharedFolderNameMachineMapping\\d+=\"(.+?)\"$/, 1]\n              execute(\"sharedfolder\", \"remove\", @uuid, \"--name\", folder)\n            end\n          end\n        end\n\n        def clonevm(master_id, snapshot_name)\n          machine_name = \"temp_clone_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}\"\n          args = [\"--register\", \"--name\", machine_name]\n          if snapshot_name\n            args += [\"--snapshot\", snapshot_name, \"--options\", \"link\"]\n          end\n\n          execute(\"clonevm\", master_id, *args)\n          return get_machine_id(machine_name)\n        end\n\n        def create_dhcp_server(network, options)\n          execute(\"dhcpserver\", \"add\", \"--ifname\", network,\n                  \"--ip\", options[:dhcp_ip],\n                  \"--netmask\", options[:netmask],\n                  \"--lowerip\", options[:dhcp_lower],\n                  \"--upperip\", options[:dhcp_upper],\n                  \"--enable\")\n        end\n\n        def create_host_only_network(options)\n          # Create the interface\n          interface = execute(\"hostonlyif\", \"create\")\n          name = interface[/^Interface '(.+?)' was successfully created$/, 1]\n\n          # Get the IP so we can determine v4 vs v6\n          ip = IPAddr.new(options[:adapter_ip])\n\n          # Configure\n          if ip.ipv4?\n            execute(\"hostonlyif\", \"ipconfig\", name,\n                    \"--ip\", options[:adapter_ip],\n                    \"--netmask\", options[:netmask])\n          elsif ip.ipv6?\n            execute(\"hostonlyif\", \"ipconfig\", name,\n                    \"--ipv6\", options[:adapter_ip],\n                    \"--netmasklengthv6\", options[:netmask].to_s)\n          end\n\n          # Return the details\n          return {\n            name: name,\n            ip:   options[:adapter_ip],\n            netmask: options[:netmask],\n            dhcp: nil\n          }\n        end\n\n        def create_snapshot(machine_id, snapshot_name)\n          execute(\"snapshot\", machine_id, \"take\", snapshot_name)\n        end\n\n        def delete_snapshot(machine_id, snapshot_name)\n          # Start with 0%\n          last = 0\n          total = \"\"\n          yield 0 if block_given?\n\n          # Snapshot and report the % progress\n          execute(\"snapshot\", machine_id, \"delete\", snapshot_name) do |type, data|\n            if type == :stderr\n              # Append the data so we can see the full view\n              total << data.gsub(\"\\r\", \"\")\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n\n              # The progress of the import will be in the last line. Do a greedy\n              # regular expression to find what we're looking for.\n              match = /.+(\\d{2})%/.match(lines.last)\n              if match\n                current = match[1].to_i\n                if current > last\n                  last = current\n                  yield current if block_given?\n                end\n              end\n            end\n          end\n        end\n\n        def list_snapshots(machine_id)\n          output = execute(\n            \"snapshot\", machine_id, \"list\", \"--machinereadable\",\n            retryable: true)\n\n          result = []\n          output.split(\"\\n\").each do |line|\n            if line =~ /^SnapshotName.*?=\"(.+?)\"$/i\n              result << $1.to_s\n            end\n          end\n\n          result.sort\n        rescue Vagrant::Errors::VBoxManageError => e\n          d = e.extra_data\n          return [] if d[:stderr].include?(\"does not have\") || d[:stdout].include?(\"does not have\")\n          raise\n        end\n\n        def restore_snapshot(machine_id, snapshot_name)\n          # Start with 0%\n          last = 0\n          total = \"\"\n          yield 0 if block_given?\n\n          execute(\"snapshot\", machine_id, \"restore\", snapshot_name) do |type, data|\n            if type == :stderr\n              # Append the data so we can see the full view\n              total << data.gsub(\"\\r\", \"\")\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n\n              # The progress of the import will be in the last line. Do a greedy\n              # regular expression to find what we're looking for.\n              match = /.+(\\d{2})%/.match(lines.last)\n              if match\n                current = match[1].to_i\n                if current > last\n                  last = current\n                  yield current if block_given?\n                end\n              end\n            end\n          end\n        end\n\n        def delete\n          execute(\"unregistervm\", @uuid, \"--delete\")\n        end\n\n        def delete_unused_host_only_networks\n          networks = []\n          execute(\"list\", \"hostonlyifs\").split(\"\\n\").each do |line|\n            if network = line[/^Name:\\s+(.+?)$/, 1]\n              networks << network\n            end\n          end\n\n          execute(\"list\", \"vms\").split(\"\\n\").each do |line|\n            if vm = line[/^\".+?\"\\s+\\{(.+?)\\}$/, 1]\n              begin\n                info = execute(\"showvminfo\", vm, \"--machinereadable\", retryable: true)\n                info.split(\"\\n\").each do |line|\n                  if adapter = line[/^hostonlyadapter\\d+=\"(.+?)\"$/, 1]\n                    networks.delete(adapter)\n                  end\n                end\n              rescue Vagrant::Errors::VBoxManageError => e\n                raise if !e.extra_data[:stderr].include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n                # VirtualBox could not find the vm. It may have been deleted\n                # by another process after we called 'vboxmanage list vms'? Ignore this error.\n              end\n            end\n          end\n\n          networks.each do |name|\n            # First try to remove any DHCP servers attached. We use `raw` because\n            # it is okay if this fails. It usually means that a DHCP server was\n            # never attached.\n            raw(\"dhcpserver\", \"remove\", \"--ifname\", name)\n\n            # Delete the actual host only network interface.\n            execute(\"hostonlyif\", \"remove\", name)\n          end\n        end\n\n        def discard_saved_state\n          execute(\"discardstate\", @uuid)\n        end\n\n        def enable_adapters(adapters)\n          args = []\n          adapters.each do |adapter|\n            args.concat([\"--nic#{adapter[:adapter]}\", adapter[:type].to_s])\n\n            if adapter[:bridge]\n              args.concat([\"--bridgeadapter#{adapter[:adapter]}\",\n                          adapter[:bridge], \"--cableconnected#{adapter[:adapter]}\", \"on\"])\n            end\n\n            if adapter[:hostonly]\n              args.concat([\"--hostonlyadapter#{adapter[:adapter]}\",\n                          adapter[:hostonly]])\n            end\n\n            if adapter[:intnet]\n              args.concat([\"--intnet#{adapter[:adapter]}\",\n                          adapter[:intnet]])\n            end\n\n            if adapter[:mac_address]\n              args.concat([\"--macaddress#{adapter[:adapter]}\",\n                          adapter[:mac_address]])\n            end\n\n            if adapter[:nic_type]\n              args.concat([\"--nictype#{adapter[:adapter]}\", adapter[:nic_type].to_s])\n            end\n          end\n\n          execute(\"modifyvm\", @uuid, *args)\n        end\n\n        def execute_command(command)\n          execute(*command)\n        end\n\n        def export(path)\n          execute(\"export\", @uuid, \"--output\", path.to_s)\n        end\n\n        def forward_ports(ports)\n          args = []\n          ports.each do |options|\n            pf_builder = [options[:name],\n              options[:protocol] || \"tcp\",\n              options[:hostip] || \"\",\n              options[:hostport],\n              options[:guestip] || \"\",\n              options[:guestport]]\n\n            args.concat([\"--natpf#{options[:adapter] || 1}\",\n                        pf_builder.join(\",\")])\n          end\n\n          execute(\"modifyvm\", @uuid, *args) if !args.empty?\n        end\n\n        def get_machine_id(machine_name)\n          output = execute(\"list\", \"vms\", retryable: true)\n          match = /^\"#{Regexp.escape(machine_name)}\" \\{(.+?)\\}$/.match(output)\n          return match[1].to_s if match\n          nil\n        end\n\n        def halt\n          execute(\"controlvm\", @uuid, \"poweroff\")\n        end\n\n        def import(ovf)\n          ovf = Vagrant::Util::Platform.cygwin_windows_path(ovf)\n\n          output = \"\"\n          total = \"\"\n          last  = 0\n          execute(\"import\", ovf) do |type, data|\n            if type == :stdout\n              # Keep track of the stdout so that we can get the VM name\n              output << data\n            elsif type == :stderr\n              # Append the data so we can see the full view\n              total << data\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n              if lines.include?(\"OK.\")\n                # The progress of the import will be in the last line. Do a greedy\n                # regular expression to find what we're looking for.\n                if current = lines.last[/.+(\\d{2})%/, 1]\n                  current = current.to_i\n                  if current > last\n                    last = current\n                    yield current if block_given?\n                  end\n                end\n              end\n            end\n          end\n\n          # Find the name of the VM name\n          name = output[/Suggested VM name \"(.+?)\"/, 1]\n          if !name\n            @logger.error(\"Couldn't find VM name in the output.\")\n            return nil\n          end\n\n          output = execute(\"list\", \"vms\")\n          if existing_vm = output[/^\"#{Regexp.escape(name)}\" \\{(.+?)\\}$/, 1]\n            return existing_vm\n          end\n\n          nil\n        end\n\n        def read_forwarded_ports(uuid=nil, active_only=false)\n          uuid ||= @uuid\n\n          @logger.debug(\"read_forward_ports: uuid=#{uuid} active_only=#{active_only}\")\n\n          results = []\n          current_nic = nil\n          info = execute(\"showvminfo\", uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            # This is how we find the nic that a FP is attached to,\n            # since this comes first.\n            if nic = line[/^nic(\\d+)=\".+?\"$/, 1]\n              current_nic = nic.to_i\n            end\n\n            # If we care about active VMs only, then we check the state\n            # to verify the VM is running.\n            if active_only && (state = line[/^VMState=\"(.+?)\"$/, 1] and state != \"running\")\n              return []\n            end\n\n            # Parse out the forwarded port information\n            if matcher = /^Forwarding.+?=\"(.+?),.+?,.*?,(.+?),.*?,(.+?)\"$/.match(line)\n              result = [current_nic, matcher[1], matcher[2].to_i, matcher[3].to_i]\n              @logger.debug(\"  - #{result.inspect}\")\n              results << result\n            end\n          end\n\n          results\n        end\n\n        def read_bridged_interfaces\n          execute(\"list\", \"bridgedifs\").split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if name = line[/^Name:\\s+(.+?)$/, 1]\n                info[:name] = name\n              elsif ip = line[/^IPAddress:\\s+(.+?)$/, 1]\n                info[:ip] = ip\n              elsif netmask = line[/^NetworkMask:\\s+(.+?)$/, 1]\n                info[:netmask] = netmask\n              elsif status = line[/^Status:\\s+(.+?)$/, 1]\n                info[:status] = status\n              end\n            end\n\n            # Return the info to build up the results\n            info\n          end\n        end\n\n        def read_dhcp_servers\n          execute(\"list\", \"dhcpservers\", retryable: true).split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if network = line[/^NetworkName:\\s+HostInterfaceNetworking-(.+?)$/, 1]\n                info[:network]      = network\n                info[:network_name] = \"HostInterfaceNetworking-#{network}\"\n              elsif ip = line[/^IP:\\s+(.+?)$/, 1]\n                info[:ip] = ip\n              elsif netmask = line[/^NetworkMask:\\s+(.+?)$/, 1]\n                info[:netmask] = netmask\n              elsif lower = line[/^lowerIPAddress:\\s+(.+?)$/, 1]\n                info[:lower] = lower\n              elsif upper = line[/^upperIPAddress:\\s+(.+?)$/, 1]\n                info[:upper] = upper\n              end\n            end\n\n            info\n          end\n        end\n\n        def read_guest_additions_version\n          output = execute(\"guestproperty\", \"get\", @uuid, \"/VirtualBox/GuestAdd/Version\",\n                           retryable: true)\n          if value = output[/^Value: (.+?)$/, 1]\n            # Split the version by _ since some distro versions modify it\n            # to look like this: 4.1.2_ubuntu, and the distro part isn't\n            # too important.\n            return value.split(\"_\").first\n          end\n\n          return nil\n        end\n\n        def read_guest_ip(adapter_number)\n          ip = read_guest_property(\"/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP\")\n          if !valid_ip_address?(ip)\n            raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: \"/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP\"\n          end\n\n          return ip\n        end\n\n        def read_guest_property(property)\n          output = execute(\"guestproperty\", \"get\", @uuid, property)\n          if output =~ /^Value: (.+?)$/\n            $1.to_s\n          else\n            raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: property\n          end\n        end\n\n        def read_host_only_interfaces\n          execute(\"list\", \"hostonlyifs\", retryable: true).split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if name = line[/^Name:\\s+(.+?)$/, 1]\n                info[:name] = name\n              elsif ip = line[/^IPAddress:\\s+(.+?)$/, 1]\n                info[:ip] = ip\n              elsif netmask = line[/^NetworkMask:\\s+(.+?)$/, 1]\n                info[:netmask] = netmask\n              elsif line =~ /^IPV6Address:\\s+(.+?)$/\n                info[:ipv6] = $1.to_s.strip\n              elsif line =~ /^IPV6NetworkMaskPrefixLength:\\s+(.+?)$/\n                info[:ipv6_prefix] = $1.to_s.strip\n              elsif status = line[/^Status:\\s+(.+?)$/, 1]\n                info[:status] = status\n              elsif line =~ /^VBoxNetworkName:\\s+(.+?)$/\n                info[:display_name] = $1.to_s\n              end\n            end\n\n            info\n          end\n        end\n\n        def read_mac_address\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if mac = line[/^macaddress1=\"(.+?)\"$/, 1]\n              return mac\n            end\n          end\n\n          nil\n        end\n\n        def read_mac_addresses\n          macs = {}\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if matcher = /^macaddress(\\d+)=\"(.+?)\"$/.match(line)\n              adapter = matcher[1].to_i\n              mac = matcher[2].to_s\n              macs[adapter] = mac\n            end\n          end\n          macs\n        end\n\n        def read_machine_folder\n          execute(\"list\", \"systemproperties\", retryable: true).split(\"\\n\").each do |line|\n            if folder = line[/^Default machine folder:\\s+(.+?)$/i, 1]\n              return folder\n            end\n          end\n\n          nil\n        end\n\n        def read_network_interfaces\n          nics = {}\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if matcher = /^nic(\\d+)=\"(.+?)\"$/.match(line)\n              adapter = matcher[1].to_i\n              type    = matcher[2].to_sym\n\n              nics[adapter] ||= {}\n              nics[adapter][:type] = type\n            elsif matcher = /^hostonlyadapter(\\d+)=\"(.+?)\"$/.match(line)\n              adapter = matcher[1].to_i\n              network = matcher[2].to_s\n\n              nics[adapter] ||= {}\n              nics[adapter][:hostonly] = network\n            elsif matcher = /^bridgeadapter(\\d+)=\"(.+?)\"$/.match(line)\n              adapter = matcher[1].to_i\n              network = matcher[2].to_s\n\n              nics[adapter] ||= {}\n              nics[adapter][:bridge] = network\n            end\n          end\n\n          nics\n        end\n\n        def read_state\n          output = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          if output =~ /^name=\"<inaccessible>\"$/\n            return :inaccessible\n          elsif state = output[/^VMState=\"(.+?)\"$/, 1]\n            return state.to_sym\n          end\n\n          nil\n        end\n\n        def read_used_ports\n          ports = []\n          execute(\"list\", \"vms\", retryable: true).split(\"\\n\").each do |line|\n            if uuid = line[/^\".+?\" \\{(.+?)\\}$/, 1]\n              # Ignore our own used ports\n              next if uuid == @uuid\n\n              begin\n                read_forwarded_ports(uuid, true).each do |_, _, hostport, _|\n                  ports << hostport\n                end\n              rescue Vagrant::Errors::VBoxManageError => e\n                raise if !e.extra_data[:stderr].include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n                # VirtualBox could not find the vm. It may have been deleted\n                # by another process after we called 'vboxmanage list vms'? Ignore this error.\n              end\n            end\n          end\n\n          ports\n        end\n\n        def read_vms\n          results = {}\n          execute(\"list\", \"vms\", retryable: true).split(\"\\n\").each do |line|\n            if line =~ /^\"(.+?)\" \\{(.+?)\\}$/\n              results[$1.to_s] = $2.to_s\n            end\n          end\n\n          results\n        end\n\n        def reconfig_host_only(interface)\n          execute(\"hostonlyif\", \"ipconfig\", interface[:name],\n                  \"--ipv6\", interface[:ipv6])\n        end\n\n        def remove_dhcp_server(network_name)\n          execute(\"dhcpserver\", \"remove\", \"--netname\", network_name)\n        end\n\n        def set_mac_address(mac)\n          execute(\"modifyvm\", @uuid, \"--macaddress1\", mac)\n        end\n\n        def set_name(name)\n          execute(\"modifyvm\", @uuid, \"--name\", name)\n        end\n\n        def share_folders(folders)\n          folders.each do |folder|\n            args = [\"--name\",\n              folder[:name],\n              \"--hostpath\",\n              folder[:hostpath]]\n            args << \"--transient\" if folder.key?(:transient) && folder[:transient]\n\n            if folder[:SharedFoldersEnableSymlinksCreate]\n              # Enable symlinks on the shared folder\n              execute(\"setextradata\", @uuid, \"VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}\", \"1\")\n            end\n\n            # Add the shared folder\n            execute(\"sharedfolder\", \"add\", @uuid, *args)\n          end\n        end\n\n        def ssh_port(expected_port)\n          @logger.debug(\"Searching for SSH port: #{expected_port.inspect}\")\n\n          # Look for the forwarded port only by comparing the guest port\n          read_forwarded_ports.each do |_, _, hostport, guestport|\n            return hostport if guestport == expected_port\n          end\n\n          nil\n        end\n\n        def resume\n          @logger.debug(\"Resuming paused VM...\")\n          execute(\"controlvm\", @uuid, \"resume\")\n        end\n\n        def start(mode)\n          command = [\"startvm\", @uuid, \"--type\", mode.to_s]\n          r = raw(*command)\n\n          if r.exit_code == 0 || r.stdout =~ /VM \".+?\" has been successfully started/\n            # Some systems return an exit code 1 for some reason. For that\n            # we depend on the output.\n            return true\n          end\n\n          # If we reached this point then it didn't work out.\n          raise Vagrant::Errors::VBoxManageError,\n            command: command.inspect,\n            stderr: r.stderr\n        end\n\n        def suspend\n          execute(\"controlvm\", @uuid, \"savestate\")\n        end\n\n        def unshare_folders(names)\n          names.each do |name|\n            begin\n              execute(\n                \"sharedfolder\", \"remove\", @uuid,\n                \"--name\", name,\n                \"--transient\")\n\n              execute(\n                \"setextradata\", @uuid,\n                \"VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{name}\")\n            rescue Vagrant::Errors::VBoxManageError => e\n              if e.extra_data[:stderr].include?(\"VBOX_E_FILE_ERROR\")\n                # The folder doesn't exist. ignore.\n              else\n                raise\n              end\n            end\n          end\n        end\n\n        def valid_ip_address?(ip)\n          # Filter out invalid IP addresses\n          # GH-4658 VirtualBox can report an IP address of 0.0.0.0 for FreeBSD guests.\n          if ip == \"0.0.0.0\"\n            return false\n          else\n            return true\n          end\n        end\n\n        def verify!\n          # This command sometimes fails if kernel drivers aren't properly loaded\n          # so we just run the command and verify that it succeeded.\n          execute(\"list\", \"hostonlyifs\")\n        end\n\n        def verify_image(path)\n          r = raw(\"import\", path.to_s, \"--dry-run\")\n          return r.exit_code == 0\n        end\n\n        def vm_exists?(uuid)\n          5.times do |i|\n            result = raw(\"showvminfo\", uuid)\n            return true if result.exit_code == 0\n\n            # If vboxmanage returned VBOX_E_OBJECT_NOT_FOUND,\n            # then the vm truly does not exist. Any other error might be transient\n            return false if result.stderr.include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n            # Sleep a bit though to give VirtualBox time to fix itself\n            sleep 2\n          end\n\n          # If we reach this point, it means that we consistently got the\n          # failure, do a standard vboxmanage now. This will raise an\n          # exception if it fails again.\n          execute(\"showvminfo\", uuid)\n          return true\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/version_4_2.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'log4r'\n\nrequire \"vagrant/util/platform\"\n\nrequire File.expand_path(\"../base\", __FILE__)\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      # Driver for VirtualBox 4.2.x\n      class Version_4_2 < Base\n        def initialize(uuid)\n          super()\n\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox_4_2\")\n          @uuid = uuid\n        end\n\n        def clear_forwarded_ports\n          args = []\n          read_forwarded_ports(@uuid).each do |nic, name, _, _|\n            args.concat([\"--natpf#{nic}\", \"delete\", name])\n          end\n\n          execute(\"modifyvm\", @uuid, *args) if !args.empty?\n        end\n\n        def clear_shared_folders\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if line =~ /^SharedFolderNameMachineMapping\\d+=\"(.+?)\"$/\n              execute(\"sharedfolder\", \"remove\", @uuid, \"--name\", $1.to_s)\n            end\n          end\n        end\n\n        def create_dhcp_server(network, options)\n          execute(\"dhcpserver\", \"add\", \"--ifname\", network,\n                  \"--ip\", options[:dhcp_ip],\n                  \"--netmask\", options[:netmask],\n                  \"--lowerip\", options[:dhcp_lower],\n                  \"--upperip\", options[:dhcp_upper],\n                  \"--enable\")\n        end\n\n        def create_host_only_network(options)\n          # Create the interface\n          execute(\"hostonlyif\", \"create\") =~ /^Interface '(.+?)' was successfully created$/\n          name = $1.to_s\n\n          # Get the IP so we can determine v4 vs v6\n          ip = IPAddr.new(options[:adapter_ip])\n\n          # Configure\n          if ip.ipv4?\n            execute(\"hostonlyif\", \"ipconfig\", name,\n                    \"--ip\", options[:adapter_ip],\n                    \"--netmask\", options[:netmask])\n          elsif ip.ipv6?\n            execute(\"hostonlyif\", \"ipconfig\", name,\n                    \"--ipv6\", options[:adapter_ip],\n                    \"--netmasklengthv6\", options[:netmask].to_s)\n          end\n\n          # Return the details\n          return {\n            name: name,\n            ip:   options[:adapter_ip],\n            netmask: options[:netmask],\n            dhcp: nil\n          }\n        end\n\n        def delete\n          execute(\"unregistervm\", @uuid, \"--delete\")\n        end\n\n        def delete_unused_host_only_networks\n          networks = []\n          execute(\"list\", \"hostonlyifs\").split(\"\\n\").each do |line|\n            networks << $1.to_s if line =~ /^Name:\\s+(.+?)$/\n          end\n\n          execute(\"list\", \"vms\").split(\"\\n\").each do |line|\n            if line =~ /^\".+?\"\\s+\\{(.+?)\\}$/\n              begin\n                info = execute(\"showvminfo\", $1.to_s, \"--machinereadable\", retryable: true)\n                info.split(\"\\n\").each do |inner_line|\n                  if inner_line =~ /^hostonlyadapter\\d+=\"(.+?)\"$/\n                    networks.delete($1.to_s)\n                  end\n                end\n              rescue Vagrant::Errors::VBoxManageError => e\n                raise if !e.extra_data[:stderr].include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n                # VirtualBox could not find the vm. It may have been deleted\n                # by another process after we called 'vboxmanage list vms'? Ignore this error.\n              end\n            end\n          end\n\n          networks.each do |name|\n            # First try to remove any DHCP servers attached. We use `raw` because\n            # it is okay if this fails. It usually means that a DHCP server was\n            # never attached.\n            raw(\"dhcpserver\", \"remove\", \"--ifname\", name)\n\n            # Delete the actual host only network interface.\n            execute(\"hostonlyif\", \"remove\", name)\n          end\n        end\n\n        def discard_saved_state\n          execute(\"discardstate\", @uuid)\n        end\n\n        def enable_adapters(adapters)\n          args = []\n          adapters.each do |adapter|\n            args.concat([\"--nic#{adapter[:adapter]}\", adapter[:type].to_s])\n\n            if adapter[:bridge]\n              args.concat([\"--bridgeadapter#{adapter[:adapter]}\",\n                          adapter[:bridge], \"--cableconnected#{adapter[:adapter]}\", \"on\"])\n            end\n\n            if adapter[:hostonly]\n              args.concat([\"--hostonlyadapter#{adapter[:adapter]}\",\n                          adapter[:hostonly], \"--cableconnected#{adapter[:adapter]}\", \"on\"])\n            end\n\n            if adapter[:intnet]\n              args.concat([\"--intnet#{adapter[:adapter]}\",\n                          adapter[:intnet], \"--cableconnected#{adapter[:adapter]}\", \"on\"])\n            end\n\n            if adapter[:mac_address]\n              args.concat([\"--macaddress#{adapter[:adapter]}\",\n                          adapter[:mac_address]])\n            end\n\n            if adapter[:nic_type]\n              args.concat([\"--nictype#{adapter[:adapter]}\", adapter[:nic_type].to_s])\n            end\n          end\n\n          execute(\"modifyvm\", @uuid, *args)\n        end\n\n        def execute_command(command)\n          execute(*command)\n        end\n\n        def export(path)\n          execute(\"export\", @uuid, \"--output\", path.to_s)\n        end\n\n        def forward_ports(ports)\n          args = []\n          ports.each do |options|\n            pf_builder = [options[:name],\n              options[:protocol] || \"tcp\",\n              options[:hostip] || \"\",\n              options[:hostport],\n              options[:guestip] || \"\",\n              options[:guestport]]\n\n            args.concat([\"--natpf#{options[:adapter] || 1}\",\n                        pf_builder.join(\",\")])\n          end\n\n          execute(\"modifyvm\", @uuid, *args) if !args.empty?\n        end\n\n        def halt\n          execute(\"controlvm\", @uuid, \"poweroff\")\n        end\n\n        def import(ovf)\n          ovf = Vagrant::Util::Platform.cygwin_windows_path(ovf)\n\n          output = \"\"\n          total = \"\"\n          last  = 0\n\n          output = execute(\"import\", \"-n\", ovf)\n          output =~ /Suggested VM name \"(.+?)\"/\n          suggested_name = $1.to_s\n          specified_name = \"#{suggested_name}_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}\" #Millisecond + Random\n\n          #Build the specified name param list\n          name_params = Array.new\n          name_params << \"--vsys\" << \"0\" << \"--vmname\" << specified_name\n\n          #Extract the disks list and build the disk target params\n          disk_params = Array.new\n          disks = output.scan(/(\\d+): Hard disk image: source image=.+, target path=(.+),/)\n          disks.each do |unit_num, path|\n            disk_params << \"--vsys\"\n            disk_params << \"0\"  #Derive vsys num .. do we support OVF's with multiple machines?\n            disk_params << \"--unit\"\n            disk_params << unit_num\n            disk_params << \"--disk\"\n            if Vagrant::Util::Platform.windows?\n              # we use the block form of sub here to ensure that if the specified_name happens to end with a number (which is fairly likely) then\n              # we won't end up having the character sequence of a \\ followed by a number be interpreted as a back reference.  For example, if\n              # specified_name were \"abc123\", then \"\\\\abc123\\\\\".reverse would be \"\\\\321cba\\\\\", and the \\3 would be treated as a back reference by the sub\n              disk_params << path.reverse.sub(\"\\\\#{suggested_name}\\\\\".reverse) { \"\\\\#{specified_name}\\\\\".reverse }.reverse # Replace only last occurrence\n            else\n              disk_params << path.reverse.sub(\"/#{suggested_name}/\".reverse, \"/#{specified_name}/\".reverse).reverse # Replace only last occurrence\n            end\n          end\n\n          execute(\"import\", ovf , *name_params, *disk_params) do |type, data|\n            if type == :stdout\n              # Keep track of the stdout so that we can get the VM name\n              output << data\n            elsif type == :stderr\n              # Append the data so we can see the full view\n              total << data\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n              if lines.include?(\"OK.\")\n                # The progress of the import will be in the last line. Do a greedy\n                # regular expression to find what we're looking for.\n                if lines.last =~ /.+(\\d{2})%/\n                  current = $1.to_i\n                  if current > last\n                    last = current\n                    yield current if block_given?\n                  end\n                end\n              end\n            end\n          end\n\n          output = execute(\"list\", \"vms\")\n\n          if output =~ /^\"#{Regexp.escape(specified_name)}\" \\{(.+?)\\}$/\n            return $1.to_s\n          end\n\n          nil\n        end\n\n        def max_network_adapters\n          8\n        end\n\n        def read_forwarded_ports(uuid=nil, active_only=false)\n          uuid ||= @uuid\n\n          @logger.debug(\"read_forward_ports: uuid=#{uuid} active_only=#{active_only}\")\n\n          results = []\n          current_nic = nil\n          info = execute(\"showvminfo\", uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            # This is how we find the nic that a FP is attached to,\n            # since this comes first.\n            current_nic = $1.to_i if line =~ /^nic(\\d+)=\".+?\"$/\n\n              # If we care about active VMs only, then we check the state\n              # to verify the VM is running.\n              if active_only && line =~ /^VMState=\"(.+?)\"$/ && $1.to_s != \"running\"\n                return []\n              end\n\n            # Parse out the forwarded port information\n            if line =~ /^Forwarding.+?=\"(.+?),.+?,.*?,(.+?),.*?,(.+?)\"$/\n              result = [current_nic, $1.to_s, $2.to_i, $3.to_i]\n              @logger.debug(\"  - #{result.inspect}\")\n              results << result\n            end\n          end\n\n          results\n        end\n\n        def read_bridged_interfaces\n          execute(\"list\", \"bridgedifs\").split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if line =~ /^Name:\\s+(.+?)$/\n                info[:name] = $1.to_s\n              elsif line =~ /^IPAddress:\\s+(.+?)$/\n                info[:ip] = $1.to_s\n              elsif line =~ /^NetworkMask:\\s+(.+?)$/\n                info[:netmask] = $1.to_s\n              elsif line =~ /^Status:\\s+(.+?)$/\n                info[:status] = $1.to_s\n              end\n            end\n\n            # Return the info to build up the results\n            info\n          end\n        end\n\n        def read_dhcp_servers\n          execute(\"list\", \"dhcpservers\", retryable: true).split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if network = line[/^NetworkName:\\s+HostInterfaceNetworking-(.+?)$/, 1]\n                info[:network]      = network\n                info[:network_name] = \"HostInterfaceNetworking-#{network}\"\n              elsif ip = line[/^IP:\\s+(.+?)$/, 1]\n                info[:ip] = ip\n              elsif netmask = line[/^NetworkMask:\\s+(.+?)$/, 1]\n                info[:netmask] = netmask\n              elsif lower = line[/^lowerIPAddress:\\s+(.+?)$/, 1]\n                info[:lower] = lower\n              elsif upper = line[/^upperIPAddress:\\s+(.+?)$/, 1]\n                info[:upper] = upper\n              end\n            end\n\n            info\n          end\n        end\n\n        def read_guest_additions_version\n          output = execute(\"guestproperty\", \"get\", @uuid, \"/VirtualBox/GuestAdd/Version\",\n                           retryable: true)\n          if output =~ /^Value: (.+?)$/\n            # Split the version by _ since some distro versions modify it\n            # to look like this: 4.1.2_ubuntu, and the distro part isn't\n            # too important.\n            value = $1.to_s\n            return value.split(\"_\").first\n          end\n\n          # If we can't get the guest additions version by guest property, try\n          # to get it from the VM info itself.\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            return $1.to_s if line =~ /^GuestAdditionsVersion=\"(.+?)\"$/\n          end\n\n          return nil\n        end\n\n        def read_guest_ip(adapter_number)\n          ip = read_guest_property(\"/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP\")\n          if !valid_ip_address?(ip)\n            raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: \"/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP\"\n          end\n\n          return ip\n        end\n\n        def read_guest_property(property)\n          output = execute(\"guestproperty\", \"get\", @uuid, property)\n          if output =~ /^Value: (.+?)$/\n            $1.to_s\n          else\n            raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: property\n          end\n        end\n\n        def read_host_only_interfaces\n          execute(\"list\", \"hostonlyifs\", retryable: true).split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if line =~ /^Name:\\s+(.+?)$/\n                info[:name] = $1.to_s\n              elsif line =~ /^IPAddress:\\s+(.+?)$/\n                info[:ip] = $1.to_s\n              elsif line =~ /^NetworkMask:\\s+(.+?)$/\n                info[:netmask] = $1.to_s\n              elsif line =~ /^IPV6Address:\\s+(.+?)$/\n                info[:ipv6] = $1.to_s.strip\n              elsif line =~ /^IPV6NetworkMaskPrefixLength:\\s+(.+?)$/\n                info[:ipv6_prefix] = $1.to_s.strip\n              elsif line =~ /^Status:\\s+(.+?)$/\n                info[:status] = $1.to_s\n              elsif line =~ /^VBoxNetworkName:\\s+(.+?)$/\n                info[:display_name] = $1.to_s\n              end\n            end\n\n            info\n          end\n        end\n\n        def read_mac_address\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            return $1.to_s if line =~ /^macaddress1=\"(.+?)\"$/\n          end\n\n          nil\n        end\n\n        def read_mac_addresses\n          macs = {}\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if matcher = /^macaddress(\\d+)=\"(.+?)\"$/.match(line)\n              adapter = matcher[1].to_i\n              mac = matcher[2].to_s\n              macs[adapter] = mac\n            end\n          end\n          macs\n        end\n\n        def read_machine_folder\n          execute(\"list\", \"systemproperties\", retryable: true).split(\"\\n\").each do |line|\n            if line =~ /^Default machine folder:\\s+(.+?)$/i\n              return $1.to_s\n            end\n          end\n\n          nil\n        end\n\n        def read_network_interfaces\n          nics = {}\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if line =~ /^nic(\\d+)=\"(.+?)\"$/\n              adapter = $1.to_i\n              type    = $2.to_sym\n\n              nics[adapter] ||= {}\n              nics[adapter][:type] = type\n            elsif line =~ /^hostonlyadapter(\\d+)=\"(.+?)\"$/\n              adapter = $1.to_i\n              network = $2.to_s\n\n              nics[adapter] ||= {}\n              nics[adapter][:hostonly] = network\n            elsif line =~ /^bridgeadapter(\\d+)=\"(.+?)\"$/\n              adapter = $1.to_i\n              network = $2.to_s\n\n              nics[adapter] ||= {}\n              nics[adapter][:bridge] = network\n            end\n          end\n\n          nics\n        end\n\n        def read_state\n          output = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          if output =~ /^name=\"<inaccessible>\"$/\n            return :inaccessible\n          elsif output =~ /^VMState=\"(.+?)\"$/\n            return $1.to_sym\n          end\n\n          nil\n        end\n\n        def read_used_ports\n          ports = []\n          execute(\"list\", \"vms\", retryable: true).split(\"\\n\").each do |line|\n            if line =~ /^\".+?\" \\{(.+?)\\}$/\n              uuid = $1.to_s\n\n              # Ignore our own used ports\n              next if uuid == @uuid\n\n              begin\n                read_forwarded_ports(uuid, true).each do |_, _, hostport, _|\n                  ports << hostport\n                end\n              rescue Vagrant::Errors::VBoxManageError => e\n                raise if !e.extra_data[:stderr].include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n                # VirtualBox could not find the vm. It may have been deleted\n                # by another process after we called 'vboxmanage list vms'? Ignore this error.\n              end\n            end\n          end\n\n          ports\n        end\n\n        def read_vms\n          results = {}\n          execute(\"list\", \"vms\", retryable: true).split(\"\\n\").each do |line|\n            if line =~ /^\"(.+?)\" \\{(.+?)\\}$/\n              results[$1.to_s] = $2.to_s\n            end\n          end\n\n          results\n        end\n\n        def reconfig_host_only(interface)\n          execute(\"hostonlyif\", \"ipconfig\", interface[:name],\n                  \"--ipv6\", interface[:ipv6])\n        end\n\n        def remove_dhcp_server(network_name)\n          execute(\"dhcpserver\", \"remove\", \"--netname\", network_name)\n        end\n\n        def set_mac_address(mac)\n          execute(\"modifyvm\", @uuid, \"--macaddress1\", mac)\n        end\n\n        def set_name(name)\n          execute(\"modifyvm\", @uuid, \"--name\", name, retryable: true)\n        end\n\n        def share_folders(folders)\n          folders.each do |folder|\n            args = [\"--name\",\n              folder[:name],\n              \"--hostpath\",\n              folder[:hostpath]]\n            args << \"--transient\" if folder.key?(:transient) && folder[:transient]\n\n            if folder[:SharedFoldersEnableSymlinksCreate]\n              # Enable symlinks on the shared folder\n              execute(\"setextradata\", @uuid, \"VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}\", \"1\")\n            end\n\n            # Add the shared folder\n            execute(\"sharedfolder\", \"add\", @uuid, *args)\n          end\n        end\n\n        def ssh_port(expected_port)\n          @logger.debug(\"Searching for SSH port: #{expected_port.inspect}\")\n\n          # Look for the forwarded port only by comparing the guest port\n          read_forwarded_ports.each do |_, _, hostport, guestport|\n            return hostport if guestport == expected_port\n          end\n\n          nil\n        end\n\n        def resume\n          @logger.debug(\"Resuming paused VM...\")\n          execute(\"controlvm\", @uuid, \"resume\")\n        end\n\n        def start(mode)\n          command = [\"startvm\", @uuid, \"--type\", mode.to_s]\n          r = raw(*command)\n\n          if r.exit_code == 0 || r.stdout =~ /VM \".+?\" has been successfully started/\n            # Some systems return an exit code 1 for some reason. For that\n            # we depend on the output.\n            return true\n          end\n\n          # If we reached this point then it didn't work out.\n          raise Vagrant::Errors::VBoxManageError,\n            command: command.inspect,\n            stderr: r.stderr\n        end\n\n        def suspend\n          execute(\"controlvm\", @uuid, \"savestate\")\n        end\n\n        def unshare_folders(names)\n          names.each do |name|\n            begin\n              execute(\n                \"sharedfolder\", \"remove\", @uuid,\n                \"--name\", name,\n                \"--transient\")\n\n            execute(\n              \"setextradata\", @uuid,\n              \"VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{name}\")\n            rescue Vagrant::Errors::VBoxManageError => e\n              if e.extra_data[:stderr].include?(\"VBOX_E_FILE_ERROR\")\n                # The folder doesn't exist. ignore.\n              else\n                raise\n              end\n            end\n          end\n        end\n\n        def valid_ip_address?(ip)\n          # Filter out invalid IP addresses\n          # GH-4658 VirtualBox can report an IP address of 0.0.0.0 for FreeBSD guests.\n          if ip == \"0.0.0.0\"\n            return false\n          else\n            return true\n          end\n        end\n\n        def verify!\n          # This command sometimes fails if kernel drivers aren't properly loaded\n          # so we just run the command and verify that it succeeded.\n          execute(\"list\", \"hostonlyifs\")\n        end\n\n        def verify_image(path)\n          r = raw(\"import\", path.to_s, \"--dry-run\")\n          return r.exit_code == 0\n        end\n\n        def vm_exists?(uuid)\n          5.times do |i|\n            result = raw(\"showvminfo\", uuid)\n            return true if result.exit_code == 0\n\n            # If vboxmanage returned VBOX_E_OBJECT_NOT_FOUND,\n            # then the vm truly does not exist. Any other error might be transient\n            return false if result.stderr.include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n            # Sleep a bit though to give VirtualBox time to fix itself\n            sleep 2\n          end\n\n          # If we reach this point, it means that we consistently got the\n          # failure, do a standard vboxmanage now. This will raise an\n          # exception if it fails again.\n          execute(\"showvminfo\", uuid)\n          return true\n        end\n\n        def create_snapshot(machine_id, snapshot_name)\n          execute(\"snapshot\", machine_id, \"take\", snapshot_name)\n        end\n\n        def delete_snapshot(machine_id, snapshot_name)\n          # Start with 0%\n          last = 0\n          total = \"\"\n          yield 0 if block_given?\n\n          # Snapshot and report the % progress\n          execute(\"snapshot\", machine_id, \"delete\", snapshot_name) do |type, data|\n            if type == :stderr\n              # Append the data so we can see the full view\n              total << data.gsub(\"\\r\", \"\")\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n\n              # The progress of the import will be in the last line. Do a greedy\n              # regular expression to find what we're looking for.\n              match = /.+(\\d{2})%/.match(lines.last)\n              if match\n                current = match[1].to_i\n                if current > last\n                  last = current\n                  yield current if block_given?\n                end\n              end\n            end\n          end\n        end\n\n        def list_snapshots(machine_id)\n          output = execute(\n            \"snapshot\", machine_id, \"list\", \"--machinereadable\",\n            retryable: true)\n\n          result = []\n          output.split(\"\\n\").each do |line|\n            if line =~ /^SnapshotName.*?=\"(.+?)\"$/i\n              result << $1.to_s\n            end\n          end\n\n          result.sort\n        rescue Vagrant::Errors::VBoxManageError => e\n          d = e.extra_data\n          return [] if d[:stderr].include?(\"does not have\") || d[:stdout].include?(\"does not have\")\n          raise\n        end\n\n        def restore_snapshot(machine_id, snapshot_name)\n          # Start with 0%\n          last = 0\n          total = \"\"\n          yield 0 if block_given?\n\n          execute(\"snapshot\", machine_id, \"restore\", snapshot_name) do |type, data|\n            if type == :stderr\n              # Append the data so we can see the full view\n              total << data.gsub(\"\\r\", \"\")\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n\n              # The progress of the import will be in the last line. Do a greedy\n              # regular expression to find what we're looking for.\n              match = /.+(\\d{2})%/.match(lines.last)\n              if match\n                current = match[1].to_i\n                if current > last\n                  last = current\n                  yield current if block_given?\n                end\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/version_4_3.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'ipaddr'\nrequire 'log4r'\n\nrequire \"vagrant/util/platform\"\n\nrequire File.expand_path(\"../base\", __FILE__)\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      # Driver for VirtualBox 4.3.x\n      class Version_4_3 < Base\n        def initialize(uuid)\n          super()\n\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox_4_3\")\n          @uuid = uuid\n        end\n\n        def clear_forwarded_ports\n          args = []\n          read_forwarded_ports(@uuid).each do |nic, name, _, _|\n            args.concat([\"--natpf#{nic}\", \"delete\", name])\n          end\n\n          execute(\"modifyvm\", @uuid, *args) if !args.empty?\n        end\n\n        def clear_shared_folders\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if line =~ /^SharedFolderNameMachineMapping\\d+=\"(.+?)\"$/\n              execute(\"sharedfolder\", \"remove\", @uuid, \"--name\", $1.to_s)\n            end\n          end\n        end\n\n        def clonevm(master_id, snapshot_name)\n          machine_name = \"temp_clone_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}\"\n          args = [\"--register\", \"--name\", machine_name]\n          if snapshot_name\n            args += [\"--snapshot\", snapshot_name, \"--options\", \"link\"]\n          end\n\n          execute(\"clonevm\", master_id, *args)\n          return get_machine_id(machine_name)\n        end\n\n        def create_dhcp_server(network, options)\n          execute(\"dhcpserver\", \"add\", \"--ifname\", network,\n                  \"--ip\", options[:dhcp_ip],\n                  \"--netmask\", options[:netmask],\n                  \"--lowerip\", options[:dhcp_lower],\n                  \"--upperip\", options[:dhcp_upper],\n                  \"--enable\")\n        end\n\n        def create_host_only_network(options)\n          # Create the interface\n          execute(\"hostonlyif\", \"create\") =~ /^Interface '(.+?)' was successfully created$/\n          name = $1.to_s\n\n          # Get the IP so we can determine v4 vs v6\n          ip = IPAddr.new(options[:adapter_ip])\n\n          # Configure\n          if ip.ipv4?\n            execute(\"hostonlyif\", \"ipconfig\", name,\n                    \"--ip\", options[:adapter_ip],\n                    \"--netmask\", options[:netmask])\n          elsif ip.ipv6?\n            execute(\"hostonlyif\", \"ipconfig\", name,\n                    \"--ipv6\", options[:adapter_ip],\n                    \"--netmasklengthv6\", options[:netmask].to_s)\n          end\n\n          # Return the details\n          return {\n            name: name,\n            ip:   options[:adapter_ip],\n            netmask: options[:netmask],\n            dhcp: nil\n          }\n        end\n\n        def reconfig_host_only(interface)\n          execute(\"hostonlyif\", \"ipconfig\", interface[:name],\n                  \"--ipv6\", interface[:ipv6])\n        end\n\n        def create_snapshot(machine_id, snapshot_name)\n          execute(\"snapshot\", machine_id, \"take\", snapshot_name)\n        end\n\n        def delete_snapshot(machine_id, snapshot_name)\n          # Start with 0%\n          last = 0\n          total = \"\"\n          yield 0 if block_given?\n\n          # Snapshot and report the % progress\n          execute(\"snapshot\", machine_id, \"delete\", snapshot_name) do |type, data|\n            if type == :stderr\n              # Append the data so we can see the full view\n              total << data.gsub(\"\\r\", \"\")\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n\n              # The progress of the import will be in the last line. Do a greedy\n              # regular expression to find what we're looking for.\n              match = /.+(\\d{2})%/.match(lines.last)\n              if match\n                current = match[1].to_i\n                if current > last\n                  last = current\n                  yield current if block_given?\n                end\n              end\n            end\n          end\n        end\n\n        def list_snapshots(machine_id)\n          output = execute(\n            \"snapshot\", machine_id, \"list\", \"--machinereadable\",\n            retryable: true)\n\n          result = []\n          output.split(\"\\n\").each do |line|\n            if line =~ /^SnapshotName.*?=\"(.+?)\"$/i\n              result << $1.to_s\n            end\n          end\n\n          result.sort\n        rescue Vagrant::Errors::VBoxManageError => e\n          d = e.extra_data\n          return [] if d[:stderr].include?(\"does not have\") || d[:stdout].include?(\"does not have\")\n          raise\n        end\n\n        def restore_snapshot(machine_id, snapshot_name)\n          # Start with 0%\n          last = 0\n          total = \"\"\n          yield 0 if block_given?\n\n          execute(\"snapshot\", machine_id, \"restore\", snapshot_name) do |type, data|\n            if type == :stderr\n              # Append the data so we can see the full view\n              total << data.gsub(\"\\r\", \"\")\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n\n              # The progress of the import will be in the last line. Do a greedy\n              # regular expression to find what we're looking for.\n              match = /.+(\\d{2})%/.match(lines.last)\n              if match\n                current = match[1].to_i\n                if current > last\n                  last = current\n                  yield current if block_given?\n                end\n              end\n            end\n          end\n        end\n\n        def delete\n          execute(\"unregistervm\", @uuid, \"--delete\")\n        end\n\n        def delete_unused_host_only_networks\n          networks = []\n          execute(\"list\", \"hostonlyifs\", retryable: true).split(\"\\n\").each do |line|\n            networks << $1.to_s if line =~ /^Name:\\s+(.+?)$/\n          end\n\n          execute(\"list\", \"vms\", retryable: true).split(\"\\n\").each do |line|\n            if line =~ /^\".+?\"\\s+\\{(.+?)\\}$/\n              begin\n                info = execute(\"showvminfo\", $1.to_s, \"--machinereadable\", retryable: true)\n                info.split(\"\\n\").each do |inner_line|\n                  if inner_line =~ /^hostonlyadapter\\d+=\"(.+?)\"$/\n                    networks.delete($1.to_s)\n                  end\n                end\n              rescue Vagrant::Errors::VBoxManageError => e\n                raise if !e.extra_data[:stderr].include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n                # VirtualBox could not find the vm. It may have been deleted\n                # by another process after we called 'vboxmanage list vms'? Ignore this error.\n              end\n            end\n          end\n\n          networks.each do |name|\n            # First try to remove any DHCP servers attached. We use `raw` because\n            # it is okay if this fails. It usually means that a DHCP server was\n            # never attached.\n            raw(\"dhcpserver\", \"remove\", \"--ifname\", name)\n\n            # Delete the actual host only network interface.\n            execute(\"hostonlyif\", \"remove\", name)\n          end\n        end\n\n        def discard_saved_state\n          execute(\"discardstate\", @uuid)\n        end\n\n        def enable_adapters(adapters)\n          args = []\n          adapters.each do |adapter|\n            args.concat([\"--nic#{adapter[:adapter]}\", adapter[:type].to_s])\n\n            if adapter[:bridge]\n              args.concat([\"--bridgeadapter#{adapter[:adapter]}\",\n                          adapter[:bridge], \"--cableconnected#{adapter[:adapter]}\", \"on\"])\n            end\n\n            if adapter[:hostonly]\n              args.concat([\"--hostonlyadapter#{adapter[:adapter]}\",\n                          adapter[:hostonly], \"--cableconnected#{adapter[:adapter]}\", \"on\"])\n            end\n\n            if adapter[:intnet]\n              args.concat([\"--intnet#{adapter[:adapter]}\",\n                          adapter[:intnet], \"--cableconnected#{adapter[:adapter]}\", \"on\"])\n            end\n\n            if adapter[:mac_address]\n              args.concat([\"--macaddress#{adapter[:adapter]}\",\n                          adapter[:mac_address]])\n            end\n\n            if adapter[:nic_type]\n              args.concat([\"--nictype#{adapter[:adapter]}\", adapter[:nic_type].to_s])\n            end\n          end\n\n          execute(\"modifyvm\", @uuid, *args)\n        end\n\n        def execute_command(command)\n          execute(*command)\n        end\n\n        def export(path)\n          execute(\"export\", @uuid, \"--output\", path.to_s)\n        end\n\n        def forward_ports(ports)\n          args = []\n          ports.each do |options|\n            pf_builder = [options[:name],\n              options[:protocol] || \"tcp\",\n              options[:hostip] || \"\",\n              options[:hostport],\n              options[:guestip] || \"\",\n              options[:guestport]]\n\n            args.concat([\"--natpf#{options[:adapter] || 1}\",\n                        pf_builder.join(\",\")])\n          end\n\n          execute(\"modifyvm\", @uuid, *args) if !args.empty?\n        end\n\n        def get_machine_id(machine_name)\n          output = execute(\"list\", \"vms\", retryable: true)\n          match = /^\"#{Regexp.escape(machine_name)}\" \\{(.+?)\\}$/.match(output)\n          return match[1].to_s if match\n          nil\n        end\n\n        def halt\n          execute(\"controlvm\", @uuid, \"poweroff\")\n        end\n\n        def import(ovf)\n          ovf = Vagrant::Util::Platform.cygwin_windows_path(ovf)\n\n          output = \"\"\n          total = \"\"\n          last  = 0\n\n          # Dry-run the import to get the suggested name and path\n          @logger.debug(\"Doing dry-run import to determine parallel-safe name...\")\n          output = execute(\"import\", \"-n\", ovf)\n          result = /Suggested VM name \"(.+?)\"/.match(output)\n          if !result\n            raise Vagrant::Errors::VirtualBoxNoName, output: output\n          end\n          suggested_name = result[1].to_s\n\n          # Append millisecond plus a random to the path in case we're\n          # importing the same box elsewhere.\n          specified_name = \"#{suggested_name}_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}\"\n          @logger.debug(\"-- Parallel safe name: #{specified_name}\")\n\n          # Build the specified name param list\n          name_params = [\n            \"--vsys\", \"0\",\n            \"--vmname\", specified_name,\n          ]\n\n          # Extract the disks list and build the disk target params\n          disk_params = []\n          disks = output.scan(/(\\d+): Hard disk image: source image=.+, target path=(.+),/)\n          disks.each do |unit_num, path|\n            disk_params << \"--vsys\"\n            disk_params << \"0\"\n            disk_params << \"--unit\"\n            disk_params << unit_num\n            disk_params << \"--disk\"\n            if Vagrant::Util::Platform.windows?\n              # we use the block form of sub here to ensure that if the specified_name happens to end with a number (which is fairly likely) then\n              # we won't end up having the character sequence of a \\ followed by a number be interpreted as a back reference.  For example, if\n              # specified_name were \"abc123\", then \"\\\\abc123\\\\\".reverse would be \"\\\\321cba\\\\\", and the \\3 would be treated as a back reference by the sub\n              disk_params << path.reverse.sub(\"\\\\#{suggested_name}\\\\\".reverse) { \"\\\\#{specified_name}\\\\\".reverse }.reverse # Replace only last occurrence\n            else\n              disk_params << path.reverse.sub(\"/#{suggested_name}/\".reverse, \"/#{specified_name}/\".reverse).reverse # Replace only last occurrence\n            end\n          end\n\n          execute(\"import\", ovf , *name_params, *disk_params) do |type, data|\n            if type == :stdout\n              # Keep track of the stdout so that we can get the VM name\n              output << data\n            elsif type == :stderr\n              # Append the data so we can see the full view\n              total << data.gsub(\"\\r\", \"\")\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n              if lines.include?(\"OK.\")\n                # The progress of the import will be in the last line. Do a greedy\n                # regular expression to find what we're looking for.\n                match = /.+(\\d{2})%/.match(lines.last)\n                if match\n                  current = match[1].to_i\n                  if current > last\n                    last = current\n                    yield current if block_given?\n                  end\n                end\n              end\n            end\n          end\n\n          return get_machine_id specified_name\n        end\n\n        def max_network_adapters\n          8\n        end\n\n        def read_forwarded_ports(uuid=nil, active_only=false)\n          uuid ||= @uuid\n\n          @logger.debug(\"read_forward_ports: uuid=#{uuid} active_only=#{active_only}\")\n\n          results = []\n          current_nic = nil\n          info = execute(\"showvminfo\", uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            # This is how we find the nic that a FP is attached to,\n            # since this comes first.\n            current_nic = $1.to_i if line =~ /^nic(\\d+)=\".+?\"$/\n\n              # If we care about active VMs only, then we check the state\n              # to verify the VM is running.\n              if active_only && line =~ /^VMState=\"(.+?)\"$/ && $1.to_s != \"running\"\n                return []\n              end\n\n            # Parse out the forwarded port information\n            if line =~ /^Forwarding.+?=\"(.+?),.+?,.*?,(.+?),.*?,(.+?)\"$/\n              result = [current_nic, $1.to_s, $2.to_i, $3.to_i]\n              @logger.debug(\"  - #{result.inspect}\")\n              results << result\n            end\n          end\n\n          results\n        end\n\n        def read_bridged_interfaces\n          execute(\"list\", \"bridgedifs\").split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if line =~ /^Name:\\s+(.+?)$/\n                info[:name] = $1.to_s\n              elsif line =~ /^IPAddress:\\s+(.+?)$/\n                info[:ip] = $1.to_s\n              elsif line =~ /^NetworkMask:\\s+(.+?)$/\n                info[:netmask] = $1.to_s\n              elsif line =~ /^Status:\\s+(.+?)$/\n                info[:status] = $1.to_s\n              end\n            end\n\n            # Return the info to build up the results\n            info\n          end\n        end\n\n        def read_dhcp_servers\n          execute(\"list\", \"dhcpservers\", retryable: true).split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if network = line[/^NetworkName:\\s+HostInterfaceNetworking-(.+?)$/, 1]\n                info[:network]      = network\n                info[:network_name] = \"HostInterfaceNetworking-#{network}\"\n              elsif ip = line[/^IP:\\s+(.+?)$/, 1]\n                info[:ip] = ip\n              elsif netmask = line[/^NetworkMask:\\s+(.+?)$/, 1]\n                info[:netmask] = netmask\n              elsif lower = line[/^lowerIPAddress:\\s+(.+?)$/, 1]\n                info[:lower] = lower\n              elsif upper = line[/^upperIPAddress:\\s+(.+?)$/, 1]\n                info[:upper] = upper\n              end\n            end\n\n            info\n          end\n        end\n\n        def read_guest_additions_version\n          output = execute(\"guestproperty\", \"get\", @uuid, \"/VirtualBox/GuestAdd/Version\",\n                           retryable: true)\n          if output =~ /^Value: (.+?)$/\n            # Split the version by _ since some distro versions modify it\n            # to look like this: 4.1.2_ubuntu, and the distro part isn't\n            # too important.\n            value = $1.to_s\n            return value.split(\"_\").first\n          end\n\n          # If we can't get the guest additions version by guest property, try\n          # to get it from the VM info itself.\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            return $1.to_s if line =~ /^GuestAdditionsVersion=\"(.+?)\"$/\n          end\n\n          return nil\n        end\n\n        def read_guest_ip(adapter_number)\n          ip = read_guest_property(\"/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP\")\n          if !valid_ip_address?(ip)\n            raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound,\n              guest_property: \"/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP\"\n          end\n\n          return ip\n        end\n\n        def read_guest_property(property)\n          output = execute(\"guestproperty\", \"get\", @uuid, property)\n          if output =~ /^Value: (.+?)$/\n            $1.to_s\n          else\n            raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: property\n          end\n        end\n\n        def read_host_only_interfaces\n          execute(\"list\", \"hostonlyifs\", retryable: true).split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if line =~ /^Name:\\s+(.+?)$/\n                info[:name] = $1.to_s\n              elsif line =~ /^IPAddress:\\s+(.+?)$/\n                info[:ip] = $1.to_s\n              elsif line =~ /^NetworkMask:\\s+(.+?)$/\n                info[:netmask] = $1.to_s\n              elsif line =~ /^IPV6Address:\\s+(.+?)$/\n                info[:ipv6] = $1.to_s.strip\n              elsif line =~ /^IPV6NetworkMaskPrefixLength:\\s+(.+?)$/\n                info[:ipv6_prefix] = $1.to_s.strip\n              elsif line =~ /^Status:\\s+(.+?)$/\n                info[:status] = $1.to_s\n              elsif line =~ /^VBoxNetworkName:\\s+(.+?)$/\n                info[:display_name] = $1.to_s\n              end\n            end\n\n            info\n          end\n        end\n\n        def read_mac_address\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            return $1.to_s if line =~ /^macaddress1=\"(.+?)\"$/\n          end\n\n          nil\n        end\n\n        def read_mac_addresses\n          macs = {}\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if matcher = /^macaddress(\\d+)=\"(.+?)\"$/.match(line)\n              adapter = matcher[1].to_i\n              mac = matcher[2].to_s\n              macs[adapter] = mac\n            end\n          end\n          macs\n        end\n\n        def read_machine_folder\n          execute(\"list\", \"systemproperties\", retryable: true).split(\"\\n\").each do |line|\n            if line =~ /^Default machine folder:\\s+(.+?)$/i\n              return $1.to_s\n            end\n          end\n\n          nil\n        end\n\n        def read_network_interfaces\n          nics = {}\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if line =~ /^nic(\\d+)=\"(.+?)\"$/\n              adapter = $1.to_i\n              type    = $2.to_sym\n\n              nics[adapter] ||= {}\n              nics[adapter][:type] = type\n            elsif line =~ /^hostonlyadapter(\\d+)=\"(.+?)\"$/\n              adapter = $1.to_i\n              network = $2.to_s\n\n              nics[adapter] ||= {}\n              nics[adapter][:hostonly] = network\n            elsif line =~ /^bridgeadapter(\\d+)=\"(.+?)\"$/\n              adapter = $1.to_i\n              network = $2.to_s\n\n              nics[adapter] ||= {}\n              nics[adapter][:bridge] = network\n            end\n          end\n\n          nics\n        end\n\n        def read_state\n          output = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          if output =~ /^name=\"<inaccessible>\"$/\n            return :inaccessible\n          elsif output =~ /^VMState=\"(.+?)\"$/\n            return $1.to_sym\n          end\n\n          nil\n        end\n\n        def read_used_ports\n          ports = []\n          execute(\"list\", \"vms\", retryable: true).split(\"\\n\").each do |line|\n            if line =~ /^\".+?\" \\{(.+?)\\}$/\n              uuid = $1.to_s\n\n              # Ignore our own used ports\n              next if uuid == @uuid\n\n              begin\n                read_forwarded_ports(uuid, true).each do |_, _, hostport, _|\n                  ports << hostport\n                end\n              rescue Vagrant::Errors::VBoxManageError => e\n                raise if !e.extra_data[:stderr].include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n                # VirtualBox could not find the vm. It may have been deleted\n                # by another process after we called 'vboxmanage list vms'? Ignore this error.\n              end\n            end\n          end\n\n          ports\n        end\n\n        def read_vms\n          results = {}\n          execute(\"list\", \"vms\", retryable: true).split(\"\\n\").each do |line|\n            if line =~ /^\"(.+?)\" \\{(.+?)\\}$/\n              results[$1.to_s] = $2.to_s\n            end\n          end\n\n          results\n        end\n\n        def remove_dhcp_server(network_name)\n          execute(\"dhcpserver\", \"remove\", \"--netname\", network_name)\n        end\n\n        def set_mac_address(mac)\n          execute(\"modifyvm\", @uuid, \"--macaddress1\", mac)\n        end\n\n        def set_name(name)\n          execute(\"modifyvm\", @uuid, \"--name\", name, retryable: true)\n        rescue Vagrant::Errors::VBoxManageError => e\n          raise if !e.extra_data[:stderr].include?(\"VERR_ALREADY_EXISTS\")\n\n          # We got VERR_ALREADY_EXISTS. This means that we're renaming to\n          # a VM name that already exists. Raise a custom error.\n          raise Vagrant::Errors::VirtualBoxNameExists,\n            stderr: e.extra_data[:stderr]\n        end\n\n        def share_folders(folders)\n          is_solaris = begin\n                         \"SunOS\" == read_guest_property(\"/VirtualBox/GuestInfo/OS/Product\")\n                       rescue\n                         false\n                       end\n          folders.each do |folder|\n            hostpath = folder[:hostpath]\n            if Vagrant::Util::Platform.windows? && is_solaris\n              hostpath = Vagrant::Util::Platform.windows_unc_path(hostpath)\n            end\n            args = [\"--name\",\n              folder[:name],\n              \"--hostpath\",\n              hostpath]\n            args << \"--transient\" if folder.key?(:transient) && folder[:transient]\n\n            if folder[:SharedFoldersEnableSymlinksCreate]\n              # Enable symlinks on the shared folder\n              execute(\"setextradata\", @uuid, \"VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}\", \"1\")\n            end\n\n            # Add the shared folder\n            execute(\"sharedfolder\", \"add\", @uuid, *args)\n          end\n        end\n\n        def ssh_port(expected_port)\n          @logger.debug(\"Searching for SSH port: #{expected_port.inspect}\")\n\n          # Look for the forwarded port only by comparing the guest port\n          read_forwarded_ports.each do |_, _, hostport, guestport|\n            return hostport if guestport == expected_port\n          end\n\n          nil\n        end\n\n        def resume\n          @logger.debug(\"Resuming paused VM...\")\n          execute(\"controlvm\", @uuid, \"resume\")\n        end\n\n        def start(mode)\n          command = [\"startvm\", @uuid, \"--type\", mode.to_s]\n          r = raw(*command)\n\n          if r.exit_code == 0 || r.stdout =~ /VM \".+?\" has been successfully started/\n            # Some systems return an exit code 1 for some reason. For that\n            # we depend on the output.\n            return true\n          end\n\n          # If we reached this point then it didn't work out.\n          raise Vagrant::Errors::VBoxManageError,\n            command: command.inspect,\n            stderr: r.stderr\n        end\n\n        def suspend\n          execute(\"controlvm\", @uuid, \"savestate\")\n        end\n\n        def unshare_folders(names)\n          names.each do |name|\n            begin\n              execute(\n                \"sharedfolder\", \"remove\", @uuid,\n                \"--name\", name,\n                \"--transient\")\n\n              execute(\n                \"setextradata\", @uuid,\n                \"VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{name}\")\n            rescue Vagrant::Errors::VBoxManageError => e\n              if e.extra_data[:stderr].include?(\"VBOX_E_FILE_ERROR\")\n                # The folder doesn't exist. ignore.\n              else\n                raise\n              end\n            end\n          end\n        end\n\n        def verify!\n          # This command sometimes fails if kernel drivers aren't properly loaded\n          # so we just run the command and verify that it succeeded.\n          execute(\"list\", \"hostonlyifs\", retryable: true)\n        end\n\n        def verify_image(path)\n          r = raw(\"import\", path.to_s, \"--dry-run\")\n          return r.exit_code == 0\n        end\n\n        def vm_exists?(uuid)\n          5.times do |i|\n            result = raw(\"showvminfo\", uuid)\n            return true if result.exit_code == 0\n\n            # If vboxmanage returned VBOX_E_OBJECT_NOT_FOUND,\n            # then the vm truly does not exist. Any other error might be transient\n            return false if result.stderr.include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n            # Sleep a bit though to give VirtualBox time to fix itself\n            sleep 2\n          end\n\n          # If we reach this point, it means that we consistently got the\n          # failure, do a standard vboxmanage now. This will raise an\n          # exception if it fails again.\n          execute(\"showvminfo\", uuid)\n          return true\n        end\n\n        protected\n\n        def valid_ip_address?(ip)\n          # Filter out invalid IP addresses\n          # GH-4658 VirtualBox can report an IP address of 0.0.0.0 for FreeBSD guests.\n          if ip == \"0.0.0.0\"\n            return false\n          else\n            return true\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/version_5_0.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'log4r'\n\nrequire \"vagrant/util/platform\"\n\nrequire File.expand_path(\"../base\", __FILE__)\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      # Driver for VirtualBox 5.0.x\n      class Version_5_0 < Base\n        def initialize(uuid)\n          super()\n\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox_5_0\")\n          @uuid = uuid\n        end\n\n        # Controller-Port-Device looks like:\n        # SATA Controller-ImageUUID-0-0 (sub out ImageUUID)\n        # - Controller: SATA Controller\n        # - Port: 0\n        # - Device: 0\n        #\n        # @param [String] controller_name - name of storage controller to attach disk to\n        # @param [String] port - port on device to attach disk to\n        # @param [String] device - device on controller for disk\n        # @param [String] type - type of disk to attach\n        # @param [String] file - disk file path\n        # @param [Hash]   opts -  additional options\n        def attach_disk(controller_name, port, device, type, file, **opts)\n          comment = \"This disk is managed externally by Vagrant. Removing or adjusting settings could potentially cause issues with Vagrant.\"\n\n          execute('storageattach', @uuid,\n                  '--storagectl', controller_name,\n                  '--port', port.to_s,\n                  '--device', device.to_s,\n                  '--type', type,\n                  '--medium', file,\n                  '--comment', comment)\n        end\n\n        def clear_forwarded_ports\n          retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do\n            args = []\n            read_forwarded_ports(@uuid).each do |nic, name, _, _|\n              args.concat([\"--natpf#{nic}\", \"delete\", name])\n            end\n\n            execute(\"modifyvm\", @uuid, *args) if !args.empty?\n          end\n        end\n\n        def clear_shared_folders\n          retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do\n            info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n            info.split(\"\\n\").each do |line|\n              if line =~ /^SharedFolderNameMachineMapping\\d+=\"(.+?)\"$/\n                execute(\"sharedfolder\", \"remove\", @uuid, \"--name\", $1.to_s)\n              end\n            end\n          end\n        end\n\n        # @param [String] source\n        # @param [String] destination\n        # @param [String] disk_format\n        def clone_disk(source, destination, disk_format, **opts)\n          execute(\"clonemedium\", source, destination, '--format', disk_format)\n        end\n\n        def clonevm(master_id, snapshot_name)\n          machine_name = \"temp_clone_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}\"\n          args = [\"--register\", \"--name\", machine_name]\n          if snapshot_name\n            args += [\"--snapshot\", snapshot_name, \"--options\", \"link\"]\n          end\n\n          execute(\"clonevm\", master_id, *args, retryable: true)\n          return get_machine_id(machine_name)\n        end\n\n        # Removes a disk from the given virtual machine\n        #\n        # @param [String] disk_uuid or file path\n        # @param [Hash]   opts -  additional options\n        def close_medium(disk_uuid)\n          execute(\"closemedium\", disk_uuid, '--delete')\n        end\n\n        def create_dhcp_server(network, options)\n          retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do\n            begin\n              execute(\"dhcpserver\", \"add\", \"--ifname\", network,\n                      \"--ip\", options[:dhcp_ip],\n                      \"--netmask\", options[:netmask],\n                      \"--lowerip\", options[:dhcp_lower],\n                      \"--upperip\", options[:dhcp_upper],\n                      \"--enable\")\n            rescue Vagrant::Errors::VBoxManageError => e\n              return if e.extra_data[:stderr] == 'VBoxManage: error: DHCP server already exists'\n              raise\n            end\n          end\n        end\n\n        # Creates a disk. Default format is VDI unless overridden\n        #\n        # @param [String] disk_file\n        # @param [Integer] disk_size - size in bytes\n        # @param [String] disk_format - format of disk, defaults to \"VDI\"\n        # @param [Hash]  opts -  additional options\n        def create_disk(disk_file, disk_size, disk_format=\"VDI\", **opts)\n          execute(\"createmedium\", '--filename', disk_file, '--sizebyte', disk_size.to_i.to_s, '--format', disk_format)\n        end\n\n\n        def create_host_only_network(options)\n          # Create the interface\n          execute(\"hostonlyif\", \"create\", retryable: true) =~ /^Interface '(.+?)' was successfully created$/\n          name = $1.to_s\n\n          # Get the IP so we can determine v4 vs v6\n          ip = IPAddr.new(options[:adapter_ip])\n\n          # Configure\n          if ip.ipv4?\n            execute(\"hostonlyif\", \"ipconfig\", name,\n                    \"--ip\", options[:adapter_ip],\n                    \"--netmask\", options[:netmask],\n                    retryable: true)\n          elsif ip.ipv6?\n            execute(\"hostonlyif\", \"ipconfig\", name,\n                    \"--ipv6\", options[:adapter_ip],\n                    \"--netmasklengthv6\", options[:netmask].to_s,\n                    retryable: true)\n          else\n            raise \"BUG: Unknown IP type: #{ip.inspect}\"\n          end\n\n          # Return the details\n          return {\n            name: name,\n            ip:   options[:adapter_ip],\n            netmask: options[:netmask],\n            dhcp: nil\n          }\n        end\n\n        def create_snapshot(machine_id, snapshot_name)\n          execute(\"snapshot\", machine_id, \"take\", snapshot_name, retryable: true)\n        end\n\n        def delete_snapshot(machine_id, snapshot_name)\n          # Start with 0%\n          last = 0\n          total = \"\"\n          yield 0 if block_given?\n\n          # Snapshot and report the % progress\n          execute(\"snapshot\", machine_id, \"delete\", snapshot_name, retryable: true) do |type, data|\n            if type == :stderr\n              # Append the data so we can see the full view\n              total << data.gsub(\"\\r\", \"\")\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n\n              # The progress of the import will be in the last line. Do a greedy\n              # regular expression to find what we're looking for.\n              match = /.+(\\d{2})%/.match(lines.last)\n              if match\n                current = match[1].to_i\n                if current > last\n                  last = current\n                  yield current if block_given?\n                end\n              end\n            end\n          end\n        end\n\n        # Lists all attached harddisks from a given virtual machine. Additionally,\n        # this method adds a new key \"Disk Name\" based on the disks file path from \"Location\"\n        #\n        # @return [Array] hdds An array of hashes of harddrive info for a guest\n        def list_hdds\n          hdds = []\n          tmp_drive = {}\n          execute('list', 'hdds', retryable: true).split(\"\\n\").each do |line|\n            if line == \"\" # separator between disks\n              hdds << tmp_drive\n              tmp_drive = {}\n              next\n            end\n            parts = line.partition(\":\")\n            key = parts.first.strip\n            value = parts.last.strip\n            tmp_drive[key] = value\n\n            if key == \"Location\"\n              tmp_drive[\"Disk Name\"] = File.basename(value, \".*\")\n            end\n          end\n          hdds << tmp_drive unless tmp_drive.empty?\n\n          hdds\n        end\n\n        def list_snapshots(machine_id)\n          output = execute(\n            \"snapshot\", machine_id, \"list\", \"--machinereadable\",\n            retryable: true)\n\n          result = []\n          output.split(\"\\n\").each do |line|\n            if line =~ /^SnapshotName.*?=\"(.+?)\"$/i\n              result << $1.to_s\n            end\n          end\n\n          result.sort\n        rescue Vagrant::Errors::VBoxManageError => e\n          d = e.extra_data\n          return [] if d[:stderr].include?(\"does not have\") || d[:stdout].include?(\"does not have\")\n          raise\n        end\n\n        # @param [String] controller_name - controller name to remove disk from\n        # @param [String] port - port on device to attach disk to\n        # @param [String] device - device on controller for disk\n        def remove_disk(controller_name, port, device)\n          execute('storageattach', @uuid,\n                  '--storagectl', controller_name,\n                  '--port', port.to_s,\n                  '--device', device.to_s,\n                  '--medium', \"none\")\n        end\n\n        # @param [String] disk_file\n        # @param [Integer] disk_size in bytes\n        # @param [Hash]   opts -  additional options\n        def resize_disk(disk_file, disk_size, **opts)\n          execute(\"modifymedium\", disk_file, '--resizebyte', disk_size.to_i.to_s)\n        end\n\n        def restore_snapshot(machine_id, snapshot_name)\n          # Start with 0%\n          last = 0\n          total = \"\"\n          yield 0 if block_given?\n\n          execute(\"snapshot\", machine_id, \"restore\", snapshot_name, retryable: true) do |type, data|\n            if type == :stderr\n              # Append the data so we can see the full view\n              total << data.gsub(\"\\r\", \"\")\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n\n              # The progress of the import will be in the last line. Do a greedy\n              # regular expression to find what we're looking for.\n              match = /.+(\\d{2})%/.match(lines.last)\n              if match\n                current = match[1].to_i\n                if current > last\n                  last = current\n                  yield current if block_given?\n                end\n              end\n            end\n          end\n        end\n\n        def delete\n          execute(\"unregistervm\", @uuid, \"--delete\", retryable: true)\n        end\n\n        def delete_unused_host_only_networks\n          networks = []\n          execute(\"list\", \"hostonlyifs\", retryable: true).split(\"\\n\").each do |line|\n            networks << $1.to_s if line =~ /^Name:\\s+(.+?)$/\n          end\n\n          execute(\"list\", \"vms\", retryable: true).split(\"\\n\").each do |line|\n            if line =~ /^\".+?\"\\s+\\{(.+?)\\}$/\n              begin\n                info = execute(\"showvminfo\", $1.to_s, \"--machinereadable\", retryable: true)\n                info.split(\"\\n\").each do |inner_line|\n                  if inner_line =~ /^hostonlyadapter\\d+=\"(.+?)\"$/\n                    networks.delete($1.to_s)\n                  end\n                end\n              rescue Vagrant::Errors::VBoxManageError => e\n                raise if !e.extra_data[:stderr].include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n                # VirtualBox could not find the vm. It may have been deleted\n                # by another process after we called 'vboxmanage list vms'? Ignore this error.\n              end\n            end\n          end\n\n          networks.each do |name|\n            # First try to remove any DHCP servers attached. We use `raw` because\n            # it is okay if this fails. It usually means that a DHCP server was\n            # never attached.\n            raw(\"dhcpserver\", \"remove\", \"--ifname\", name)\n\n            # Delete the actual host only network interface.\n            execute(\"hostonlyif\", \"remove\", name, retryable: true)\n          end\n        end\n\n        def discard_saved_state\n          execute(\"discardstate\", @uuid, retryable: true)\n        end\n\n        def enable_adapters(adapters)\n          args = []\n          adapters.each do |adapter|\n            args.concat([\"--nic#{adapter[:adapter]}\", adapter[:type].to_s])\n\n            if adapter[:bridge]\n              args.concat([\"--bridgeadapter#{adapter[:adapter]}\",\n                           adapter[:bridge], \"--cableconnected#{adapter[:adapter]}\", \"on\"])\n            end\n\n            if adapter[:hostonly]\n              args.concat([\"--hostonlyadapter#{adapter[:adapter]}\",\n                           adapter[:hostonly], \"--cableconnected#{adapter[:adapter]}\", \"on\"])\n            end\n\n            if adapter[:intnet]\n              args.concat([\"--intnet#{adapter[:adapter]}\",\n                           adapter[:intnet], \"--cableconnected#{adapter[:adapter]}\", \"on\"])\n            end\n\n            if adapter[:mac_address]\n              args.concat([\"--macaddress#{adapter[:adapter]}\",\n                           adapter[:mac_address]])\n            end\n\n            if adapter[:nic_type]\n              args.concat([\"--nictype#{adapter[:adapter]}\", adapter[:nic_type].to_s])\n            end\n          end\n\n          execute(\"modifyvm\", @uuid, *args, retryable: true)\n        end\n\n        def execute_command(command)\n          execute(*command)\n        end\n\n        def export(path)\n          retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do\n            begin\n              execute(\"export\", @uuid, \"--output\", path.to_s)\n            rescue Vagrant::Errors::VBoxManageError => e\n              raise if !e.extra_data[:stderr].include?(\"VERR_E_FILE_ERROR\")\n\n              # If the file already exists we'll throw a custom error\n              raise Vagrant::Errors::VirtualBoxFileExists,\n                    stderr: e.extra_data[:stderr]\n            end\n          end\n        end\n\n        def forward_ports(ports)\n          args = []\n          ports.each do |options|\n            pf_builder = [options[:name],\n                          options[:protocol] || \"tcp\",\n                          options[:hostip] || \"\",\n                          options[:hostport],\n                          options[:guestip] || \"\",\n                          options[:guestport]]\n\n            args.concat([\"--natpf#{options[:adapter] || 1}\",\n                         pf_builder.join(\",\")])\n          end\n\n          execute(\"modifyvm\", @uuid, *args, retryable: true) if !args.empty?\n        end\n\n        def get_machine_id(machine_name)\n          output = execute(\"list\", \"vms\", retryable: true)\n          match = /^\"#{Regexp.escape(machine_name)}\" \\{(.+?)\\}$/.match(output)\n          return match[1].to_s if match\n          nil\n        end\n\n        # Returns port and device for an attached disk given a disk uuid. Returns\n        # empty hash if disk is not attachd to guest\n        #\n        # @param [String] disk_uuid - the UUID for the disk we are searching for\n        # @return [Hash] disk_info - Contains a device and port number\n        def get_port_and_device(disk_uuid)\n          disk = {}\n\n          storage_controllers = read_storage_controllers\n          storage_controllers.each do |controller|\n            controller.attachments.each do |attachment|\n              if disk_uuid == attachment[:uuid]\n                disk[:port] = attachment[:port]\n                disk[:device] = attachment[:device]\n                return disk\n              end\n            end\n          end\n\n          return disk\n        end\n\n        def halt\n          execute(\"controlvm\", @uuid, \"poweroff\", retryable: true)\n        end\n\n        def import(ovf)\n          ovf = Vagrant::Util::Platform.windows_path(ovf)\n\n          output = \"\"\n          total = \"\"\n          last  = 0\n\n          # Dry-run the import to get the suggested name and path\n          @logger.debug(\"Doing dry-run import to determine parallel-safe name...\")\n          output = execute(\"import\", \"-n\", ovf)\n          result = /Suggested VM name \"(.+?)\"/.match(output)\n          if !result\n            raise Vagrant::Errors::VirtualBoxNoName, output: output\n          end\n          suggested_name = result[1].to_s\n\n          # Append millisecond plus a random to the path in case we're\n          # importing the same box elsewhere.\n          specified_name = \"#{suggested_name}_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}\"\n          @logger.debug(\"-- Parallel safe name: #{specified_name}\")\n\n          # Build the specified name param list\n          name_params = [\n            \"--vsys\", \"0\",\n            \"--vmname\", specified_name,\n          ]\n\n          # Extract the disks list and build the disk target params\n          disk_params = []\n          disks = output.scan(/(\\d+): Hard disk image: source image=.+, target path=(.+),/)\n          disks.each do |unit_num, path|\n            disk_params << \"--vsys\"\n            disk_params << \"0\"\n            disk_params << \"--unit\"\n            disk_params << unit_num\n            disk_params << \"--disk\"\n            if Vagrant::Util::Platform.windows?\n              # we use the block form of sub here to ensure that if the specified_name happens to end with a number (which is fairly likely) then\n              # we won't end up having the character sequence of a \\ followed by a number be interpreted as a back reference.  For example, if\n              # specified_name were \"abc123\", then \"\\\\abc123\\\\\".reverse would be \"\\\\321cba\\\\\", and the \\3 would be treated as a back reference by the sub\n              disk_params << path.reverse.sub(\"\\\\#{suggested_name}\\\\\".reverse) { \"\\\\#{specified_name}\\\\\".reverse }.reverse # Replace only last occurrence\n            else\n              disk_params << path.reverse.sub(\"/#{suggested_name}/\".reverse, \"/#{specified_name}/\".reverse).reverse # Replace only last occurrence\n            end\n          end\n\n          execute(\"import\", ovf , *name_params, *disk_params, retryable: true) do |type, data|\n            if type == :stdout\n              # Keep track of the stdout so that we can get the VM name\n              output << data\n            elsif type == :stderr\n              # Append the data so we can see the full view\n              total << data.gsub(\"\\r\", \"\")\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n              if lines.include?(\"OK.\")\n                # The progress of the import will be in the last line. Do a greedy\n                # regular expression to find what we're looking for.\n                match = /.+(\\d{2})%/.match(lines.last)\n                if match\n                  current = match[1].to_i\n                  if current > last\n                    last = current\n                    yield current if block_given?\n                  end\n                end\n              end\n            end\n          end\n\n          return get_machine_id(specified_name)\n        end\n\n        def max_network_adapters\n          8\n        end\n\n        def read_forwarded_ports(uuid=nil, active_only=false)\n          uuid ||= @uuid\n\n          @logger.debug(\"read_forward_ports: uuid=#{uuid} active_only=#{active_only}\")\n\n          results = []\n          current_nic = nil\n          info = execute(\"showvminfo\", uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            # This is how we find the nic that a FP is attached to,\n            # since this comes first.\n            current_nic = $1.to_i if line =~ /^nic(\\d+)=\".+?\"$/\n\n            # If we care about active VMs only, then we check the state\n            # to verify the VM is running.\n            if active_only && line =~ /^VMState=\"(.+?)\"$/ && $1.to_s != \"running\"\n              return []\n            end\n\n            # Parse out the forwarded port information\n            # Forwarding(1)=\"172.22.8.201tcp32977,tcp,172.22.8.201,32977,,3777\"\n            # Forwarding(2)=\"tcp32978,tcp,,32978,,3777\"\n            if line =~ /^Forwarding.+?=\"(.+?),.+?,(.*?),(.+?),.*?,(.+?)\"$/\n              result = [current_nic, $1.to_s, $3.to_i, $4.to_i, $2]\n              @logger.debug(\"  - #{result.inspect}\")\n              results << result\n            end\n          end\n\n          results\n        end\n\n        def read_bridged_interfaces\n          execute(\"list\", \"bridgedifs\").split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if line =~ /^Name:\\s+(.+?)$/\n                info[:name] = $1.to_s\n              elsif line =~ /^IPAddress:\\s+(.+?)$/\n                info[:ip] = $1.to_s\n              elsif line =~ /^NetworkMask:\\s+(.+?)$/\n                info[:netmask] = $1.to_s\n              elsif line =~ /^Status:\\s+(.+?)$/\n                info[:status] = $1.to_s\n              end\n            end\n\n            # Return the info to build up the results\n            info\n          end\n        end\n\n        def read_dhcp_servers\n          execute(\"list\", \"dhcpservers\", retryable: true).split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if network = line[/^NetworkName:\\s+HostInterfaceNetworking-(.+?)$/, 1]\n                info[:network]      = network\n                info[:network_name] = \"HostInterfaceNetworking-#{network}\"\n              elsif ip = line[/^IP:\\s+(.+?)$/, 1]\n                info[:ip] = ip\n              elsif netmask = line[/^NetworkMask:\\s+(.+?)$/, 1]\n                info[:netmask] = netmask\n              elsif lower = line[/^lowerIPAddress:\\s+(.+?)$/, 1]\n                info[:lower] = lower\n              elsif upper = line[/^upperIPAddress:\\s+(.+?)$/, 1]\n                info[:upper] = upper\n              end\n            end\n\n            info\n          end\n        end\n\n        def read_guest_additions_version\n          output = execute(\"guestproperty\", \"get\", @uuid, \"/VirtualBox/GuestAdd/Version\",\n                           retryable: true)\n          if output =~ /^Value: (.+?)$/\n            # Split the version by _ since some distro versions modify it\n            # to look like this: 4.1.2_ubuntu, and the distro part isn't\n            # too important.\n            value = $1.to_s\n            return value.split(\"_\").first\n          end\n\n          # If we can't get the guest additions version by guest property, try\n          # to get it from the VM info itself.\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            return $1.to_s if line =~ /^GuestAdditionsVersion=\"(.+?)\"$/\n          end\n\n          return nil\n        end\n\n        def read_guest_ip(adapter_number)\n          ip = read_guest_property(\"/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP\")\n          if ip.end_with?(\".1\")\n            @logger.warn(\"VBoxManage guest property returned: #{ip}. Result resembles IP of DHCP server and is being ignored.\")\n            ip = nil\n          end\n\n          if !valid_ip_address?(ip)\n            raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound,\n                  guest_property: \"/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP\"\n          end\n\n          return ip\n        end\n\n        def read_guest_property(property)\n          output = execute(\"guestproperty\", \"get\", @uuid, property)\n          if output =~ /^Value: (.+?)$/\n            $1.to_s\n          else\n            raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: property\n          end\n        end\n\n        def read_host_only_interfaces\n          execute(\"list\", \"hostonlyifs\", retryable: true).split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if line =~ /^Name:\\s+(.+?)$/\n                info[:name] = $1.to_s\n              elsif line =~ /^IPAddress:\\s+(.+?)$/\n                info[:ip] = $1.to_s\n              elsif line =~ /^NetworkMask:\\s+(.+?)$/\n                info[:netmask] = $1.to_s\n              elsif line =~ /^IPV6Address:\\s+(.+?)$/\n                info[:ipv6] = $1.to_s.strip\n              elsif line =~ /^IPV6NetworkMaskPrefixLength:\\s+(.+?)$/\n                info[:ipv6_prefix] = $1.to_s.strip\n              elsif line =~ /^Status:\\s+(.+?)$/\n                info[:status] = $1.to_s\n              elsif line =~ /^VBoxNetworkName:\\s+(.+?)$/\n                info[:display_name] = $1.to_s\n              end\n            end\n\n            info\n          end\n        end\n\n        def read_mac_address\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            return $1.to_s if line =~ /^macaddress1=\"(.+?)\"$/\n          end\n\n          nil\n        end\n\n        def read_mac_addresses\n          macs = {}\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if matcher = /^macaddress(\\d+)=\"(.+?)\"$/.match(line)\n              adapter = matcher[1].to_i\n              mac = matcher[2].to_s\n              macs[adapter] = mac\n            end\n          end\n          macs\n        end\n\n        def read_machine_folder\n          info = execute(\"list\", \"systemproperties\", retryable: true)\n          info.each_line do |line|\n            match = line.match(/Default machine folder:\\s+(?<folder>.+?)$/i)\n            next if match.nil?\n            return match[:folder]\n          end\n\n          @logger.warn(\"failed to determine machine folder from system properties\")\n          @logger.debug(\"processed output for machine folder lookup:\\n#{info}\")\n\n          raise Vagrant::Errors::VirtualBoxMachineFolderNotFound\n        end\n\n        def read_network_interfaces\n          nics = {}\n          info = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          info.split(\"\\n\").each do |line|\n            if line =~ /^nic(\\d+)=\"(.+?)\"$/\n              adapter = $1.to_i\n              type    = $2.to_sym\n\n              nics[adapter] ||= {}\n              nics[adapter][:type] = type\n            elsif line =~ /^hostonlyadapter(\\d+)=\"(.+?)\"$/\n              adapter = $1.to_i\n              network = $2.to_s\n\n              nics[adapter] ||= {}\n              nics[adapter][:hostonly] = network\n            elsif line =~ /^bridgeadapter(\\d+)=\"(.+?)\"$/\n              adapter = $1.to_i\n              network = $2.to_s\n\n              nics[adapter] ||= {}\n              nics[adapter][:bridge] = network\n            end\n          end\n\n          nics\n        end\n\n        def read_state\n          output = execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true)\n          if output =~ /^name=\"<inaccessible>\"$/\n            return :inaccessible\n          elsif output =~ /^VMState=\"(.+?)\"$/\n            return $1.to_sym\n          end\n\n          nil\n        end\n\n        def read_used_ports\n          used_ports = Hash.new{|hash, key| hash[key] = Set.new}\n          execute(\"list\", \"vms\", retryable: true).split(\"\\n\").each do |line|\n            if line =~ /^\".+?\" \\{(.+?)\\}$/\n              uuid = $1.to_s\n\n              # Ignore our own used ports\n              next if uuid == @uuid\n\n              begin\n                read_forwarded_ports(uuid, true).each do |_, _, hostport, _, hostip|\n                  hostip = '*' if hostip.nil? || hostip.empty?\n                  used_ports[hostport].add?(hostip)\n                end\n              rescue Vagrant::Errors::VBoxManageError => e\n                raise if !e.extra_data[:stderr].include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n                # VirtualBox could not find the vm. It may have been deleted\n                # by another process after we called 'vboxmanage list vms'? Ignore this error.\n              end\n            end\n          end\n\n          used_ports\n        end\n\n        def read_vms\n          results = {}\n          execute(\"list\", \"vms\", retryable: true).split(\"\\n\").each do |line|\n            if line =~ /^\"(.+?)\" \\{(.+?)\\}$/\n              results[$1.to_s] = $2.to_s\n            end\n          end\n\n          results\n        end\n\n        def reconfig_host_only(interface)\n          execute(\"hostonlyif\", \"ipconfig\", interface[:name],\n                  \"--ipv6\", interface[:ipv6], retryable: true)\n        end\n\n        def remove_dhcp_server(network_name)\n          execute(\"dhcpserver\", \"remove\", \"--netname\", network_name, retryable: true)\n        end\n\n        def set_mac_address(mac)\n          mac = \"auto\" if !mac\n          execute(\"modifyvm\", @uuid, \"--macaddress1\", mac, retryable: true)\n        end\n\n        def set_name(name)\n          retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do\n            begin\n              execute(\"modifyvm\", @uuid, \"--name\", name)\n            rescue Vagrant::Errors::VBoxManageError => e\n              raise if !e.extra_data[:stderr].include?(\"VERR_ALREADY_EXISTS\")\n\n              # We got VERR_ALREADY_EXISTS. This means that we're renaming to\n              # a VM name that already exists. Raise a custom error.\n              raise Vagrant::Errors::VirtualBoxNameExists,\n                    stderr: e.extra_data[:stderr]\n            end\n          end\n        end\n\n        def share_folders(folders)\n          is_solaris = begin\n                         \"SunOS\" == read_guest_property(\"/VirtualBox/GuestInfo/OS/Product\")\n                       rescue\n                         false\n                       end\n          folders.each do |folder|\n            # NOTE: Guest additions on Solaris guests do not properly handle\n            # UNC style paths so prevent conversion (See GH-7264)\n            if is_solaris\n              hostpath = folder[:hostpath]\n            else\n              hostpath = Vagrant::Util::Platform.windows_path(folder[:hostpath])\n            end\n            args = [\"--name\",\n                    folder[:name],\n                    \"--hostpath\",\n                    hostpath]\n            args << \"--transient\" if folder.key?(:transient) && folder[:transient]\n\n            args << \"--automount\" if folder.key?(:automount) && folder[:automount]\n\n            if folder[:SharedFoldersEnableSymlinksCreate]\n              # Enable symlinks on the shared folder\n              execute(\"setextradata\", @uuid, \"VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}\", \"1\", retryable: true)\n            end\n\n            # Add the shared folder\n            execute(\"sharedfolder\", \"add\", @uuid, *args, retryable: true)\n          end\n        end\n\n        # Returns information for a given disk\n        #\n        # @param [String] disk_type - can be \"disk\", \"dvd\", or \"floppy\"\n        # @param [String] disk_uuid_or_file\n        # @return [Hash] disk\n        def show_medium_info(disk_type, disk_uuid_or_file)\n          disk = {}\n          execute('showmediuminfo', disk_type, disk_uuid_or_file, retryable: true).split(\"\\n\").each do |line|\n            parts = line.partition(\":\")\n            key = parts.first.strip\n            value = parts.last.strip\n            disk[key] = value\n\n            if key == \"Location\"\n              disk[\"Disk Name\"] = File.basename(value, \".*\")\n            end\n          end\n          disk\n        end\n\n        def ssh_port(expected_port)\n          @logger.debug(\"Searching for SSH port: #{expected_port.inspect}\")\n\n          # Look for the forwarded port. Valid based on the guest port, but will do\n          # scoring based matching to determine best value when multiple results are\n          # available.\n          matches = read_forwarded_ports.map do |_, name, hostport, guestport, host_ip|\n            next if guestport != expected_port\n            match = [0, hostport]\n            match[0] += 1 if name == \"ssh\"\n            match[0] += 1 if name.downcase == \"ssh\"\n            match[0] += 1 if host_ip == \"127.0.0.1\"\n            match\n          end.compact\n\n          result = matches.sort_by(&:first).last\n\n          result.last if result\n        end\n\n        def resume\n          @logger.debug(\"Resuming paused VM...\")\n          execute(\"controlvm\", @uuid, \"resume\")\n        end\n\n        def start(mode)\n          command = [\"startvm\", @uuid, \"--type\", mode.to_s]\n          retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do\n            r = raw(*command)\n\n            if r.exit_code == 0 || r.stdout =~ /VM \".+?\" has been successfully started/\n              # Some systems return an exit code 1 for some reason. For that\n              # we depend on the output.\n              return true\n            end\n\n            # If we reached this point then it didn't work out.\n            raise Vagrant::Errors::VBoxManageError,\n                  command: command.inspect,\n                  stderr: r.stderr\n          end\n        end\n\n        def suspend\n          execute(\"controlvm\", @uuid, \"savestate\", retryable: true)\n        end\n\n        def unshare_folders(names)\n          names.each do |name|\n            retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do\n              begin\n                execute(\n                  \"sharedfolder\", \"remove\", @uuid,\n                  \"--name\", name,\n                  \"--transient\")\n\n                execute(\n                  \"setextradata\", @uuid,\n                  \"VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{name}\")\n              rescue Vagrant::Errors::VBoxManageError => e\n                raise if !e.extra_data[:stderr].include?(\"VBOX_E_FILE_ERROR\")\n              end\n            end\n          end\n        end\n\n        def verify!\n          # This command sometimes fails if kernel drivers aren't properly loaded\n          # so we just run the command and verify that it succeeded.\n          execute(\"list\", \"hostonlyifs\", retryable: true)\n        end\n\n        def verify_image(path)\n          r = raw(\"import\", path.to_s, \"--dry-run\")\n          return r.exit_code == 0\n        end\n\n        def vm_exists?(uuid)\n          5.times do |i|\n            result = raw(\"showvminfo\", uuid)\n            return true if result.exit_code == 0\n\n            # If vboxmanage returned VBOX_E_OBJECT_NOT_FOUND,\n            # then the vm truly does not exist. Any other error might be transient\n            return false if result.stderr.include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n\n            # Sleep a bit though to give VirtualBox time to fix itself\n            sleep 2\n          end\n\n          # If we reach this point, it means that we consistently got the\n          # failure, do a standard vboxmanage now. This will raise an\n          # exception if it fails again.\n          execute(\"showvminfo\", uuid)\n          return true\n        end\n\n        # @param [VagrantPlugins::VirtualboxProvider::Driver] driver\n        # @param [String] defined_disk_path\n        # @return [String] destination - The cloned disk\n        def vmdk_to_vdi(defined_disk_path)\n          source = defined_disk_path\n          destination = File.join(File.dirname(source), File.basename(source, \".*\")) + \".vdi\"\n\n          clone_disk(source, destination, 'VDI')\n\n          destination\n        end\n\n        # @param [VagrantPlugins::VirtualboxProvider::Driver] driver\n        # @param [String] defined_disk_path\n        # @return [String] destination - The cloned disk\n        def vdi_to_vmdk(defined_disk_path)\n          source = defined_disk_path\n          destination = File.join(File.dirname(source), File.basename(source, \".*\")) + \".vmdk\"\n\n          clone_disk(source, destination, 'VMDK')\n\n          destination\n        end\n\n        # Helper method to get a list of storage controllers added to the\n        # current VM\n        #\n        # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray]\n        def read_storage_controllers\n          vm_info = show_vm_info\n          count = vm_info.count { |key, value| key.match(/^storagecontrollername\\d+$/) }\n          all_disks = list_hdds\n\n          storage_controllers = Model::StorageControllerArray.new\n\n          (0..count - 1).each do |n|\n            # basic controller metadata\n            name = vm_info[\"storagecontrollername#{n}\"]\n            type = vm_info[\"storagecontrollertype#{n}\"]\n            maxportcount = vm_info[\"storagecontrollermaxportcount#{n}\"].to_i\n\n            # build attachments array\n            attachments = []\n            vm_info.each do |k, v|\n              if /^#{name}-ImageUUID-(\\d+)-(\\d+)$/ =~ k\n                port = $1.to_s\n                device = $2.to_s\n                uuid = v\n                location = vm_info[\"#{name}-#{port}-#{device}\"]\n\n                extra_disk_data = all_disks.detect { |d| d[\"UUID\"] == uuid }\n\n                attachment = { port: port,\n                               device: device,\n                               uuid: uuid,\n                               location: location }\n\n                extra_disk_data&.each do |dk,dv|\n                  # NOTE: We convert the keys from VirtualBox to symbols\n                  # to be consistent with the other keys\n                  attachment[dk.downcase.gsub(' ', '_').to_sym] = dv\n                end\n\n                attachments << attachment\n              end\n            end\n\n            storage_controllers << Model::StorageController.new(name, type, maxportcount, attachments)\n          end\n\n          storage_controllers\n        end\n\n        protected\n\n        def valid_ip_address?(ip)\n          # Filter out invalid IP addresses\n          # GH-4658 VirtualBox can report an IP address of 0.0.0.0 for FreeBSD guests.\n          if ip == \"0.0.0.0\" || ip.nil?\n            return false\n          else\n            return true\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/version_5_1.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../version_5_0\", __FILE__)\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      # Driver for VirtualBox 5.1.x\n      class Version_5_1 < Version_5_0\n        def initialize(uuid)\n          super\n\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox_5_1\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/version_5_2.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../version_5_0\", __FILE__)\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      # Driver for VirtualBox 5.2.x\n      class Version_5_2 < Version_5_0\n        def initialize(uuid)\n          super\n\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox_5_2\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/version_6_0.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../version_5_0\", __FILE__)\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      # Driver for VirtualBox 6.0.x\n      class Version_6_0 < Version_5_0\n        def initialize(uuid)\n          super\n\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox_6_0\")\n        end\n\n        def import(ovf)\n          ovf = Vagrant::Util::Platform.windows_path(ovf)\n\n          output = \"\"\n          total = \"\"\n          last  = 0\n\n          # Dry-run the import to get the suggested name and path\n          @logger.debug(\"Doing dry-run import to determine parallel-safe name...\")\n          output = execute(\"import\", \"-n\", ovf)\n          result = /Suggested VM name \"(.+?)\"/.match(output)\n          if !result\n            raise Vagrant::Errors::VirtualBoxNoName, output: output\n          end\n          suggested_name = result[1].to_s\n\n          # Append millisecond plus a random to the path in case we're\n          # importing the same box elsewhere.\n          specified_name = \"#{suggested_name}_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}\"\n          @logger.debug(\"-- Parallel safe name: #{specified_name}\")\n\n          # Build the specified name param list\n          name_params = [\n            \"--vsys\", \"0\",\n            \"--vmname\", specified_name,\n          ]\n\n          # Target path for disks is no longer a full path. Extract the path for the\n          # settings file to determine the base directory which we can then use to\n          # build the disk paths\n          result = /Suggested VM settings file name \"(?<settings_path>.+?)\"/.match(output)\n          if !result\n            @logger.warn(\"Failed to locate base path for disks. Using current working directory.\")\n            base_path = \".\"\n          else\n            base_path = result[:settings_path]\n            if Vagrant::Util::Platform.windows? || Vagrant::Util::Platform.wsl?\n              base_path.gsub!('\\\\', '/')\n            end\n            base_path = File.dirname(base_path)\n          end\n\n          @logger.info(\"Base path for disk import: #{base_path}\")\n\n          # Extract the disks list and build the disk target params\n          disk_params = []\n          disks = output.scan(/(\\d+): Hard disk image: source image=.+, target path=(.+),/)\n          disks.each do |unit_num, path|\n            path = File.join(base_path, File.basename(path))\n            disk_params << \"--vsys\"\n            disk_params << \"0\"\n            disk_params << \"--unit\"\n            disk_params << unit_num\n            disk_params << \"--disk\"\n            disk_params << path.reverse.sub(\"/#{suggested_name}/\".reverse, \"/#{specified_name}/\".reverse).reverse # Replace only last occurrence\n          end\n\n          execute(\"import\", ovf , *name_params, *disk_params, retryable: true) do |type, data|\n            if type == :stdout\n              # Keep track of the stdout so that we can get the VM name\n              output << data\n            elsif type == :stderr\n              # Append the data so we can see the full view\n              total << data.gsub(\"\\r\", \"\")\n\n              # Break up the lines. We can't get the progress until we see an \"OK\"\n              lines = total.split(\"\\n\")\n              if lines.include?(\"OK.\")\n                # The progress of the import will be in the last line. Do a greedy\n                # regular expression to find what we're looking for.\n                match = /.+(\\d{2})%/.match(lines.last)\n                if match\n                  current = match[1].to_i\n                  if current > last\n                    last = current\n                    yield current if block_given?\n                  end\n                end\n              end\n            end\n          end\n\n          return get_machine_id specified_name\n        end\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/version_6_1.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../version_6_0\", __FILE__)\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      # Driver for VirtualBox 6.1.x\n      class Version_6_1 < Version_6_0\n        def initialize(uuid)\n          super\n\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox_6_1\")\n        end\n\n        def read_dhcp_servers\n          execute(\"list\", \"dhcpservers\", retryable: true).split(\"\\n\\n\").collect do |block|\n            info = {}\n\n            block.split(\"\\n\").each do |line|\n              if network = line[/^NetworkName:\\s+HostInterfaceNetworking-(.+?)$/, 1]\n                info[:network]      = network\n                info[:network_name] = \"HostInterfaceNetworking-#{network}\"\n              elsif ip = line[/^Dhcpd IP:\\s+(.+?)$/, 1]\n                info[:ip] = ip\n              elsif netmask = line[/^NetworkMask:\\s+(.+?)$/, 1]\n                info[:netmask] = netmask\n              elsif lower = line[/^LowerIPAddress:\\s+(.+?)$/, 1]\n                info[:lower] = lower\n              elsif upper = line[/^UpperIPAddress:\\s+(.+?)$/, 1]\n                info[:upper] = upper\n              end\n            end\n\n            info\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/version_7_0.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"rexml\"\n\nrequire File.expand_path(\"../version_6_1\", __FILE__)\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      # Driver for VirtualBox 7.0.x\n      class Version_7_0 < Version_6_1\n        # VirtualBox version requirement for using host only networks\n        # instead of host only interfaces\n        HOSTONLY_NET_REQUIREMENT=Gem::Requirement.new(\">= 7\")\n        # Prefix of name used for host only networks\n        HOSTONLY_NAME_PREFIX=\"vagrantnet-vbox\"\n        DEFAULT_NETMASK=\"255.255.255.0\"\n\n        def initialize(uuid)\n          super\n\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox_7_0\")\n        end\n\n        def read_bridged_interfaces\n          ifaces = super\n          return ifaces if !use_host_only_nets?\n\n          # Get a list of all subnets which are in use for hostonly networks\n          hostonly_ifaces = read_host_only_networks.map do |net|\n            IPAddr.new(net[:lowerip]).mask(net[:networkmask])\n          end\n\n          # Prune any hostonly interfaces in the list\n          ifaces.delete_if { |i|\n            addr = begin\n                     IPAddr.new(i[:ip]).mask(i[:netmask])\n                   rescue IPAddr::Error => err\n                     @logger.warn(\"skipping bridged interface due to parse error #{err} (#{i}) \")\n                     nil\n                   end\n            addr.nil? ||\n              hostonly_ifaces.include?(addr)\n          }\n\n          ifaces\n        end\n\n        def delete_unused_host_only_networks\n          return super if !use_host_only_nets?\n\n          # First get the list of existing host only network names\n          network_names = read_host_only_networks.map { |net| net[:name] }\n          # Prune the network names to only include ones we manage\n          network_names.delete_if { |name| !name.start_with?(HOSTONLY_NAME_PREFIX) }\n\n          @logger.debug(\"managed host only network names: #{network_names}\")\n\n          return if network_names.empty?\n\n          # Next get the list of host only networks currently in use\n          inuse_names = []\n          execute(\"list\", \"vms\", retryable: true).split(\"\\n\").each do |line|\n            match = line.match(/^\".+?\"\\s+\\{(?<vmid>.+?)\\}$/)\n            next if match.nil?\n            begin\n              info = execute(\"showvminfo\", match[:vmid].to_s, \"--machinereadable\", retryable: true)\n              info.split(\"\\n\").each do |vmline|\n                if vmline.start_with?(\"hostonly-network\")\n                  net_name = vmline.split(\"=\", 2).last.to_s.gsub('\"', \"\")\n                  inuse_names << net_name\n                end\n              end\n            rescue Vagrant::Errors::VBoxManageError => err\n              raise if !err.extra_data[:stderr].include?(\"VBOX_E_OBJECT_NOT_FOUND\")\n            end\n          end\n\n          @logger.debug(\"currently in use network names: #{inuse_names}\")\n\n          # Now remove all the networks not in use\n          (network_names - inuse_names).each do |name|\n            execute(\"hostonlynet\", \"remove\", \"--name\", name, retryable: true)\n          end\n        end\n\n        def enable_adapters(adapters)\n          return super if !use_host_only_nets?\n\n          hostonly_adapters = adapters.find_all { |adapter| adapter[:hostonly] }\n          other_adapters = adapters - hostonly_adapters\n          super(other_adapters) if !other_adapters.empty?\n\n          if !hostonly_adapters.empty?\n            args = []\n            hostonly_adapters.each do |adapter|\n              args.concat([\"--nic#{adapter[:adapter]}\", \"hostonlynet\"])\n              args.concat([\"--host-only-net#{adapter[:adapter]}\", adapter[:hostonly],\n                           \"--cableconnected#{adapter[:adapter]}\", \"on\"])\n            end\n\n            execute(\"modifyvm\", @uuid, *args, retryable: true)\n          end\n        end\n\n        def create_host_only_network(options)\n          # If we are not on macOS, just setup the hostonly interface\n          return super if !use_host_only_nets?\n\n          opts = {\n            netmask: options.fetch(:netmask, DEFAULT_NETMASK),\n          }\n\n          if options[:type] == :dhcp\n            opts[:lower] = options[:dhcp_lower]\n            opts[:upper] = options[:dhcp_upper]\n          else\n            addr = IPAddr.new(options[:adapter_ip])\n            opts[:upper] = opts[:lower] = addr.mask(opts[:netmask]).to_range.first.to_s\n          end\n\n          name_idx = read_host_only_networks.map { |hn|\n            next if !hn[:name].start_with?(HOSTONLY_NAME_PREFIX)\n            hn[:name].sub(HOSTONLY_NAME_PREFIX, \"\").to_i\n          }.compact.max.to_i + 1\n          opts[:name] = HOSTONLY_NAME_PREFIX + name_idx.to_s\n\n          execute(\"hostonlynet\", \"add\",\n                  \"--name\", opts[:name],\n                  \"--netmask\", opts[:netmask],\n                  \"--lower-ip\", opts[:lower],\n                  \"--upper-ip\", opts[:upper],\n                  retryable: true)\n\n          {\n            name: opts[:name],\n            ip: options[:adapter_ip],\n            netmask: opts[:netmask],\n          }\n        end\n\n        # Disabled when host only nets are in use\n        def reconfig_host_only(options)\n          return super if !use_host_only_nets?\n        end\n\n        # Disabled when host only nets are in use since\n        # the host only nets will provide the dhcp server\n        def remove_dhcp_server(*_, **_)\n          super if !use_host_only_nets?\n        end\n\n        # Disabled when host only nets are in use since\n        # the host only nets will provide the dhcp server\n        def create_dhcp_server(*_, **_)\n          super if !use_host_only_nets?\n        end\n\n        def read_host_only_interfaces\n          return super if !use_host_only_nets?\n\n          # When host only nets are in use, read them and\n          # reformat the information to line up with how\n          # the interfaces is structured\n          read_host_only_networks.map do |net|\n            addr = begin\n                     IPAddr.new(net[:lowerip])\n                   rescue IPAddr::Error => err\n                     @logger.warn(\"invalid host only network lower IP encountered: #{err} (#{net})\")\n                     next\n                   end\n            # Address of the interface will be the lower bound of the range or\n            # the first available address in the subnet\n            if addr == addr.mask(net[:networkmask])\n              addr = addr.succ\n            end\n\n            net[:netmask] = net[:networkmask]\n            if addr.ipv4?\n              net[:ip] = addr.to_s\n              net[:ipv6] = \"\"\n            else\n              net[:ip] = \"\"\n              net[:ipv6] = addr.to_s\n              net[:ipv6_prefix] = net[:netmask]\n            end\n\n            net[:status] = net[:state] == \"Enabled\" ? \"Up\" : \"Down\"\n\n            net\n          end.compact\n        end\n\n        def read_network_interfaces\n          return super if !use_host_only_nets?\n\n          {}.tap do |nics|\n            execute(\"showvminfo\", @uuid, \"--machinereadable\", retryable: true).each_line do |line|\n              if m = line.match(/nic(?<adapter>\\d+)=\"(?<type>.+?)\"$/)\n                nics[m[:adapter].to_i] ||= {}\n                if m[:type] == \"hostonlynetwork\"\n                  nics[m[:adapter].to_i][:type] = :hostonly\n                else\n                  nics[m[:adapter].to_i][:type] = m[:type].to_sym\n                end\n              elsif m = line.match(/^bridgeadapter(?<adapter>\\d+)=\"(?<network>.+?)\"$/)\n                nics[m[:adapter].to_i] ||= {}\n                nics[m[:adapter].to_i][:bridge] = m[:network]\n              elsif m = line.match(/^hostonly-network(?<adapter>\\d+)=\"(?<network>.+?)\"$/)\n                nics[m[:adapter].to_i] ||= {}\n                nics[m[:adapter].to_i][:hostonly] = m[:network]\n              end\n            end\n          end\n        end\n\n        # The initial VirtualBox 7.0 release has an issue with displaying port\n        # forward information. When a single port forward is defined, the forwarding\n        # information can be found in the `showvminfo` output. Once more than a\n        # single port forward is defined, no forwarding information is provided\n        # in the `showvminfo` output. To work around this we grab the VM configuration\n        # file from the `showvminfo` output and extract the port forward information\n        # from there instead.\n        def read_forwarded_ports(uuid=nil, active_only=false)\n          # Only use this override for the 7.0.0 release.\n          return super if get_version.to_s != \"7.0.0\"\n\n          uuid ||= @uuid\n\n          @logger.debug(\"read_forward_ports: uuid=#{uuid} active_only=#{active_only}\")\n\n          results = []\n\n          info = execute(\"showvminfo\", uuid, \"--machinereadable\", retryable: true)\n          result = info.match(/CfgFile=\"(?<path>.+?)\"/)\n          if result.nil?\n            raise Vagrant::Errors::VirtualBoxConfigNotFound,\n                  uuid: uuid\n          end\n\n          File.open(result[:path], \"r\") do |f|\n            doc = REXML::Document.new(f)\n            networks = REXML::XPath.each(doc.root, \"Machine/Hardware/Network/Adapter\")\n            networks.each do |net|\n              REXML::XPath.each(doc.root, net.xpath + \"/NAT/Forwarding\") do |fwd|\n                # Result Array values:\n                # [NIC Slot, Name, Host Port, Guest Port, Host IP]\n                result = [\n                  net.attribute(\"slot\").value.to_i + 1,\n                  fwd.attribute(\"name\")&.value.to_s,\n                  fwd.attribute(\"hostport\")&.value.to_i,\n                  fwd.attribute(\"guestport\")&.value.to_i,\n                  fwd.attribute(\"hostip\")&.value.to_s\n                ]\n                @logger.debug(\" - #{result.inspect}\")\n                results << result\n              end\n            end\n          end\n\n          results\n        end\n\n        protected\n\n        # Generate list of host only networks\n        # NOTE: This is darwin specific\n        def read_host_only_networks\n          networks = []\n          current = nil\n          execute(\"list\", \"hostonlynets\", retryable: true).split(\"\\n\").each do |line|\n            line.chomp!\n            next if line.empty?\n            key, value = line.split(\":\", 2).map(&:strip)\n            key = key.downcase\n            if key == \"name\"\n              networks.push(current) if !current.nil?\n              current = Vagrant::Util::HashWithIndifferentAccess.new\n            end\n            current[key] = value\n          end\n          networks.push(current) if !current.nil?\n\n          networks\n        end\n\n        private\n\n        # Returns if hostonlynets are enabled on the current\n        # host platform\n        #\n        # @return [Boolean]\n        def use_host_only_nets?\n          Vagrant::Util::Platform.darwin? &&\n            HOSTONLY_NET_REQUIREMENT.satisfied_by?(get_version)\n        end\n\n        # VirtualBox version in use\n        #\n        # @return [Gem::Version]\n        def get_version\n          return @version if @version\n          @version = Gem::Version.new(Meta.new.version)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/version_7_1.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../version_7_0\", __FILE__)\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      # Driver for VirtualBox 7.1.x\n      class Version_7_1 < Version_7_0\n        def initialize(uuid)\n          super\n\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox_7_1\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/driver/version_7_2.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../version_7_0\", __FILE__)\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Driver\n      # Driver for VirtualBox 7.2.x\n      class Version_7_2 < Version_7_1\n        def initialize(uuid)\n          super\n\n          @logger = Log4r::Logger.new(\"vagrant::provider::virtualbox_7_2\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/model/forwarded_port.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Model\n      # Represents a single forwarded port for VirtualBox. This has various\n      # helpers and defaults for a forwarded port.\n      class ForwardedPort\n        # The NAT adapter on which to attach the forwarded port.\n        #\n        # @return [Integer]\n        attr_reader :adapter\n\n        # If true, this port should be auto-corrected.\n        #\n        # @return [Boolean]\n        attr_reader :auto_correct\n\n        # The unique ID for the forwarded port.\n        #\n        # @return [String]\n        attr_reader :id\n\n        # The protocol to forward.\n        #\n        # @return [String]\n        attr_reader :protocol\n\n        # The IP that the forwarded port will connect to on the guest machine.\n        #\n        # @return [String]\n        attr_reader :guest_ip\n\n        # The port on the guest to be exposed on the host.\n        #\n        # @return [Integer]\n        attr_reader :guest_port\n\n        # The IP that the forwarded port will bind to on the host machine.\n        #\n        # @return [String]\n        attr_reader :host_ip\n\n        # The port on the host used to access the port on the guest.\n        #\n        # @return [Integer]\n        attr_reader :host_port\n\n        def initialize(id, host_port, guest_port, options)\n          @id         = id\n          @guest_port = guest_port\n          @host_port  = host_port\n\n          options ||= {}\n          @auto_correct = false\n          @auto_correct = options[:auto_correct] if options.key?(:auto_correct)\n          @adapter  = (options[:adapter] || 1).to_i\n          @guest_ip = options[:guest_ip] || nil\n          @host_ip = options[:host_ip] || nil\n          @protocol = options[:protocol] || \"tcp\"\n        end\n\n        # This corrects the host port and changes it to the given new port.\n        #\n        # @param [Integer] new_port The new port\n        def correct_host_port(new_port)\n          @host_port = new_port\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/model/storage_controller.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Model\n      # Represents a storage controller for VirtualBox. Storage controllers\n      # have a type, a name, and can have hard disks or optical drives attached.\n      class StorageController\n        IDE_CONTROLLER_TYPES = [\"PIIX4\", \"PIIX3\", \"ICH6\"].map(&:freeze).freeze\n        SATA_CONTROLLER_TYPES = [\"IntelAhci\"].map(&:freeze).freeze\n        SCSI_CONTROLLER_TYPES = [\"LsiLogic\", \"LsiLogicSas\", \"BusLogic\", \"VirtioSCSI\"].map(&:freeze).freeze\n\n        IDE_DEVICES_PER_PORT = 2.freeze\n        SATA_DEVICES_PER_PORT = 1.freeze\n        SCSI_DEVICES_PER_PORT = 1.freeze\n\n        IDE_BOOT_PRIORITY = 1.freeze\n        SATA_BOOT_PRIORITY = 2.freeze\n        SCSI_BOOT_PRIORITY = 3.freeze\n\n        # The name of the storage controller.\n        #\n        # @return [String]\n        attr_reader :name\n\n        # The specific type of controller.\n        #\n        # @return [String]\n        attr_reader :type\n\n        # The maximum number of avilable ports for the storage controller.\n        #\n        # @return [Integer]\n        attr_reader :maxportcount\n\n        # The number of devices that can be attached to each port. For SATA\n        # controllers, this will usually be 1, and for IDE controllers this\n        # will usually be 2.\n        # @return [Integer]\n        attr_reader :devices_per_port\n\n        # The maximum number of individual disks that can be attached to the\n        # storage controller. For SATA controllers, this equals the maximum\n        # number of ports. For IDE controllers, this will be twice the max\n        # number of ports (primary/secondary).\n        #\n        # @return [Integer]\n        attr_reader :limit\n\n        # The boot priority of the storage controller. This does not seem to\n        # depend on the controller number returned by `showvminfo`.\n        # Experimentation has determined that VirtualBox will try to boot from\n        # the first controller it finds with a hard disk, in this order:\n        #   IDE, SATA, SCSI\n        #\n        # @return [Integer]\n        attr_reader :boot_priority\n\n        # The list of disks/ISOs attached to each storage controller.\n        #\n        # @return [Array<Hash>]\n        attr_reader :attachments\n\n        def initialize(name, type, maxportcount, attachments)\n          @name = name\n          @type = type\n\n          @maxportcount = maxportcount.to_i\n\n          if IDE_CONTROLLER_TYPES.include?(@type)\n            @storage_bus = :ide\n            @devices_per_port = IDE_DEVICES_PER_PORT\n            @boot_priority = IDE_BOOT_PRIORITY\n          elsif SATA_CONTROLLER_TYPES.include?(@type)\n            @storage_bus = :sata\n            @devices_per_port = SATA_DEVICES_PER_PORT\n            @boot_priority = SATA_BOOT_PRIORITY\n          elsif SCSI_CONTROLLER_TYPES.include?(@type)\n            @storage_bus = :scsi\n            @devices_per_port = SCSI_DEVICES_PER_PORT\n            @boot_priority = SCSI_BOOT_PRIORITY\n          else\n            @storage_bus = :unknown\n            @devices_per_port = 1\n          end\n\n          @limit = @maxportcount * @devices_per_port\n\n          attachments ||= []\n          @attachments = attachments\n        end\n\n        # Get a single storage device, either by port/device address or by\n        # UUID.\n        #\n        # @param [Hash] opts - A hash of options to match\n        # @return [Hash] attachment - Attachment information\n        def get_attachment(opts = {})\n          if opts[:port] && opts[:device]\n            @attachments.detect { |a| a[:port] == opts[:port] &&\n                                      a[:device] == opts[:device] }\n          elsif opts[:uuid]\n            @attachments.detect { |a| a[:uuid] == opts[:uuid] }\n          end\n        end\n\n        # Returns true if the storage controller has a supported type.\n        #\n        # @return [Boolean]\n        def supported?\n          [:ide, :sata, :scsi].include?(@storage_bus)\n        end\n\n        # Returns true if the storage controller is a IDE type controller.\n        #\n        # @return [Boolean]\n        def ide?\n          @storage_bus == :ide\n        end\n\n        # Returns true if the storage controller is a SATA type controller.\n        #\n        # @return [Boolean]\n        def sata?\n          @storage_bus == :sata\n        end\n\n        # Returns true if the storage controller is a SCSI type controller.\n        #\n        # @return [Boolean]\n        def scsi?\n          @storage_bus == :scsi\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/model/storage_controller_array.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../cap/validate_disk_ext\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Model\n      # A collection of storage controllers. Includes finder methods to look\n      # up a storage controller by given attributes.\n      class StorageControllerArray < Array\n        # Returns a storage controller with the given name. Raises an\n        # exception if a matching controller can't be found.\n        #\n        # @param [String] name - The name of the storage controller\n        # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController]\n        def get_controller(name)\n          controller = detect { |c| c.name == name }\n          if !controller\n            raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, name: name\n          end\n          controller\n        end\n\n        # Find the controller containing the primary disk (i.e. the boot\n        # disk). This is used to determine which controller virtual disks\n        # should be attached to.\n        #\n        # Raises an exception if no supported controllers are found.\n        #\n        # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController]\n        def get_primary_controller\n          ordered = find_all(&:supported?).sort_by(&:boot_priority)\n          controller = ordered.detect { |c| c.attachments.any? { |a| hdd?(a) } }\n\n          if !controller\n            raise Vagrant::Errors::VirtualBoxDisksNoSupportedControllers,\n              supported_types: supported_types.join(\", \")\n          end\n\n          controller\n        end\n\n        # Find the attachment representing the primary disk (i.e. the boot\n        # disk). We can't rely on the order of #list_hdds, as they will not\n        # always come in port order, but primary is always Port 0 Device 0.\n        #\n        # @return [Hash] attachment - Primary disk attachment information\n        def get_primary_attachment\n          attachment = nil\n\n          controller = get_primary_controller\n          attachment = controller.get_attachment(port: \"0\", device: \"0\")\n          if !attachment\n            raise Vagrant::Errors::VirtualBoxDisksPrimaryNotFound\n          end\n\n          attachment\n        end\n\n        # Returns the first supported storage controller for attaching dvds.\n        # Will raise an exception if no suitable controller can be found.\n        #\n        # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController]\n        def get_dvd_controller\n          ordered = find_all(&:supported?).sort_by(&:boot_priority)\n          controller = ordered.first\n          if !controller\n            raise Vagrant::Errors::VirtualBoxDisksNoSupportedControllers,\n              supported_types: supported_types.join(\", \")\n          end\n\n          controller\n        end\n\n        private\n\n        # Determine whether the given attachment is a hard disk.\n        #\n        # @param [Hash] attachment - Attachment information\n        # @return [Boolean]\n        def hdd?(attachment)\n          if !attachment\n            false\n          else\n            ext = File.extname(attachment[:location].to_s).downcase.split('.').last\n            VagrantPlugins::ProviderVirtualBox::Cap::ValidateDiskExt.validate_disk_ext(nil, ext)\n          end\n        end\n\n        # Returns a list of all the supported controller types.\n        #\n        # @return [Array<String>]\n        def supported_types\n          StorageController::SATA_CONTROLLER_TYPES + StorageController::IDE_CONTROLLER_TYPES +\n            StorageController::SCSI_CONTROLLER_TYPES\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"VirtualBox provider\"\n      description <<-EOF\n      The VirtualBox provider allows Vagrant to manage and control\n      VirtualBox-based virtual machines.\n      EOF\n\n      provider(:virtualbox, priority: 6) do\n        require File.expand_path(\"../provider\", __FILE__)\n        Provider\n      end\n\n      config(:virtualbox, :provider) do\n        require File.expand_path(\"../config\", __FILE__)\n        Config\n      end\n\n      synced_folder(:virtualbox) do\n        require File.expand_path(\"../synced_folder\", __FILE__)\n        SyncedFolder\n      end\n\n      provider_capability(:virtualbox, :forwarded_ports) do\n        require_relative \"cap\"\n        Cap\n      end\n\n      provider_capability(:virtualbox, :nic_mac_addresses) do\n        require_relative \"cap\"\n        Cap\n      end\n\n      provider_capability(:virtualbox, :public_address) do\n        require_relative \"cap/public_address\"\n        Cap::PublicAddress\n      end\n\n      provider_capability(:virtualbox, :configure_disks) do\n        require_relative \"cap/configure_disks\"\n        Cap::ConfigureDisks\n      end\n\n      provider_capability(:virtualbox, :cleanup_disks) do\n        require_relative \"cap/cleanup_disks\"\n        Cap::CleanupDisks\n      end\n\n      provider_capability(:virtualbox, :validate_disk_ext) do\n        require_relative \"cap/validate_disk_ext\"\n        Cap::ValidateDiskExt\n      end\n\n      provider_capability(:virtualbox, :default_disk_exts) do\n        require_relative \"cap/validate_disk_ext\"\n        Cap::ValidateDiskExt\n      end\n\n      provider_capability(:virtualbox, :set_default_disk_ext) do\n        require_relative \"cap/validate_disk_ext\"\n        Cap::ValidateDiskExt\n      end\n\n      provider_capability(:virtualbox, :snapshot_list) do\n        require_relative \"cap\"\n        Cap\n      end\n\n      synced_folder_capability(:virtualbox, \"mount_options\") do\n        require_relative \"cap/mount_options\"\n        Cap::MountOptions\n      end\n\n      synced_folder_capability(:virtualbox, \"mount_type\") do\n        require_relative \"cap/mount_options\"\n        Cap::MountOptions\n      end\n\n      synced_folder_capability(:virtualbox, \"mount_name\") do\n        require_relative \"cap/mount_options\"\n        Cap::MountOptions\n      end\n    end\n\n    autoload :Action, File.expand_path(\"../action\", __FILE__)\n\n    # Drop some autoloads in here to optimize the performance of loading\n    # our drivers only when they are needed.\n    module Driver\n      autoload :Meta, File.expand_path(\"../driver/meta\", __FILE__)\n      autoload :Version_4_0, File.expand_path(\"../driver/version_4_0\", __FILE__)\n      autoload :Version_4_1, File.expand_path(\"../driver/version_4_1\", __FILE__)\n      autoload :Version_4_2, File.expand_path(\"../driver/version_4_2\", __FILE__)\n      autoload :Version_4_3, File.expand_path(\"../driver/version_4_3\", __FILE__)\n      autoload :Version_5_0, File.expand_path(\"../driver/version_5_0\", __FILE__)\n      autoload :Version_5_1, File.expand_path(\"../driver/version_5_1\", __FILE__)\n      autoload :Version_5_2, File.expand_path(\"../driver/version_5_2\", __FILE__)\n      autoload :Version_6_0, File.expand_path(\"../driver/version_6_0\", __FILE__)\n      autoload :Version_6_1, File.expand_path(\"../driver/version_6_1\", __FILE__)\n      autoload :Version_7_0, File.expand_path(\"../driver/version_7_0\", __FILE__)\n      autoload :Version_7_1, File.expand_path(\"../driver/version_7_1\", __FILE__)\n      autoload :Version_7_2, File.expand_path(\"../driver/version_7_2\", __FILE__)\n    end\n\n    module Model\n      autoload :ForwardedPort, File.expand_path(\"../model/forwarded_port\", __FILE__)\n      autoload :StorageController, File.expand_path(\"../model/storage_controller\", __FILE__)\n      autoload :StorageControllerArray, File.expand_path(\"../model/storage_controller_array\", __FILE__)\n    end\n\n    module Util\n      autoload :CompileForwardedPorts, File.expand_path(\"../util/compile_forwarded_ports\", __FILE__)\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/provider.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    class Provider < Vagrant.plugin(\"2\", :provider)\n      attr_reader :driver\n\n      def self.installed?\n        Driver::Meta.new\n        true\n      rescue Vagrant::Errors::VirtualBoxInvalidVersion,\n             Vagrant::Errors::VirtualBoxNotDetected,\n             Vagrant::Errors::VirtualBoxKernelModuleNotLoaded,\n             Vagrant::Errors::VirtualBoxInstallIncomplete\n        return false\n      end\n\n      def self.usable?(raise_error=false)\n        # Instantiate the driver, which will determine the VirtualBox\n        # version and all that, which checks for VirtualBox being present\n        Driver::Meta.new\n        true\n      rescue Vagrant::Errors::VirtualBoxInvalidVersion,\n             Vagrant::Errors::VirtualBoxNotDetected,\n             Vagrant::Errors::VirtualBoxKernelModuleNotLoaded,\n             Vagrant::Errors::VirtualBoxInstallIncomplete,\n             Vagrant::Errors::VBoxManageNotFoundError\n        raise if raise_error\n        return false\n      end\n\n      def initialize(machine)\n        @logger  = Log4r::Logger.new(\"vagrant::provider::virtualbox\")\n        @machine = machine\n\n        # This method will load in our driver, so we call it now to\n        # initialize it.\n        machine_id_changed\n      end\n\n      # @see Vagrant::Plugin::V1::Provider#action\n      def action(name)\n        # Attempt to get the action method from the Action class if it\n        # exists, otherwise return nil to show that we don't support the\n        # given action.\n        action_method = \"action_#{name}\"\n        return Action.send(action_method) if Action.respond_to?(action_method)\n        nil\n      end\n\n      # If the machine ID changed, then we need to rebuild our underlying\n      # driver.\n      def machine_id_changed\n        id = @machine.id\n\n        begin\n          @logger.debug(\"Instantiating the driver for machine ID: #{@machine.id.inspect}\")\n          @driver = Driver::Meta.new(id)\n        rescue Driver::Meta::VMNotFound\n          # The virtual machine doesn't exist, so we probably have a stale\n          # ID. Just clear the id out of the machine and reload it.\n          @logger.debug(\"VM not found! Clearing saved machine ID and reloading.\")\n          id = nil\n          retry\n        end\n      end\n\n      # Returns the SSH info for accessing the VirtualBox VM.\n      def ssh_info\n        # If the VM is not running that we can't possibly SSH into it\n        return nil if state.id != :running\n\n        # Return what we know. The host is always \"127.0.0.1\" because\n        # VirtualBox VMs are always local. The port we try to discover\n        # by reading the forwarded ports.\n        return {\n          host: \"127.0.0.1\",\n          port: @driver.ssh_port(@machine.config.ssh.guest_port)\n        }\n      end\n\n      # Return the state of VirtualBox virtual machine by actually\n      # querying VBoxManage.\n      #\n      # @return [Symbol]\n      def state\n        # We have to check if the UID matches to avoid issues with\n        # VirtualBox.\n        if Vagrant::Util::Platform.wsl_windows_access_bypass?(@machine.data_dir)\n          @logger.warn(\"Skipping UID check on machine by user request for WSL Windows access.\")\n        else\n          uid = @machine.uid\n          if uid && uid.to_s != Process.uid.to_s\n            raise Vagrant::Errors::VirtualBoxUserMismatch,\n              original_uid: uid.to_s,\n              uid: Process.uid.to_s\n          end\n        end\n\n        # Determine the ID of the state here.\n        state_id = nil\n        state_id = :not_created if !@driver.uuid\n        state_id = @driver.read_state if !state_id\n        state_id = :unknown if !state_id\n\n        # Translate into short/long descriptions\n        short = state_id.to_s.gsub(\"_\", \" \")\n        long  = I18n.t(\"vagrant.commands.status.#{state_id}\")\n\n        # If we're not created, then specify the special ID flag\n        if state_id == :not_created\n          state_id = Vagrant::MachineState::NOT_CREATED_ID\n        end\n\n        # Return the state\n        Vagrant::MachineState.new(state_id, short, long)\n      end\n\n      # Returns a human-friendly string version of this provider which\n      # includes the machine's ID that this provider represents, if it\n      # has one.\n      #\n      # @return [String]\n      def to_s\n        id = @machine.id ? @machine.id : \"new VM\"\n        \"VirtualBox (#{id})\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/synced_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\nrequire \"vagrant/util/platform\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    class SyncedFolder < Vagrant.plugin(\"2\", :synced_folder)\n      def usable?(machine, raise_errors=false)\n        # These synced folders only work if the provider if VirtualBox\n        return false if machine.provider_name != :virtualbox\n\n        # This only happens with `vagrant package --base`. Sigh.\n        return true if !machine.provider_config\n\n        machine.provider_config.functional_vboxsf\n      end\n\n      def prepare(machine, folders, _opts)\n        share_folders(machine, folders, false)\n      end\n\n      def enable(machine, folders, _opts)\n        share_folders(machine, folders, true)\n\n        # short guestpaths first, so we don't step on ourselves\n        folders = folders.sort_by do |id, data|\n          if data[:guestpath]\n            data[:guestpath].length\n          else\n            # A long enough path to just do this at the end.\n            10000\n          end\n        end\n\n        # Go through each folder and mount\n        machine.ui.output(I18n.t(\"vagrant.actions.vm.share_folders.mounting\"))\n        fstab_folders = []\n        folders.each do |id, data|\n          if data[:guestpath]\n            # Guest path specified, so mount the folder to specified point\n            machine.ui.detail(I18n.t(\"vagrant.actions.vm.share_folders.mounting_entry\",\n                                  guestpath: data[:guestpath],\n                                  hostpath: data[:hostpath]))\n\n            # Dup the data so we can pass it to the guest API\n            data = data.dup\n\n            # Calculate the owner and group\n            ssh_info = machine.ssh_info\n            data[:owner] ||= ssh_info[:username]\n            data[:group] ||= ssh_info[:username]\n\n            # Mount the actual folder\n            machine.guest.capability(\n              :mount_virtualbox_shared_folder,\n              os_friendly_id(id), data[:guestpath], data)\n          else\n            # If no guest path is specified, then automounting is disabled\n            machine.ui.detail(I18n.t(\"vagrant.actions.vm.share_folders.nomount_entry\",\n                                  hostpath: data[:hostpath]))\n          end\n        end\n      end\n\n      def disable(machine, folders, _opts)\n        if machine.guest.capability?(:unmount_virtualbox_shared_folder)\n          folders.each do |id, data|\n            machine.guest.capability(\n              :unmount_virtualbox_shared_folder,\n              data[:guestpath], data)\n          end\n        end\n\n        # Remove the shared folders from the VM metadata\n        names = folders.map { |id, _data| os_friendly_id(id) }\n        driver(machine).unshare_folders(names)\n      end\n\n      def cleanup(machine, opts)\n        driver(machine).clear_shared_folders if machine.id && machine.id != \"\"\n      end\n\n      protected\n\n      # This is here so that we can stub it for tests\n      def driver(machine)\n        machine.provider.driver\n      end\n\n      def os_friendly_id(id)\n        id.gsub(/[\\s\\/\\\\]/,'_').sub(/^_/, '')\n      end\n\n      # share_folders sets up the shared folder definitions on the\n      # VirtualBox VM.\n      #\n      # The transient parameter determines if we're FORCING transient\n      # or not. If this is false, then any shared folders will be\n      # shared as non-transient unless they've specifically asked for\n      # transient.\n      def share_folders(machine, folders, transient)\n        defs = []\n        warn_user_symlink = false\n\n        folders.each do |id, data|\n          hostpath = data[:hostpath]\n          if !data[:hostpath_exact]\n            hostpath = Vagrant::Util::Platform.cygwin_windows_path(hostpath)\n          end\n\n          enable_symlink_create = true\n\n          if ENV['VAGRANT_DISABLE_VBOXSYMLINKCREATE']\n            enable_symlink_create = false\n          end\n\n          unless data[:SharedFoldersEnableSymlinksCreate].nil?\n            enable_symlink_create = data[:SharedFoldersEnableSymlinksCreate]\n          end\n\n          warn_user_symlink ||= enable_symlink_create\n\n          # Only setup the shared folders that match our transient level\n          if (!!data[:transient]) == transient\n            defs << {\n              name: os_friendly_id(id),\n              hostpath: hostpath.to_s,\n              transient: transient,\n              SharedFoldersEnableSymlinksCreate: enable_symlink_create,\n              automount: !!data[:automount]\n            }\n          end\n        end\n\n        if warn_user_symlink\n          display_symlink_create_warning(machine.env)\n        end\n\n        driver(machine).share_folders(defs)\n      end\n\n      def display_symlink_create_warning(env)\n        d_file = env.data_dir.join(\"vbox_symlink_create_warning\")\n        if !d_file.exist?\n          FileUtils.touch(d_file.to_path)\n          env.ui.warn(I18n.t(\"vagrant.virtualbox.warning.shared_folder_symlink_create\"))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/providers/virtualbox/util/compile_forwarded_ports.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/scoped_hash_override\"\n\nmodule VagrantPlugins\n  module ProviderVirtualBox\n    module Util\n      module CompileForwardedPorts\n        include Vagrant::Util::ScopedHashOverride\n\n        # This method compiles the forwarded ports into {ForwardedPort}\n        # models.\n        def compile_forwarded_ports(config)\n          mappings = {}\n\n          config.vm.networks.each do |type, options|\n            if type == :forwarded_port\n              guest_port = options[:guest]\n              host_port  = options[:host]\n              host_ip    = options[:host_ip]\n              protocol   = options[:protocol] || \"tcp\"\n              options    = scoped_hash_override(options, :virtualbox)\n              id         = options[:id]\n\n              # If the forwarded port was marked as disabled, ignore.\n              next if options[:disabled]\n\n              key = \"#{host_ip}#{protocol}#{host_port}\"\n              mappings[key] =\n                Model::ForwardedPort.new(id, host_port, guest_port, options)\n            end\n          end\n\n          mappings.values\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/cap/guest/alpine/ansible_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../facts\"\nrequire_relative \"../pip/pip\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Cap\n      module Guest\n        module Alpine\n          module AnsibleInstall\n\n            def self.ansible_install(machine, install_mode, ansible_version, pip_args, pip_install_cmd = \"\")\n              case install_mode\n                when :pip\n                  pip_setup machine, pip_install_cmd\n                  Pip::pip_install machine, \"ansible\", ansible_version, pip_args, true\n                when :pip_args_only\n                  pip_setup machine, pip_install_cmd\n                  Pip::pip_install machine, \"\", \"\", pip_args, false\n                else\n                  ansible_apk_install machine\n              end\n            end\n\n            private\n\n            def self.ansible_apk_install(machine)\n              machine.communicate.sudo \"apk add --update --no-cache python3 ansible\"\n              machine.communicate.sudo \"if [ ! -e /usr/bin/python ]; then ln -sf python3 /usr/bin/python ; fi\"\n              machine.communicate.sudo \"if [ ! -e /usr/bin/pip ]; then ln -sf pip3 /usr/bin/pip ; fi\"\n            end\n\n            def self.pip_setup(machine, pip_install_cmd = \"\")\n              machine.communicate.sudo \"apk add --update --no-cache python3\"\n              machine.communicate.sudo \"if [ ! -e /usr/bin/python ]; then ln -sf python3 /usr/bin/python ; fi\"\n              machine.communicate.sudo \"apk add --update --no-cache --virtual .build-deps python3-dev libffi-dev openssl-dev build-base\"\n              Pip::get_pip machine, pip_install_cmd\n            end\n\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/cap/guest/arch/ansible_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../errors\"\nrequire_relative \"../pip/pip\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Cap\n      module Guest\n        module Arch\n          module AnsibleInstall\n\n            def self.ansible_install(machine, install_mode, ansible_version, pip_args, pip_install_cmd = \"\")\n              case install_mode\n              when :pip\n                pip_setup machine, pip_install_cmd\n                Pip::pip_install machine, \"ansible\", ansible_version, pip_args, true\n\n              when :pip_args_only\n                pip_setup machine, pip_install_cmd\n                Pip::pip_install machine, \"\", \"\", pip_args, false\n\n              else\n                machine.communicate.sudo \"pacman -Syy --noconfirm\"\n                machine.communicate.sudo \"pacman -S --noconfirm ansible\"\n              end\n            end\n\n            private\n\n            def self.pip_setup(machine, pip_install_cmd = \"\")\n              machine.communicate.sudo \"pacman -Syy --noconfirm\"\n              machine.communicate.sudo \"pacman -S --noconfirm base-devel curl git python\"\n\n              Pip::get_pip machine, pip_install_cmd\n            end\n\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/cap/guest/debian/ansible_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../pip/pip\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Cap\n      module Guest\n        module Debian\n          module AnsibleInstall\n\n            def self.ansible_install(machine, install_mode, ansible_version, pip_args, pip_install_cmd = \"\")\n              case install_mode\n              when :pip\n                pip_setup machine, pip_install_cmd\n                Pip::pip_install machine, \"ansible\", ansible_version, pip_args, true\n              when :pip_args_only\n                pip_setup machine, pip_install_cmd\n                Pip::pip_install machine, \"\", \"\", pip_args, false\n              else\n                ansible_apt_install machine\n              end\n            end\n\n            private\n\n            def self.ansible_apt_install(machine)\ninstall_backports_if_wheezy_release = <<INLINE_CRIPT\nCODENAME=`lsb_release -cs`\nif [ x$CODENAME == 'xwheezy' ]; then\n  echo 'deb http://http.debian.net/debian wheezy-backports main' > /etc/apt/sources.list.d/wheezy-backports.list\nfi\nINLINE_CRIPT\n\n              machine.communicate.sudo install_backports_if_wheezy_release\n              machine.communicate.sudo \"apt-get update -y -qq\"\n              machine.communicate.sudo \"DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --option \\\"Dpkg::Options::=--force-confold\\\" ansible\"\n            end\n\n            def self.pip_setup(machine, pip_install_cmd = \"\")\n              machine.communicate.sudo \"apt-get update -y -qq\"\n              python_dev_pkg = \"python-dev\"\n              if machine.communicate.test \"apt-cache show python-dev-is-python3\"\n                python_dev_pkg = \"python-dev-is-python3\"\n              end\n              machine.communicate.sudo \"DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --option \\\"Dpkg::Options::=--force-confold\\\" build-essential curl git libssl-dev libffi-dev #{python_dev_pkg}\"\n              Pip::get_pip machine, pip_install_cmd\n            end\n\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/cap/guest/facts.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\nmodule VagrantPlugins\n  module Ansible\n    module Cap\n      module Guest\n        module Facts\n\n          def self.dnf?(machine)\n            machine.communicate.test \"/usr/bin/which -s dnf\"\n          end\n\n          def self.yum?(machine)\n            machine.communicate.test \"/usr/bin/which -s yum\"\n          end\n\n          def self.rpm_package_manager(machine)\n            dnf?(machine) ? \"dnf\" : \"yum\"\n          end\n\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/cap/guest/fedora/ansible_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../facts\"\nrequire_relative \"../pip/pip\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Cap\n      module Guest\n        module Fedora\n          module AnsibleInstall\n\n            def self.ansible_install(machine, install_mode, ansible_version, pip_args, pip_install_cmd = \"\")\n              case install_mode\n              when :pip\n                pip_setup machine, pip_install_cmd\n                Pip::pip_install machine, \"ansible\", ansible_version, pip_args, true\n              when :pip_args_only\n                pip_setup machine, pip_install_cmd\n                Pip::pip_install machine, \"\", \"\", pip_args, false\n              else\n                rpm_package_manager = Facts::rpm_package_manager(machine)\n\n                machine.communicate.sudo \"#{rpm_package_manager} -y install ansible\"\n              end\n            end\n\n            private\n\n            def self.pip_setup(machine, pip_install_cmd = \"\")\n              rpm_package_manager = Facts::rpm_package_manager(machine)\n\n              machine.communicate.sudo \"#{rpm_package_manager} install -y curl gcc gmp-devel libffi-devel openssl-devel python-crypto python-devel python-dnf python-setuptools redhat-rpm-config\"\n              Pip::get_pip machine, pip_install_cmd\n            end\n\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/cap/guest/freebsd/ansible_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../errors\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Cap\n      module Guest\n        module FreeBSD\n          module AnsibleInstall\n\n            def self.ansible_install(machine, install_mode, ansible_version, pip_args, pip_install_cmd = \"\")\n              if install_mode != :default\n                raise Ansible::Errors::AnsiblePipInstallIsNotSupported\n              else\n                machine.communicate.sudo \"pkg install -qy py37-ansible\"\n              end\n            end\n\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/cap/guest/pip/pip.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\nmodule VagrantPlugins\n  module Ansible\n    module Cap\n      module Guest\n        module Pip\n\n          DEFAULT_PIP_INSTALL_CMD = \"curl https://bootstrap.pypa.io/get-pip.py | sudo python\".freeze\n\n          def self.pip_install(machine, package = \"\", version = \"\", pip_args = \"\", upgrade = true)\n            upgrade_arg = \"--upgrade\" if upgrade\n            version_arg = \"\"\n\n            if !version.to_s.empty? && version.to_s.to_sym != :latest\n              version_arg = \"==#{version}\"\n            end\n\n            args_array = [pip_args, upgrade_arg, \"#{package}#{version_arg}\"]\n            args_array.reject! { |a| a.nil? || a.empty? }\n\n            pip_install = \"pip install\"\n            pip_install += \" #{args_array.join(' ')}\" unless args_array.empty?\n\n            machine.communicate.sudo pip_install\n          end\n\n          def self.get_pip(machine, pip_install_cmd = DEFAULT_PIP_INSTALL_CMD)\n            # The objective here is to get pip either by default\n            # or by the argument passed in. The objective is not\n            # to circumvent the pip setup by passing in nothing.\n            # Thus, we stick with the default on an empty string.\n            # Typecast added in the check for safety.\n\n            if pip_install_cmd.to_s.empty?\n              pip_install_cmd = DEFAULT_PIP_INSTALL_CMD\n            end\n\n            machine.ui.detail I18n.t(\"vagrant.provisioners.ansible.installing_pip\")\n            machine.communicate.execute pip_install_cmd\n          end\n\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/cap/guest/posix/ansible_installed.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module Ansible\n    module Cap\n      module Guest\n        module POSIX\n          module AnsibleInstalled\n\n            # Check if Ansible is installed (at the given version).\n            # @return [true, false]\n            def self.ansible_installed(machine, version)\n              command = 'test -x \"$(command -v ansible)\"'\n\n              unless version.empty?\n                command << \"&& [[ $(python3 -c \\\"import importlib.metadata; print(importlib.metadata.version('ansible'))\\\") == \\\"#{version}\\\" ]]\"\n              end\n\n              machine.communicate.test command, sudo: false\n            end\n\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/cap/guest/redhat/ansible_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../facts\"\nrequire_relative \"../pip/pip\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Cap\n      module Guest\n        module RedHat\n          module AnsibleInstall\n\n            def self.ansible_install(machine, install_mode, ansible_version, pip_args, pip_install_cmd = \"\")\n              case install_mode\n              when :pip\n                pip_setup machine, pip_install_cmd\n                Pip::pip_install machine, \"ansible\", ansible_version, pip_args, true\n              when :pip_args_only\n                pip_setup machine, pip_install_cmd\n                Pip::pip_install machine, \"\", \"\", pip_args, false\n              else\n                ansible_rpm_install machine\n              end\n            end\n\n            private\n\n            def self.ansible_rpm_install(machine)\n              rpm_package_manager = Facts::rpm_package_manager(machine)\n\n              epel = machine.communicate.execute \"#{rpm_package_manager} repolist epel | grep -q epel\", error_check: false\n              if epel != 0\n                machine.communicate.sudo \"sudo rpm -i #{ansible_epel_download_url(machine)}\"\n              end\n              machine.communicate.sudo \"#{rpm_package_manager} -y --enablerepo=epel install ansible\"\n            end\n\n            def self.pip_setup(machine, pip_install_cmd = \"\")\n              rpm_package_manager = Facts::rpm_package_manager(machine)\n\n              # Use other packages for RHEL > 7 and set alternatives for RHEL 8\n              machine.communicate.sudo(%Q{\n                source /etc/os-release\n                MAJOR=$(echo $VERSION_ID | cut -d. -f1)\n                if [ $MAJOR -ge 8 ]; then\n                  #{rpm_package_manager} -y install curl gcc libffi-devel openssl-devel python3-cryptography python3-devel python3-setuptools\n                else\n                  #{rpm_package_manager} -y install curl gcc libffi-devel openssl-devel python-crypto python-devel python-setuptools\n                fi\n                if [ $MAJOR -eq 8 ]; then\n                  alternatives --set python /usr/bin/python3\n                fi\n              })\n\n              # pip is already installed as dependency for RHEL > 7\n              if machine.communicate.test(\"test ! -f /usr/bin/pip3\")\n                Pip::get_pip machine, pip_install_cmd\n              end\n              # Set pip-alternative for RHEL 8\n              machine.communicate.sudo(%Q{\n                source /etc/os-release\n                MAJOR=$(echo $VERSION_ID | cut -d. -f1)\n                if [ $MAJOR -eq 8 ]; then\n                  alternatives --install /usr/bin/pip pip /usr/local/bin/pip 1\n                fi\n              })\n            end\n\n            def self.ansible_epel_download_url(machine)\n              dist = \"\"\n              machine.communicate.execute(\"rpm -E %dist\") do |type, data|\n                dist << data if type == :stdout\n              end\n              dist.strip!\n              dist_major_version = dist.match(/.*el(\\d+).*/)&.captures&.first\n              \"https://dl.fedoraproject.org/pub/epel/epel-release-latest-#{dist_major_version}.noarch.rpm\"\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/cap/guest/suse/ansible_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../errors\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Cap\n      module Guest\n        module SUSE\n          module AnsibleInstall\n\n            def self.ansible_install(machine, install_mode, ansible_version, pip_args, pip_install_cmd = \"\")\n              if install_mode != :default\n                raise Ansible::Errors::AnsiblePipInstallIsNotSupported\n              else\n                machine.communicate.sudo(\"zypper --non-interactive --quiet install ansible\")\n              end\n            end\n\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../debian/ansible_install\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Cap\n      module Guest\n        module Ubuntu\n          module AnsibleInstall\n\n            def self.ansible_install(machine, install_mode, ansible_version, pip_args, pip_install_cmd = \"\")\n              if install_mode != :default\n                Debian::AnsibleInstall::ansible_install machine, install_mode, ansible_version, pip_args, pip_install_cmd\n              else\n                ansible_apt_install machine\n              end\n            end\n\n            private\n\n            def self.ansible_apt_install(machine)\n              unless machine.communicate.test(\"test -x \\\"$(which add-apt-repository)\\\"\")\n                machine.communicate.sudo \"\"\"\n                  apt-get update -y -qq && \\\n                  DEBIAN_FRONTEND=noninteractive apt-get install -y -qq software-properties-common --option \\\"Dpkg::Options::=--force-confold\\\"\n                \"\"\"\n              end\n              machine.communicate.sudo \"\"\"\n                add-apt-repository ppa:ansible/ansible -y && \\\n                apt-get update -y -qq && \\\n                DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ansible --option \\\"Dpkg::Options::=--force-confold\\\"\n              \"\"\"\n            end\n\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/config/base.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../constants\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Config\n      class Base < Vagrant.plugin(\"2\", :config)\n\n        GALAXY_COMMAND_DEFAULT = \"ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force\".freeze\n        PLAYBOOK_COMMAND_DEFAULT = \"ansible-playbook\".freeze\n\n        attr_accessor :become\n        attr_accessor :become_user\n        attr_accessor :compatibility_mode\n        attr_accessor :config_file\n        attr_accessor :extra_vars\n        attr_accessor :galaxy_role_file\n        attr_accessor :galaxy_roles_path\n        attr_accessor :galaxy_command\n        attr_accessor :groups\n        attr_accessor :host_vars\n        attr_accessor :inventory_path\n        attr_accessor :limit\n        attr_accessor :playbook\n        attr_accessor :playbook_command\n        attr_accessor :raw_arguments\n        attr_accessor :skip_tags\n        attr_accessor :start_at_task\n        attr_accessor :tags\n        attr_accessor :vault_password_file\n        attr_accessor :verbose\n        attr_accessor :version\n\n        #\n        # Deprecated options\n        #\n        alias :sudo :become\n        def sudo=(value)\n          show_deprecation_info 'sudo', 'become'\n          @become = value\n        end\n        alias :sudo_user :become_user\n        def sudo_user=(value)\n          show_deprecation_info 'sudo_user', 'become_user'\n          @become_user = value\n        end\n\n        def initialize\n          @become              = UNSET_VALUE\n          @become_user         = UNSET_VALUE\n          @compatibility_mode  = Ansible::COMPATIBILITY_MODE_AUTO\n          @config_file         = UNSET_VALUE\n          @extra_vars          = UNSET_VALUE\n          @galaxy_role_file    = UNSET_VALUE\n          @galaxy_roles_path   = UNSET_VALUE\n          @galaxy_command      = UNSET_VALUE\n          @groups              = UNSET_VALUE\n          @host_vars           = UNSET_VALUE\n          @inventory_path      = UNSET_VALUE\n          @limit               = UNSET_VALUE\n          @playbook            = UNSET_VALUE\n          @playbook_command    = UNSET_VALUE\n          @raw_arguments       = UNSET_VALUE\n          @skip_tags           = UNSET_VALUE\n          @start_at_task       = UNSET_VALUE\n          @tags                = UNSET_VALUE\n          @vault_password_file = UNSET_VALUE\n          @verbose             = UNSET_VALUE\n          @version             = UNSET_VALUE\n        end\n\n        def finalize!\n          @become              = false                    if @become              != true\n          @become_user         = nil                      if @become_user         == UNSET_VALUE\n          @compatibility_mode  = nil                      unless Ansible::COMPATIBILITY_MODES.include?(@compatibility_mode)\n          @config_file         = nil                      if @config_file         == UNSET_VALUE\n          @extra_vars          = nil                      if @extra_vars          == UNSET_VALUE\n          @galaxy_role_file    = nil                      if @galaxy_role_file    == UNSET_VALUE\n          @galaxy_roles_path   = nil                      if @galaxy_roles_path   == UNSET_VALUE\n          @galaxy_command      = GALAXY_COMMAND_DEFAULT   if @galaxy_command      == UNSET_VALUE\n          @groups              = {}                       if @groups              == UNSET_VALUE\n          @host_vars           = {}                       if @host_vars           == UNSET_VALUE\n          @inventory_path      = nil                      if @inventory_path      == UNSET_VALUE\n          @limit               = nil                      if @limit               == UNSET_VALUE\n          @playbook            = nil                      if @playbook            == UNSET_VALUE\n          @playbook_command    = PLAYBOOK_COMMAND_DEFAULT if @playbook_command    == UNSET_VALUE\n          @raw_arguments       = nil                      if @raw_arguments       == UNSET_VALUE\n          @skip_tags           = nil                      if @skip_tags           == UNSET_VALUE\n          @start_at_task       = nil                      if @start_at_task       == UNSET_VALUE\n          @tags                = nil                      if @tags                == UNSET_VALUE\n          @vault_password_file = nil                      if @vault_password_file == UNSET_VALUE\n          @verbose             = false                    if @verbose             == UNSET_VALUE\n          @version             = \"\"                       if @version             == UNSET_VALUE\n        end\n\n        # Just like the normal configuration \"validate\" method except that\n        # it returns an array of errors that should be merged into some\n        # other error accumulator.\n        def validate(machine)\n          @errors = _detected_errors\n\n          # Validate that a compatibility mode was provided\n          if !compatibility_mode\n            @errors << I18n.t(\"vagrant.provisioners.ansible.errors.no_compatibility_mode\",\n              valid_modes: Ansible::COMPATIBILITY_MODES.map { |s| \"'#{s}'\" }.join(', '))\n          end\n\n          # Validate that a playbook path was provided\n          if !playbook\n            @errors << I18n.t(\"vagrant.provisioners.ansible.errors.no_playbook\")\n          end\n\n          # Validate that extra_vars is either a Hash or a String (for a file path)\n          if extra_vars\n            extra_vars_is_valid = extra_vars.kind_of?(Hash) || extra_vars.kind_of?(String)\n            if extra_vars.kind_of?(String)\n              # Accept the usage of '@' prefix in Vagrantfile\n              # (e.g. '@vars.yml' and 'vars.yml' are both supported)\n              match_data = /^@?(.+)$/.match(extra_vars)\n              extra_vars_path = match_data[1].to_s\n              @extra_vars = '@' + extra_vars_path\n            end\n\n            if !extra_vars_is_valid\n              @errors << I18n.t(\n                \"vagrant.provisioners.ansible.errors.extra_vars_invalid\",\n                type:  extra_vars.class.to_s,\n                value: extra_vars.to_s)\n            end\n          end\n\n          if raw_arguments\n            if raw_arguments.kind_of?(String)\n              @raw_arguments = [raw_arguments]\n            elsif !raw_arguments.kind_of?(Array)\n              @errors << I18n.t(\n                \"vagrant.provisioners.ansible.errors.raw_arguments_invalid\",\n                type:  raw_arguments.class.to_s,\n                value: raw_arguments.to_s)\n            end\n          end\n\n        end\n\n        protected\n\n        def show_deprecation_info(deprecated_option, new_option)\n          puts \"DEPRECATION: The '#{deprecated_option}' option for the Ansible provisioner is deprecated.\"\n          puts \"Please use the '#{new_option}' option instead.\"\n          puts \"The '#{deprecated_option}' option will be removed in a future release of Vagrant.\\n\\n\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/config/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"base\"\nrequire_relative \"../helpers\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Config\n      class Guest < Base\n\n        attr_accessor :provisioning_path\n        attr_accessor :tmp_path\n        attr_accessor :install\n        attr_accessor :install_mode\n        attr_accessor :pip_args\n        attr_accessor :pip_install_cmd\n\n        def initialize\n          super\n\n          @install           = UNSET_VALUE\n          @install_mode      = UNSET_VALUE\n          @pip_args          = UNSET_VALUE\n          @pip_install_cmd   = UNSET_VALUE\n          @provisioning_path = UNSET_VALUE\n          @tmp_path          = UNSET_VALUE\n        end\n\n        def finalize!\n          super\n\n          @install           = true                   if @install           == UNSET_VALUE\n          @install_mode      = :default               if @install_mode      == UNSET_VALUE\n          @pip_args          = \"\"                     if @pip_args          == UNSET_VALUE\n          @pip_install_cmd   = \"\"                     if @pip_install_cmd   == UNSET_VALUE\n          @provisioning_path = \"/vagrant\"             if provisioning_path  == UNSET_VALUE\n          @tmp_path          = \"/tmp/vagrant-ansible\" if tmp_path           == UNSET_VALUE\n        end\n\n        def validate(machine)\n          super\n\n          case @install_mode.to_s.to_sym\n          when :pip\n            @install_mode = :pip\n          when :pip_args_only\n            @install_mode = :pip_args_only\n          else\n            @install_mode = :default\n          end\n\n          { \"ansible local provisioner\" => @errors }\n        end\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/config/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"base\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Config\n      class Host < Base\n\n        attr_accessor :ask_become_pass\n        attr_accessor :ask_vault_pass\n        attr_accessor :force_remote_user\n        attr_accessor :host_key_checking\n        attr_accessor :raw_ssh_args\n\n        #\n        # Deprecated options\n        #\n        alias :ask_sudo_pass :ask_become_pass\n        def ask_sudo_pass=(value)\n          show_deprecation_info 'ask_sudo_pass', 'ask_become_pass'\n          @ask_become_pass = value\n        end\n\n        def initialize\n          super\n\n          @ask_become_pass     = false\n          @ask_vault_pass      = false\n          @force_remote_user   = true\n          @host_key_checking   = false\n          @raw_ssh_args        = UNSET_VALUE\n        end\n\n        def finalize!\n          super\n\n          @ask_become_pass     = false if @ask_become_pass   != true\n          @ask_vault_pass      = false if @ask_vault_pass    != true\n          @force_remote_user   = true  if @force_remote_user != false\n          @host_key_checking   = false if @host_key_checking != true\n          @raw_ssh_args        = nil   if @raw_ssh_args      == UNSET_VALUE\n        end\n\n        def validate(machine)\n          super\n\n          if raw_ssh_args\n            if raw_ssh_args.kind_of?(String)\n              @raw_ssh_args = [raw_ssh_args]\n            elsif !raw_ssh_args.kind_of?(Array)\n              @errors << I18n.t(\n                \"vagrant.provisioners.ansible.errors.raw_ssh_args_invalid\",\n                type:  raw_ssh_args.class.to_s,\n                value: raw_ssh_args.to_s)\n            end\n          end\n\n          { \"ansible remote provisioner\" => @errors }\n        end\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/constants.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\nmodule VagrantPlugins\n  module Ansible\n    COMPATIBILITY_MODE_AUTO     = \"auto\".freeze\n    COMPATIBILITY_MODE_V1_8     = \"1.8\".freeze\n    COMPATIBILITY_MODE_V2_0     = \"2.0\".freeze\n    SAFE_COMPATIBILITY_MODE     = COMPATIBILITY_MODE_V1_8\n    COMPATIBILITY_MODES         = [\n      COMPATIBILITY_MODE_AUTO,\n      COMPATIBILITY_MODE_V1_8,\n      COMPATIBILITY_MODE_V2_0,\n    ].freeze\n  end\nend"
  },
  {
    "path": "plugins/provisioners/ansible/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Errors\n      class AnsibleError < Vagrant::Errors::VagrantError\n        error_namespace(\"vagrant.provisioners.ansible.errors\")\n      end\n\n      class AnsibleCommandFailed < AnsibleError\n        error_key(:ansible_command_failed)\n      end\n\n      class AnsibleCompatibilityModeConflict < AnsibleError\n        error_key(:ansible_compatibility_mode_conflict)\n      end\n\n      class AnsibleNotFoundOnGuest < AnsibleError\n        error_key(:ansible_not_found_on_guest)\n      end\n\n      class AnsibleNotFoundOnHost < AnsibleError\n        error_key(:ansible_not_found_on_host)\n      end\n\n      class AnsiblePipInstallIsNotSupported < AnsibleError\n        error_key(:cannot_support_pip_install)\n      end\n\n      class AnsibleProgrammingError < AnsibleError\n        error_key(:ansible_programming_error)\n      end\n\n      class AnsibleVersionMismatch < AnsibleError\n        error_key(:ansible_version_mismatch)\n      end\n\n    end\n  end\nend"
  },
  {
    "path": "plugins/provisioners/ansible/helpers.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Ansible\n    class Helpers\n      def self.expand_path_in_unix_style(path, base_dir)\n        # Remove the possible drive letter, which is added\n        # by `File.expand_path` when running on a Windows host\n        File.expand_path(path, base_dir).sub(/^[a-zA-Z]:/, \"\")\n      end\n\n      def self.as_list_argument(v)\n        v.kind_of?(Array) ? v.join(',') : v\n      end\n   end\n  end\nend"
  },
  {
    "path": "plugins/provisioners/ansible/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Ansible\n    class Plugin < Vagrant.plugin(\"2\")\n\n      name \"ansible\"\n      description <<-DESC\n      Provides support for provisioning your virtual machines with Ansible\n      from the Vagrant host (`ansible`) or from the guests (`ansible_local`).\n      DESC\n\n      config(\"ansible\", :provisioner) do\n        require_relative \"config/host\"\n        Config::Host\n      end\n\n      config(\"ansible_local\", :provisioner) do\n        require_relative \"config/guest\"\n        Config::Guest\n      end\n\n      provisioner(\"ansible\") do\n        require_relative \"provisioner/host\"\n        Provisioner::Host\n      end\n\n      provisioner(\"ansible_local\") do\n        require_relative \"provisioner/guest\"\n        Provisioner::Guest\n      end\n\n      guest_capability(:linux, :ansible_installed) do\n        require_relative \"cap/guest/posix/ansible_installed\"\n        Cap::Guest::POSIX::AnsibleInstalled\n      end\n\n      guest_capability(:freebsd, :ansible_installed) do\n        require_relative \"cap/guest/posix/ansible_installed\"\n        Cap::Guest::POSIX::AnsibleInstalled\n      end\n\n      guest_capability(:arch, :ansible_install) do\n        require_relative \"cap/guest/arch/ansible_install\"\n        Cap::Guest::Arch::AnsibleInstall\n      end\n\n      guest_capability(:alpine, :ansible_install) do\n        require_relative \"cap/guest/alpine/ansible_install\"\n        Cap::Guest::Alpine::AnsibleInstall\n      end\n\n      guest_capability(:debian, :ansible_install) do\n        require_relative \"cap/guest/debian/ansible_install\"\n        Cap::Guest::Debian::AnsibleInstall\n      end\n\n      guest_capability(:ubuntu, :ansible_install) do\n        require_relative \"cap/guest/ubuntu/ansible_install\"\n        Cap::Guest::Ubuntu::AnsibleInstall\n      end\n\n      guest_capability(:fedora, :ansible_install) do\n        require_relative \"cap/guest/fedora/ansible_install\"\n        Cap::Guest::Fedora::AnsibleInstall\n      end\n\n      guest_capability(:redhat, :ansible_install) do\n        require_relative \"cap/guest/redhat/ansible_install\"\n        Cap::Guest::RedHat::AnsibleInstall\n      end\n\n      guest_capability(:suse, :ansible_install) do\n        require_relative \"cap/guest/suse/ansible_install\"\n        Cap::Guest::SUSE::AnsibleInstall\n      end\n\n      guest_capability(:freebsd, :ansible_install) do\n        require_relative \"cap/guest/freebsd/ansible_install\"\n        Cap::Guest::FreeBSD::AnsibleInstall\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/provisioner/base.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../constants\"\nrequire_relative \"../errors\"\nrequire_relative \"../helpers\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Provisioner\n\n      # This class is a base class where the common functionality shared between\n      # both Ansible provisioners are stored.\n      # This is **not an actual provisioner**.\n      # Instead, {Host} (ansible) or {Guest} (ansible_local) should be used.\n\n      class Base < Vagrant.plugin(\"2\", :provisioner)\n\n        RANGE_PATTERN = %r{(?:\\[[a-z]:[a-z]\\]|\\[[0-9]+?:[0-9]+?\\])}.freeze\n\n        ANSIBLE_PARAMETER_NAMES = {\n          Ansible::COMPATIBILITY_MODE_V1_8 => {\n            ansible_host: \"ansible_ssh_host\",\n            ansible_password: \"ansible_ssh_pass\",\n            ansible_port: \"ansible_ssh_port\",\n            ansible_user: \"ansible_ssh_user\",\n            ask_become_pass: \"ask-sudo-pass\",\n            become: \"sudo\",\n            become_user: \"sudo-user\",\n          },\n          Ansible::COMPATIBILITY_MODE_V2_0 => {\n            ansible_host: \"ansible_host\",\n            ansible_password: \"ansible_password\",\n            ansible_port: \"ansible_port\",\n            ansible_user: \"ansible_user\",\n            ask_become_pass: \"ask-become-pass\",\n            become: \"become\",\n            become_user: \"become-user\",\n          }\n        }\n\n        protected\n\n        def initialize(machine, config)\n          super\n          @control_machine = nil\n\n          @command_arguments = []\n          @environment_variables = {}\n          @inventory_machines = {}\n          @inventory_path = nil\n\n          @gathered_version_stdout = nil\n          @gathered_version_major = nil\n          @gathered_version = nil\n\n          @ansible_package_version_map = {}\n        end\n\n        def set_and_check_compatibility_mode\n          begin\n            set_gathered_ansible_version(gather_ansible_version(\"ansible\"))\n          rescue StandardError => e\n            # Nothing to do here, as the fallback on safe compatibility_mode is done below\n            @logger.error(\"Error while gathering the ansible version: #{e.to_s}\")\n          end\n\n          begin\n            set_gathered_ansible_package_version(\"ansible-core\", gather_ansible_version(\"ansible-core\"))\n          rescue StandardError => e\n             @logger.error(\"Error while gathering the ansible-core version: #{e}\")\n          end\n          \n          if @gathered_version_major\n            if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO\n              detect_compatibility_mode\n            elsif @gathered_version_major.to_i < 2 && config.compatibility_mode == Ansible::COMPATIBILITY_MODE_V2_0\n              # A better version comparator will be needed\n              # when more compatibility modes come... but so far let's keep it simple!\n              raise Ansible::Errors::AnsibleCompatibilityModeConflict,\n                ansible_version: @gathered_version,\n                system: @control_machine,\n                compatibility_mode: config.compatibility_mode\n            end\n          end\n\n          if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO\n            config.compatibility_mode = Ansible::SAFE_COMPATIBILITY_MODE\n\n            @machine.env.ui.warn(I18n.t(\"vagrant.provisioners.ansible.compatibility_mode_not_detected\",\n              compatibility_mode: config.compatibility_mode,\n              gathered_version: @gathered_version_stdout) +\n            \"\\n\")\n          end\n\n          unless Ansible::COMPATIBILITY_MODES.slice(1..-1).include?(config.compatibility_mode)\n            raise Ansible::Errors::AnsibleProgrammingError,\n              message: \"The config.compatibility_mode must be correctly set at this stage!\",\n              details: \"config.compatibility_mode: '#{config.compatibility_mode}'\"\n          end\n\n          @lexicon = ANSIBLE_PARAMETER_NAMES[config.compatibility_mode]\n        end\n\n        def check_files_existence\n          check_path_is_a_file(config.playbook, :playbook)\n\n          check_path_exists(config.inventory_path, :inventory_path) if config.inventory_path\n          check_path_is_a_file(config.config_file, :config_file) if config.config_file\n          check_path_is_a_file(config.extra_vars[1..-1], :extra_vars) if has_an_extra_vars_file_argument\n          check_path_is_a_file(config.galaxy_role_file, :galaxy_role_file) if config.galaxy_role_file\n          check_path_is_a_file(config.vault_password_file, :vault_password_file) if config.vault_password_file\n        end\n\n        def get_environment_variables_for_shell_execution\n          shell_env_vars = []\n          @environment_variables.each_pair do |k, v|\n            if k =~ /ANSIBLE_SSH_ARGS|ANSIBLE_ROLES_PATH|ANSIBLE_CONFIG/\n              shell_env_vars << \"#{k}='#{v}'\"\n            else\n              shell_env_vars << \"#{k}=#{v}\"\n            end\n          end\n          shell_env_vars\n        end\n\n        def ansible_galaxy_command_for_shell_execution\n          command_values = {\n            role_file: \"'#{get_galaxy_role_file}'\",\n            roles_path: \"'#{get_galaxy_roles_path}'\"\n          }\n\n          shell_command = get_environment_variables_for_shell_execution\n\n          shell_command << config.galaxy_command % command_values\n\n          shell_command.flatten.join(' ')\n        end\n\n        def ansible_playbook_command_for_shell_execution\n          shell_command = get_environment_variables_for_shell_execution\n\n          shell_command << config.playbook_command\n\n          shell_args = []\n          @command_arguments.each do |arg|\n            if arg =~ /(--start-at-task|--limit)=(.+)/\n              shell_args << %Q(#{$1}=\"#{$2}\")\n            elsif arg =~ /(--extra-vars)=(.+)/\n              shell_args << %Q(%s=%s) % [$1, $2.shellescape]\n            else\n              shell_args << arg\n            end\n          end\n\n          shell_command << shell_args\n\n          # Add the raw arguments at the end, to give them the highest precedence\n          shell_command << config.raw_arguments if config.raw_arguments\n\n          shell_command << config.playbook\n\n          shell_command.flatten.join(' ')\n        end\n\n        def prepare_common_command_arguments\n          # By default we limit by the current machine,\n          # but this can be overridden by the `limit` option.\n          if config.limit\n            @command_arguments << \"--limit=#{Helpers::as_list_argument(config.limit)}\"\n          else\n            @command_arguments << \"--limit=#{@machine.name}\"\n          end\n\n          @command_arguments << get_inventory_argument(inventory_path)\n          @command_arguments << \"--extra-vars=#{extra_vars_argument}\" if config.extra_vars\n          @command_arguments << \"--#{@lexicon[:become]}\" if config.become\n          @command_arguments << \"--#{@lexicon[:become_user]}=#{config.become_user}\" if config.become_user\n          @command_arguments << \"#{verbosity_argument}\" if verbosity_is_enabled?\n          @command_arguments << \"--vault-password-file=#{config.vault_password_file}\" if config.vault_password_file\n          @command_arguments << \"--tags=#{Helpers::as_list_argument(config.tags)}\" if config.tags\n          @command_arguments << \"--skip-tags=#{Helpers::as_list_argument(config.skip_tags)}\" if config.skip_tags\n          @command_arguments << \"--start-at-task=#{config.start_at_task}\" if config.start_at_task\n        end\n\n        def get_inventory_argument(inventory_path)\n          ansible_core_version = @ansible_package_version_map[\"ansible-core\"]\n          # Default to --inventory-file if version info is not available\n          return \"--inventory-file=#{inventory_path}\" if ansible_core_version.nil? || ansible_core_version.empty?\n          \n          major = ansible_core_version[:major].to_i\n          minor = ansible_core_version[:minor].to_i\n          \n          # Use --inventory for ansible-core >= 2.19\n          if major > 2 || (major == 2 && minor >= 19)\n            \"--inventory=#{inventory_path}\"\n          else\n            \"--inventory-file=#{inventory_path}\"\n          end\n        end\n\n        def prepare_common_environment_variables\n          # Ensure Ansible output isn't buffered so that we receive output\n          # on a task-by-task basis.\n          @environment_variables[\"PYTHONUNBUFFERED\"] = 1\n\n          # When Ansible output is piped in Vagrant integration, its default colorization is\n          # automatically disabled and the only way to re-enable colors is to use ANSIBLE_FORCE_COLOR.\n          @environment_variables[\"ANSIBLE_FORCE_COLOR\"] = \"true\" if @machine.env.ui.color?\n          # Setting ANSIBLE_NOCOLOR is \"unnecessary\" at the moment, but this could change in the future\n          # (e.g. local provisioner [GH-2103], possible change in vagrant/ansible integration, etc.)\n          @environment_variables[\"ANSIBLE_NOCOLOR\"] = \"true\" if !@machine.env.ui.color?\n\n          # Use ANSIBLE_ROLES_PATH to tell ansible-playbook where to look for roles\n          # (there is no equivalent command line argument in ansible-playbook)\n          @environment_variables[\"ANSIBLE_ROLES_PATH\"] = get_galaxy_roles_path if config.galaxy_roles_path\n\n          prepare_ansible_config_environment_variable\n        end\n\n        def prepare_ansible_config_environment_variable\n          @environment_variables[\"ANSIBLE_CONFIG\"] = config.config_file if config.config_file\n        end\n\n        # Auto-generate \"safe\" inventory file based on Vagrantfile,\n        # unless inventory_path is explicitly provided\n        def inventory_path\n          if config.inventory_path\n            config.inventory_path\n          else\n            @inventory_path ||= generate_inventory\n          end\n        end\n\n        def get_inventory_host_vars_string(machine_name)\n          # In Ruby, Symbol and String values are different, but\n          # Vagrant has to unify them for better user experience.\n          vars = config.host_vars[machine_name.to_sym]\n          if !vars\n            vars = config.host_vars[machine_name.to_s]\n          end\n          s = nil\n          if vars.is_a?(Hash)\n            s = vars.each.collect {\n              |k, v|\n                if v.is_a?(String) && v.include?(' ') && !v.match(/^('|\")[^'\"]+('|\")$/)\n                  v = %Q('#{v}')\n                end\n                \"#{k}=#{v}\"\n              }.join(\" \")\n          elsif vars.is_a?(Array)\n            s = vars.join(\" \")\n          elsif vars.is_a?(String)\n            s = vars\n          end\n          if s and !s.empty? then s else nil end\n        end\n\n        def generate_inventory\n          inventory = \"# Generated by Vagrant\\n\\n\"\n\n          # This \"abstract\" step must fill the @inventory_machines list\n          # and return the list of supported host(s)\n          inventory += generate_inventory_machines\n\n          inventory += generate_inventory_groups\n\n          # This \"abstract\" step must create the inventory file and\n          # return its location path\n          # TODO: explain possible race conditions, etc.\n          @inventory_path = ship_generated_inventory(inventory)\n        end\n\n        # Write out groups information.\n        # All defined groups will be included, but only supported\n        # machines and defined child groups will be included.\n        def generate_inventory_groups\n          groups_of_groups = {}\n          defined_groups = []\n          group_vars = {}\n          inventory_groups = \"\"\n\n          # Verify if host range patterns exist and warn\n          if config.groups.any? { |gm| gm.to_s[RANGE_PATTERN] }\n            @machine.ui.warn(I18n.t(\"vagrant.provisioners.ansible.ansible_host_pattern_detected\"))\n          end\n\n          config.groups.each_pair do |gname, gmembers|\n            if gname.is_a?(Symbol)\n              gname = gname.to_s\n            end\n\n            if gmembers.is_a?(String)\n              gmembers = gmembers.split(/\\s+/)\n            elsif gmembers.is_a?(Hash)\n              gmembers = gmembers.each.collect{ |k, v| \"#{k}=#{v}\" }\n            elsif !gmembers.is_a?(Array)\n              gmembers = []\n            end\n\n            if gname.end_with?(\":children\")\n              groups_of_groups[gname] = gmembers\n              defined_groups << gname.sub(/:children$/, '')\n            elsif gname.end_with?(\":vars\")\n              group_vars[gname] = gmembers\n            else\n              defined_groups << gname\n              inventory_groups += \"\\n[#{gname}]\\n\"\n              gmembers.each do |gm|\n                # TODO : Expand and validate host range patterns\n                # against @inventory_machines list before adding them\n                # otherwise abort with an error message\n                if gm[RANGE_PATTERN]\n                  inventory_groups += \"#{gm}\\n\"\n                end\n                inventory_groups += \"#{gm}\\n\" if @inventory_machines.include?(gm.to_sym)\n              end\n            end\n          end\n\n          defined_groups.uniq!\n          groups_of_groups.each_pair do |gname, gmembers|\n            inventory_groups += \"\\n[#{gname}]\\n\"\n            gmembers.each do |gm|\n              inventory_groups += \"#{gm}\\n\" if defined_groups.include?(gm)\n            end\n          end\n\n          group_vars.each_pair do |gname, gmembers|\n            if defined_groups.include?(gname.sub(/:vars$/, \"\")) || gname == \"all:vars\"\n              inventory_groups += \"\\n[#{gname}]\\n\" + gmembers.join(\"\\n\") + \"\\n\"\n            end\n          end\n\n          return inventory_groups\n        end\n\n        def has_an_extra_vars_file_argument\n          config.extra_vars && config.extra_vars.kind_of?(String) && config.extra_vars =~ /^@.+$/\n        end\n\n        def extra_vars_argument\n          if has_an_extra_vars_file_argument\n            # A JSON or YAML file is referenced.\n            config.extra_vars\n          else\n            # Expected to be a Hash after config validation.\n            config.extra_vars.to_json\n          end\n        end\n\n        def get_galaxy_role_file\n          Helpers::expand_path_in_unix_style(config.galaxy_role_file, get_provisioning_working_directory)\n        end\n\n        def get_galaxy_roles_path\n          base_dir = get_provisioning_working_directory\n          if config.galaxy_roles_path\n            Helpers::expand_path_in_unix_style(config.galaxy_roles_path, base_dir)\n          else\n            playbook_path = Helpers::expand_path_in_unix_style(config.playbook, base_dir)\n            File.join(Pathname.new(playbook_path).parent, 'roles')\n          end\n        end\n\n        def ui_running_ansible_command(name, command)\n          @machine.ui.detail I18n.t(\"vagrant.provisioners.ansible.running_#{name}\")\n          if verbosity_is_enabled?\n            # Show the ansible command in use\n            @machine.env.ui.detail command\n          end\n        end\n\n        def verbosity_is_enabled?\n          config.verbose && !config.verbose.to_s.empty?\n        end\n\n        def verbosity_argument\n          if config.verbose.to_s =~ /^-?(v+)$/\n            \"-#{$+}\"\n          else\n            # safe default, in case input strays\n            '-v'\n          end\n        end\n\n        private\n\n        def detect_compatibility_mode\n          if !@gathered_version_major || config.compatibility_mode != Ansible::COMPATIBILITY_MODE_AUTO\n            raise Ansible::Errors::AnsibleProgrammingError,\n              message: \"The detect_compatibility_mode() function shouldn't have been called!\",\n              details: %Q(config.compatibility_mode: '#{config.compatibility_mode}'\ngathered version major number: '#{@gathered_version_major}'\ngathered version stdout version:\n#{@gathered_version_stdout})\n          end\n\n          if @gathered_version_major.to_i <= 1\n            config.compatibility_mode = Ansible::COMPATIBILITY_MODE_V1_8\n          else\n            config.compatibility_mode = Ansible::COMPATIBILITY_MODE_V2_0\n          end\n\n          @logger.info(I18n.t(\"vagrant.provisioners.ansible.compatibility_mode_warning\",\n            compatibility_mode: config.compatibility_mode,\n            ansible_version: @gathered_version) +\n          \"\\n\")\n        end\n\n        def set_gathered_ansible_version(stdout_output)\n          @gathered_version_stdout = stdout_output\n          if !@gathered_version_stdout.empty?\n            first_line = @gathered_version_stdout.lines[0]\n            ansible_version_pattern = first_line.match(/(^ansible\\s+)(.+)$/)\n            if ansible_version_pattern\n              _, @gathered_version, _ = ansible_version_pattern.captures\n              @gathered_version.strip!\n              if @gathered_version\n                @gathered_version_major = @gathered_version.match(/(\\d+)\\..+$/).captures[0].to_i\n              end\n            end\n          end\n        end\n\n        def set_gathered_ansible_package_version(ansible_package, stdout_output)\n          if !stdout_output.empty?\n            first_line = stdout_output.lines[0]\n            ansible_version_pattern = first_line.match(/^#{ansible_package}\\s+(.+)$/)\n            if ansible_version_pattern\n              gathered_version = ansible_version_pattern.captures.first\n              gathered_version.strip!\n              if !gathered_version.empty?\n                gathered_version_major = gathered_version.match(/(\\d+)\\..+$/).captures[0].to_i\n                gathered_version_minor = gathered_version.match(/\\d+\\.(\\d+)\\..+$/).captures[0].to_i\n                @ansible_package_version_map[ansible_package] = {\n                  version: gathered_version,\n                  major: gathered_version_major,\n                  minor: gathered_version_minor\n                }\n              end\n            end\n          end\n        end\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/provisioner/guest.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire_relative \"base\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Provisioner\n      class Guest < Base\n        include Vagrant::Util\n\n        def initialize(machine, config)\n          super\n          @control_machine = \"guest\"\n          @logger = Log4r::Logger.new(\"vagrant::provisioners::ansible_guest\")\n        end\n\n        def provision\n          check_files_existence\n          check_and_install_ansible\n\n          execute_ansible_galaxy_on_guest if config.galaxy_role_file\n          execute_ansible_playbook_on_guest\n        end\n\n        protected\n\n        #\n        # This handles verifying the Ansible installation, installing it if it was\n        # requested, and so on. This method will raise exceptions if things are wrong.\n        # The compatibility mode checks are also performed here in order to fetch the\n        # Ansible version information only once.\n        #\n        # Current limitations:\n        #   - The installation of a specific Ansible version is only supported by\n        #     the \"pip\" install_mode. Note that \"pip\" installation also takes place\n        #     via a default command. If pip needs to be installed differently then\n        #     the command can be overwritten by supplying \"pip_install_cmd\" in the\n        #     config settings.\n        #   - There is no absolute guarantee that the automated installation will replace\n        #     a previous Ansible installation (although it works fine in many cases)\n        #\n        def check_and_install_ansible\n          @logger.info(\"Checking for Ansible installation...\")\n\n          # If the guest cannot check if Ansible is installed,\n          # print a warning and try to continue without any installation attempt...\n          if !@machine.guest.capability?(:ansible_installed)\n            @machine.ui.warn(I18n.t(\"vagrant.provisioners.ansible.cannot_detect\"))\n            return\n          end\n\n          # Try to install Ansible (if needed and requested)\n          if config.install &&\n             (config.version.to_s.to_sym == :latest ||\n              !@machine.guest.capability(:ansible_installed, config.version))\n            @machine.ui.detail I18n.t(\"vagrant.provisioners.ansible.installing\")\n            @machine.guest.capability(:ansible_install, config.install_mode, config.version, config.pip_args, config.pip_install_cmd)\n          end\n\n          # This step will also fetch the Ansible version data into related instance variables\n          set_and_check_compatibility_mode\n\n          # Check if requested ansible version is available\n          if (!config.version.empty? &&\n              config.version.to_s.to_sym != :latest &&\n              config.version != @gathered_version)\n            raise Ansible::Errors::AnsibleVersionMismatch,\n              system: @control_machine,\n              required_version: config.version,\n              current_version: @gathered_version\n          end\n        end\n\n        def gather_ansible_version(package = 'ansible')\n          raw_output = \"\"\n\n          result = @machine.communicate.execute(\n            \"python3 -c \\\"import importlib.metadata; print('#{package} ' + importlib.metadata.version('#{package}'))\\\"\",\n            error_class: Ansible::Errors::AnsibleNotFoundOnGuest,\n            error_key: :ansible_not_found_on_guest) do |type, output|\n            if type == :stdout && output.lines[0]\n              raw_output = output.lines[0]\n            end\n          end\n\n          if result != 0\n            raw_output = \"\"\n          end\n\n          raw_output\n        end\n\n        def get_provisioning_working_directory\n          config.provisioning_path\n        end\n\n        def execute_ansible_galaxy_on_guest\n          prepare_ansible_config_environment_variable\n\n          execute_ansible_command_on_guest \"galaxy\", ansible_galaxy_command_for_shell_execution\n        end\n\n        def execute_ansible_playbook_on_guest\n          prepare_common_command_arguments\n          prepare_common_environment_variables\n\n          execute_ansible_command_on_guest \"playbook\", ansible_playbook_command_for_shell_execution\n        end\n\n        def execute_ansible_command_on_guest(name, command)\n          remote_command = \"cd #{config.provisioning_path} && #{command}\"\n\n          ui_running_ansible_command name, remote_command\n\n          result = execute_on_guest(remote_command)\n          raise Ansible::Errors::AnsibleCommandFailed if result != 0\n        end\n\n        def execute_on_guest(command)\n          @machine.communicate.execute(command, error_check: false) do |type, data|\n            if [:stderr, :stdout].include?(type)\n              @machine.env.ui.info(data, new_line: false, prefix: false)\n            end\n          end\n        end\n\n        def ship_generated_inventory(inventory_content)\n          inventory_basedir = File.join(config.tmp_path, \"inventory\")\n          inventory_path = File.join(inventory_basedir, \"vagrant_ansible_local_inventory\")\n\n          @machine.communicate.sudo(\"mkdir -p #{inventory_basedir}\")\n          @machine.communicate.sudo(\"chown -R -h #{@machine.ssh_info[:username]} #{config.tmp_path}\")\n          @machine.communicate.sudo(\"rm -f #{inventory_path}\", error_check: false)\n\n          Tempfile.open(\"vagrant-ansible-local-inventory-#{@machine.name}\") do |f|\n            f.binmode\n            f.write(inventory_content)\n            f.fsync\n            f.close\n            @machine.communicate.upload(f.path, inventory_path)\n          end\n\n          return inventory_basedir\n        end\n\n        def generate_inventory_machines\n          machines = \"\"\n\n          # TODO: Instead, why not loop over active_machines and skip missing guests, like in Host?\n          machine.env.machine_names.each do |machine_name|\n            begin\n              @inventory_machines[machine_name] = machine_name\n              if @machine.name == machine_name\n                machines += \"#{machine_name} ansible_connection=local\\n\"\n              else\n                machines += \"#{machine_name}\\n\"\n              end\n              host_vars = get_inventory_host_vars_string(machine_name)\n              machines.sub!(/\\n$/, \" #{host_vars}\\n\") if host_vars\n            end\n          end\n\n          return machines\n        end\n\n        def check_path(path, test_args, option_name)\n          # Checks for the existence of given file (or directory) on the guest system,\n          # and error if it doesn't exist.\n\n          remote_path = Helpers::expand_path_in_unix_style(path, config.provisioning_path)\n          command = \"test #{test_args} '#{remote_path}'\"\n\n          @machine.communicate.execute(\n            command,\n            error_class: Ansible::Errors::AnsibleError,\n            error_key: :config_file_not_found,\n            config_option: option_name,\n            path: remote_path,\n            system: @control_machine\n          )\n        end\n\n        def check_path_is_a_file(path, error_message_key)\n          check_path(path, \"-f\", error_message_key)\n        end\n\n        def check_path_exists(path, error_message_key)\n          check_path(path, \"-e\", error_message_key)\n        end\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/ansible/provisioner/host.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"thread\"\n\nrequire_relative \"base\"\n\nmodule VagrantPlugins\n  module Ansible\n    module Provisioner\n      class Host < Base\n\n        @@lock = Mutex.new\n\n        def initialize(machine, config)\n          super\n          @control_machine = \"host\"\n          @logger = Log4r::Logger.new(\"vagrant::provisioners::ansible_host\")\n        end\n\n        def provision\n          # At this stage, the SSH access is guaranteed to be ready\n          @ssh_info = @machine.ssh_info\n\n          warn_for_unsupported_platform\n          check_files_existence\n          check_ansible_version_and_compatibility\n\n          execute_ansible_galaxy_from_host if config.galaxy_role_file\n          execute_ansible_playbook_from_host\n        end\n\n        protected\n\n        VAGRANT_ARG_SEPARATOR = 'VAGRANT_ARG_SEP'\n\n        def warn_for_unsupported_platform\n          if Vagrant::Util::Platform.windows?\n            @machine.env.ui.warn(I18n.t(\"vagrant.provisioners.ansible.windows_not_supported_for_control_machine\") + \"\\n\")\n          end\n        end\n\n        def check_ansible_version_and_compatibility\n          # This step will also fetch the Ansible version data into related instance variables\n          set_and_check_compatibility_mode\n\n          # Skip this check when not required, nor possible\n          if !@gathered_version || config.version.empty? || config.version.to_s.to_sym == :latest\n            return\n          end\n\n          if config.version != @gathered_version\n            raise Ansible::Errors::AnsibleVersionMismatch,\n              system: @control_machine,\n              required_version: config.version,\n              current_version: @gathered_version\n          end\n        end\n\n        def prepare_command_arguments\n          # Connect with native OpenSSH client\n          # Other modes (e.g. paramiko) are not officially supported,\n          # but can be enabled via raw_arguments option.\n          @command_arguments << \"--connection=ssh\"\n\n          # Increase the SSH connection timeout, as the Ansible default value (10 seconds)\n          # is a bit demanding for some overloaded developer boxes. This is particularly\n          # helpful when additional virtual networks are configured, as their availability\n          # is not controlled during vagrant boot process.\n          @command_arguments << \"--timeout=30\"\n\n          if !config.force_remote_user\n            # Pass the vagrant ssh username as Ansible default remote user, because\n            # the ansible_ssh_user/ansible_user parameter won't be added to the auto-generated inventory.\n            @command_arguments << \"--user=#{@ssh_info[:username]}\"\n          elsif config.inventory_path\n            # Using an extra variable is the only way to ensure that the Ansible remote user\n            # is overridden (as the ansible inventory is not under vagrant control)\n            @command_arguments << \"--extra-vars=#{@lexicon[:ansible_user]}='#{@ssh_info[:username]}'\"\n          end\n\n          @command_arguments << \"--#{@lexicon[:ask_become_pass]}\" if config.ask_become_pass\n          @command_arguments << \"--ask-vault-pass\" if config.ask_vault_pass\n\n          prepare_common_command_arguments\n        end\n\n\n        def prepare_environment_variables\n          prepare_common_environment_variables\n\n          # Some Ansible options must be passed as environment variables,\n          # as there is no equivalent command line arguments\n          @environment_variables[\"ANSIBLE_HOST_KEY_CHECKING\"] = \"#{config.host_key_checking}\"\n\n          # ANSIBLE_SSH_ARGS is required for Multiple SSH keys, SSH forwarding and custom SSH settings\n          @environment_variables[\"ANSIBLE_SSH_ARGS\"] = ansible_ssh_args unless ansible_ssh_args.empty?\n        end\n\n        def execute_command_from_host(command)\n          begin\n            result = Vagrant::Util::Subprocess.execute(*command) do |type, data|\n              if type == :stdout || type == :stderr\n                @machine.env.ui.detail(data, new_line: false, prefix: false)\n              end\n            end\n            raise Ansible::Errors::AnsibleCommandFailed if result.exit_code != 0\n          rescue Vagrant::Errors::CommandUnavailable\n            raise Ansible::Errors::AnsibleNotFoundOnHost\n          end\n        end\n\n        def gather_ansible_version(package = 'ansible')\n          raw_output = ''\n          command = ['python3', '-c',\n                     \"import importlib.metadata; print('#{package} ' + importlib.metadata.version('#{package}'))\"]\n\n          command << {\n            notify: [:stdout, :stderr]\n          }\n\n          begin\n            result = Vagrant::Util::Subprocess.execute(*command) do |type, output|\n              if type == :stdout && output.lines[0]\n                raw_output = output\n              end\n            end\n            if result.exit_code != 0\n              raw_output = \"\"\n            end\n          rescue Vagrant::Errors::CommandUnavailable\n            raise Ansible::Errors::AnsibleNotFoundOnHost\n          end\n\n          raw_output\n        end\n\n        def execute_ansible_galaxy_from_host\n          prepare_ansible_config_environment_variable\n\n          command_values = {\n            role_file: get_galaxy_role_file,\n            roles_path: get_galaxy_roles_path\n          }\n          command_template = config.galaxy_command.gsub(' ', VAGRANT_ARG_SEPARATOR)\n          str_command = command_template % command_values\n\n          command = str_command.split(VAGRANT_ARG_SEPARATOR)\n          command << {\n            env: @environment_variables,\n            # Write stdout and stderr data, since it's the regular Ansible output\n            notify: [:stdout, :stderr],\n            workdir: @machine.env.root_path.to_s\n          }\n\n          ui_running_ansible_command \"galaxy\", ansible_galaxy_command_for_shell_execution\n\n          execute_command_from_host command\n        end\n\n        def execute_ansible_playbook_from_host\n          prepare_environment_variables\n          prepare_command_arguments\n\n          # Assemble the full ansible-playbook command\n          command = [config.playbook_command] << @command_arguments\n\n          # Add the raw arguments at the end, to give them the highest precedence\n          command << config.raw_arguments if config.raw_arguments\n\n          command << config.playbook\n          command = command.flatten\n\n          command << {\n            env: @environment_variables,\n            # Write stdout and stderr data, since it's the regular Ansible output\n            notify: [:stdout, :stderr],\n            workdir: @machine.env.root_path.to_s\n          }\n\n          ui_running_ansible_command \"playbook\", ansible_playbook_command_for_shell_execution\n\n          execute_command_from_host command\n        end\n\n        def ship_generated_inventory(inventory_content)\n          inventory_path = Pathname.new(File.join(@machine.env.local_data_path.join, %w(provisioners ansible inventory)))\n          FileUtils.mkdir_p(inventory_path) unless File.directory?(inventory_path)\n\n          inventory_file = Pathname.new(File.join(inventory_path, 'vagrant_ansible_inventory'))\n          @@lock.synchronize do\n            if !File.exist?(inventory_file) or inventory_content != File.read(inventory_file)\n              begin\n                # ansible dir inventory will ignore files starting with '.'\n                inventory_tmpfile = Tempfile.new('.vagrant_ansible_inventory', inventory_path)\n                inventory_tmpfile.write(inventory_content)\n                inventory_tmpfile.close\n                File.rename(inventory_tmpfile.path, inventory_file)\n              ensure\n                inventory_tmpfile.close\n                inventory_tmpfile.unlink\n              end\n            end\n          end\n\n          return inventory_path\n        end\n\n        def generate_inventory_machines\n          machines = \"\"\n\n          @machine.env.active_machines.each do |am|\n            begin\n              m = @machine.env.machine(*am)\n\n              # Call only once the SSH and WinRM info computation\n              # Note that machines configured with WinRM communicator, also have a \"partial\" ssh_info.\n              m_ssh_info = m.ssh_info\n              host_vars = get_inventory_host_vars_string(m.name)\n              if m.config.vm.communicator == :winrm\n                m_winrm_net_info = CommunicatorWinRM::Helper.winrm_info(m) # can raise a WinRMNotReady exception...\n                machines += get_inventory_winrm_machine(m, m_winrm_net_info)\n                machines.sub!(/\\n$/, \" #{host_vars}\\n\") if host_vars\n                @inventory_machines[m.name] = m\n              elsif !m_ssh_info.nil?\n                machines += get_inventory_ssh_machine(m, m_ssh_info)\n                machines.sub!(/\\n$/, \" #{host_vars}\\n\") if host_vars\n                @inventory_machines[m.name] = m\n              else\n                @logger.error(\"Auto-generated inventory: Impossible to get SSH information for machine '#{m.name} (#{m.provider_name})'. This machine should be recreated.\")\n                # Let a note about this missing machine\n                machines += \"# MISSING: '#{m.name}' machine was probably removed without using Vagrant. This machine should be recreated.\\n\"\n              end\n            rescue Vagrant::Errors::MachineNotFound, CommunicatorWinRM::Errors::WinRMNotReady => e\n              @logger.info(\"Auto-generated inventory: Skip machine '#{am[0]} (#{am[1]})', which is not configured for this Vagrant environment.\")\n            end\n          end\n\n          return machines\n        end\n\n        def get_provisioning_working_directory\n          machine.env.root_path\n        end\n\n        def get_inventory_ssh_machine(machine, ssh_info)\n          forced_remote_user = \"\"\n          if config.force_remote_user\n            forced_remote_user = \"#{@lexicon[:ansible_user]}='#{ssh_info[:username]}' \"\n          end\n\n          \"#{machine.name} #{@lexicon[:ansible_host]}=#{ssh_info[:host]} #{@lexicon[:ansible_port]}=#{ssh_info[:port]} #{forced_remote_user}ansible_ssh_private_key_file='#{ssh_info[:private_key_path][0]}'\\n\"\n        end\n\n        def get_inventory_winrm_machine(machine, winrm_net_info)\n          forced_remote_user = \"\"\n          if config.force_remote_user\n            forced_remote_user = \"#{@lexicon[:ansible_user]}='#{machine.config.winrm.username}' \"\n          end\n\n          \"#{machine.name} ansible_connection=winrm #{@lexicon[:ansible_host]}=#{winrm_net_info[:host]} #{@lexicon[:ansible_port]}=#{winrm_net_info[:port]} #{forced_remote_user}#{@lexicon[:ansible_password]}='#{machine.config.winrm.password}'\\n\"\n        end\n\n        def ansible_ssh_args\n          @ansible_ssh_args ||= prepare_ansible_ssh_args\n        end\n\n        def prepare_ansible_ssh_args\n          ssh_options = []\n\n          # Use an SSH ProxyCommand when using the Docker provider with the intermediate host\n          if @machine.provider_name == :docker && machine.provider.host_vm?\n            docker_host_ssh_info = machine.provider.host_vm.ssh_info\n\n            proxy_cmd = \"ssh #{docker_host_ssh_info[:username]}@#{docker_host_ssh_info[:host]}\" +\n              \" -p #{docker_host_ssh_info[:port]} -i #{docker_host_ssh_info[:private_key_path][0]}\"\n\n            # Use same options than plugins/providers/docker/communicator.rb\n            # Note: this could be improved (DRY'ed) by sharing these settings.\n            proxy_cmd += \" -o Compression=yes -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no\"\n\n            proxy_cmd += \" -o ForwardAgent=yes\" if @ssh_info[:forward_agent]\n\n            proxy_cmd += \" exec nc %h %p 2>/dev/null\"\n\n            ssh_options << \"-o ProxyCommand='#{ proxy_cmd }'\"\n            # TODO ssh_options << \"-o ProxyCommand=\\\"#{ proxy_cmd }\\\"\"\n          end\n\n          # Use an SSH ProxyCommand when corresponding Vagrant setting is defined\n          if @machine.ssh_info[:proxy_command]\n            proxy_cmd = @machine.ssh_info[:proxy_command]\n            ssh_options << \"-o ProxyCommand='#{ proxy_cmd }'\"\n          end\n\n          # Don't access user's known_hosts file, except when host_key_checking is enabled.\n          ssh_options << \"-o UserKnownHostsFile=/dev/null\" unless config.host_key_checking\n\n          # Compare to lib/vagrant/util/ssh.rb\n          ssh_options << \"-o IdentitiesOnly=yes\" if !Vagrant::Util::Platform.solaris? && @ssh_info[:keys_only]\n\n          # Multiple Private Keys\n          unless !config.inventory_path && @ssh_info[:private_key_path].size == 1\n            @ssh_info[:private_key_path].each do |key|\n              ssh_options += [\"-o\", \"IdentityFile=%s\" % [key.gsub('%', '%%')]]\n            end\n          end\n\n          # SSH Forwarding\n          ssh_options << \"-o ForwardAgent=yes\" if @ssh_info[:forward_agent]\n\n          # Unchecked SSH Parameters\n          ssh_options.concat(config.raw_ssh_args) if config.raw_ssh_args\n\n          # Re-enable ControlPersist Ansible defaults,\n          # which are lost when ANSIBLE_SSH_ARGS is defined.\n          unless ssh_options.empty?\n            ssh_options << \"-o ControlMaster=auto\"\n            ssh_options << \"-o ControlPersist=60s\"\n            # Intentionally keep ControlPath undefined to let ansible-playbook\n            # automatically sets this option to Ansible default value\n          end\n\n          ssh_options.join(' ')\n        end\n\n        def check_path(path, path_test_method, option_name)\n          # Checks for the existence of given file (or directory) on the host system,\n          # and error if it doesn't exist.\n\n          expanded_path = Pathname.new(path).expand_path(@machine.env.root_path)\n          if !expanded_path.public_send(path_test_method)\n            raise Ansible::Errors::AnsibleError,\n                  _key: :config_file_not_found,\n                  config_option: option_name,\n                  path: expanded_path,\n                  system: @control_machine\n          end\n        end\n\n        def check_path_is_a_file(path, option_name)\n          check_path(path, \"file?\", option_name)\n        end\n\n        def check_path_exists(path, option_name)\n          check_path(path, \"exist?\", option_name)\n        end\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/cfengine/cap/debian/cfengine_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CFEngine\n    module Cap\n      module Debian\n        module CFEngineInstall\n          def self.cfengine_install(machine, config)\n            machine.communicate.tap do |comm|\n              comm.sudo(\"mkdir -p #{File.dirname(config.deb_repo_file)} && /bin/echo #{config.deb_repo_line} > #{config.deb_repo_file}\")\n              comm.sudo(\"GPGFILE=`tempfile`; wget -O $GPGFILE #{config.repo_gpg_key_url} && apt-key add $GPGFILE; rm -f $GPGFILE\")\n              \n              comm.sudo(\"apt-get install -y --force-yes apt-transport-https\");\n              \n              comm.sudo(\"apt-get update\")\n              comm.sudo(\"apt-get install -y #{config.package_name}\")\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/cfengine/cap/linux/cfengine_installed.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CFEngine\n    module Cap\n      module Linux\n        module CFEngineInstalled\n          def self.cfengine_installed(machine)\n            machine.communicate.test(\n              \"test -d /var/cfengine && test -x /var/cfengine/bin/cf-agent\", sudo: true)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/cfengine/cap/linux/cfengine_needs_bootstrap.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module CFEngine\n    module Cap\n      module Linux\n        module CFEngineNeedsBootstrap\n          def self.cfengine_needs_bootstrap(machine, config)\n            logger = Log4r::Logger.new(\"vagrant::plugins::cfengine::cap_linux_cfengine_bootstrap\")\n\n            machine.communicate.tap do |comm|\n              # We hardcode fixing the permissions on /var/cfengine/ppkeys/, if it exists,\n              # because otherwise CFEngine will fail to bootstrap.\n              if comm.test(\"test -d /var/cfengine/ppkeys\", sudo: true)\n                logger.debug(\"Fixing permissions on /var/cfengine/ppkeys\")\n                comm.sudo(\"chmod -R 600 /var/cfengine/ppkeys\")\n              end\n\n              logger.debug(\"Checking if CFEngine is bootstrapped...\")\n              bootstrapped = comm.test(\"test -f /var/cfengine/policy_server.dat\", sudo: true)\n              if bootstrapped && !config.force_bootstrap\n                logger.info(\"CFEngine already bootstrapped, no need to do it again\")\n                return false\n              end\n\n              logger.info(\"CFEngine needs bootstrap.\")\n              return true\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/cfengine/cap/redhat/cfengine_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module CFEngine\n    module Cap\n      module RedHat\n        module CFEngineInstall\n          def self.cfengine_install(machine, config)\n            logger = Log4r::Logger.new(\"vagrant::plugins::cfengine::cap_redhat_cfengine_install\")\n\n            machine.communicate.tap do |comm|\n              logger.info(\"Adding the CFEngine repository to #{config.yum_repo_file}\")\n              comm.sudo(\"mkdir -p #{File.dirname(config.yum_repo_file)} && (echo '[cfengine-repository]'; echo 'name=CFEngine Community Yum Repository'; echo 'baseurl=#{config.yum_repo_url}'; echo 'enabled=1'; echo 'gpgcheck=1') > #{config.yum_repo_file}\")\n              logger.info(\"Installing CFEngine Community Yum Repository GPG KEY from #{config.repo_gpg_key_url}\")\n              comm.sudo(\"GPGFILE=$(mktemp) && wget -O $GPGFILE #{config.repo_gpg_key_url} && rpm --import $GPGFILE; rm -f $GPGFILE\")\n\n              if dnf?(machine)\n                comm.sudo(\"dnf -y install #{config.package_name}\")\n              else\n                comm.sudo(\"yum -y install #{config.package_name}\")\n              end\n            end\n          end\n\n          protected\n\n          def self.dnf?(machine)\n            machine.communicate.test(\"/usr/bin/which -s dnf\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/cfengine/cap/suse/cfengine_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module CFEngine\n    module Cap\n      module SUSE\n        module CFEngineInstall\n          def self.cfengine_install(machine, config)\n            machine.communicate.tap do |comm|\n              comm.sudo(\"rpm --import #{config.repo_gpg_key_url}\")\n\n              comm.sudo(\"zypper addrepo -t YUM #{config.yum_repo_url} CFEngine\")\n              comm.sudo(\"zypper se #{config.package_name} && zypper -n install #{config.package_name}\")\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/cfengine/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nmodule VagrantPlugins\n  module CFEngine\n    class Config < Vagrant.plugin(\"2\", :config)\n      attr_accessor :am_policy_hub\n      attr_accessor :extra_agent_args\n      attr_accessor :classes\n      attr_accessor :deb_repo_file\n      attr_accessor :deb_repo_line\n      attr_accessor :files_path\n      attr_accessor :force_bootstrap\n      attr_accessor :install\n      attr_accessor :mode\n      attr_accessor :policy_server_address\n      attr_accessor :repo_gpg_key_url\n      attr_accessor :run_file\n      attr_accessor :upload_path\n      attr_accessor :yum_repo_file\n      attr_accessor :yum_repo_url\n      attr_accessor :package_name\n\n      def initialize\n        @am_policy_hub    = UNSET_VALUE\n        @classes          = UNSET_VALUE\n        @deb_repo_file    = UNSET_VALUE\n        @deb_repo_line    = UNSET_VALUE\n        @extra_agent_args = UNSET_VALUE\n        @files_path       = UNSET_VALUE\n        @force_bootstrap  = UNSET_VALUE\n        @install          = UNSET_VALUE\n        @mode             = UNSET_VALUE\n        @policy_server_address = UNSET_VALUE\n        @repo_gpg_key_url = UNSET_VALUE\n        @run_file         = UNSET_VALUE\n        @upload_path      = UNSET_VALUE\n        @yum_repo_file    = UNSET_VALUE\n        @yum_repo_url     = UNSET_VALUE\n        @package_name     = UNSET_VALUE\n      end\n\n      def finalize!\n        @am_policy_hub = false if @am_policy_hub == UNSET_VALUE\n\n        @classes = nil if @classes == UNSET_VALUE\n\n        if @deb_repo_file == UNSET_VALUE\n          @deb_repo_file = \"/etc/apt/sources.list.d/cfengine-community.list\"\n        end\n\n        if @deb_repo_line == UNSET_VALUE\n          @deb_repo_line = \"deb https://cfengine.com/pub/apt/packages stable main\"\n        end\n\n        @extra_agent_args = nil if @extra_agent_args == UNSET_VALUE\n\n        @files_path = nil if @files_path == UNSET_VALUE\n\n        @force_bootstrap = false if @force_bootstrap == UNSET_VALUE\n\n        @install = true if @install == UNSET_VALUE\n        @install = @install.to_sym if @install.respond_to?(:to_sym)\n\n        @mode = :bootstrap if @mode == UNSET_VALUE\n        @mode = @mode.to_sym\n\n        @run_file = nil if @run_file == UNSET_VALUE\n\n        @policy_server_address = nil if @policy_server_address == UNSET_VALUE\n\n        if @repo_gpg_key_url == UNSET_VALUE\n          @repo_gpg_key_url = \"https://cfengine.com/pub/gpg.key\"\n        end\n\n        @upload_path = \"/tmp/vagrant-cfengine-file\" if @upload_path == UNSET_VALUE\n\n        if @yum_repo_file == UNSET_VALUE\n          @yum_repo_file = \"/etc/yum.repos.d/cfengine-community.repo\"\n        end\n\n        if @yum_repo_url == UNSET_VALUE\n          @yum_repo_url = \"https://cfengine.com/pub/yum/$basearch\"\n        end\n\n        if @package_name == UNSET_VALUE\n            @package_name = \"cfengine-community\"\n        end\n      end\n\n      def validate(machine)\n        errors = _detected_errors\n\n        valid_modes = [:bootstrap, :single_run]\n        errors << I18n.t(\"vagrant.cfengine_config.invalid_mode\") if !valid_modes.include?(@mode)\n\n        if @mode == :bootstrap\n          if !@policy_server_address && !@am_policy_hub\n            errors << I18n.t(\"vagrant.cfengine_config.policy_server_address\")\n          end\n        end\n\n        if @classes && !@classes.is_a?(Array)\n          errors << I18n.t(\"vagrant.cfengine_config.classes_array\")\n        end\n\n        if @files_path\n          expanded = Pathname.new(@files_path).expand_path(machine.env.root_path)\n          if !expanded.directory?\n            errors << I18n.t(\"vagrant.cfengine_config.files_path_not_directory\")\n          end\n        end\n\n        if @run_file\n          expanded = Pathname.new(@run_file).expand_path(machine.env.root_path)\n          if !expanded.file?\n            errors << I18n.t(\"vagrant.cfengine_config.run_file_not_found\")\n          end\n        end\n\n        { \"CFEngine\" => errors }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/cfengine/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module CFEngine\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"CFEngine Provisioner\"\n      description <<-DESC\n      Provisions machines with CFEngine.\n      DESC\n\n      config(:cfengine, :provisioner) do\n        require_relative \"config\"\n        Config\n      end\n\n      guest_capability(\"debian\", \"cfengine_install\") do\n        require_relative \"cap/debian/cfengine_install\"\n        Cap::Debian::CFEngineInstall\n      end\n\n      guest_capability(\"linux\", \"cfengine_needs_bootstrap\") do\n        require_relative \"cap/linux/cfengine_needs_bootstrap\"\n        Cap::Linux::CFEngineNeedsBootstrap\n      end\n\n      guest_capability(\"linux\", \"cfengine_installed\") do\n        require_relative \"cap/linux/cfengine_installed\"\n        Cap::Linux::CFEngineInstalled\n      end\n\n      guest_capability(\"redhat\", \"cfengine_install\") do\n        require_relative \"cap/redhat/cfengine_install\"\n        Cap::RedHat::CFEngineInstall\n      end\n\n      guest_capability(\"suse\", \"cfengine_install\") do\n        require_relative \"cap/suse/cfengine_install\"\n        Cap::SUSE::CFEngineInstall\n      end\n\n      provisioner(:cfengine) do\n        require_relative \"provisioner\"\n        Provisioner\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/cfengine/provisioner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module CFEngine\n    class Provisioner < Vagrant.plugin(\"2\", :provisioner)\n      def provision\n        if @machine.config.vm.communicator == :winrm\n          raise Vagrant::Errors::ProvisionerWinRMUnsupported,\n            name: \"cfengine\"\n        end\n\n        @logger = Log4r::Logger.new(\"vagrant::plugins::cfengine\")\n\n        @logger.info(\"Checking for CFEngine installation...\")\n        handle_cfengine_installation\n\n        if @config.files_path\n          @machine.ui.info(I18n.t(\"vagrant.cfengine_installing_files_path\"))\n          install_files(Pathname.new(@config.files_path).expand_path(@machine.env.root_path))\n        end\n\n        handle_cfengine_bootstrap if @config.mode == :bootstrap\n\n        if @config.mode == :single_run\n          # Just let people know\n          @machine.ui.info(I18n.t(\"vagrant.cfengine_single_run\"))\n        end\n\n        if @config.run_file\n          @machine.ui.info(I18n.t(\"vagrant.cfengine_single_run_execute\"))\n          path = Pathname.new(@config.run_file).expand_path(@machine.env.root_path)\n          machine.communicate.upload(path.to_s, @config.upload_path)\n          cfagent(\"-KI -f #{@config.upload_path}#{cfagent_classes_args}#{cfagent_extra_args}\")\n        end\n      end\n\n      protected\n\n      # This runs cf-agent with the given arguments.\n      def cfagent(args, options=nil)\n        options ||= {}\n        command = \"/var/cfengine/bin/cf-agent #{args}\"\n\n        @machine.communicate.sudo(command, error_check: options[:error_check]) do |type, data|\n          if [:stderr, :stdout].include?(type)\n            # Output the data with the proper color based on the stream.\n            color = type == :stdout ? :green : :red\n            @machine.ui.info(\n              data,\n              color: color, new_line: false, prefix: false)\n          end\n        end\n      end\n\n      # Returns the arguments for the classes configuration if they are\n      # set.\n      #\n      # @return [String]\n      def cfagent_classes_args\n        return \"\" if !@config.classes\n\n        args = @config.classes.map { |c| \"-D#{c}\" }.join(\" \")\n        return \" #{args}\"\n      end\n\n      # Extra arguments for calles to cf-agent.\n      #\n      # @return [String]\n      def cfagent_extra_args\n        return \"\" if !@config.extra_agent_args\n        return \" #{@config.extra_agent_args}\"\n      end\n\n      # This handles checking if the CFEngine installation needs to\n      # be bootstrapped, and bootstraps if it does.\n      def handle_cfengine_bootstrap\n        @logger.info(\"Bootstrapping CFEngine...\")\n        if !@machine.guest.capability(:cfengine_needs_bootstrap, @config)\n          @machine.ui.info(I18n.t(\"vagrant.cfengine_no_bootstrap\"))\n          return\n        end\n\n        # Needs bootstrap, let's determine the parameters\n        policy_server_address = @config.policy_server_address\n        if !policy_server_address\n          policy_server_address = @machine.guest.capability(:read_ip_address)\n          raise Vagrant::Errors::CFEngineCantAutodetectIP if !policy_server_address\n          @machine.ui.info(I18n.t(\"vagrant.cfengine_detected_ip\", address: policy_server_address))\n        end\n\n        @machine.ui.info(I18n.t(\"vagrant.cfengine_bootstrapping\",\n                                policy_server: policy_server_address))\n        result = cfagent(\"--bootstrap #{policy_server_address}\", error_check: false)\n        raise Vagrant::Errors::CFEngineBootstrapFailed if result != 0\n\n        # Policy hubs need to do additional things before they're ready\n        # to accept agents. Force that run now...\n        if @config.am_policy_hub\n          @machine.ui.info(I18n.t(\"vagrant.cfengine_bootstrapping_policy_hub\"))\n          cfagent(\"-KI -f /var/cfengine/masterfiles/failsafe.cf#{cfagent_classes_args}\")\n          cfagent(\"-KI #{cfagent_classes_args}#{cfagent_extra_args}\")\n        end\n      end\n\n      # This handles verifying the CFEngine installation, installing it\n      # if it was requested, and so on. This method will raise exceptions\n      # if things are wrong.\n      def handle_cfengine_installation\n        if !@machine.guest.capability?(:cfengine_installed)\n          @machine.ui.warn(I18n.t(\"vagrant.cfengine_cant_detect\"))\n          return\n        end\n\n        installed = @machine.guest.capability(:cfengine_installed)\n        if !installed || @config.install == :force\n          raise Vagrant::Errors::CFEngineNotInstalled if !@config.install\n\n          @machine.ui.info(I18n.t(\"vagrant.cfengine_installing\"))\n          @machine.guest.capability(:cfengine_install, @config)\n\n          if !@machine.guest.capability(:cfengine_installed)\n            raise Vagrant::Errors::CFEngineInstallFailed\n          end\n        end\n      end\n\n      # This installs a set of files into the CFEngine folder within\n      # the machine.\n      #\n      # @param [Pathname] local_path\n      def install_files(local_path)\n        @logger.debug(\"Copying local files to CFEngine: #{local_path}\")\n        @machine.communicate.sudo(\"rm -rf /tmp/cfengine-files\")\n        @machine.communicate.upload(local_path.to_s, \"/tmp/cfengine-files\")\n        @machine.communicate.sudo(\"cp -R /tmp/cfengine-files/* /var/cfengine\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/cap/debian/chef_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../omnibus\"\n\nmodule VagrantPlugins\n  module Chef\n    module Cap\n      module Debian\n        module ChefInstall\n          def self.chef_install(machine, project, version, channel, omnibus_url, options = {})\n            machine.communicate.sudo(\"apt-get update -y -qq\")\n            machine.communicate.sudo(\"apt-get install -y -qq curl\")\n\n            command = Omnibus.sh_command(project, version, channel, omnibus_url, options)\n            machine.communicate.sudo(command)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/cap/freebsd/chef_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../omnibus\"\n\nmodule VagrantPlugins\n  module Chef\n    module Cap\n      module FreeBSD\n        module ChefInstall\n          def self.chef_install(machine, project, version, channel, omnibus_url, options = {})\n            machine.communicate.sudo(\"pkg install -qy curl bash\")\n\n            command = Omnibus.sh_command(project, version, channel, omnibus_url, options)\n            machine.communicate.sudo(command)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/cap/freebsd/chef_installed.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module Chef\n    module Cap\n      module FreeBSD\n        module ChefInstalled\n          # Check if Chef is installed at the given version.\n          # @return [true, false]\n          def self.chef_installed(machine, product, version)\n            product_name = product == 'chef-workstation' ? 'chef-workstation' : 'chef'\n            verify_bin = product_name == 'chef-workstation' ? 'chef' : 'chef-client'\n            verify_path = \"/opt/#{product_name}/bin/#{verify_bin}\"\n            command = \"test -x #{verify_path}\"\n\n            if version != :latest\n              command << \"&& #{verify_path} --version | grep '#{version}'\"\n            end\n\n            machine.communicate.test(command, sudo: true)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/cap/linux/chef_installed.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module Chef\n    module Cap\n      module Linux\n        module ChefInstalled\n          # Check if Chef is installed at the given version.\n          # @return [true, false]\n          def self.chef_installed(machine, product, version)\n            product_name = product == 'chef-workstation' ? 'chef-workstation' : 'chef'\n            verify_bin = product_name == 'chef-workstation' ? 'chef' : 'chef-client'\n            verify_path = \"/opt/#{product_name}/bin/#{verify_bin}\"\n            command = \"test -x #{verify_path}\"\n\n            if version != :latest\n              command << \"&& #{verify_path} --version | grep '#{version}'\"\n            end\n\n            machine.communicate.test(command, sudo: true)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/cap/omnios/chef_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../omnibus\"\n\nmodule VagrantPlugins\n  module Chef\n    module Cap\n      module OmniOS\n        module ChefInstall\n          def self.chef_install(machine, project, version, channel, omnibus_url, options = {})\n            su = machine.config.solaris.suexec_cmd\n\n            machine.communicate.execute(\"#{su} pkg list --no-refresh web/curl > /dev/null 2>&1 || pkg install -q --accept web/curl\")\n\n            command = Omnibus.sh_command(project, version, channel, omnibus_url, options)\n            machine.communicate.execute(\"#{su} #{command}\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/cap/omnios/chef_installed.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module Chef\n    module Cap\n      module OmniOS\n        module ChefInstalled\n          # TODO: this is the same code as cap/linux/chef_installed, consider merging\n          # Check if Chef is installed at the given version.\n          # @return [true, false]\n          def self.chef_installed(machine, product, version)\n            product_name = product == 'chef-workstation' ? 'chef-workstation' : 'chef'\n            verify_bin = product_name == 'chef-workstation' ? 'chef' : 'chef-client'\n            verify_path = \"/opt/#{product_name}/bin/#{verify_bin}\"\n            command = \"test -x #{verify_path}\"\n\n            if version != :latest\n              command << \"&& #{verify_path} --version | grep '#{version}'\"\n            end\n\n            machine.communicate.test(command, sudo: true)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/cap/redhat/chef_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../omnibus\"\n\nmodule VagrantPlugins\n  module Chef\n    module Cap\n      module Redhat\n        module ChefInstall\n          def self.chef_install(machine, project, version, channel, omnibus_url, options = {})\n            machine.communicate.sudo <<-EOH.gsub(/^ {14}/, '')\n              if command -v dnf; then\n                dnf -y install curl\n              else\n                yum -y install curl\n              fi\n            EOH\n\n            command = Omnibus.sh_command(project, version, channel, omnibus_url, options)\n            machine.communicate.sudo(command)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/cap/suse/chef_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../omnibus\"\n\nmodule VagrantPlugins\n  module Chef\n    module Cap\n      module Suse\n        module ChefInstall\n          def self.chef_install(machine, project, version, channel, omnibus_url, options = {})\n            unless curl?(machine)\n              machine.communicate.sudo(\"zypper -n -q update\")\n              machine.communicate.sudo(\"zypper -n -q install curl\")\n            end\n\n            command = Omnibus.sh_command(project, version, channel, omnibus_url, options)\n            machine.communicate.sudo(command)\n          end\n\n          protected\n\n          def self.curl?(machine)\n            machine.communicate.test(\"/usr/bin/which -s curl\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/cap/windows/chef_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../omnibus\"\n\nmodule VagrantPlugins\n  module Chef\n    module Cap\n      module Windows\n        module ChefInstall\n          def self.chef_install(machine, project, version, channel, omnibus_url, options = {})\n            command = Omnibus.ps_command(project, version, channel, omnibus_url, options)\n            machine.communicate.sudo(command)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/cap/windows/chef_installed.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module Chef\n    module Cap\n      module Windows\n        module ChefInstalled\n          # Check if Chef is installed at the given version.\n          # @return [true, false]\n          def self.chef_installed(machine, product, version)\n            verify_bin = product == 'chef-workstation' ? 'chef' : 'chef-client'\n            if version != :latest\n              command = 'if ((&' + verify_bin + ' --version) -Match \"' + version.to_s + '\"){ exit 0 } else { exit 1 }'\n            else\n              command = 'if ((&' + verify_bin + ' --version) -Match \"Chef*\"){ exit 0 } else { exit 1 }'\n            end\n            machine.communicate.test(command, sudo: true)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/command_builder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module Chef\n    class CommandBuilder\n      def self.command(type, config, options = {})\n        new(type, config, options).command\n      end\n\n      attr_reader :type\n      attr_reader :config\n      attr_reader :options\n\n      def initialize(type, config, options = {})\n        @type    = type\n        @config  = config\n        @options = options.dup\n\n        if type != :client && type != :solo\n          raise \"Unknown type `#{type.inspect}'!\"\n        end\n      end\n\n      def command\n        if config.binary_env\n          \"#{config.binary_env} #{binary_path} #{args}\"\n        else\n          \"#{binary_path} #{args}\"\n        end\n      end\n\n      protected\n\n      def binary_path\n        binary_path = \"chef-#{type}\"\n\n        if config.binary_path\n          binary_path = File.join(config.binary_path, binary_path)\n          if windows?\n            binary_path = windows_friendly_path(binary_path)\n          end\n        end\n\n        binary_path\n      end\n\n      def args\n        args =  \"\"\n        args << \" --config #{provisioning_path(\"#{type}.rb\")}\"\n        args << \" --json-attributes #{provisioning_path(\"dna.json\")}\"\n        args << \" --local-mode\" if options[:local_mode]\n        args << \" --legacy-mode\" if options[:legacy_mode]\n        args << \" --log_level #{config.log_level}\" if config.log_level\n        args << \" --no-color\" if !options[:colored]\n\n        if config.install && (config.version == :latest || config.version.to_s >= \"11.0\")\n          args << \" --force-formatter\"\n        end\n\n        args << \" #{config.arguments.strip}\" if config.arguments\n\n        args.strip\n      end\n\n      def provisioning_path(file)\n        if windows?\n          path = config.provisioning_path || \"C:/vagrant-chef\"\n          return windows_friendly_path(File.join(path, file))\n        else\n          path = config.provisioning_path || \"/tmp/vagrant-chef\"\n          return File.join(path, file)\n        end\n      end\n\n      def windows_friendly_path(path)\n        path = path.gsub(\"/\", \"\\\\\")\n        path = \"c:#{path}\" if path.start_with?(\"\\\\\")\n        return path\n      end\n\n      def windows?\n        !!options[:windows]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/config/base.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/presence\"\n\nmodule VagrantPlugins\n  module Chef\n    module Config\n      class Base < Vagrant.plugin(\"2\", :config)\n        include Vagrant::Util::Presence\n\n        # The path to Chef's bin/ directory.\n        # @return [String]\n        attr_accessor :binary_path\n\n        # Arbitrary environment variables to set before running the Chef\n        # provisioner command.\n        # @return [String]\n        attr_accessor :binary_env\n\n        # The name of the Chef project to install. This is \"chef\" for the Chef\n        # Client or \"chefdk\" for the Chef Development Kit. Other product names\n        # may be available as well.\n        # @return [String]\n        attr_accessor :product\n\n        # Install Chef on the system if it does not exist. Default is true.\n        # This is a trinary attribute (it can have three values):\n        #\n        # - true (bool) install Chef\n        # - false (bool) do not install Chef\n        # - \"force\" (string) install Chef, even if it is already installed at\n        #   the proper version\n        #\n        # @return [true, false, String]\n        attr_accessor :install\n\n        # The Chef log level. See the Chef docs for acceptable values.\n        # @return [String, Symbol]\n        attr_accessor :log_level\n\n        # The channel from which to download Chef. Currently known values are\n        # \"current\" and \"stable\", but more may be added in the future. The\n        # default is \"stable\".\n        # @return [String]\n        attr_accessor :channel\n\n        # The version of Chef to install. If Chef is already installed on the\n        # system, the installed version is compared with the requested version.\n        # If they match, no action is taken. If they do not match, version of\n        # the value specified in this attribute will be installed over top of\n        # the existing version (a warning will be displayed).\n        #\n        # You can also specify \"latest\" (default), which will install the latest\n        # version of Chef on the system. In this case, Chef will use whatever\n        # version is on the system. To force the newest version of Chef to be\n        # installed on every provision, set the {#install} option to \"force\".\n        #\n        # @return [String]\n        attr_accessor :version\n\n        # Location of Omnibus installation scripts.\n        # This URL specifies the location of install.sh/install.ps1 for\n        # Linux/Unix and Windows respectively.\n        #\n        # It defaults to https://omnitruck.chef.io/. The full URL is then:\n        #  - Linux/Unix: https://omnitruck.chef.io/install.sh\n        #  - Windows: https://omnitruck.chef.io/install.ps1\n        #\n        # If you want to have https://example.com/install.sh as Omnibus script\n        # for your Linux/Unix installations, you should set this option to\n        # https://example.com\n        #\n        # @return [String]\n        attr_accessor :omnibus_url\n\n        # The path where the Chef installer will be downloaded to. Only valid if\n        # install is true or \"force\". It defaults to nil, which means that the\n        # omnibus installer will choose the destination and you have no control\n        # over it.\n        #\n        # @return [String]\n        attr_accessor :installer_download_path\n\n        def initialize\n          super\n\n          @binary_path = UNSET_VALUE\n          @binary_env  = UNSET_VALUE\n          @product     = UNSET_VALUE\n          @install     = UNSET_VALUE\n          @log_level   = UNSET_VALUE\n          @channel     = UNSET_VALUE\n          @version     = UNSET_VALUE\n          @omnibus_url = UNSET_VALUE\n          @installer_download_path = UNSET_VALUE\n        end\n\n        def finalize!\n          @binary_path = nil       if @binary_path == UNSET_VALUE\n          @binary_env  = nil       if @binary_env == UNSET_VALUE\n          @product     = \"chef\"    if @product == UNSET_VALUE\n          @install     = true      if @install == UNSET_VALUE\n          @log_level   = :info     if @log_level == UNSET_VALUE\n          @channel     = \"stable\"  if @channel == UNSET_VALUE\n          @version     = :latest   if @version == UNSET_VALUE\n          @omnibus_url = 'https://omnitruck.chef.io' if @omnibus_url == UNSET_VALUE\n          @installer_download_path = nil  if @installer_download_path == UNSET_VALUE\n\n          # Make sure the install is a symbol if it's not a boolean\n          if @install.respond_to?(:to_sym)\n            @install = @install.to_sym\n          end\n\n          # Make sure the version is a symbol if it's not a boolean\n          if @version.respond_to?(:to_sym)\n            @version = @version.to_sym\n          end\n\n          # Make sure the log level is a symbol\n          @log_level = @log_level.to_sym\n        end\n\n        # Like validate, but returns a list of errors to append.\n        #\n        # @return [Array<String>]\n        def validate_base(machine)\n          errors = _detected_errors\n\n          if !present?(log_level)\n            errors << I18n.t(\"vagrant.provisioners.chef.log_level_empty\")\n          end\n\n          errors\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/config/base_runner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/counter\"\n\nrequire_relative \"base\"\n\nmodule VagrantPlugins\n  module Chef\n    module Config\n      # This is the config base for Chef provisioners that need a full Chef\n      # Runner object, like chef-solo or chef-client. For provisioners like\n      # chef-apply, these options are not valid\n      class BaseRunner < Base\n        attr_accessor :arguments\n        attr_accessor :attempts\n        attr_accessor :custom_config_path\n        attr_accessor :encrypted_data_bag_secret_key_path\n        attr_accessor :environment\n        attr_accessor :formatter\n        attr_accessor :http_proxy\n        attr_accessor :http_proxy_user\n        attr_accessor :http_proxy_pass\n        attr_accessor :https_proxy\n        attr_accessor :https_proxy_user\n        attr_accessor :https_proxy_pass\n        attr_accessor :json\n        attr_accessor :no_proxy\n        attr_accessor :node_name\n        attr_accessor :provisioning_path\n        attr_accessor :run_list\n        attr_accessor :file_cache_path\n        attr_accessor :file_backup_path\n        attr_accessor :verbose_logging\n        attr_accessor :enable_reporting\n\n        def initialize\n          super\n\n          @arguments          = UNSET_VALUE\n          @attempts           = UNSET_VALUE\n          @custom_config_path = UNSET_VALUE\n\n          # /etc/chef/client.rb config options\n          @encrypted_data_bag_secret_key_path = UNSET_VALUE\n          @environment        = UNSET_VALUE\n          @formatter          = UNSET_VALUE\n          @http_proxy         = UNSET_VALUE\n          @http_proxy_user    = UNSET_VALUE\n          @http_proxy_pass    = UNSET_VALUE\n          @https_proxy        = UNSET_VALUE\n          @https_proxy_user   = UNSET_VALUE\n          @https_proxy_pass   = UNSET_VALUE\n          @no_proxy           = UNSET_VALUE\n          @node_name          = UNSET_VALUE\n          @provisioning_path  = UNSET_VALUE\n          @file_cache_path    = UNSET_VALUE\n          @file_backup_path   = UNSET_VALUE\n          @verbose_logging    = UNSET_VALUE\n          @enable_reporting   = UNSET_VALUE\n\n          # Runner options\n          @json     = {}\n          @run_list = []\n        end\n\n        def finalize!\n          super\n\n          @arguments          = nil if @arguments == UNSET_VALUE\n          @attempts           = 1   if @attempts == UNSET_VALUE\n          @custom_config_path = nil if @custom_config_path == UNSET_VALUE\n          @environment        = nil if @environment == UNSET_VALUE\n          @formatter          = nil if @formatter == UNSET_VALUE\n          @http_proxy         = nil if @http_proxy == UNSET_VALUE\n          @http_proxy_user    = nil if @http_proxy_user == UNSET_VALUE\n          @http_proxy_pass    = nil if @http_proxy_pass == UNSET_VALUE\n          @https_proxy        = nil if @https_proxy == UNSET_VALUE\n          @https_proxy_user   = nil if @https_proxy_user == UNSET_VALUE\n          @https_proxy_pass   = nil if @https_proxy_pass == UNSET_VALUE\n          @no_proxy           = nil if @no_proxy == UNSET_VALUE\n          @node_name          = nil if @node_name == UNSET_VALUE\n          @provisioning_path  = nil if @provisioning_path == UNSET_VALUE\n          @file_backup_path   = nil if @file_backup_path == UNSET_VALUE\n          @file_cache_path    = nil if @file_cache_path == UNSET_VALUE\n          @verbose_logging    = false if @verbose_logging == UNSET_VALUE\n          @enable_reporting   = true  if @enable_reporting == UNSET_VALUE\n\n          if @encrypted_data_bag_secret_key_path == UNSET_VALUE\n            @encrypted_data_bag_secret_key_path = nil\n          end\n        end\n\n        def merge(other)\n          super.tap do |result|\n            result.instance_variable_set(:@json, @json.merge(other.json))\n            result.instance_variable_set(:@run_list, (@run_list + other.run_list))\n          end\n        end\n\n        # Just like the normal configuration \"validate\" method except that\n        # it returns an array of errors that should be merged into some\n        # other error accumulator.\n        def validate_base(machine)\n          errors = super\n\n          if @custom_config_path\n            expanded = File.expand_path(@custom_config_path, machine.env.root_path)\n            if !File.file?(expanded)\n              errors << I18n.t(\"vagrant.config.chef.custom_config_path_missing\")\n            end\n          end\n\n          errors\n        end\n\n        # Adds a recipe to the run list\n        def add_recipe(name)\n          name = \"recipe[#{name}]\" unless name =~ /^recipe\\[(.+?)\\]$/\n            run_list << name\n        end\n\n        # Adds a role to the run list\n        def add_role(name)\n          name = \"role[#{name}]\" unless name =~ /^role\\[(.+?)\\]$/\n            run_list << name\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/config/chef_apply.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/presence\"\n\nrequire_relative \"base\"\n\nmodule VagrantPlugins\n  module Chef\n    module Config\n      class ChefApply < Base\n        include Vagrant::Util::Presence\n\n        # The raw recipe text (as a string) to execute via chef-apply.\n        # @return [String]\n        attr_accessor :recipe\n\n        # The path (on the guest) where the uploaded apply recipe should be\n        # written (/tmp/vagrant-chef-apply-#.rb).\n        # @return [String]\n        attr_accessor :upload_path\n\n        def initialize\n          super\n\n          @recipe      = UNSET_VALUE\n          @upload_path = UNSET_VALUE\n        end\n\n        def finalize!\n          super\n\n          @recipe = nil if @recipe == UNSET_VALUE\n          @upload_path = \"/tmp/vagrant-chef-apply\" if @upload_path == UNSET_VALUE\n        end\n\n        def validate(machine)\n          errors = validate_base(machine)\n\n          if !present?(recipe)\n            errors << I18n.t(\"vagrant.provisioners.chef.recipe_empty\")\n          end\n\n          if !present?(upload_path)\n            errors << I18n.t(\"vagrant.provisioners.chef.upload_path_empty\")\n          end\n\n          { \"chef apply provisioner\" => errors }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/config/chef_client.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/presence\"\nrequire \"vagrant/util/which\"\n\nrequire_relative \"base_runner\"\n\nmodule VagrantPlugins\n  module Chef\n    module Config\n      class ChefClient < BaseRunner\n        include Vagrant::Util::Presence\n\n        # The URL endpoint to the Chef Server.\n        # @return [String]\n        attr_accessor :chef_server_url\n\n        # The path on disk to the Chef client key,\n        # @return [String]\n        attr_accessor :client_key_path\n\n        # Delete the client key when the VM is destroyed. Default is false.\n        # @return [true, false]\n        attr_accessor :delete_client\n\n        # Delete the node when the VM is destroyed. Default is false.\n        # @return [true, false]\n        attr_accessor :delete_node\n\n        # The path to the validation key on disk.\n        # @return [String]\n        attr_accessor :validation_key_path\n\n        # The name of the validation client.\n        # @return [String]\n        attr_accessor :validation_client_name\n\n        def initialize\n          super\n\n          @chef_server_url        = UNSET_VALUE\n          @client_key_path        = UNSET_VALUE\n          @delete_client          = UNSET_VALUE\n          @delete_node            = UNSET_VALUE\n          @validation_key_path    = UNSET_VALUE\n          @validation_client_name = UNSET_VALUE\n        end\n\n        def finalize!\n          super\n\n          @chef_server_url        = nil if @chef_server_url == UNSET_VALUE\n          @client_key_path        = nil if @client_key_path == UNSET_VALUE\n          @delete_client          = false if @delete_client == UNSET_VALUE\n          @delete_node            = false if @delete_node == UNSET_VALUE\n          @validation_client_name = \"chef-validator\" if @validation_client_name == UNSET_VALUE\n          @validation_key_path    = nil if @validation_key_path == UNSET_VALUE\n        end\n\n        def validate(machine)\n          errors = validate_base(machine)\n\n          if !present?(chef_server_url)\n            errors << I18n.t(\"vagrant.config.chef.server_url_empty\")\n          end\n\n          if !present?(validation_key_path)\n            errors << I18n.t(\"vagrant.config.chef.validation_key_path\")\n          end\n\n          { \"chef client provisioner\" => errors }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/config/chef_solo.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/presence\"\n\nrequire_relative \"base_runner\"\n\nmodule VagrantPlugins\n  module Chef\n    module Config\n      class ChefSolo < BaseRunner\n        include Vagrant::Util::Presence\n\n        # The path on disk where Chef cookbooks are stored.\n        # Default is \"cookbooks\".\n        # @return [String]\n        attr_accessor :cookbooks_path\n\n        # The path where data bags are stored on disk.\n        # @return [String]\n        attr_accessor :data_bags_path\n\n        # The path where environments are stored on disk.\n        # @return [String]\n        attr_accessor :environments_path\n\n        # The path where nodes are stored on disk.\n        # @return [String]\n        attr_accessor :nodes_path\n\n        # A URL download a remote recipe from. Note: you should use chef-apply\n        # instead.\n        #\n        # @deprecated\n        #\n        # @return [String]\n        attr_accessor :recipe_url\n\n        # Enable chef-solo legacy mode rather than local mode.\n        # @return [true, false]\n        attr_accessor :legacy_mode\n\n        # The path where roles are stored on disk.\n        # @return [String]\n        attr_accessor :roles_path\n\n        # The type of synced folders to use.\n        # @return [String]\n        attr_accessor :synced_folder_type\n\n        def initialize\n          super\n\n          @cookbooks_path      = UNSET_VALUE\n          @data_bags_path      = UNSET_VALUE\n          @environments_path   = UNSET_VALUE\n          @nodes_path          = UNSET_VALUE\n          @recipe_url          = UNSET_VALUE\n          @legacy_mode         = UNSET_VALUE\n          @roles_path          = UNSET_VALUE\n          @synced_folder_type  = UNSET_VALUE\n        end\n\n        #------------------------------------------------------------\n        # Internal methods\n        #------------------------------------------------------------\n\n        def finalize!\n          super\n\n          @recipe_url = nil if @recipe_url == UNSET_VALUE\n          @synced_folder_type = nil if @synced_folder_type == UNSET_VALUE\n          @legacy_mode = false if @legacy_mode == UNSET_VALUE\n\n          if @cookbooks_path == UNSET_VALUE\n            @cookbooks_path = []\n            @cookbooks_path << [:host, \"cookbooks\"] if !@recipe_url\n            @cookbooks_path << [:vm, \"cookbooks\"]\n          end\n\n          @data_bags_path    = [] if @data_bags_path == UNSET_VALUE\n          @nodes_path        = [] if @nodes_path == UNSET_VALUE\n          @roles_path        = [] if @roles_path == UNSET_VALUE\n          @environments_path = [] if @environments_path == UNSET_VALUE\n          @environments_path = [@environments_path].flatten\n\n          # Make sure the path is an array.\n          @cookbooks_path    = prepare_folders_config(@cookbooks_path)\n          @data_bags_path    = prepare_folders_config(@data_bags_path)\n          @nodes_path        = prepare_folders_config(@nodes_path)\n          @roles_path        = prepare_folders_config(@roles_path)\n          @environments_path = prepare_folders_config(@environments_path)\n        end\n\n        def validate(machine)\n          errors = validate_base(machine)\n\n          if !present?(Array(cookbooks_path))\n            errors << I18n.t(\"vagrant.config.chef.cookbooks_path_empty\")\n          end\n\n          if environment && !present?(environments_path)\n            errors << I18n.t(\"vagrant.config.chef.environment_path_required\")\n          end\n\n          environments_path.each do |type, raw_path|\n            next if type != :host\n\n            path = Pathname.new(raw_path).expand_path(machine.env.root_path)\n            if !path.directory?\n              errors << I18n.t(\"vagrant.config.chef.environment_path_missing\",\n                path: raw_path.to_s\n              )\n            end\n          end\n\n          { \"chef solo provisioner\" => errors }\n        end\n\n        protected\n\n        # This takes any of the configurations that take a path or\n        # array of paths and turns it into the proper format.\n        #\n        # @return [Array]\n        def prepare_folders_config(config)\n          # Make sure the path is an array\n          config = [config] if !config.is_a?(Array) || config.first.is_a?(Symbol)\n\n          return [] if config.flatten.compact.empty?\n\n          # Make sure all the paths are in the proper format\n          config.map do |path|\n            path = [:host, path] if !path.is_a?(Array)\n            path\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/config/chef_zero.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/presence\"\n\nrequire_relative \"chef_solo\"\n\nmodule VagrantPlugins\n  module Chef\n    module Config\n      class ChefZero < BaseRunner\n        include Vagrant::Util::Presence\n\n        # The path on disk where Chef cookbooks are stored.\n        # Default is \"cookbooks\".\n        # @return [String]\n        attr_accessor :cookbooks_path\n\n        # The path where data bags are stored on disk.\n        # @return [String]\n        attr_accessor :data_bags_path\n\n        # The path where environments are stored on disk.\n        # @return [String]\n        attr_accessor :environments_path\n\n        # The path where nodes are stored on disk.\n        # @return [String]\n        attr_accessor :nodes_path\n\n        # The path where roles are stored on disk.\n        # @return [String]\n        attr_accessor :roles_path\n\n        # The type of synced folders to use.\n        # @return [String]\n        attr_accessor :synced_folder_type\n\n        def initialize\n          super\n\n          @cookbooks_path      = UNSET_VALUE\n          @data_bags_path      = UNSET_VALUE\n          @environments_path   = UNSET_VALUE\n          @nodes_path          = UNSET_VALUE\n          @roles_path          = UNSET_VALUE\n          @synced_folder_type  = UNSET_VALUE\n        end\n\n        def finalize!\n          super\n\n          @synced_folder_type = nil if @synced_folder_type == UNSET_VALUE\n\n          if @cookbooks_path == UNSET_VALUE\n            @cookbooks_path = []\n            @cookbooks_path << [:host, \"cookbooks\"] if !@recipe_url\n            @cookbooks_path << [:vm, \"cookbooks\"]\n          end\n\n          @data_bags_path    = [] if @data_bags_path == UNSET_VALUE\n          @nodes_path        = [] if @nodes_path == UNSET_VALUE\n          @roles_path        = [] if @roles_path == UNSET_VALUE\n          @environments_path = [] if @environments_path == UNSET_VALUE\n          @environments_path = [@environments_path].flatten\n\n          # Make sure the path is an array.\n          @cookbooks_path    = prepare_folders_config(@cookbooks_path)\n          @data_bags_path    = prepare_folders_config(@data_bags_path)\n          @nodes_path        = prepare_folders_config(@nodes_path)\n          @roles_path        = prepare_folders_config(@roles_path)\n          @environments_path = prepare_folders_config(@environments_path)\n\n        end\n\n        def validate(machine)\n          errors = validate_base(machine)\n\n          if !present?(Array(cookbooks_path))\n            errors << I18n.t(\"vagrant.config.chef.cookbooks_path_empty\")\n          end\n\n          if !present?(Array(nodes_path))\n            errors << I18n.t(\"vagrant.config.chef.nodes_path_empty\")\n          else\n            missing_paths = Array.new\n            nodes_path.each { |dir| missing_paths << dir[1] if !File.exist? dir[1] }\n            # If it exists at least one path on disk it's ok for Chef provisioning\n            if missing_paths.size == nodes_path.size\n              errors << I18n.t(\"vagrant.config.chef.nodes_path_missing\", path: missing_paths.to_s)\n            end\n          end\n\n          if environment && environments_path.empty?\n            errors << I18n.t(\"vagrant.config.chef.environment_path_required\")\n          end\n\n          environments_path.each do |type, raw_path|\n            next if type != :host\n\n            path = Pathname.new(raw_path).expand_path(machine.env.root_path)\n            if !path.directory?\n              errors << I18n.t(\"vagrant.config.chef.environment_path_missing\",\n                path: raw_path.to_s\n              )\n            end\n          end\n\n          { \"chef zero provisioner\" => errors }\n        end\n\n        protected\n\n        # This takes any of the configurations that take a path or\n        # array of paths and turns it into the proper format.\n        #\n        # @return [Array]\n        def prepare_folders_config(config)\n          # Make sure the path is an array\n          config = [config] if !config.is_a?(Array) || config.first.is_a?(Symbol)\n\n          return [] if config.flatten.compact.empty?\n\n          # Make sure all the paths are in the proper format\n          config.map do |path|\n            path = [:host, File.expand_path(path)] if !path.is_a?(Array)\n            path\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/installer.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module Chef\n    class Installer\n      def initialize(machine, options = {})\n        @machine     = machine\n        @product     = options.fetch(:product)\n        @channel     = options.fetch(:channel)\n        @version     = options.fetch(:version)\n        @force       = options.fetch(:force)\n        @omnibus_url = options.fetch(:omnibus_url)\n        @options     = options.dup\n      end\n\n      # This handles verifying the Chef installation, installing it if it was\n      # requested, and so on. This method will raise exceptions if things are\n      # wrong.\n      def ensure_installed\n        # If the guest cannot check if Chef is installed, just exit printing a\n        # warning...\n        if !@machine.guest.capability?(:chef_installed)\n          @machine.ui.warn(I18n.t(\"vagrant.chef_cant_detect\"))\n          return\n        end\n\n        if !should_install_chef?\n          @machine.ui.info(I18n.t(\"vagrant.chef_already_installed\",\n            version: @version.to_s))\n          return\n        end\n\n        @machine.ui.detail(I18n.t(\"vagrant.chef_installing\",\n          version: @version.to_s))\n        @machine.guest.capability(:chef_install, @product, @version, @channel, @omnibus_url, @options)\n\n        if !@machine.guest.capability(:chef_installed, @product, @version)\n          raise Provisioner::Base::ChefError, :install_failed\n        end\n      end\n\n      # Determine if Chef should be installed. Chef is installed if the \"force\"\n      # option is given or if the guest does not have Chef installed at the\n      # proper version.\n      def should_install_chef?\n        @force || !@machine.guest.capability(:chef_installed, @product, @version)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/omnibus.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module Chef\n    # Read more about the Omnibus installer here:\n    #\n    #   https://docs.chef.io/install_omnibus.html\n    #\n    module Omnibus\n      def sh_command(project, version, channel, omnibus_url, options = {})\n        command =  \"curl -sL #{omnibus_url}/install.sh | bash\"\n        command << \" -s -- -P \\\"#{project}\\\" -c \\\"#{channel}\\\"\"\n\n        if version != :latest\n          command << \" -v \\\"#{version}\\\"\"\n        end\n\n        if options[:download_path]\n          command << \" -d \\\"#{options[:download_path]}\\\"\"\n        end\n\n        command\n      end\n      module_function :sh_command\n\n      def ps_command(project, version, channel, omnibus_url, options = {})\n        command =  \". { iwr -useb #{omnibus_url}/install.ps1 } | iex; install\"\n        command << \" -project '#{project}' -channel '#{channel}'\"\n\n        if version != :latest\n          command << \" -version '#{version}'\"\n        end\n\n        command\n      end\n      module_function :ps_command\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nrequire_relative \"command_builder\"\n\nmodule VagrantPlugins\n  module Chef\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"chef\"\n      description <<-DESC\n      Provides support for provisioning your virtual machines with\n      Chef via `chef-solo`, `chef-client`, `chef-zero` or `chef-apply`.\n      DESC\n\n      config(:chef_apply, :provisioner) do\n        require_relative \"config/chef_apply\"\n        Config::ChefApply\n      end\n\n      config(:chef_client, :provisioner) do\n        require_relative \"config/chef_client\"\n        Config::ChefClient\n      end\n\n      config(:chef_solo, :provisioner) do\n        require_relative \"config/chef_solo\"\n        Config::ChefSolo\n      end\n\n      config(:chef_zero, :provisioner) do\n        require_relative \"config/chef_zero\"\n        Config::ChefZero\n      end\n\n      provisioner(:chef_apply) do\n        require_relative \"provisioner/chef_apply\"\n        Provisioner::ChefApply\n      end\n\n      provisioner(:chef_client) do\n        require_relative \"provisioner/chef_client\"\n        Provisioner::ChefClient\n      end\n\n      provisioner(:chef_solo)   do\n        require_relative \"provisioner/chef_solo\"\n        Provisioner::ChefSolo\n      end\n\n      provisioner(:chef_zero)   do\n        require_relative \"provisioner/chef_zero\"\n        Provisioner::ChefZero\n      end\n\n      guest_capability(:debian, :chef_install) do\n        require_relative \"cap/debian/chef_install\"\n        Cap::Debian::ChefInstall\n      end\n\n      guest_capability(:freebsd, :chef_install) do\n        require_relative \"cap/freebsd/chef_install\"\n        Cap::FreeBSD::ChefInstall\n      end\n\n      guest_capability(:freebsd, :chef_installed) do\n        require_relative \"cap/freebsd/chef_installed\"\n        Cap::FreeBSD::ChefInstalled\n      end\n\n      guest_capability(:linux, :chef_installed) do\n        require_relative \"cap/linux/chef_installed\"\n        Cap::Linux::ChefInstalled\n      end\n\n      guest_capability(:omnios, :chef_install) do\n        require_relative \"cap/omnios/chef_install\"\n        Cap::OmniOS::ChefInstall\n      end\n\n      guest_capability(:omnios, :chef_installed) do\n        require_relative \"cap/omnios/chef_installed\"\n        Cap::OmniOS::ChefInstalled\n      end\n\n      guest_capability(:redhat, :chef_install) do\n        require_relative \"cap/redhat/chef_install\"\n        Cap::Redhat::ChefInstall\n      end\n\n      guest_capability(:suse, :chef_install) do\n        require_relative \"cap/suse/chef_install\"\n        Cap::Suse::ChefInstall\n      end\n\n      guest_capability(:windows, :chef_install) do\n        require_relative \"cap/windows/chef_install\"\n        Cap::Windows::ChefInstall\n      end\n\n      guest_capability(:windows, :chef_installed) do\n        require_relative \"cap/windows/chef_installed\"\n        Cap::Windows::ChefInstalled\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/provisioner/base.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\n\nrequire_relative \"../../../../lib/vagrant/util/presence\"\nrequire_relative \"../../../../lib/vagrant/util/template_renderer\"\n\nrequire_relative \"../installer\"\n\nmodule VagrantPlugins\n  module Chef\n    module Provisioner\n      # This class is a base class where the common functionality shared between\n      # chef-solo and chef-client provisioning are stored. This is **not an actual\n      # provisioner**. Instead, {ChefSolo} or {ChefServer} should be used.\n      class Base < Vagrant.plugin(\"2\", :provisioner)\n        include Vagrant::Util\n        include Vagrant::Util::Presence\n\n        class ChefError < Vagrant::Errors::VagrantError\n          error_namespace(\"vagrant.provisioners.chef\")\n        end\n\n        def initialize(machine, config)\n          super\n\n          @logger = Log4r::Logger.new(\"vagrant::provisioners::chef\")\n\n          if @config.respond_to?(:node_name) && !present?(@config.node_name)\n            # First attempt to get the node name from the hostname, and if that\n            # is not present, generate/retrieve a random hostname.\n            hostname = @machine.config.vm.hostname\n            if present?(hostname)\n              @machine.ui.info I18n.t(\"vagrant.provisioners.chef.using_hostname_node_name\",\n                hostname: hostname,\n              )\n              @config.node_name = hostname\n            else\n              cache = @machine.data_dir.join(\"chef_node_name\")\n              if !cache.exist?\n                @machine.ui.info I18n.t(\"vagrant.provisioners.chef.generating_node_name\")\n                cache.open(\"w+\") do |f|\n                  f.write(\"vagrant-#{SecureRandom.hex(4)}\")\n                end\n              end\n\n              if cache.file?\n                @logger.info(\"Loading cached node_name...\")\n                @config.node_name = cache.read.strip\n              end\n            end\n          end\n        end\n\n        def install_chef\n          return if !config.install\n\n          @logger.info(\"Checking for Chef installation...\")\n          installer = Installer.new(@machine,\n            product: config.product,\n            channel: config.channel,\n            version: config.version,\n            omnibus_url: config.omnibus_url,\n            force: config.install == :force,\n            download_path:  config.installer_download_path\n          )\n          installer.ensure_installed\n        end\n\n        def verify_binary(binary)\n          # Checks for the existence of chef binary and error if it\n          # doesn't exist.\n          if windows?\n            command = \"if ((&'#{binary}' -v) -Match 'Chef*'){ exit 0 } else { exit 1 }\"\n          else\n            command = \"sh -c 'command -v #{binary}'\"\n          end\n\n          @machine.communicate.sudo(\n            command,\n            error_class: ChefError,\n            error_key: :chef_not_detected,\n            binary: binary,\n          )\n        end\n\n        # Returns the path to the Chef binary, taking into account the\n        # `binary_path` configuration option.\n        def chef_binary_path(binary)\n          return binary if !@config.binary_path\n          return File.join(@config.binary_path, binary)\n        end\n\n        def chown_provisioning_folder\n          paths = [\n            guest_provisioning_path,\n            guest_file_backup_path,\n            guest_file_cache_path,\n          ]\n\n          @machine.communicate.tap do |comm|\n            paths.each do |path|\n              if windows?\n                comm.sudo(\"mkdir \"\"#{path}\"\" -f\")\n              else\n                comm.sudo(\"mkdir -p #{path}\")\n                comm.sudo(\"chown -h #{@machine.ssh_info[:username]} #{path}\")\n              end\n            end\n          end\n        end\n\n        def setup_config(template, filename, template_vars)\n          # If we have custom configuration, upload it\n          remote_custom_config_path = nil\n          if @config.custom_config_path\n            expanded = File.expand_path(\n              @config.custom_config_path, @machine.env.root_path)\n            remote_custom_config_path = File.join(\n              guest_provisioning_path, \"custom-config.rb\")\n\n            @machine.communicate.upload(expanded, remote_custom_config_path)\n          end\n\n          config_file = TemplateRenderer.render(template, {\n            custom_configuration: remote_custom_config_path,\n            encrypted_data_bag_secret: guest_encrypted_data_bag_secret_key_path,\n            environment:      @config.environment,\n            file_cache_path:  guest_file_cache_path,\n            file_backup_path: guest_file_backup_path,\n            log_level:        @config.log_level.to_sym,\n            node_name:        @config.node_name,\n            verbose_logging:  @config.verbose_logging,\n            enable_reporting: @config.enable_reporting,\n            http_proxy:       @config.http_proxy,\n            http_proxy_user:  @config.http_proxy_user,\n            http_proxy_pass:  @config.http_proxy_pass,\n            https_proxy:      @config.https_proxy,\n            https_proxy_user: @config.https_proxy_user,\n            https_proxy_pass: @config.https_proxy_pass,\n            no_proxy:         @config.no_proxy,\n            formatter:        @config.formatter\n          }.merge(template_vars))\n\n          # Create a temporary file to store the data so we can upload it.\n          remote_file = File.join(guest_provisioning_path, filename)\n          @machine.communicate.sudo(remove_command(remote_file), error_check: false)\n          Tempfile.open(\"vagrant-chef-provisioner-config\") do |f|\n            f.binmode\n            f.write(config_file)\n            f.fsync\n            f.close\n            @machine.communicate.upload(f.path, remote_file)\n          end\n        end\n\n        def setup_json\n          @machine.ui.info I18n.t(\"vagrant.provisioners.chef.json\")\n\n          # Get the JSON that we're going to expose to Chef\n          json = @config.json\n          json[:run_list] = @config.run_list if @config.run_list &&\n            !@config.run_list.empty?\n          json = JSON.pretty_generate(json)\n\n          # Create a temporary file to store the data so we can upload it.\n          remote_file = File.join(guest_provisioning_path, \"dna.json\")\n          @machine.communicate.sudo(remove_command(remote_file), error_check: false)\n          Tempfile.open(\"vagrant-chef-provisioner-config\") do |f|\n            f.binmode\n            f.write(json)\n            f.fsync\n            f.close\n            @machine.communicate.upload(f.path, remote_file)\n          end\n        end\n\n        def upload_encrypted_data_bag_secret\n          remote_file = guest_encrypted_data_bag_secret_key_path\n          return if !remote_file\n\n          @machine.ui.info I18n.t(\n            \"vagrant.provisioners.chef.upload_encrypted_data_bag_secret_key\")\n\n          @machine.communicate.sudo(remove_command(remote_file), error_check: false)\n          @machine.communicate.upload(encrypted_data_bag_secret_key_path, remote_file)\n        end\n\n        def delete_encrypted_data_bag_secret\n          remote_file = guest_encrypted_data_bag_secret_key_path\n          return if remote_file.nil?\n\n          @machine.communicate.sudo(remove_command(remote_file), error_check: false)\n        end\n\n        def encrypted_data_bag_secret_key_path\n          File.expand_path(@config.encrypted_data_bag_secret_key_path,\n            @machine.env.root_path)\n        end\n\n        def guest_encrypted_data_bag_secret_key_path\n          if @config.encrypted_data_bag_secret_key_path\n            File.join(guest_provisioning_path, \"encrypted_data_bag_secret_key\")\n          end\n        end\n\n        def guest_provisioning_path\n          if !@config.provisioning_path.nil?\n            return @config.provisioning_path\n          end\n\n          if windows?\n            \"C:/vagrant-chef\"\n          else\n            \"/tmp/vagrant-chef\"\n          end\n        end\n\n        def guest_file_backup_path\n          if !@config.file_backup_path.nil?\n            return @config.file_backup_path\n          end\n\n          if windows?\n            \"C:/chef/backup\"\n          else\n            \"/var/chef/backup\"\n          end\n        end\n\n        def guest_file_cache_path\n          if !@config.file_cache_path.nil?\n            return @config.file_cache_path\n          end\n\n          if windows?\n            \"C:/chef/cache\"\n          else\n            \"/var/chef/cache\"\n          end\n        end\n\n        def remove_command(path)\n          if windows?\n            \"if (test-path \"\"#{path}\"\") {rm \"\"#{path}\"\" -force -recurse}\"\n          else\n            \"rm -f #{path}\"\n          end\n        end\n\n        def windows?\n          @machine.config.vm.communicator == :winrm\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/provisioner/chef_apply.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"digest/md5\"\nrequire \"tempfile\"\n\nrequire_relative \"base\"\n\nmodule VagrantPlugins\n  module Chef\n    module Provisioner\n      class ChefApply < Base\n        def provision\n          install_chef\n          verify_binary(chef_binary_path(\"chef-apply\"))\n\n          command = \"chef-apply\"\n          command << \" \\\"#{target_recipe_path}\\\"\"\n          command << \" --log_level #{config.log_level}\"\n\n          user = @machine.ssh_info[:username]\n\n          # Reset upload path permissions for the current ssh user\n          if windows?\n            @machine.communicate.sudo(\"mkdir \"\"#{config.upload_path}\"\" -f\")\n          else\n            @machine.communicate.sudo(\"mkdir -p #{config.upload_path}\")\n            @machine.communicate.sudo(\"chown -R #{user} #{config.upload_path}\")\n          end\n\n          # Upload the recipe\n          upload_recipe\n\n          @machine.ui.info(I18n.t(\"vagrant.provisioners.chef.running_apply\",\n            script: config.path)\n          )\n\n          # Execute it with sudo\n          @machine.communicate.sudo(command) do |type, data|\n            if [:stderr, :stdout].include?(type)\n              # Output the data with the proper color based on the stream.\n              color = (type == :stdout) ? :green : :red\n\n              # Chomp the data to avoid the newlines that the Chef outputs\n              @machine.env.ui.info(data.chomp, color: color, prefix: false)\n            end\n          end\n        end\n\n        # The destination (on the guest) where the recipe will live\n        # @return [String]\n        def target_recipe_path\n          key = Digest::MD5.hexdigest(config.recipe)\n          File.join(config.upload_path, \"recipe-#{key}.rb\")\n        end\n\n        # Write the raw recipe contents to a tempfile and upload that to the\n        # machine.\n        def upload_recipe\n          # Write the raw recipe contents to a tempfile and upload\n          Tempfile.open([\"vagrant-chef-apply\", \".rb\"]) do |f|\n            f.binmode\n            f.write(config.recipe)\n            f.fsync\n            f.close\n\n            # Upload the tempfile to the guest\n            @machine.communicate.upload(f.path, target_recipe_path)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/provisioner/chef_client.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'pathname'\n\nrequire 'vagrant'\nrequire 'vagrant/util/presence'\nrequire 'vagrant/util/subprocess'\n\nrequire_relative \"base\"\n\nmodule VagrantPlugins\n  module Chef\n    module Provisioner\n      # This class implements provisioning via chef-client, allowing provisioning\n      # with a chef server.\n      class ChefClient < Base\n        include Vagrant::Util::Presence\n\n        def configure(root_config)\n          raise ChefError, :server_validation_key_required if @config.validation_key_path.nil?\n          raise ChefError, :server_validation_key_doesnt_exist if !File.file?(validation_key_path)\n          raise ChefError, :server_url_required if @config.chef_server_url.nil?\n        end\n\n        def provision\n          install_chef\n          verify_binary(chef_binary_path(\"chef-client\"))\n          chown_provisioning_folder\n          create_client_key_folder\n          upload_validation_key\n          upload_encrypted_data_bag_secret\n          setup_json\n          setup_server_config\n          run_chef_client\n          delete_encrypted_data_bag_secret\n        end\n\n        def cleanup\n          if @config.delete_node\n            delete_from_chef_server(\"node\")\n          end\n\n          if @config.delete_client\n            delete_from_chef_server(\"client\")\n          end\n        end\n\n        def create_client_key_folder\n          @machine.ui.info I18n.t(\"vagrant.provisioners.chef.client_key_folder\")\n          path = Pathname.new(guest_client_key_path)\n\n          if windows?\n            @machine.communicate.sudo(\"mkdir \"\"#{path.dirname}\"\" -f\")\n          else\n            @machine.communicate.sudo(\"mkdir -p #{path.dirname}\")\n          end\n        end\n\n        def upload_validation_key\n          @machine.ui.info I18n.t(\"vagrant.provisioners.chef.upload_validation_key\")\n          @machine.communicate.upload(validation_key_path, guest_validation_key_path)\n        end\n\n        def setup_server_config\n          setup_config(\"provisioners/chef_client/client\", \"client.rb\", {\n            chef_server_url: @config.chef_server_url,\n            validation_client_name: @config.validation_client_name,\n            validation_key: guest_validation_key_path,\n            client_key: guest_client_key_path,\n          })\n        end\n\n        def run_chef_client\n          if @config.run_list && @config.run_list.empty?\n            @machine.ui.warn(I18n.t(\"vagrant.chef_run_list_empty\"))\n          end\n\n          command = CommandBuilder.command(:client, @config,\n            windows: windows?,\n            colored: @machine.env.ui.color?,\n          )\n          \n          still_active = 259 #provisioner has asked chef to reboot \n          \n          @config.attempts.times do |attempt|\n            exit_status = 0\n            while exit_status == 0 || exit_status == still_active \n              if @machine.guest.capability?(:wait_for_reboot)\n                @machine.guest.capability(:wait_for_reboot)\n              elsif attempt > 0\n                sleep 10\n                @machine.communicate.wait_for_ready(@machine.config.vm.boot_timeout)\n              end\n              if attempt == 0\n                @machine.ui.info I18n.t(\"vagrant.provisioners.chef.running_client\")\n              else\n                @machine.ui.info I18n.t(\"vagrant.provisioners.chef.running_client_again\")\n              end\n\n              opts = { error_check: false, elevated: true }\n              exit_status = @machine.communicate.sudo(command, opts) do |type, data|\n                # Output the data with the proper color based on the stream.\n                color = type == :stdout ? :green : :red\n\n                data = data.chomp\n                next if data.empty?\n\n                @machine.ui.info(data, color: color)\n              end\n\n              # There is no need to run Chef again if it converges\n              return if exit_status == 0\n            end\n          end\n\n          # If we reached this point then Chef never converged! Error.\n          raise ChefError, :no_convergence\n        end\n\n        def validation_key_path\n          File.expand_path(@config.validation_key_path, @machine.env.root_path)\n        end\n\n        def guest_client_key_path\n          if !@config.client_key_path.nil?\n            return @config.client_key_path\n          end\n\n          if windows?\n            \"C:/chef/client.pem\"\n          else\n            \"/etc/chef/client.pem\"\n          end\n        end\n\n        def guest_client_rb_path\n          File.join(guest_provisioning_path, \"client.rb\")\n        end\n\n        def guest_validation_key_path\n          File.join(guest_provisioning_path, \"validation.pem\")\n        end\n\n        def delete_from_chef_server(deletable)\n          node_name = @config.node_name\n\n          if !present?(node_name)\n            @machine.ui.warn(I18n.t(\"vagrant.provisioners.chef.missing_node_name\",\n              deletable: deletable,\n            ))\n            return\n          end\n\n          @machine.ui.info(I18n.t(\"vagrant.provisioners.chef.deleting_from_server\",\n            deletable: deletable, name: node_name))\n\n          command =  \"knife #{deletable} delete #{node_name}\"\n          command << \" --config '#{guest_client_rb_path}'\"\n          command << \" --yes\"\n\n          output = []\n          result = @machine.communicate.sudo(command, error_check: false) do |_, data|\n            output << data\n          end\n\n          if result != 0\n            @machine.ui.error(\"There were errors removing the #{deletable} from the Chef Server:\")\n            @machine.ui.error(\"\")\n            @machine.ui.error(output.join(\"\\n\"))\n            @machine.ui.error(\"\")\n            @machine.ui.error(\"Vagrant will continue destroying the virtual machine, but you may need\")\n            @machine.ui.error(\"to manually delete the #{deletable} from the Chef Server!\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/provisioner/chef_solo.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"digest/md5\"\nrequire \"securerandom\"\nrequire \"set\"\n\nrequire \"log4r\"\n\nrequire \"vagrant/util/counter\"\nrequire \"vagrant/action/builtin/mixin_synced_folders\"\n\nrequire_relative \"base\"\n\nmodule VagrantPlugins\n  module Chef\n    module Provisioner\n      # This class implements provisioning via chef-solo.\n      class ChefSolo < Base\n        extend Vagrant::Util::Counter\n        include Vagrant::Util::Counter\n        include Vagrant::Action::Builtin::MixinSyncedFolders\n\n        attr_reader :environments_folders\n        attr_reader :cookbook_folders\n        attr_reader :node_folders\n        attr_reader :role_folders\n        attr_reader :data_bags_folders\n\n        def initialize(machine, config)\n          super\n          @logger = Log4r::Logger.new(\"vagrant::provisioners::chef_solo\")\n          @shared_folders = []\n        end\n\n        def configure(root_config)\n          @cookbook_folders  = expanded_folders(@config.cookbooks_path, \"cookbooks\")\n          @role_folders      = expanded_folders(@config.roles_path, \"roles\")\n          @data_bags_folders = expanded_folders(@config.data_bags_path, \"data_bags\")\n          @environments_folders = expanded_folders(@config.environments_path, \"environments\")\n          @node_folders = expanded_folders(@config.nodes_path, \"nodes\")\n\n          existing = synced_folders(@machine, cached: true)\n          share_folders(root_config, \"csc\", @cookbook_folders, existing)\n          share_folders(root_config, \"csr\", @role_folders, existing)\n          share_folders(root_config, \"csdb\", @data_bags_folders, existing)\n          share_folders(root_config, \"cse\", @environments_folders, existing)\n          share_folders(root_config, \"csn\", @node_folders, existing)\n        end\n\n        def provision\n          install_chef\n          # Verify that the proper shared folders exist.\n          check = []\n          @shared_folders.each do |type, local_path, remote_path|\n            # We only care about checking folders that have a local path, meaning\n            # they were shared from the local machine, rather than assumed to\n            # exist on the VM.\n            check << remote_path if local_path\n          end\n\n          chown_provisioning_folder\n          verify_shared_folders(check)\n          verify_binary(chef_binary_path(\"chef-solo\"))\n          upload_encrypted_data_bag_secret\n          setup_json\n          setup_solo_config\n          run_chef_solo\n          delete_encrypted_data_bag_secret\n        end\n\n        # Converts paths to a list of properly expanded paths with types.\n        def expanded_folders(paths, appended_folder=nil)\n          # Convert the path to an array if it is a string or just a single\n          # path element which contains the folder location (:host or :vm)\n          paths = [paths] if paths.is_a?(String) || paths.first.is_a?(Symbol)\n\n          results = []\n          paths.each do |type, path|\n            # Create the local/remote path based on whether this is a host\n            # or VM path.\n            local_path = nil\n            remote_path = nil\n            if type == :host\n              # Get the expanded path that the host path points to\n              local_path = File.expand_path(path, @machine.env.root_path)\n\n              if File.exist?(local_path)\n                # Path exists on the host, setup the remote path. We use\n                # the MD5 of the local path so that it is predictable.\n                key         = Digest::MD5.hexdigest(local_path)\n                remote_path = \"#{guest_provisioning_path}/#{key}\"\n              else\n                appended_folder = \"cookbooks\" if appended_folder.nil?\n                @machine.ui.warn(I18n.t(\"vagrant.provisioners.chef.#{appended_folder}_folder_not_found_warning\",\n                                       path: local_path.to_s))\n                next\n              end\n            else\n              # Path already exists on the virtual machine. Expand it\n              # relative to where we're provisioning.\n\n              # Remove drive letter if running on a windows host. This is a bit\n              # of a hack but is the most portable way I can think of at the moment\n              # to achieve this. Otherwise, Vagrant attempts to share at some crazy\n              # path like /home/vagrant/c:/foo/bar\n              remote_path = File.expand_path(path.sub(/^[a-zA-Z]:\\//, \"/\"), guest_provisioning_path.sub(/^[a-zA-Z]:\\//, \"/\"))\n              remote_path.sub!(/^[a-zA-Z]:\\//, \"/\")\n            end\n\n            # If we have specified a folder name to append then append it\n            if type == :host\n              remote_path += \"/#{appended_folder}\" if appended_folder\n            end\n\n            # Append the result\n            results << [type, local_path, remote_path]\n          end\n\n          results\n        end\n\n        # Shares the given folders with the given prefix. The folders should\n        # be of the structure resulting from the `expanded_folders` function.\n        def share_folders(root_config, prefix, folders, existing=nil)\n          existing_set = Set.new\n          (existing || []).each do |_, fs|\n            fs.each do |id, data|\n              existing_set.add(data[:guestpath])\n            end\n          end\n\n          folders.each do |type, local_path, remote_path|\n            next if type != :host\n\n            # If this folder already exists, then we don't share it, it means\n            # it was already put down on disk.\n            #\n            # NOTE: This is currently commented out because it was causing\n            # major bugs (GH-5199). We will investigate why this is in more\n            # detail for 1.8.0, but we wanted to fix this in a patch release\n            # and this was the hammer that did that.\n=begin\n            if existing_set.include?(remote_path)\n              @logger.debug(\"Not sharing #{local_path}, exists as #{remote_path}\")\n              next\n            end\n=end\n\n            key = Digest::MD5.hexdigest(remote_path)\n            key = key[0..8]\n\n            opts = {}\n            opts[:id] = \"v-#{prefix}-#{key}\"\n            opts[:type] = @config.synced_folder_type if @config.synced_folder_type\n\n            root_config.vm.synced_folder(local_path, remote_path, opts)\n          end\n\n          @shared_folders += folders\n        end\n\n        def setup_solo_config\n          setup_config(\"provisioners/chef_solo/solo\", \"solo.rb\", solo_config)\n        end\n\n        def solo_config\n          {\n            cookbooks_path: guest_paths(@cookbook_folders),\n            recipe_url: @config.recipe_url,\n            nodes_path: guest_paths(@node_folders),\n            roles_path: guest_paths(@role_folders),\n            data_bags_path: guest_paths(@data_bags_folders),\n            environments_path: guest_paths(@environments_folders).first\n          }\n        end\n\n        def run_chef_solo\n          if @config.run_list && @config.run_list.empty?\n            @machine.ui.warn(I18n.t(\"vagrant.chef_run_list_empty\"))\n          end\n\n          command = CommandBuilder.command(:solo, @config,\n            windows: windows?,\n            colored: @machine.env.ui.color?,\n            legacy_mode: @config.legacy_mode,\n          )\n\n          still_active = 259 #provisioner has asked chef to reboot\n\n          @config.attempts.times do |attempt|\n            exit_status = 0\n            while exit_status == 0 || exit_status == still_active\n              if @machine.guest.capability?(:wait_for_reboot)\n                @machine.guest.capability(:wait_for_reboot)\n              elsif attempt > 0\n                sleep 10\n                @machine.communicate.wait_for_ready(@machine.config.vm.boot_timeout)\n              end\n              if attempt == 0\n                @machine.ui.info I18n.t(\"vagrant.provisioners.chef.running_solo\")\n              else\n                @machine.ui.info I18n.t(\"vagrant.provisioners.chef.running_solo_again\")\n              end\n\n              opts = { error_check: false, elevated: true }\n              exit_status = @machine.communicate.sudo(command, opts) do |type, data|\n                # Output the data with the proper color based on the stream.\n                color = type == :stdout ? :green : :red\n\n                data = data.chomp\n                next if data.empty?\n\n                @machine.ui.info(data, color: color)\n              end\n\n              # There is no need to run Chef again if it converges\n              return if exit_status == 0\n            end\n          end\n\n          # If we reached this point then Chef never converged! Error.\n          raise ChefError, :no_convergence\n        end\n\n        def verify_shared_folders(folders)\n          folders.each do |folder|\n            @logger.debug(\"Checking for shared folder: #{folder}\")\n            if !@machine.communicate.test(\"test -d #{folder}\", sudo: true)\n              raise ChefError, :missing_shared_folders\n            end\n          end\n        end\n\n        protected\n\n        # Extracts only the remote paths from a list of folders\n        def guest_paths(folders)\n          folders.map { |parts| parts[2] }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/chef/provisioner/chef_zero.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"digest/md5\"\nrequire \"securerandom\"\nrequire \"set\"\n\nrequire \"log4r\"\n\nrequire \"vagrant/util/counter\"\n\nrequire_relative \"chef_solo\"\n\nmodule VagrantPlugins\n  module Chef\n    module Provisioner\n      # This class implements provisioning via chef-zero.\n      class ChefZero < ChefSolo\n        def initialize(machine, config)\n          super\n          @logger = Log4r::Logger.new(\"vagrant::provisioners::chef_zero\")\n        end\n\n        def provision\n          install_chef\n          # Verify that the proper shared folders exist.\n          check = []\n          @shared_folders.each do |type, local_path, remote_path|\n            # We only care about checking folders that have a local path, meaning\n            # they were shared from the local machine, rather than assumed to\n            # exist on the VM.\n            check << remote_path if local_path\n          end\n\n          chown_provisioning_folder\n          verify_shared_folders(check)\n          verify_binary(chef_binary_path(\"chef-client\"))\n          upload_encrypted_data_bag_secret\n          setup_json\n          setup_zero_config\n          run_chef_zero\n          delete_encrypted_data_bag_secret\n        end\n\n        def setup_zero_config\n          setup_config(\"provisioners/chef_zero/zero\", \"client.rb\", {\n            local_mode: true,\n            enable_reporting: false,\n            cookbooks_path: guest_paths(@cookbook_folders),\n            nodes_path: guest_paths(@node_folders),\n            roles_path: guest_paths(@role_folders),\n            data_bags_path: guest_paths(@data_bags_folders),\n            environments_path: guest_paths(@environments_folders).first,\n          })\n        end\n\n        def run_chef_zero\n          if @config.run_list && @config.run_list.empty?\n            @machine.ui.warn(I18n.t(\"vagrant.chef_run_list_empty\"))\n          end\n\n          command = CommandBuilder.command(:client, @config,\n            windows:    windows?,\n            colored:    @machine.env.ui.color?,\n            local_mode: true,\n          )\n\n          still_active = 259 #provisioner has asked chef to reboot\n\n          @config.attempts.times do |attempt|\n            exit_status = 0\n            while exit_status == 0 || exit_status == still_active\n              if @machine.guest.capability?(:wait_for_reboot)\n                @machine.guest.capability(:wait_for_reboot)\n              elsif attempt > 0\n                sleep 10\n                @machine.communicate.wait_for_ready(@machine.config.vm.boot_timeout)\n              end\n              if attempt == 0\n                @machine.ui.info I18n.t(\"vagrant.provisioners.chef.running_zero\")\n              else\n                @machine.ui.info I18n.t(\"vagrant.provisioners.chef.running_zero_again\")\n              end\n\n              opts = { error_check: false, elevated: true }\n              exit_status = @machine.communicate.sudo(command, opts) do |type, data|\n                # Output the data with the proper color based on the stream.\n                color = type == :stdout ? :green : :red\n\n                data = data.chomp\n                next if data.empty?\n\n                @machine.ui.info(data, color: color)\n              end\n\n              # There is no need to run Chef again if it converges\n              return if exit_status == 0\n            end\n          end\n\n          # If we reached this point then Chef never converged! Error.\n          raise ChefError, :no_convergence\n        end\n\n        def verify_shared_folders(folders)\n          folders.each do |folder|\n            @logger.debug(\"Checking for shared folder: #{folder}\")\n            if !@machine.communicate.test(\"test -d #{folder}\", sudo: true)\n              raise ChefError, :missing_shared_folders\n            end\n          end\n        end\n\n        protected\n\n        # Extracts only the remote paths from a list of folders\n        def guest_paths(folders)\n          folders.map { |parts| parts[2] }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/container/client.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'digest/sha1'\n\nmodule VagrantPlugins\n  module ContainerProvisioner\n    class Client\n      def initialize(machine, container_command)\n        @machine = machine\n        @container_command =  container_command\n      end\n\n      # Build an image given a path to a Dockerfile\n      #\n      # @param [Array<Array<String, Hash>>] - Path and options to the Dockerfile to pass to\n      #   container build command\n      def build_images(images)\n        @machine.communicate.tap do |comm|\n          images.each do |path, opts|\n            @machine.ui.info(I18n.t(\"vagrant.container_building_single\", path: path))\n            comm.sudo(\"#{@container_command} build #{opts[:args]} #{path}\") do |type, data|\n              handle_comm(type, data)\n            end\n          end\n        end\n      end\n\n      # Pull image given a list of images\n      #\n      # @param [String] - Image name\n      def pull_images(*images)\n        @machine.communicate.tap do |comm|\n          images.each do |image|\n            @machine.ui.info(I18n.t(\"vagrant.container_pulling_single\", name: image))\n            comm.sudo(\"#{@container_command} pull #{image}\") do |type, data|\n              handle_comm(type, data)\n            end\n          end\n        end\n      end\n\n      def run(containers)\n        containers.each do |name, config|\n          cids_dir = \"/var/lib/vagrant/cids\"\n          config[:cidfile] ||= \"#{cids_dir}/#{Digest::SHA1.hexdigest name}\"\n\n          @machine.ui.info(I18n.t(\"vagrant.container_running\", name: name))\n          @machine.communicate.sudo(\"mkdir -p #{cids_dir}\")\n          run_container({\n            name: name,\n            original_name: name,\n          }.merge(config))\n        end\n      end\n\n      # Run a OCI container. If the container does not exist it will be\n      # created. If the image is stale it will be recreated and restarted\n      def run_container(config)\n        raise \"Container's cidfile was not provided!\" if !config[:cidfile]\n\n        id = \"$(cat #{config[:cidfile]})\"\n\n        if container_exists?(id)\n          if container_args_changed?(config)\n            @machine.ui.info(I18n.t(\"vagrant.container_restarting_container_args\",\n              name: config[:name],\n            ))\n            stop_container(id)\n            create_container(config)\n          elsif container_image_changed?(config)\n            @machine.ui.info(I18n.t(\"vagrant.container_restarting_container_image\",\n              name: config[:name],\n            ))\n            stop_container(id)\n            create_container(config)\n          else\n            start_container(id)\n          end\n        else\n          create_container(config)\n        end\n      end\n\n      def container_exists?(id)\n        lookup_container(id, true)\n      end\n\n      # Start container\n      #\n      # @param String - Image id\n      def start_container(id)\n        if !container_running?(id)\n          @machine.communicate.sudo(\"#{@container_command} start #{id}\")\n        end\n      end\n\n      # Stop and remove container\n      #\n      # @param String - Image id\n      def stop_container(id)\n        @machine.communicate.sudo %[\n          #{@container_command} stop #{id}\n          #{@container_command} rm #{id}\n        ]\n      end\n\n      def container_running?(id)\n        lookup_container(id)\n      end\n\n      def container_image_changed?(config)\n        # Returns true if there is a container running with the given :name,\n        # and the container is not using the latest :image.\n\n        # Here, \"<cmd> inspect <container>\" returns the id of the image\n        # that the container is using. We check that the latest image that\n        # has been built with that name (:image)  matches the one that the\n        # container is running.\n        cmd = (\"#{@container_command} inspect --format='{{.Image}}' #{config[:name]} |\" +\n               \" grep $(#{@container_command} images -q #{config[:image]})\")\n        return !@machine.communicate.test(cmd)\n      end\n\n      def container_args_changed?(config)\n        path = container_data_path(config)\n        return true if !path.exist?\n\n        args = container_run_args(config)\n        sha  = Digest::SHA1.hexdigest(args)\n        return true if path.read.chomp != sha\n\n        return false\n      end\n\n      def create_container(config)\n        args = container_run_args(config)\n\n        @machine.communicate.sudo %[rm -f \"#{config[:cidfile]}\"]\n        @machine.communicate.sudo %[#{@container_command} run #{args}]\n\n        sha  = Digest::SHA1.hexdigest(args)\n        container_data_path(config).open(\"w+\") do |f|\n          f.write(sha)\n        end\n      end\n\n      # Looks up if a container with a given id exists using the\n      # `ps` command. Returns Boolean\n      #\n      # @param String - Image id\n      def lookup_container(id, list_all = false)\n        container_ps = \"sudo #{@container_command} ps -q\"\n        container_ps << \" -a\" if list_all\n        @machine.communicate.tap do |comm|\n          return comm.test(\"#{container_ps} --no-trunc | grep -wFq #{id}\")\n        end\n      end\n\n      def container_name(config)\n        name = config[:name]\n\n        # If the name is the automatically assigned name, then\n        # replace the \"/\" with \"-\" because \"/\" is not a valid\n        # character for a container name.\n        name = name.gsub(\"/\", \"-\").gsub(\":\", \"-\") if name == config[:original_name]\n        name\n      end\n\n      # Compiles run arguments to be appended to command string.\n      # Returns String\n      def container_run_args(config)\n        name = container_name(config)\n\n        args = \"--cidfile=#{config[:cidfile]} \"\n        args << \"-d \" if config[:daemonize]\n        args << \"--name #{name} \" if name && config[:auto_assign_name]\n        args << \"--restart=#{config[:restart]}\" if config[:restart]\n        args << \" #{config[:args]}\" if config[:args]\n\n        \"#{args} #{config[:image]} #{config[:cmd]}\".strip\n      end\n\n      def container_data_path(config)\n        name = container_name(config)\n        @machine.data_dir.join(\"#{@container_command}-#{name}\")\n      end\n\n      protected\n\n      # This handles outputting the communication data back to the UI\n      def handle_comm(type, data)\n        if [:stderr, :stdout].include?(type)\n          # Clear out the newline since we add one\n          data = data.chomp\n          return if data.empty?\n\n          options = {}\n          #options[:color] = color if !config.keep_color\n\n          @machine.ui.info(data.chomp, **options)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/container/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'set'\n\nmodule VagrantPlugins\n  module ContainerProvisioner\n    class Config < Vagrant.plugin(\"2\", :config)\n      attr_reader :images\n      attr_accessor :post_install_provisioner\n\n      def initialize\n        @images = Set.new\n        @post_install_provisioner = nil\n\n        @__build_images   = []\n        @__containers     = Hash.new { |h, k| h[k] = {} }\n      end\n\n      # Accessor for internal state.\n      def build_images\n        @__build_images\n      end\n\n      # Accessor for the internal state.\n      def containers\n        @__containers\n      end\n\n      # Defines an image to build using `<cmd> build` within the machine.\n      #\n      # @param [String] path Path to the Dockerfile to pass to\n      #   container build command\n      def build_image(path, **opts)\n        @__build_images << [path, opts]\n      end\n\n      def images=(images)\n        @images = Set.new(images)\n      end\n\n      def pull_images(*images)\n        @images += images.map(&:to_s)\n      end\n\n      def post_install_provision(name, **options, &block)\n        proxy = VagrantPlugins::Kernel_V2::VMConfig.new\n        proxy.provision(name, **options, &block)\n        @post_install_provisioner = proxy.provisioners.first\n      end\n\n      def run(name, **options)\n        @__containers[name.to_s] = options.dup\n      end\n\n      def merge(other)\n        super.tap do |result|\n          result.pull_images(*(other.images + self.images))\n\n          build_images = @__build_images.dup\n          build_images += other.build_images\n          result.instance_variable_set(:@__build_images, build_images)\n\n          containers = {}\n          @__containers.each do |name, params|\n            containers[name] = params.dup\n          end\n          other.containers.each do |name, params|\n            containers[name] = @__containers[name].merge(params)\n          end\n\n          result.instance_variable_set(:@__containers, containers)\n        end\n      end\n\n      def finalize!\n        @__containers.each do |name, params|\n          params[:image] ||= name\n          params[:auto_assign_name] = true if !params.key?(:auto_assign_name)\n          params[:daemonize] = true if !params.key?(:daemonize)\n          params[:restart] = \"always\" if !params.key?(:restart)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/container/installer.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module ContainerProvisioner\n    class Installer\n      def initialize(machine)\n        @machine = machine\n      end\n\n      def ensure_installed\n        # nothing to do\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/container/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module ContainerProvisioner\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"container\"\n      description <<-DESC\n      Provides support for provisioning your virtual machines with\n      OCI images and containers.\n      DESC\n\n      config(:container, :provisioner) do\n        require_relative \"config\"\n        Config\n      end\n\n      provisioner(:container) do\n        require_relative \"provisioner\"\n        Provisioner\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/container/provisioner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"client\"\nrequire_relative \"installer\"\n\nmodule VagrantPlugins\n  module ContainerProvisioner\n    class Provisioner < Vagrant.plugin(\"2\", :provisioner)\n      def initialize(machine, config, installer = nil, client = nil)\n        super(machine, config)\n\n        @installer = installer || Installer.new(@machine)\n        @client    = client    || Client.new(@machine, \"\")\n        @logger = Log4r::Logger.new(\"vagrant::provisioners::container\")\n      end\n\n      def provision\n        # nothing to do\n      end\n\n      def run_provisioner(env)\n        klass  = Vagrant.plugin(\"2\").manager.provisioners[env[:provisioner].type]\n        result = klass.new(env[:machine], env[:provisioner].config)\n        result.config.finalize!\n\n        result.provision\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/cap/centos/docker_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    module Cap\n      module Centos\n        module DockerInstall\n          def self.docker_install(machine)\n            machine.communicate.tap do |comm|\n              comm.sudo(\"yum -q -y update\")\n              comm.sudo(\"yum -q -y remove docker-io* || true\")\n              comm.sudo(\"yum install -y -q yum-utils\")\n              comm.sudo(\"yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo\")\n              comm.sudo(\"yum makecache\")\n              comm.sudo(\"yum install -y -q docker-ce\")\n            end\n\n            case machine.guest.capability(\"flavor\")\n            when :centos\n              docker_enable_service(machine)\n            else\n              docker_enable_systemctl(machine)\n            end\n          end\n\n          def self.docker_enable_systemctl(machine)\n            machine.communicate.tap do |comm|\n              comm.sudo(\"systemctl start docker.service\")\n              comm.sudo(\"systemctl enable docker.service\")\n            end\n          end\n\n          def self.docker_enable_service(machine)\n            machine.communicate.tap do |comm|\n              comm.sudo(\"service docker start\")\n              comm.sudo(\"chkconfig docker on\")\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/cap/centos/docker_start_service.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    module Cap\n      module Centos\n        module DockerStartService\n          def self.docker_start_service(machine)\n            case machine.guest.capability(\"flavor\")\n            when :centos\n              machine.communicate.tap do |comm|\n                comm.sudo(\"service docker start\")\n                comm.sudo(\"chkconfig docker on\")\n              end\n            else\n              machine.communicate.tap do |comm|\n                comm.sudo(\"systemctl start docker.service\")\n                comm.sudo(\"systemctl enable docker.service\")\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/cap/debian/docker_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    module Cap\n      module Debian\n        module DockerInstall\n          def self.docker_install(machine)\n            machine.communicate.tap do |comm|\n              comm.sudo(\"apt-get update -qq -y\")\n              comm.sudo(\"apt-get install -qq -y --force-yes curl apt-transport-https\")\n              comm.sudo(\"apt-get purge -qq -y lxc-docker* || true\")\n              comm.sudo(\"curl -sSL https://get.docker.com/ | sh\")\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/cap/debian/docker_start_service.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    module Cap\n      module Debian\n        module DockerStartService\n          def self.docker_start_service(machine)\n            machine.communicate.sudo(\"service docker start\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/cap/fedora/docker_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    module Cap\n      module Fedora\n        module DockerInstall\n          def self.docker_install(machine)\n            machine.communicate.tap do |comm|\n              if dnf?(machine)\n                comm.sudo(\"dnf -y install docker\")\n              else\n                comm.sudo(\"yum -y install docker\")\n              end\n              comm.sudo(\"systemctl start docker.service\")\n              comm.sudo(\"systemctl enable docker.service\")\n            end\n          end\n\n          protected\n\n          def self.dnf?(machine)\n            machine.communicate.test(\"/usr/bin/which -s dnf\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/cap/linux/docker_configure_vagrant_user.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    module Cap\n      module Linux\n        module DockerConfigureVagrantUser\n          def self.docker_configure_vagrant_user(machine)\n            ssh_info = machine.ssh_info\n\n            machine.communicate.tap do |comm|\n              if comm.test(\"getent group docker\") && !comm.test(\"id -Gn | grep docker\")\n                comm.sudo(\"usermod -a -G docker #{ssh_info[:username]}\")\n                comm.reset!\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/cap/linux/docker_daemon_running.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    module Cap\n      module Linux\n        module DockerDaemonRunning\n          def self.docker_daemon_running(machine)\n            machine.communicate.test(\"test -f /var/run/docker.pid\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/cap/linux/docker_installed.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    module Cap\n      module Linux\n        module DockerInstalled\n          def self.docker_installed(machine)\n            paths = [\n              \"/bin/docker\",\n              \"/usr/bin/docker\",\n              \"/usr/local/bin/docker\",\n              \"/usr/sbin/docker\",\n            ]\n\n            paths.each do |p|\n              if machine.communicate.test(\"test -f #{p}\", sudo: true)\n                return true\n              end\n            end\n\n            return false\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/cap/windows/docker_daemon_running.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    module Cap\n      module Windows\n        module DockerDaemonRunning\n          def self.docker_daemon_running(machine)\n            machine.communicate.test(\"tasklist | find \\\"`\\\"dockerd`\\\"\\\"\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/client.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../container/client\"\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    class Client < VagrantPlugins::ContainerProvisioner::Client\n      def initialize(machine)\n        super(machine, \"docker\")\n        @container_command = \"docker\"\n      end\n\n      def start_service\n        if !daemon_running? && @machine.guest.capability?(:docker_start_service)\n          @machine.guest.capability(:docker_start_service)\n        end\n      end\n\n      def daemon_running?\n        @machine.guest.capability(:docker_daemon_running)\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../container/config\"\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    class Config < VagrantPlugins::ContainerProvisioner::Config\n      def post_install_provision(name, **options, &block)\n        # Abort\n        raise DockerError, :wrong_provisioner if options[:type] == \"docker\"\n\n        proxy = VagrantPlugins::Kernel_V2::VMConfig.new\n        proxy.provision(name, **options, &block)\n        @post_install_provisioner = proxy.provisioners.first\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/installer.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../container/installer\"\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    class Installer < VagrantPlugins::ContainerProvisioner::Installer\n      # This handles verifying the Docker installation, installing it if it was\n      # requested, and so on. This method will raise exceptions if things are\n      # wrong.\n      # @return [Boolean] - false if docker cannot be detected on machine, else\n      #                     true if docker installs correctly or is installed\n      def ensure_installed\n        if !@machine.guest.capability?(:docker_installed)\n          @machine.ui.warn(I18n.t(\"vagrant.docker_cant_detect\"))\n          return false\n        end\n\n        if !@machine.guest.capability(:docker_installed)\n          @machine.ui.detail(I18n.t(\"vagrant.docker_installing\"))\n          @machine.guest.capability(:docker_install)\n        end\n\n        if !@machine.guest.capability(:docker_installed)\n          raise DockerError, :install_failed\n        end\n\n        if @machine.guest.capability?(:docker_configure_vagrant_user)\n          @machine.guest.capability(:docker_configure_vagrant_user)\n        end\n\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"docker\"\n      description <<-DESC\n      Provides support for provisioning your virtual machines with\n      Docker images and containers.\n      DESC\n\n      config(:docker, :provisioner) do\n        require_relative \"config\"\n        Config\n      end\n\n      guest_capability(\"debian\", \"docker_install\") do\n        require_relative \"cap/debian/docker_install\"\n        Cap::Debian::DockerInstall\n      end\n\n      guest_capability(\"debian\", \"docker_start_service\") do\n        require_relative \"cap/debian/docker_start_service\"\n        Cap::Debian::DockerStartService\n      end\n\n      guest_capability(\"fedora\", \"docker_install\") do\n        require_relative \"cap/fedora/docker_install\"\n        Cap::Fedora::DockerInstall\n      end\n\n      guest_capability(\"centos\", \"docker_install\") do\n        require_relative \"cap/centos/docker_install\"\n        Cap::Centos::DockerInstall\n      end\n\n      guest_capability(\"centos\", \"docker_start_service\") do\n        require_relative \"cap/centos/docker_start_service\"\n        Cap::Centos::DockerStartService\n      end\n\n      guest_capability(\"linux\", \"docker_installed\") do\n        require_relative \"cap/linux/docker_installed\"\n        Cap::Linux::DockerInstalled\n      end\n\n      guest_capability(\"linux\", \"docker_configure_vagrant_user\") do\n        require_relative \"cap/linux/docker_configure_vagrant_user\"\n        Cap::Linux::DockerConfigureVagrantUser\n      end\n\n      guest_capability(\"linux\", \"docker_daemon_running\") do\n        require_relative \"cap/linux/docker_daemon_running\"\n        Cap::Linux::DockerDaemonRunning\n      end\n\n      guest_capability(\"windows\", \"docker_daemon_running\") do\n        require_relative \"cap/windows/docker_daemon_running\"\n        Cap::Windows::DockerDaemonRunning\n      end\n\n      provisioner(:docker) do\n        require_relative \"provisioner\"\n        Provisioner\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/docker/provisioner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../container/provisioner\"\n\nrequire_relative \"client\"\nrequire_relative \"installer\"\n\nmodule VagrantPlugins\n  module DockerProvisioner\n    class DockerError < Vagrant::Errors::VagrantError\n      error_namespace(\"vagrant.provisioners.docker\")\n    end\n\n    class Provisioner < VagrantPlugins::ContainerProvisioner::Provisioner\n      def initialize(machine, config, installer = nil, client = nil)\n        super(machine, config)\n\n        @installer = installer || Installer.new(@machine)\n        @client    = client    || Client.new(@machine)\n        @logger = Log4r::Logger.new(\"vagrant::provisioners::docker\")\n      end\n\n      def provision\n        @logger.info(\"Checking for Docker installation...\")\n        if @installer.ensure_installed\n          if !config.post_install_provisioner.nil?\n            @logger.info(\"Running post setup provision script...\")\n            env = {\n                  callable: method(:run_provisioner),\n                  provisioner: config.post_install_provisioner,\n                  machine: machine}\n            machine.env.hook(:run_provisioner, env)\n          end\n        end\n\n        # Attempt to start service if not running\n        @client.start_service\n        raise DockerError, :not_running if !@client.daemon_running?\n\n        if config.images.any?\n          @machine.ui.info(I18n.t(\"vagrant.docker_pulling_images\"))\n          @client.pull_images(*config.images)\n        end\n\n        if config.build_images.any?\n          @machine.ui.info(I18n.t(\"vagrant.docker_building_images\"))\n          @client.build_images(config.build_images)\n        end\n\n        if config.containers.any?\n          @machine.ui.info(I18n.t(\"vagrant.docker_starting_containers\"))\n          @client.run(config.containers)\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/file/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nmodule VagrantPlugins\n  module FileUpload\n    class Config < Vagrant.plugin(\"2\", :config)\n      attr_accessor :source\n      attr_accessor :destination\n\n      def validate(machine)\n        errors = _detected_errors\n        if !source\n          errors << I18n.t(\"vagrant.provisioners.file.no_source_file\")\n        end\n        if !destination\n          errors << I18n.t(\"vagrant.provisioners.file.no_dest_file\")\n        end\n        if source\n          s = Pathname.new(source).expand_path(machine.env.root_path)\n          if !s.exist?\n            errors << I18n.t(\"vagrant.provisioners.file.path_invalid\",\n                              path: s.to_s)\n          end\n        end\n\n        { \"File provisioner\" => errors }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/file/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module FileUpload\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"file\"\n      description <<-DESC\n      Provides support for provisioning your virtual machines with\n      uploaded files.\n      DESC\n\n      config(:file, :provisioner) do\n        require File.expand_path(\"../config\", __FILE__)\n        Config\n      end\n\n      provisioner(:file) do\n        require File.expand_path(\"../provisioner\", __FILE__)\n        Provisioner\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/file/provisioner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module FileUpload\n    class Provisioner < Vagrant.plugin(\"2\", :provisioner)\n      def provision\n        @machine.communicate.tap do |comm|\n          source = File.expand_path(config.source, @machine.env.cwd)\n          destination = expand_guest_path(config.destination)\n\n          # If the source is a directory determine if any path modifications\n          # need to be applied to the source for upload behavior. If the original\n          # source value ends with a \".\" or if the original source does not end\n          # with a \".\" but the original destination ends with a file separator\n          # then append a \".\" character to the new source. This ensures that\n          # the contents of the directory are uploaded to the destination and\n          # not folder itself.\n          if File.directory?(source)\n            if config.source.end_with?(\".\") ||\n                (!config.destination.end_with?(File::SEPARATOR) &&\n                !config.source.end_with?(\"#{File::SEPARATOR}.\"))\n              source = File.join(source, \".\")\n            end\n          end\n\n          @machine.ui.detail(I18n.t(\"vagrant.actions.vm.provision.file.locations\",\n                                   src: config.source, dst: config.destination))\n          # now upload the file\n          comm.upload(source, destination)\n        end\n      end\n\n      private\n\n      # Expand the guest path if the guest has the capability\n      def expand_guest_path(destination)\n        if machine.guest.capability?(:shell_expand_guest_path)\n          machine.guest.capability(:shell_expand_guest_path, destination)\n        else\n          destination\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/podman/cap/centos/podman_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module PodmanProvisioner\n    module Cap\n      module Centos\n        module PodmanInstall\n          def self.podman_install(machine, kubic)\n            if kubic\n              # Official install instructions for podman\n              # https://podman.io/getting-started/installation.html\n              case machine.guest.capability(\"flavor\")\n              when :centos_7\n                machine.communicate.tap do |comm|\n                  comm.sudo(\"curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/CentOS_7/devel:kubic:libcontainers:stable.repo\")\n                  comm.sudo(\"yum -q -y install podman\")\n                end\n              when :centos_8\n                machine.communicate.tap do |comm|\n                  comm.sudo(\"dnf -y module disable container-tools &> /dev/null || echo 'container-tools module does not exist'\")\n                  comm.sudo(\"dnf -y install 'dnf-command(copr)'\")\n                  comm.sudo(\"dnf -y copr enable rhcontainerbot/container-selinux\")\n                  comm.sudo(\"curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/CentOS_8/devel:kubic:libcontainers:stable.repo\")\n                  comm.sudo(\"dnf -y install podman\")\n                end\n              end\n            else\n              machine.communicate.tap do |comm|\n                comm.sudo(\"yum -q -y install podman\")\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/podman/cap/linux/podman_installed.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module PodmanProvisioner\n    module Cap\n      module Linux\n        module PodmanInstalled\n          def self.podman_installed(machine)\n            machine.communicate.test(\"command -v podman\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/podman/cap/redhat/podman_install.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module PodmanProvisioner\n    module Cap\n      module Redhat\n        module PodmanInstall\n          def self.podman_install(machine, kubic)\n            # Official install instructions for podman\n            # https://podman.io/getting-started/installation.html\n            case machine.guest.capability(\"flavor\")\n            when :rhel_7\n              machine.communicate.tap do |comm|\n                comm.sudo(\"subscription-manager repos --enable=rhel-7-server-extras-rpms\")\n                comm.sudo(\"yum -q -y install podman\")\n              end\n            when :rhel_8\n              machine.communicate.tap do |comm|\n                comm.sudo(\"yum module enable -y container-tools\")\n                comm.sudo(\"yum module install -y container-tools\")\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/podman/client.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../container/client\"\n\nmodule VagrantPlugins\n  module PodmanProvisioner\n    class Client < VagrantPlugins::ContainerProvisioner::Client\n      def initialize(machine)\n        super(machine, \"podman\")\n        @container_command = \"podman\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/podman/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../container/config\"\n\nmodule VagrantPlugins\n  module PodmanProvisioner\n    class Config < VagrantPlugins::ContainerProvisioner::Config\n      attr_accessor :kubic\n\n      def initialize\n        super()\n        @kubic = UNSET_VALUE\n      end\n\n      def finalize!\n        super()\n        @kubic = false if @kubic == UNSET_VALUE\n      end\n\n      def post_install_provision(name, **options, &block)\n        # Abort\n        raise PodmanError, :wrong_provisioner if options[:type] == \"podman\"\n\n        proxy = VagrantPlugins::Kernel_V2::VMConfig.new\n        proxy.provision(name, **options, &block)\n        @post_install_provisioner = proxy.provisioners.first\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/podman/installer.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../container/installer\"\n\nmodule VagrantPlugins\n  module PodmanProvisioner\n    class Installer < VagrantPlugins::ContainerProvisioner::Installer\n      # This handles verifying the Podman installation, installing it if it was\n      # requested, and so on. This method will raise exceptions if things are\n      # wrong.\n      # @params [Boolean] - if true install should use kubic project (this will)\n      #                     add a yum repo.\n      #                     if false install comes from default yum\n      # @return [Boolean] - false if podman cannot be detected on machine, else\n      #                     true if podman installs correctly or is installed\n      def ensure_installed(kubic)\n        if !@machine.guest.capability?(:podman_installed)\n          @machine.ui.warn(\"Podman can not be installed\")\n          return false\n        end\n\n        if !@machine.guest.capability(:podman_installed)\n          @machine.ui.detail(\"Podman installing\")\n          @machine.guest.capability(:podman_install, kubic)\n        end\n\n        if !@machine.guest.capability(:podman_installed)\n          raise PodmanError, :install_failed\n        end\n\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/podman/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module PodmanProvisioner\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"podman\"\n      description <<-DESC\n      Provides support for provisioning your virtual machines with\n      OCI images and containers using Podman.\n      DESC\n\n      config(:podman, :provisioner) do\n        require_relative \"config\"\n        Config\n      end\n\n      guest_capability(\"redhat\", \"podman_install\") do\n        require_relative \"cap/redhat/podman_install\"\n        Cap::Redhat::PodmanInstall\n      end\n\n      guest_capability(\"centos\", \"podman_install\") do\n        require_relative \"cap/centos/podman_install\"\n        Cap::Centos::PodmanInstall\n      end\n\n      guest_capability(\"linux\", \"podman_installed\") do\n        require_relative \"cap/linux/podman_installed\"\n        Cap::Linux::PodmanInstalled\n      end\n\n      provisioner(:podman) do\n        require_relative \"provisioner\"\n        Provisioner\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/podman/provisioner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../container/provisioner\"\n\nrequire_relative \"installer\"\nrequire_relative \"client\"\n\nmodule VagrantPlugins\n  module PodmanProvisioner\n    class PodmanError < Vagrant::Errors::VagrantError\n      error_namespace(\"vagrant.provisioners.podman\")\n    end\n\n    class Provisioner < VagrantPlugins::ContainerProvisioner::Provisioner\n      def initialize(machine, config, installer = nil, client = nil)\n        super(machine, config, installer, client)\n\n        @installer = installer || Installer.new(@machine)\n        @client    = client    || Client.new(@machine)\n        @logger = Log4r::Logger.new(\"vagrant::provisioners::podman\")\n      end\n\n      def provision\n        @logger.info(\"Checking for Podman installation...\")\n\n        if @installer.ensure_installed(config.kubic)\n          if !config.post_install_provisioner.nil?\n            @logger.info(\"Running post setup provision script...\")\n            env = {\n                  callable: method(:run_provisioner),\n                  provisioner: config.post_install_provisioner,\n                  machine: machine}\n            machine.env.hook(:run_provisioner, env)\n          end\n        end\n\n        if config.images.any?\n          @machine.ui.info(I18n.t(\"vagrant.docker_pulling_images\"))\n          @client.pull_images(*config.images)\n        end\n\n        if config.build_images.any?\n          @machine.ui.info(I18n.t(\"vagrant.docker_building_images\"))\n          @client.build_images(config.build_images)\n        end\n\n        if config.containers.any?\n          @machine.ui.info(I18n.t(\"vagrant.docker_starting_containers\"))\n          @client.run(config.containers)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/puppet/config/puppet.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module Puppet\n    module Config\n      class Puppet < Vagrant.plugin(\"2\", :config)\n\n        # The path to Puppet's bin/ directory.\n        # @return [String]\n        attr_accessor :binary_path\n\n        attr_accessor :facter\n        attr_accessor :structured_facts\n        attr_accessor :hiera_config_path\n        attr_accessor :manifest_file\n        attr_accessor :manifests_path\n        attr_accessor :environment\n        attr_accessor :environment_path\n        attr_accessor :environment_variables\n        attr_accessor :module_path\n        attr_accessor :options\n        attr_accessor :synced_folder_type\n        attr_accessor :synced_folder_args\n        attr_accessor :temp_dir\n        attr_accessor :working_directory\n\n        def initialize\n          super\n\n          @binary_path           = UNSET_VALUE\n          @hiera_config_path     = UNSET_VALUE\n          @manifest_file         = UNSET_VALUE\n          @manifests_path        = UNSET_VALUE\n          @environment           = UNSET_VALUE\n          @environment_path      = UNSET_VALUE\n          @environment_variables = UNSET_VALUE\n          @module_path           = UNSET_VALUE\n          @options               = []\n          @facter                = {}\n          @synced_folder_type    = UNSET_VALUE\n          @temp_dir              = UNSET_VALUE\n          @working_directory     = UNSET_VALUE\n          @structured_facts   = UNSET_VALUE\n        end\n\n        def nfs=(value)\n          puts \"DEPRECATION: The 'nfs' setting for the Puppet provisioner is\"\n          puts \"deprecated. Please use the 'synced_folder_type' setting instead.\"\n          puts \"The 'nfs' setting will be removed in the next version of Vagrant.\"\n\n          if value\n            @synced_folder_type = \"nfs\"\n          else\n            @synced_folder_type = nil\n          end\n        end\n\n        def merge(other)\n          super.tap do |result|\n            result.facter  = @facter.merge(other.facter)\n          end\n        end\n\n        def finalize!\n          super\n\n          if @environment_path == UNSET_VALUE && @manifests_path == UNSET_VALUE\n            #If both are unset, assume 'manifests' mode for now. TBD: Switch to environments by default?\n            @manifests_path = [:host, \"manifests\"]\n          end\n\n          # If the paths are just strings, assume they are 'host' paths (rather than guest)\n          if @environment_path != UNSET_VALUE && !@environment_path.is_a?(Array)\n            @environment_path = [:host, @environment_path]\n          end\n          if @manifests_path != UNSET_VALUE && !@manifests_path.is_a?(Array)\n            @manifests_path = [:host, @manifests_path]\n          end\n          @hiera_config_path = nil if @hiera_config_path == UNSET_VALUE\n\n          if @environment_path == UNSET_VALUE\n            @manifests_path[0] = @manifests_path[0].to_sym\n            @environment_path = nil\n            @manifest_file  = \"default.pp\" if @manifest_file == UNSET_VALUE\n            @environment  = \"\" if @environment == UNSET_VALUE\n          else\n            @environment_path[0] = @environment_path[0].to_sym\n            @environment  = \"production\" if @environment == UNSET_VALUE\n            if @manifests_path == UNSET_VALUE\n              @manifests_path = nil\n            end\n            if @manifest_file == UNSET_VALUE\n              @manifest_file = nil\n            end\n          end\n\n          if @environment_variables == UNSET_VALUE\n            @environment_variables = {}\n          end\n\n          @binary_path        = nil     if @binary_path == UNSET_VALUE\n          @module_path        = nil     if @module_path == UNSET_VALUE\n          @synced_folder_type = nil     if @synced_folder_type == UNSET_VALUE\n          @synced_folder_args = nil if @synced_folder_args == UNSET_VALUE\n          @temp_dir           = \"/tmp/vagrant-puppet\" if @temp_dir == UNSET_VALUE\n          @working_directory  = nil     if @working_directory == UNSET_VALUE\n          @structured_facts   = nil     if @structured_facts == UNSET_VALUE\n        end\n\n        # Returns the module paths as an array of paths expanded relative to the\n        # root path.\n        def expanded_module_paths(root_path)\n          return [] if !module_path\n\n          # Get all the paths and expand them relative to the root path, returning\n          # the array of expanded paths\n          paths = module_path\n          paths = [paths] if !paths.is_a?(Array)\n          paths.map do |path|\n            Pathname.new(path).expand_path(root_path)\n          end\n        end\n\n        def validate(machine)\n          errors = _detected_errors\n\n          # Calculate the manifests and module paths based on env\n          this_expanded_module_paths = expanded_module_paths(machine.env.root_path)\n\n          # Manifests path/file validation\n          if manifests_path != nil && manifests_path[0].to_sym == :host\n            expanded_path = Pathname.new(manifests_path[1]).\n              expand_path(machine.env.root_path)\n            if !expanded_path.directory?\n              errors << I18n.t(\"vagrant.provisioners.puppet.manifests_path_missing\",\n                               path: expanded_path.to_s)\n            else\n              expanded_manifest_file = expanded_path.join(manifest_file)\n              if !expanded_manifest_file.file? && !expanded_manifest_file.directory?\n                errors << I18n.t(\"vagrant.provisioners.puppet.manifest_missing\",\n                                 manifest: expanded_manifest_file.to_s)\n              end\n            end\n          elsif environment_path != nil && environment_path[0].to_sym == :host\n            # Environments path/file validation\n            expanded_path = Pathname.new(environment_path[1]).\n              expand_path(machine.env.root_path)\n            if !expanded_path.directory?\n              errors << I18n.t(\"vagrant.provisioners.puppet.environment_path_missing\",\n                               path: expanded_path.to_s)\n            else\n              expanded_environment_file = expanded_path.join(environment)\n              if !expanded_environment_file.file? && !expanded_environment_file.directory?\n                errors << I18n.t(\"vagrant.provisioners.puppet.environment_missing\",\n                                 environment: environment.to_s,\n                                 environmentpath: expanded_path.to_s)\n              end\n            end\n          end\n\n          if environment_path == nil && manifests_path == nil\n              errors << \"Please specify either a Puppet environment_path + environment (preferred) or manifests_path (deprecated).\"\n          end\n\n          # Module paths validation\n          this_expanded_module_paths.each do |path|\n            if !path.directory?\n              errors << I18n.t(\"vagrant.provisioners.puppet.module_path_missing\",\n                               path: path)\n            end\n          end\n          { \"puppet provisioner\" => errors }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/puppet/config/puppet_server.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module Puppet\n    module Config\n      class PuppetServer < Vagrant.plugin(\"2\", :config)\n        # The path to Puppet's bin/ directory.\n        # @return [String]\n        attr_accessor :binary_path\n\n        attr_accessor :client_cert_path\n        attr_accessor :client_private_key_path\n        attr_accessor :facter\n        attr_accessor :options\n        attr_accessor :puppet_server\n        attr_accessor :puppet_node\n\n        def initialize\n          super\n\n          @binary_path             = UNSET_VALUE\n          @client_cert_path        = UNSET_VALUE\n          @client_private_key_path = UNSET_VALUE\n          @facter                  = {}\n          @options                 = []\n          @puppet_node             = UNSET_VALUE\n          @puppet_server           = UNSET_VALUE\n        end\n\n        def merge(other)\n          super.tap do |result|\n            result.facter  = @facter.merge(other.facter)\n          end\n        end\n\n        def finalize!\n          super\n\n          @binary_path      = nil if @binary_path == UNSET_VALUE\n          @client_cert_path = nil if @client_cert_path == UNSET_VALUE\n          @client_private_key_path = nil if @client_private_key_path == UNSET_VALUE\n          @puppet_node   = nil if @puppet_node == UNSET_VALUE\n          @puppet_server = \"puppet\" if @puppet_server == UNSET_VALUE\n        end\n\n        def validate(machine)\n          errors = _detected_errors\n\n          if (client_cert_path && !client_private_key_path) ||\n            (client_private_key_path && !client_cert_path)\n            errors << I18n.t(\n              \"vagrant.provisioners.puppet_server.client_cert_and_private_key\")\n          end\n\n          if client_cert_path\n            path = Pathname.new(client_cert_path).\n              expand_path(machine.env.root_path)\n            if !path.file?\n              errors << I18n.t(\n                \"vagrant.provisioners.puppet_server.client_cert_not_found\")\n            end\n          end\n\n          if client_private_key_path\n            path = Pathname.new(client_private_key_path).\n              expand_path(machine.env.root_path)\n            if !path.file?\n              errors << I18n.t(\n                \"vagrant.provisioners.puppet_server.client_private_key_not_found\")\n            end\n          end\n\n          if !puppet_node && (client_cert_path || client_private_key_path)\n            errors << I18n.t(\n              \"vagrant.provisioners.puppet_server.cert_requires_node\")\n          end\n\n          { \"puppet server provisioner\" => errors }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/puppet/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Puppet\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"puppet\"\n      description <<-DESC\n      Provides support for provisioning your virtual machines with\n      Puppet either using `puppet apply` or a Puppet server.\n      DESC\n\n      config(:puppet, :provisioner) do\n        require_relative \"config/puppet\"\n        Config::Puppet\n      end\n\n      config(:puppet_server, :provisioner) do\n        require_relative \"config/puppet_server\"\n        Config::PuppetServer\n      end\n\n      provisioner(:puppet) do\n        require_relative \"provisioner/puppet\"\n        Provisioner::Puppet\n      end\n\n      provisioner(:puppet_server) do\n        require_relative \"provisioner/puppet_server\"\n        Provisioner::PuppetServer\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/puppet/provisioner/puppet.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"digest/md5\"\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module Puppet\n    module Provisioner\n      class PuppetError < Vagrant::Errors::VagrantError\n        error_namespace(\"vagrant.provisioners.puppet\")\n      end\n\n      class Puppet < Vagrant.plugin(\"2\", :provisioner)\n        def initialize(machine, config)\n          super\n\n          @logger = Log4r::Logger.new(\"vagrant::provisioners::puppet\")\n        end\n\n        def configure(root_config)\n          # Calculate the paths we're going to use based on the environment\n          root_path = @machine.env.root_path\n          @expanded_module_paths   = @config.expanded_module_paths(root_path)\n\n          # Setup the module paths\n          @module_paths = []\n          @expanded_module_paths.each_with_index do |path, _|\n            key = Digest::MD5.hexdigest(path.to_s)\n            @module_paths << [path, File.join(config.temp_dir, \"modules-#{key}\")]\n          end\n\n          folder_opts = {}\n          folder_opts[:type] = @config.synced_folder_type if @config.synced_folder_type\n          folder_opts[:owner] = \"root\" if !@config.synced_folder_type\n          folder_opts[:args] = @config.synced_folder_args if @config.synced_folder_args\n          folder_opts[:nfs__quiet] = true\n\n          if @config.environment_path.is_a?(Array)\n            # Share the environments directory with the guest\n            if @config.environment_path[0].to_sym == :host\n              root_config.vm.synced_folder(\n                File.expand_path(@config.environment_path[1], root_path),\n                environments_guest_path, folder_opts)\n            end\n          end\n          if @config.manifest_file\n            @manifest_file  = File.join(manifests_guest_path, @config.manifest_file)\n            # Share the manifests directory with the guest\n            if @config.manifests_path[0].to_sym == :host\n              root_config.vm.synced_folder(\n                File.expand_path(@config.manifests_path[1], root_path),\n                manifests_guest_path, folder_opts)\n            end\n          end\n\n          # Share the module paths\n          @module_paths.each do |from, to|\n            root_config.vm.synced_folder(from, to, folder_opts)\n          end\n        end\n\n        def parse_environment_metadata\n          # Parse out the environment manifest path since puppet apply doesn't do that for us.\n          environment_conf = File.join(environments_guest_path, @config.environment, \"environment.conf\")\n          if @machine.communicate.test(\"test -e #{environment_conf}\", sudo: true)\n            @machine.communicate.sudo(\"cat #{environment_conf}\") do | type, data|\n              if type == :stdout\n                data.each_line do |line|\n                  if line =~ /^\\s*manifest\\s+=\\s+([^\\s]+)/\n                    @manifest_file = $1\n                    @manifest_file.gsub! \"$codedir\", File.dirname(environments_guest_path)\n                    @manifest_file.gsub! \"$environment\", @config.environment\n                    if !@manifest_file.start_with? \"/\"\n                      @manifest_file = File.join(environments_guest_path, @config.environment, @manifest_file)\n                    end\n                    @logger.debug(\"Using manifest from environment.conf: #{@manifest_file}\")\n                  end\n                end\n              end\n            end\n          end\n        end\n\n        def provision\n          # If the machine has a wait for reboot functionality, then\n          # do that (primarily Windows)\n          if @machine.guest.capability?(:wait_for_reboot)\n            @machine.guest.capability(:wait_for_reboot)\n          end\n\n          # In environment mode we still need to specify a manifest file, if its not, use the one from env config if specified.\n          if !@manifest_file\n            @manifest_file = \"#{environments_guest_path}/#{@config.environment}/manifests\"\n            parse_environment_metadata\n          end\n          # Check that the shared folders are properly shared\n          check = []\n          if @config.manifests_path.is_a?(Array) && @config.manifests_path[0] == :host\n            check << manifests_guest_path\n          end\n          if @config.environment_path.is_a?(Array) && @config.environment_path[0] == :host\n            check << environments_guest_path\n          end\n          @module_paths.each do |host_path, guest_path|\n            check << guest_path\n          end\n\n          # Make sure the temporary directory is properly set up\n          if windows?\n            tmp_command = \"mkdir -p #{config.temp_dir}\"\n            comm_opts = { shell: :powershell}\n          else\n            tmp_command = \"mkdir -p #{config.temp_dir}; chmod 0777 #{config.temp_dir}\"\n            comm_opts = {}\n          end\n\n          @machine.communicate.tap do |comm|\n            comm.sudo(tmp_command, comm_opts)\n          end\n\n          verify_shared_folders(check)\n\n          # Verify Puppet is installed and run it\n          puppet_bin = \"puppet\"\n          verify_binary(puppet_bin)\n\n          # Upload Hiera configuration if we have it\n          @hiera_config_path = nil\n          if config.hiera_config_path\n            local_hiera_path   = File.expand_path(config.hiera_config_path,\n              @machine.env.root_path)\n            @hiera_config_path = File.join(config.temp_dir, \"hiera.yaml\")\n            @machine.communicate.upload(local_hiera_path, @hiera_config_path)\n          end\n\n          # Build up the structured custom facts if we have any\n          # With structured facts on, we assume the config.facter is yaml.\n          if config.structured_facts && !config.facter.empty?\n            @facter_config_path = \"/etc/puppetlabs/facter/facts.d/vagrant_facts.yaml\"\n            if windows?\n              @facter_config_path = \"/ProgramData/PuppetLabs/facter/facts.d/vagrant_facts.yaml\"\n            end\n            t = Tempfile.new(\"vagrant_facts.yaml\")\n            t.write(config.facter.to_yaml)\n            t.close()\n            @machine.communicate.tap do |comm|\n              comm.upload(t.path, File.join(@config.temp_dir, \"vagrant_facts.yaml\"))\n              comm.sudo(\"cp #{config.temp_dir}/vagrant_facts.yaml #{@facter_config_path}\")\n            end\n           end\n\n          run_puppet_apply\n        end\n\n        def manifests_guest_path\n          if config.manifests_path[0] == :host\n            # The path is on the host, so point to where it is shared\n            key = Digest::MD5.hexdigest(config.manifests_path[1])\n            File.join(config.temp_dir, \"manifests-#{key}\")\n          else\n            # The path is on the VM, so just point directly to it\n            config.manifests_path[1]\n          end\n        end\n\n        def environments_guest_path\n          if config.environment_path[0] == :host\n            # The path is on the host, so point to where it is shared\n            File.join(config.temp_dir, \"environments\")\n          else\n            # The path is on the VM, so just point directly to it\n            config.environment_path[1]\n          end\n        end\n\n        def verify_binary(binary)\n          # Determine the command to use to test whether Puppet is available.\n          # This is very platform dependent.\n          test_cmd = \"sh -c 'command -v #{binary}'\"\n          if windows?\n            test_cmd = \"where.exe #{binary}\"\n            if @config.binary_path\n              test_cmd = \"where.exe \\\"#{@config.binary_path}:#{binary}\\\"\"\n            end\n          end\n\n          if !machine.communicate.test(test_cmd)\n            @config.binary_path = \"/opt/puppetlabs/bin/\"\n            @machine.communicate.sudo(\n              \"test -x /opt/puppetlabs/bin/#{binary}\",\n              error_class: PuppetError,\n              error_key: :not_detected,\n              binary: binary)\n          end\n        end\n\n        def run_puppet_apply\n          default_module_path = \"/etc/puppet/modules\"\n          if windows?\n            default_module_path = \"/ProgramData/PuppetLabs/puppet/etc/modules\"\n          end\n\n          options = [config.options].flatten\n          module_paths = @module_paths.map { |_, to| to }\n          if !@module_paths.empty?\n            # Append the default module path\n            module_paths << default_module_path\n\n            # Add the command line switch to add the module path\n            module_path_sep = windows? ? \";\" : \":\"\n            options << \"--modulepath '#{module_paths.join(module_path_sep)}'\"\n          end\n\n          if @hiera_config_path\n            options << \"--hiera_config=#{@hiera_config_path}\"\n          end\n\n          if !@machine.env.ui.color?\n            options << \"--color=false\"\n          end\n\n          options << \"--detailed-exitcodes\"\n          if config.environment_path\n            options << \"--environmentpath #{environments_guest_path}/\"\n            options << \"--environment #{@config.environment}\"\n          end\n\n          options << @manifest_file\n          options = options.join(\" \")\n\n          # Build up the custom facts if we have any\n          facter = nil\n          # Build up the (non-structured) custom facts if we have any\n          if !config.structured_facts && !config.facter.empty?\n            facts = []\n            config.facter.each do |key, value|\n              facts << \"FACTER_#{key}='#{value}'\"\n            end\n\n            # If we're on Windows, we need to use the PowerShell style\n            if windows?\n              facts.map! { |v| \"$env:#{v};\" }\n            end\n\n            facter = facts.join(\" \")\n          end\n\n          puppet_bin = \"puppet\"\n          if @config.binary_path\n            puppet_bin = File.join(@config.binary_path, puppet_bin)\n          end\n\n          env_vars = nil\n          if !config.environment_variables.nil? && !config.environment_variables.empty?\n            env_vars = config.environment_variables.map do |env_key, env_value|\n              \"#{env_key}=\\\"#{env_value}\\\"\"\n            end\n\n            if windows?\n              env_vars.map! do |env_var_string|\n                \"$env:#{env_var_string}\"\n              end\n              env_vars = env_vars.join(\"; \")\n              env_vars << \";\"\n            else\n              env_vars = env_vars.join(\" \")\n            end\n          end\n\n          command = [\n            env_vars,\n            facter,\n            puppet_bin,\n            \"apply\",\n            options\n          ].compact.map(&:to_s).join(\" \")\n          if config.working_directory\n            if windows?\n              command = \"cd #{config.working_directory}; if ($?) \\{ #{command} \\}\"\n            else\n              command = \"cd #{config.working_directory} && #{command}\"\n            end\n          end\n\n          if config.environment_path\n            @machine.ui.info(I18n.t(\n              \"vagrant.provisioners.puppet.running_puppet_env\",\n              environment: config.environment))\n          else\n            @machine.ui.info(I18n.t(\n              \"vagrant.provisioners.puppet.running_puppet\",\n              manifest: config.manifest_file))\n          end\n\n          opts = {\n            elevated: true,\n            error_class: Vagrant::Errors::VagrantError,\n            error_key: :ssh_bad_exit_status_muted,\n            good_exit: [0,2],\n          }\n\n          if windows?\n            opts[:shell] = :powershell\n          end\n          @machine.communicate.sudo(command, opts) do |type, data|\n            if !data.chomp.empty?\n              @machine.ui.info(data.chomp)\n            end\n          end\n        end\n\n        def verify_shared_folders(folders)\n          folders.each do |folder|\n            @logger.debug(\"Checking for shared folder: #{folder}\")\n            if windows?\n              testcommand = \"Test-Path #{folder}\"\n              comm_opts = { shell: :powershell}\n            else\n              testcommand = \"test -d #{folder}\"\n              comm_opts = { sudo: true}\n            end\n\n            if !@machine.communicate.test(testcommand, comm_opts)\n              raise PuppetError, :missing_shared_folders\n            end\n          end\n        end\n\n        def windows?\n          @machine.config.vm.communicator == :winrm || @machine.config.vm.communicator == :winssh\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/puppet/provisioner/puppet_server.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module Puppet\n    module Provisioner\n      class PuppetServerError < Vagrant::Errors::VagrantError\n        error_namespace(\"vagrant.provisioners.puppet_server\")\n      end\n\n      class PuppetServer < Vagrant.plugin(\"2\", :provisioner)\n        def provision\n          if @machine.config.vm.communicator == :winrm\n            raise Vagrant::Errors::ProvisionerWinRMUnsupported,\n              name: \"puppet_server\"\n          end\n\n          verify_binary(\"puppet\")\n          run_puppet_agent\n        end\n\n        def verify_binary(binary)\n          if @config.binary_path\n            test_cmd = \"test -x #{@config.binary_path}/#{binary}\"\n          else\n            test_cmd = \"which #{binary}\"\n          end\n\n          @machine.communicate.sudo(\n            test_cmd,\n            error_class: PuppetServerError,\n            error_key: :not_detected,\n            binary: binary)\n        end\n\n        def run_puppet_agent\n          options = config.options\n          options = [options] if !options.is_a?(Array)\n\n          # Intelligently set the puppet node cert name based on certain\n          # external parameters.\n          cn = nil\n          if config.puppet_node\n            # If a node name is given, we use that directly for the certname\n            cn = config.puppet_node\n          elsif @machine.config.vm.hostname\n            # If a host name is given, we explicitly set the certname to\n            # nil so that the hostname becomes the cert name.\n            cn = nil\n          else\n            # Otherwise, we default to the name of the box.\n            cn = @machine.config.vm.box\n          end\n\n          # Add the certname option if there is one\n          options += [\"--certname\", cn] if cn\n\n          # A shortcut to make things easier\n          comm = @machine.communicate\n\n          # If we have client certs specified, then upload them\n          if config.client_cert_path && config.client_private_key_path\n            @machine.ui.info(\n              I18n.t(\"vagrant.provisioners.puppet_server.uploading_client_cert\"))\n            dirname = \"/tmp/puppet-#{Time.now.to_i}-#{rand(1000)}\"\n            comm.sudo(\"mkdir -p #{dirname}\")\n            comm.sudo(\"mkdir -p #{dirname}/certs\")\n            comm.sudo(\"mkdir -p #{dirname}/private_keys\")\n            comm.sudo(\"chmod -R 0777 #{dirname}\")\n            comm.upload(config.client_cert_path, \"#{dirname}/certs/#{cn}.pem\")\n            comm.upload(config.client_private_key_path,\n              \"#{dirname}/private_keys/#{cn}.pem\")\n\n            # Setup the options so that they point to our directories\n            options << \"--certdir=#{dirname}/certs\"\n            options << \"--privatekeydir=#{dirname}/private_keys\"\n          end\n\n          # Disable colors if we must\n          if !@machine.env.ui.color?\n            options << \"--color=false\"\n          end\n\n          # Build up the custom facts if we have any\n          facter = \"\"\n          if !config.facter.empty?\n            facts = []\n            config.facter.each do |key, value|\n              facts << \"FACTER_#{key}='#{value}'\"\n            end\n\n            facter = \"#{facts.join(\" \")} \"\n          end\n\n\n          puppet_bin = \"puppet\"\n          if @config.binary_path\n            puppet_bin = File.join(@config.binary_path, puppet_bin)\n          end\n          options = options.join(\" \")\n          command = \"#{facter} #{puppet_bin} agent --onetime --no-daemonize #{options} \" +\n            \"--server #{config.puppet_server} --detailed-exitcodes || [ $? -eq 2 ]\"\n\n          @machine.ui.info I18n.t(\"vagrant.provisioners.puppet_server.running_puppetd\")\n          @machine.communicate.sudo(command) do |type, data|\n            if !data.chomp.empty?\n              @machine.ui.info(data.chomp)\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/salt/bootstrap_downloader.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'open-uri'\nrequire 'digest'\n\nrequire_relative \"./errors\"\n\nmodule VagrantPlugins\n  module Salt\n    class BootstrapDownloader\n      WINDOWS_URL = \"﻿https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.ps1\"\n      URL = \"https://github.com/saltstack/salt-bootstrap/releases/latest/download/bootstrap-salt.sh\"\n      SHA256_SUFFIX = \".sha256\"\n\n      def initialize(guest)\n        @guest = guest\n        @logger  = Log4r::Logger.new(\"vagrant::salt::bootstrap_downloader\")\n      end\n\n      def source_url \n        @guest == :windows ? WINDOWS_URL : URL\n      end\n\n      def get_bootstrap_script\n        @logger.debug \"Downloading bootstrap script from #{source_url}\"\n        script_file = download(source_url)\n\n        verify_sha256(script_file)\n\n        @logger.info \"Downloaded and verified salt-bootstrap script\"\n        script_file\n      end\n\n      def verify_sha256(script)\n        @logger.debug \"Downloading sha256 file from #{source_url}#{SHA256_SUFFIX}\"\n        sha256_file = download(\"#{source_url}#{SHA256_SUFFIX}\")\n        sha256 = extract_sha256(sha256_file.read)\n        sha256_file.close\n\n        @logger.debug \"Computing sha256 value from script file\"\n        computed_sha256 = Digest::SHA256.hexdigest(script.read)\n        script.rewind\n\n        @logger.debug \"Comparing sha256 values\"\n        if computed_sha256 != sha256\n          @logger.debug \"Mismatched sha256, expected #{sha256} but computed #{computed_sha256}\"\n          raise Salt::Errors::InvalidShasumError, source: source_url, expected_sha: sha256, computed_sha: computed_sha256\n        end\n        @logger.debug \"Sha256 values match\"\n      end\n\n      def extract_sha256(text)\n        text.scan(/\\b([a-f0-9]{64})\\b/).last.first\n      end\n\n      def download(url)\n        URI(url).open\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/salt/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\nrequire \"vagrant/util/deep_merge\"\n\nmodule VagrantPlugins\n  module Salt\n    class Config < Vagrant.plugin(\"2\", :config)\n      ## salty-vagrant options\n      attr_accessor :minion_config\n      attr_accessor :minion_json_config\n      attr_accessor :minion_key\n      attr_accessor :minion_pub\n      attr_accessor :master_config\n      attr_accessor :master_json_config\n      attr_accessor :master_key\n      attr_accessor :master_pub\n      attr_accessor :grains_config\n      attr_accessor :run_highstate\n      attr_accessor :run_overstate\n      attr_accessor :orchestrations\n      attr_accessor :always_install\n      attr_accessor :bootstrap_script\n      attr_accessor :verbose\n      attr_accessor :seed_master\n      attr_reader   :pillar_data\n      attr_accessor :colorize\n      attr_accessor :log_level\n      attr_accessor :masterless\n      attr_accessor :minion_id\n      attr_accessor :salt_call_args\n      attr_accessor :salt_args\n\n      ## bootstrap options\n      attr_accessor :temp_config_dir\n      attr_accessor :install_type\n      attr_accessor :install_args\n      attr_accessor :install_master\n      attr_accessor :install_syndic\n      attr_accessor :no_minion\n      attr_accessor :bootstrap_options\n      attr_accessor :version\n      attr_accessor :python_version\n      attr_accessor :run_service\n      attr_accessor :master_id\n\n      def initialize\n        @minion_config = UNSET_VALUE\n        @minion_json_config = UNSET_VALUE\n        @minion_key = UNSET_VALUE\n        @minion_pub = UNSET_VALUE\n        @master_config = UNSET_VALUE\n        @master_json_config = UNSET_VALUE\n        @master_key = UNSET_VALUE\n        @master_pub = UNSET_VALUE\n        @grains_config = UNSET_VALUE\n        @run_highstate = UNSET_VALUE\n        @run_overstate = UNSET_VALUE\n        @always_install = UNSET_VALUE\n        @bootstrap_script = UNSET_VALUE\n        @verbose = UNSET_VALUE\n        @seed_master = UNSET_VALUE\n        @pillar_data = UNSET_VALUE\n        @colorize = UNSET_VALUE\n        @log_level = UNSET_VALUE\n        @temp_config_dir = UNSET_VALUE\n        @install_type = UNSET_VALUE\n        @install_args = UNSET_VALUE\n        @install_master = UNSET_VALUE\n        @install_syndic = UNSET_VALUE\n        @no_minion = UNSET_VALUE\n        @bootstrap_options = UNSET_VALUE\n        @masterless = UNSET_VALUE\n        @minion_id = UNSET_VALUE\n        @version = UNSET_VALUE\n        @python_version = UNSET_VALUE\n        @run_service = UNSET_VALUE\n        @master_id = UNSET_VALUE\n        @salt_call_args = UNSET_VALUE\n        @salt_args = UNSET_VALUE\n      end\n\n      def finalize!\n        @grains_config      = nil if @grains_config == UNSET_VALUE\n        @run_highstate      = nil if @run_highstate == UNSET_VALUE\n        @run_overstate      = nil if @run_overstate == UNSET_VALUE\n        @always_install     = nil if @always_install == UNSET_VALUE\n        @bootstrap_script   = nil if @bootstrap_script == UNSET_VALUE\n        @verbose            = nil if @verbose == UNSET_VALUE\n        @seed_master        = nil if @seed_master == UNSET_VALUE\n        @pillar_data        = {}  if @pillar_data == UNSET_VALUE\n        @colorize           = nil if @colorize == UNSET_VALUE\n        @log_level          = nil if @log_level == UNSET_VALUE\n        @temp_config_dir    = nil if @temp_config_dir == UNSET_VALUE\n        @install_type       = nil if @install_type == UNSET_VALUE\n        @install_args       = nil if @install_args == UNSET_VALUE\n        @install_master     = nil if @install_master == UNSET_VALUE\n        @install_syndic     = nil if @install_syndic == UNSET_VALUE\n        @no_minion          = nil if @no_minion == UNSET_VALUE\n        @bootstrap_options  = nil if @bootstrap_options == UNSET_VALUE\n        @masterless         = false if @masterless == UNSET_VALUE\n        @minion_id          = nil if @minion_id == UNSET_VALUE\n        @version            = nil if @version == UNSET_VALUE\n        @python_version     = nil if @python_version == UNSET_VALUE\n        @run_service        = nil if @run_service == UNSET_VALUE\n        @master_id          = nil if @master_id == UNSET_VALUE\n        @salt_call_args     = nil if @salt_call_args == UNSET_VALUE\n        @salt_args          = nil if @salt_args == UNSET_VALUE\n        @minion_json_config = nil if @minion_json_config == UNSET_VALUE\n        @master_json_config = nil if @master_json_config == UNSET_VALUE\n\n        # NOTE: Optimistic defaults are set in the provisioner. UNSET_VALUEs\n        # are converted there to allow proper detection of unset values.\n        # @minion_config      = nil if @minion_config == UNSET_VALUE\n        # @minion_key         = nil if @minion_key == UNSET_VALUE\n        # @minion_pub         = nil if @minion_pub == UNSET_VALUE\n        # @master_config      = nil if @master_config == UNSET_VALUE\n        # @master_key         = nil if @master_key == UNSET_VALUE\n        # @master_pub         = nil if @master_pub == UNSET_VALUE\n      end\n\n      def pillar(data)\n        @pillar_data = {} if @pillar_data == UNSET_VALUE\n        @pillar_data = Vagrant::Util::DeepMerge.deep_merge(@pillar_data, data)\n      end\n\n      def validate(machine)\n        errors = _detected_errors\n        if @minion_config && @minion_config != UNSET_VALUE\n          expanded = Pathname.new(@minion_config).expand_path(machine.env.root_path)\n          if !expanded.file?\n            errors << I18n.t(\"vagrant.provisioners.salt.minion_config_nonexist\", missing_config_file: expanded)\n          end\n        end\n\n        if @master_config && @master_config != UNSET_VALUE\n          expanded = Pathname.new(@master_config).expand_path(machine.env.root_path)\n          if !expanded.file?\n            errors << I18n.t(\"vagrant.provisioners.salt.master_config_nonexist\",  missing_config_file: expanded)\n          end\n        end\n\n        if @minion_key || @minion_pub\n          if !@minion_key || !@minion_pub\n            errors << I18n.t(\"vagrant.provisioners.salt.missing_key\")\n          end\n        end\n\n        if @master_key || @master_pub\n          if !@master_key || !@master_pub\n            errors << I18n.t(\"vagrant.provisioners.salt.missing_key\")\n          end\n        end\n\n        if @grains_config\n          expanded = Pathname.new(@grains_config).expand_path(machine.env.root_path)\n          if !expanded.file?\n            errors << I18n.t(\"vagrant.provisioners.salt.grains_config_nonexist\")\n          end\n        end\n\n        if @install_master && !@no_minion && !@seed_master && @run_highstate\n          errors << I18n.t(\"vagrant.provisioners.salt.must_accept_keys\")\n        end\n\n        if @salt_call_args && !@salt_call_args.is_a?(Array)\n          errors << I18n.t(\"vagrant.provisioners.salt.args_array\")\n        end\n\n        if @salt_args && !@salt_args.is_a?(Array)\n          errors << I18n.t(\"vagrant.provisioners.salt.args_array\")\n        end\n\n        if @python_version && @python_version.is_a?(String) && !@python_version.scan(/\\D/).empty?\n          errors << I18n.t(\"vagrant.provisioners.salt.python_version\")\n        end\n\n        if @python_version && !(@python_version.is_a?(Integer) || @python_version.is_a?(String))\n          errors << I18n.t(\"vagrant.provisioners.salt.python_version\")\n        end\n\n        # install_type is not supported in a Windows environment\n        if machine.config.vm.communicator != :winrm\n          if @version && !@install_type\n            errors << I18n.t(\"vagrant.provisioners.salt.version_type_missing\")\n          end\n        end\n\n        return {\"salt provisioner\" => errors}\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/salt/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Salt\n    module Errors\n      class SaltError < Vagrant::Errors::VagrantError\n        error_namespace(\"vagrant.provisioners.salt\")\n      end\n\n      class InvalidShasumError < SaltError\n        error_key(:salt_invalid_shasum_error)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/salt/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Salt\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"salt\"\n      description <<-DESC\n      Provisions virtual machines using SaltStack\n      DESC\n\n      config(:salt, :provisioner) do\n        require File.expand_path(\"../config\", __FILE__)\n        Config\n      end\n\n      provisioner(:salt) do\n        require File.expand_path(\"../provisioner\", __FILE__)\n        Provisioner\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/salt/provisioner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'json'\n\nrequire_relative \"bootstrap_downloader\"\n\nmodule VagrantPlugins\n  module Salt\n    class Provisioner < Vagrant.plugin(\"2\", :provisioner)\n\n      # Default path values to set within configuration only\n      # if configuration value is unset and local path exists\n      OPTIMISTIC_PATH_DEFAULTS = Hash[*[\n        \"minion_config\", \"salt/minion\",\n        \"minion_key\", \"salt/key/minion.key\",\n        \"minion_pub\", \"salt/key/minion.pub\",\n        \"master_config\", \"salt/master\",\n        \"master_key\", \"salt/key/master.key\",\n        \"master_pub\", \"salt/key/master.pub\"\n      ].map(&:freeze)].freeze\n\n      def provision\n        set_default_configs\n        upload_configs\n        upload_keys\n        run_bootstrap_script\n        call_overstate\n        call_highstate\n        call_orchestrate\n      end\n\n      # Return a list of accepted keys\n      def keys(group='minions')\n        out = @machine.communicate.sudo(\"salt-key --out json\") do |type, output|\n          begin\n            if type == :stdout\n              out = JSON::load(output)\n              break out[group]\n            end\n          end\n        end\n        return out\n      end\n\n      ## Utilities\n      def expanded_path(rel_path)\n        Pathname.new(rel_path).expand_path(@machine.env.root_path)\n      end\n\n      def binaries_found\n        if @machine.config.vm.communicator == :winrm\n          which_cmd = \"Get-Command\"\n        else\n          which_cmd = \"which\"\n        end\n\n        ## Determine States, ie: install vs configure\n        desired_binaries = []\n        if !@config.no_minion\n          desired_binaries.push('salt-minion')\n          desired_binaries.push('salt-call')\n        end\n\n        if @config.install_master\n          if @machine.config.vm.communicator == :winrm\n            raise Vagrant::Errors::ProvisionerWinRMUnsupported,\n              name: \"salt.install_master\"\n          else\n            desired_binaries.push('salt-master')\n          end\n        end\n\n        if @config.install_syndic\n          if @machine.config.vm.communicator == :winrm\n            raise Vagrant::Errors::ProvisionerWinRMUnsupported,\n              name: \"salt.install_syndic\"\n          else\n            desired_binaries.push('salt-syndic')\n          end\n        end\n\n        found = true\n        for binary in desired_binaries\n          @machine.env.ui.info \"Checking if %s is installed\" % binary\n          if !@machine.communicate.test(\"%s %s\" % [which_cmd, binary])\n            @machine.env.ui.info \"%s was not found.\" % binary\n            found = false\n          else\n            @machine.env.ui.info \"%s found\" % binary\n          end\n        end\n\n        return found\n      end\n\n      def need_configure\n        @config.minion_config or @config.minion_key or @config.master_config or @config.master_key or @config.grains_config or @config.version or @config.minion_json_config or @config.master_json_config\n      end\n\n      def need_install\n        if @config.always_install\n          return true\n        else\n          return !binaries_found()\n        end\n      end\n\n      def temp_config_dir\n        if @machine.config.vm.communicator == :winrm\n          return @config.temp_config_dir || \"C:\\\\tmp\"\n        else\n          return @config.temp_config_dir || \"/tmp\"\n        end\n      end\n\n      # Generates option string for bootstrap script\n      def bootstrap_options(install, configure, config_dir)\n        # Any extra options passed to bootstrap\n        if @config.bootstrap_options\n          options = @config.bootstrap_options\n        else\n          options = \"\"\n        end\n\n        if @config.master_json_config && @machine.config.vm.communicator != :winrm\n          config = @config.master_json_config\n          options = \"%s -J '#{config}'\" % [options]\n        end\n\n        if @config.minion_json_config && @machine.config.vm.communicator != :winrm\n          config = @config.minion_json_config\n          options = \"%s -j '#{config}'\" % [options]\n        end\n\n        if configure && @machine.config.vm.communicator != :winrm\n          options = \"%s -F -c %s\" % [options, config_dir]\n        end\n\n        if @config.seed_master && @config.install_master && @machine.config.vm.communicator != :winrm\n          seed_dir = \"/tmp/minion-seed-keys\"\n          @machine.communicate.sudo(\"mkdir -p -m777 #{seed_dir}\")\n          @config.seed_master.each do |name, keyfile|\n            sourcepath = expanded_path(keyfile).to_s\n            dest = \"#{seed_dir}/#{name}\"\n            @machine.communicate.upload(sourcepath, dest)\n          end\n          options = \"#{options} -k #{seed_dir}\"\n        end\n\n        if configure && !install\n          if @machine.config.vm.communicator == :winrm\n            options = \"%s -ConfigureOnly\" % options\n          else\n            options = \"%s -C\" % options\n          end\n        end\n\n        if @config.install_master && @machine.config.vm.communicator != :winrm\n          options = \"%s -M\" % options\n        end\n\n        if @config.install_syndic && @machine.config.vm.communicator != :winrm\n          options = \"%s -S\" % options\n        end\n\n        if @config.no_minion && @machine.config.vm.communicator != :winrm\n          options = \"%s -N\" % options\n        end\n\n        if @config.python_version && @machine.config.vm.communicator != :winrm\n          options = \"%s -x python%s\" % [options, @config.python_version]\n        end\n\n        if @config.install_type && @machine.config.vm.communicator != :winrm\n          options = \"%s %s\" % [options, @config.install_type]\n        end\n\n        if @config.install_args && @machine.config.vm.communicator != :winrm\n          options = \"%s %s\" % [options, @config.install_args]\n        end\n\n        if @config.verbose\n          @machine.env.ui.info \"Using Bootstrap Options: %s\" % options\n        end\n\n        return options\n      end\n\n      ## Actions\n      # Get pillar string to pass with the salt command\n      def get_pillar\n        if !@config.pillar_data.empty?\n          if @machine.config.vm.communicator == :winrm\n            # ' doesn't have any special behavior on the command prompt,\n            # so '{\"x\":\"y\"}' becomes '{x:y}' with literal single quotes.\n            # However, \"\"\" will become \" , and \\\\\"\"\" will become \\\" .\n            # Use \\\\\"\" instead of \\\\\"\"\" for literal inner-value quotes\n            # to avoid issue with odd number of quotes.\n            # --% disables special PowerShell parsing on the rest of the line.\n            \" --% pillar=#{@config.pillar_data.to_json.gsub(/(?<!\\\\)\\\"/, '\"\"\"').gsub(/\\\\\\\"/, %q(\\\\\\\\\\\"\"))}\"\n          else\n            \" pillar='#{@config.pillar_data.to_json}'\"\n          end\n        end\n      end\n\n      # Get colorization option string to pass with the salt command\n      def get_colorize\n        @config.colorize ? \" --force-color\" : \" --no-color\"\n      end\n\n      # Get log output level option string to pass with the salt command\n      def get_loglevel\n        log_levels = [\"all\", \"garbage\", \"trace\", \"debug\", \"info\", \"warning\", \"error\", \"quiet\"]\n        if log_levels.include? @config.log_level\n          \" --log-level=#{@config.log_level}\"\n        else\n          \" --log-level=debug\"\n        end\n      end\n\n      # Get command-line options for masterless provisioning\n      def get_masterless\n        options = \"\"\n\n        if @config.masterless\n          options = \" --local\"\n          if @config.minion_id\n            options += \" --id #{@config.minion_id}\"\n          end\n        end\n\n        return options\n      end\n\n      # Append additional arguments to the salt command\n      def get_salt_args\n        \" \" + Array(@config.salt_args).join(\" \")\n      end\n\n      # Append additional arguments to the salt-call command\n      def get_call_args\n        \" \" + Array(@config.salt_call_args).join(\" \")\n      end\n\n      # Copy master and minion configs to VM\n      def upload_configs\n        if @config.minion_config\n          @machine.env.ui.info \"Copying salt minion config to vm.\"\n          @machine.communicate.upload(expanded_path(@config.minion_config).to_s, temp_config_dir + \"/minion\")\n        end\n\n        if @config.master_config\n          @machine.env.ui.info \"Copying salt master config to vm.\"\n          @machine.communicate.upload(expanded_path(@config.master_config).to_s, temp_config_dir + \"/master\")\n        end\n\n        if @config.grains_config\n          @machine.env.ui.info \"Copying salt grains config to vm.\"\n          @machine.communicate.upload(expanded_path(@config.grains_config).to_s, temp_config_dir + \"/grains\")\n        end\n      end\n\n      # Copy master and minion keys to VM\n      def upload_keys\n        if @config.minion_key and @config.minion_pub\n          @machine.env.ui.info \"Uploading minion keys.\"\n          @machine.communicate.upload(expanded_path(@config.minion_key).to_s, temp_config_dir + \"/minion.pem\")\n          @machine.communicate.sudo(\"chmod u+w #{temp_config_dir}/minion.pem\")\n          @machine.communicate.upload(expanded_path(@config.minion_pub).to_s, temp_config_dir + \"/minion.pub\")\n        end\n\n        if @config.master_key and @config.master_pub\n          @machine.env.ui.info \"Uploading master keys.\"\n          @machine.communicate.upload(expanded_path(@config.master_key).to_s, temp_config_dir + \"/master.pem\")\n          @machine.communicate.sudo(\"chmod u+w #{temp_config_dir}/master.pem\")\n          @machine.communicate.upload(expanded_path(@config.master_pub).to_s, temp_config_dir + \"/master.pub\")\n        end\n      end\n\n      # Get bootstrap file location, bundled or custom\n      def get_bootstrap\n        if @config.bootstrap_script\n          bootstrap_abs_path = expanded_path(@config.bootstrap_script)\n        else\n          bootstrap_downloader = BootstrapDownloader.new(@machine.config.vm.guest)\n          bootstrap_script = bootstrap_downloader.get_bootstrap_script\n          bootstrap_abs_path = expanded_path(bootstrap_script.path)\n        end\n\n        return bootstrap_abs_path\n      end\n\n      # Determine if we are configure and/or installing, then do either\n      def run_bootstrap_script\n        install = need_install()\n        configure = need_configure()\n        config_dir = temp_config_dir()\n        options = bootstrap_options(install, configure, config_dir)\n\n        if configure or install\n          if configure and !install\n            @machine.env.ui.info \"Salt binaries found. Configuring only.\"\n          else\n            @machine.env.ui.info \"Bootstrapping Salt... (this may take a while)\"\n          end\n\n          bootstrap_path = get_bootstrap\n          if @machine.config.vm.communicator == :winrm\n            if @config.version\n              options += \" -version %s\" % @config.version\n            end\n            if @config.python_version\n              options += \" -pythonVersion %s\" % @config.python_version\n            end\n            if @config.run_service\n              @machine.env.ui.info \"Salt minion will be stopped after installing.\"\n              options += \" -runservice %s\" % @config.run_service\n            end\n            if @config.minion_id\n              @machine.env.ui.info \"Setting minion to @config.minion_id.\"\n              options += \" -minion %s\" % @config.minion_id\n            end\n            if @config.master_id\n              @machine.env.ui.info \"Setting master to @config.master_id.\"\n              options += \" -master %s\" % @config.master_id\n            end\n            bootstrap_destination = File.join(config_dir, \"bootstrap_salt.ps1\")\n          else\n            if @config.version\n              options += \" %s\" % @config.version\n            end\n            bootstrap_destination = File.join(config_dir, \"bootstrap_salt.sh\")\n          end\n\n          if @machine.communicate.test(\"test -f %s\" % bootstrap_destination)\n            @machine.communicate.sudo(\"rm -f %s\" % bootstrap_destination)\n          end\n          @machine.communicate.upload(bootstrap_path.to_s, bootstrap_destination)\n          @machine.communicate.sudo(\"chmod +x %s\" % bootstrap_destination)\n          if @machine.config.vm.communicator == :winrm\n            bootstrap = @machine.communicate.sudo(\"powershell.exe -NonInteractive -NoProfile -executionpolicy bypass -file %s %s\" % [bootstrap_destination, options]) do |type, data|\n              if data[0] == \"\\n\"\n                # Remove any leading newline but not whitespace. If we wanted to\n                # remove newlines and whitespace we would have used data.lstrip\n                data = data[1..-1]\n              end\n              if @config.verbose\n                @machine.env.ui.info(data.rstrip)\n              end\n            end\n          else\n            bootstrap = @machine.communicate.sudo(\"%s %s\" % [bootstrap_destination, options]) do |type, data|\n              if data[0] == \"\\n\"\n                # Remove any leading newline but not whitespace. If we wanted to\n                # remove newlines and whitespace we would have used data.lstrip\n                data = data[1..-1]\n              end\n              if @config.verbose\n                @machine.env.ui.info(data.rstrip)\n              end\n            end\n          end\n\n          if !bootstrap\n            raise Salt::Errors::SaltError, :bootstrap_failed\n          end\n\n          if configure and !install\n            @machine.env.ui.info \"Salt successfully configured!\"\n          elsif configure and install\n            @machine.env.ui.info \"Salt successfully configured and installed!\"\n          elsif !configure and install\n            @machine.env.ui.info \"Salt successfully installed!\"\n          end\n        else\n          @machine.env.ui.info \"Salt did not need installing or configuring.\"\n        end\n      end\n\n      def call_overstate\n        if @config.run_overstate\n          # If verbose is on, do not duplicate a failed command's output in the error message.\n          ssh_opts = {}\n          if @config.verbose\n            ssh_opts = { error_key: :ssh_bad_exit_status_muted }\n          end\n          if @config.install_master\n            @machine.env.ui.info \"Calling state.overstate... (this may take a while)\"\n            @machine.communicate.sudo(\"salt '*' saltutil.sync_all\")\n            @machine.communicate.sudo(\"salt-run state.over\", ssh_opts) do |type, data|\n              if @config.verbose\n                @machine.env.ui.info(data)\n              end\n            end\n          else\n            @machine.env.ui.info \"run_overstate does not make sense on a minion. Not running state.overstate.\"\n          end\n        else\n          @machine.env.ui.info \"run_overstate set to false. Not running state.overstate.\"\n        end\n      end\n\n      def call_highstate\n        if @config.run_highstate\n          # If verbose is on, do not duplicate a failed command's output in the error message.\n          ssh_opts = {}\n          if @config.verbose\n            ssh_opts = { error_key: :ssh_bad_exit_status_muted }\n          end\n\n          @machine.env.ui.info \"Calling state.highstate... (this may take a while)\"\n          if @config.install_master\n            unless @config.masterless\n              @machine.communicate.sudo(\"salt '*' saltutil.sync_all\")\n            end\n            options = \"#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_salt_args}\"\n            @machine.communicate.sudo(\"salt '*' state.highstate --verbose#{options}\", ssh_opts) do |type, data|\n            if @config.verbose\n                @machine.env.ui.info(data.rstrip)\n            end\n          end\n          else\n            if @machine.config.vm.communicator == :winrm\n              opts = { elevated: true }\n              unless @config.masterless\n                @machine.communicate.execute(\"salt-call saltutil.sync_all\", opts)\n              end\n              # TODO: something equivalent to { error_key: :ssh_bad_exit_status_muted }?\n              options = \"#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_call_args}\"\n              @machine.communicate.execute(\"salt-call state.highstate --retcode-passthrough#{options}\", opts) do |type, data|\n                if @config.verbose\n                  @machine.env.ui.info(data.rstrip)\n                end\n              end\n            else\n              unless @config.masterless\n                @machine.communicate.sudo(\"salt-call saltutil.sync_all\")\n              end\n              options = \"#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_call_args}\"\n              @machine.communicate.sudo(\"salt-call state.highstate --retcode-passthrough#{options}\", ssh_opts) do |type, data|\n                if @config.verbose\n                  @machine.env.ui.info(data.rstrip)\n                end\n              end\n            end\n          end\n        else\n          @machine.env.ui.info \"run_highstate set to false. Not running state.highstate.\"\n        end\n      end\n\n      def call_orchestrate\n        if !@config.orchestrations\n          @machine.env.ui.info \"orchestrate is nil. Not running state.orchestrate.\"\n          return\n        end\n\n        if !@config.install_master\n          @machine.env.ui.info \"orchestrate does not make sense on a minion. Not running state.orchestrate.\"\n          return\n        end\n\n        log_output = lambda do |type, data|\n          if @config.verbose\n            @machine.env.ui.info(data)\n          end\n        end\n\n        # If verbose is on, do not duplicate a failed command's output in the error message.\n        ssh_opts = {}\n        if @config.verbose\n          ssh_opts = { error_key: :ssh_bad_exit_status_muted }\n        end\n\n        @machine.env.ui.info \"Running the following orchestrations: #{@config.orchestrations}\"\n        @machine.env.ui.info \"Running saltutil.sync_all before orchestrating\"\n        @machine.communicate.sudo(\"salt '*' saltutil.sync_all\", ssh_opts, &log_output)\n\n        @config.orchestrations.each do |orchestration|\n          cmd = \"salt-run -l info state.orchestrate #{orchestration}\"\n          @machine.env.ui.info \"Calling #{cmd}... (this may take a while)\"\n          @machine.communicate.sudo(cmd, ssh_opts, &log_output)\n        end\n      end\n\n      # Sets optimistic default values into config\n      def set_default_configs\n        OPTIMISTIC_PATH_DEFAULTS.each do |config_key, config_default|\n          if config.send(config_key) == Config::UNSET_VALUE\n            config_value = File.exist?(expanded_path(config_default)) ? config_default : nil\n            config.send(\"#{config_key}=\", config_value)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/shell/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'uri'\n\nmodule VagrantPlugins\n  module Shell\n    class Config < Vagrant.plugin(\"2\", :config)\n      attr_accessor :inline\n      attr_accessor :path\n      attr_accessor :md5\n      attr_accessor :sha1\n      attr_accessor :sha256\n      attr_accessor :sha384\n      attr_accessor :sha512\n      attr_accessor :env\n      attr_accessor :upload_path\n      attr_accessor :args\n      attr_accessor :privileged\n      attr_accessor :binary\n      attr_accessor :keep_color\n      attr_accessor :name\n      attr_accessor :sensitive\n      attr_accessor :powershell_args\n      attr_accessor :powershell_elevated_interactive\n      attr_accessor :reboot\n      attr_accessor :reset\n\n      def initialize\n        @args                  = UNSET_VALUE\n        @inline                = UNSET_VALUE\n        @path                  = UNSET_VALUE\n        @md5                   = UNSET_VALUE\n        @sha1                  = UNSET_VALUE\n        @sha256                = UNSET_VALUE\n        @sha384                = UNSET_VALUE\n        @sha512                = UNSET_VALUE\n        @env                   = UNSET_VALUE\n        @upload_path           = UNSET_VALUE\n        @privileged            = UNSET_VALUE\n        @binary                = UNSET_VALUE\n        @keep_color            = UNSET_VALUE\n        @name                  = UNSET_VALUE\n        @sensitive             = UNSET_VALUE\n        @reboot                = UNSET_VALUE\n        @reset                 = UNSET_VALUE\n        @powershell_args       = UNSET_VALUE\n        @powershell_elevated_interactive  = UNSET_VALUE\n      end\n\n      def finalize!\n        @args                 = nil if @args == UNSET_VALUE\n        @inline               = nil if @inline == UNSET_VALUE\n        @path                 = nil if @path == UNSET_VALUE\n        @md5                  = nil if @md5 == UNSET_VALUE\n        @sha1                 = nil if @sha1 == UNSET_VALUE\n        @sha256               = nil if @sha256 == UNSET_VALUE\n        @sha384               = nil if @sha384 == UNSET_VALUE\n        @sha512               = nil if @sha512 == UNSET_VALUE\n        @env                  = {}  if @env == UNSET_VALUE\n        @upload_path          = nil if @upload_path == UNSET_VALUE\n        @privileged           = true if @privileged == UNSET_VALUE\n        @binary               = false if @binary == UNSET_VALUE\n        @keep_color           = false if @keep_color == UNSET_VALUE\n        @name                 = nil if @name == UNSET_VALUE\n        @sensitive            = false if @sensitive == UNSET_VALUE\n        @reboot               = false if @reboot == UNSET_VALUE\n        @reset                = false if @reset == UNSET_VALUE\n        @powershell_args      = \"-ExecutionPolicy Bypass\" if @powershell_args == UNSET_VALUE\n        @powershell_elevated_interactive = false if @powershell_elevated_interactive == UNSET_VALUE\n\n        if @args && args_valid?\n          @args = @args.is_a?(Array) ? @args.map { |a| a.to_s } : @args.to_s\n        end\n\n        if @sensitive\n          @env.each do |_, v|\n            Vagrant::Util::CredentialScrubber.sensitive(v)\n          end\n        end\n      end\n\n      def validate(machine)\n        errors = _detected_errors\n\n        # Validate that the parameters are properly set\n        if path && inline\n          errors << I18n.t(\"vagrant.provisioners.shell.path_and_inline_set\")\n        elsif !path && !inline && !reset && !reboot\n          errors << I18n.t(\"vagrant.provisioners.shell.no_path_or_inline\")\n        end\n\n        # If it is not an URL, we validate the existence of a script to upload\n        if path && !remote?\n          expanded_path = Pathname.new(path).expand_path(machine.env.root_path)\n          if !expanded_path.file?\n            errors << I18n.t(\"vagrant.provisioners.shell.path_invalid\",\n                              path: expanded_path)\n          else\n            data = expanded_path.read(16)\n            if data && !data.valid_encoding?\n              errors << I18n.t(\n                \"vagrant.provisioners.shell.invalid_encoding\",\n                actual: data.encoding.to_s,\n                default: Encoding.default_external.to_s,\n                path: expanded_path.to_s)\n            end\n          end\n        end\n\n        if !env.is_a?(Hash)\n          errors << I18n.t(\"vagrant.provisioners.shell.env_must_be_a_hash\")\n        end\n\n        if !args_valid?\n          errors << I18n.t(\"vagrant.provisioners.shell.args_bad_type\")\n        end\n\n        if powershell_elevated_interactive && !privileged\n          errors << I18n.t(\"vagrant.provisioners.shell.interactive_not_elevated\")\n        end\n\n        { \"shell provisioner\" => errors }\n      end\n\n      # Args are optional, but if they're provided we only support them as a\n      # string or as an array.\n      def args_valid?\n        return true if !args\n        return true if args.is_a?(String)\n        return true if args.is_a?(Integer)\n        if args.is_a?(Array)\n          args.each do |a|\n            return false if !a.kind_of?(String) && !a.kind_of?(Integer)\n          end\n\n          return true\n        end\n      end\n\n      def remote?\n        path =~ URI.regexp([\"ftp\", \"http\", \"https\"])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/shell/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module Shell\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"shell\"\n      description <<-DESC\n      Provides support for provisioning your virtual machines with\n      shell scripts.\n      DESC\n\n      config(:shell, :provisioner) do\n        require File.expand_path(\"../config\", __FILE__)\n        Config\n      end\n\n      provisioner(:shell) do\n        require File.expand_path(\"../provisioner\", __FILE__)\n        Provisioner\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/provisioners/shell/provisioner.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tempfile\"\n\nrequire \"vagrant/util/downloader\"\nrequire \"vagrant/util/line_buffer\"\nrequire \"vagrant/util/retryable\"\n\nmodule VagrantPlugins\n  module Shell\n    class Provisioner < Vagrant.plugin(\"2\", :provisioner)\n      include Vagrant::Util::Retryable\n\n      DEFAULT_WINDOWS_SHELL_EXT = \".ps1\".freeze\n\n      CMD_WINDOWS_SHELL_EXT = \".bat\".freeze\n\n      def provision\n        args = \"\"\n        if config.args.is_a?(String)\n          args = \" #{config.args.to_s}\"\n        elsif config.args.is_a?(Array)\n          args = config.args.map { |a| quote_and_escape(a) }\n          args = \" #{args.join(\" \")}\"\n        end\n\n        # In cases where the connection is just being reset\n        # bail out before attempting to do any actual provisioning\n        return if !config.path && !config.inline\n\n        case @machine.config.vm.communicator\n        when :winrm\n          provision_winrm(args)\n        when :winssh\n          provision_winssh(args)\n        else\n          provision_ssh(args)\n        end\n      ensure\n        if config.reboot\n          @machine.guest.capability(:reboot)\n        else\n          @machine.communicate.reset! if config.reset\n        end\n      end\n\n      def upload_path\n        if !defined?(@_upload_path)\n          case @machine.config.vm.guest\n          when :windows\n            @_upload_path = Vagrant::Util::Platform.unix_windows_path(config.upload_path.to_s)\n          else\n            @_upload_path = config.upload_path.to_s\n          end\n\n          if @_upload_path.empty?\n            case @machine.config.vm.guest\n            when :windows\n              @_upload_path = \"C:/tmp/vagrant-shell\"\n            else\n              @_upload_path = \"/tmp/vagrant-shell\"\n            end\n          end\n        end\n        @_upload_path\n      end\n\n      protected\n\n      def build_outputs\n        outputs = {\n          stdout: Vagrant::Util::LineBuffer.new { |line| handle_comm(:stdout, line) },\n          stderr: Vagrant::Util::LineBuffer.new { |line| handle_comm(:stderr, line) },\n        }\n        block = proc { |type, data|\n          outputs[type] << data if outputs[type]\n        }\n        return outputs, block\n      end\n\n      # This handles outputting the communication line back to the UI\n      def handle_comm(type, data)\n        if [:stderr, :stdout].include?(type)\n          # Output the line with the proper color based on the stream.\n          color = type == :stdout ? :green : :red\n\n          options = {}\n          options[:color] = color if !config.keep_color\n\n          @machine.ui.detail(data.chomp, **options)\n        end\n      end\n\n      # This is the provision method called if SSH is what is running\n      # on the remote end, which assumes a POSIX-style host.\n      def provision_ssh(args)\n        env = config.env.map { |k,v| \"#{k}=#{quote_and_escape(v.to_s)}\" }\n        env = env.join(\" \")\n\n        command =  \"chmod +x '#{upload_path}'\"\n        command << \" &&\"\n        command << \" #{env}\" if !env.empty?\n        command << \" #{upload_path}#{args}\"\n\n        with_script_file do |path|\n          # Upload the script to the machine\n          @machine.communicate.tap do |comm|\n            # Reset upload path permissions for the current ssh user\n            info = nil\n            retryable(on: Vagrant::Errors::SSHNotReady, tries: 3, sleep: 2) do\n              info = @machine.ssh_info\n              raise Vagrant::Errors::SSHNotReady if info.nil?\n            end\n\n            comm.upload(path.to_s, upload_path)\n            user = info[:username]\n            comm.sudo(\"chown -R #{user} #{upload_path}\",\n                      error_check: false)\n\n            if config.name\n              @machine.ui.detail(I18n.t(\"vagrant.provisioners.shell.running\",\n                                      script: \"script: #{config.name}\"))\n            elsif config.path\n              @machine.ui.detail(I18n.t(\"vagrant.provisioners.shell.running\",\n                                      script: path.to_s))\n            else\n              @machine.ui.detail(I18n.t(\"vagrant.provisioners.shell.running\",\n                                      script: \"inline script\"))\n            end\n\n            # Execute it with sudo\n            outputs, handler = build_outputs\n            begin\n              comm.execute(\n                command,\n                sudo: config.privileged,\n                error_key: :ssh_bad_exit_status_muted,\n                &handler\n              )\n            ensure\n              outputs.values.map(&:close)\n            end\n          end\n        end\n      end\n\n      # This is the provision method called if Windows OpenSSH is what is running\n      # on the remote end, which assumes a non-POSIX-style host.\n      def provision_winssh(args)\n        with_script_file do |path|\n          # Upload the script to the machine\n          @machine.communicate.tap do |comm|\n            env = config.env.map{|k,v| comm.generate_environment_export(k, v)}.join(';')\n\n            remote_ext = get_windows_ext(path)\n            remote_path = add_extension(upload_path, remote_ext)\n\n            if remote_ext == \".bat\"\n              command = \"#{env}\\n cmd.exe /c \\\"#{remote_path}\\\" #{args}\"\n            else\n              # Copy powershell_args from configuration\n              shell_args = config.powershell_args\n              # For PowerShell scripts bypass the execution policy unless already specified\n              shell_args += \" -ExecutionPolicy Bypass\" if config.powershell_args !~ /[-\\/]ExecutionPolicy/i\n              # CLIXML output is kinda useless, especially on non-windows hosts\n              shell_args += \" -OutputFormat Text\" if config.powershell_args !~ /[-\\/]OutputFormat/i\n              command = \"#{env}\\npowershell #{shell_args} -file \\\"#{remote_path}\\\"#{args}\"\n            end\n\n            # Reset upload path permissions for the current ssh user\n            info = nil\n            retryable(on: Vagrant::Errors::SSHNotReady, tries: 3, sleep: 2) do\n              info = @machine.ssh_info\n              raise Vagrant::Errors::SSHNotReady if info.nil?\n            end\n\n            comm.upload(path.to_s, remote_path)\n\n            if config.name\n              @machine.ui.detail(I18n.t(\"vagrant.provisioners.shell.running\",\n                                      script: \"script: #{config.name}\"))\n            elsif config.path\n              @machine.ui.detail(I18n.t(\"vagrant.provisioners.shell.running\",\n                                      script: path.to_s))\n            else\n              @machine.ui.detail(I18n.t(\"vagrant.provisioners.shell.running\",\n                                      script: \"inline script\"))\n            end\n\n            # Execute it with sudo\n            begin\n              outputs, handler = build_outputs\n              comm.execute(\n                command,\n                shell: :powershell,\n                error_key: :ssh_bad_exit_status_muted,\n                &handler\n              )\n            ensure\n              outputs.values.map(&:close)\n            end\n          end\n        end\n      end\n\n      # This provisions using WinRM, which assumes a PowerShell\n      # console on the other side.\n      def provision_winrm(args)\n        if @machine.guest.capability?(:wait_for_reboot)\n          @machine.guest.capability(:wait_for_reboot)\n        end\n\n        with_script_file do |path|\n          @machine.communicate.tap do |comm|\n            # Make sure that the upload path has an extension, since\n            # having an extension is critical for Windows execution\n            winrm_upload_path = add_extension(upload_path,  get_windows_ext(path))\n\n            # Upload it\n            comm.upload(path.to_s, winrm_upload_path)\n\n            # Build the environment\n            env = config.env.map { |k,v| \"$env:#{k} = #{quote_and_escape(v.to_s)}\" }\n            env = env.join(\"; \")\n\n            # Calculate the path that we'll be executing\n            exec_path = winrm_upload_path\n            exec_path.gsub!('/', '\\\\')\n            exec_path = \"c:#{exec_path}\" if exec_path.start_with?(\"\\\\\")\n\n            # Copy powershell_args from configuration\n            shell_args = config.powershell_args\n\n            # For PowerShell scripts bypass the execution policy unless already specified\n            shell_args += \" -ExecutionPolicy Bypass\" if config.powershell_args !~ /[-\\/]ExecutionPolicy/i\n\n            # CLIXML output is kinda useless, especially on non-windows hosts\n            shell_args += \" -OutputFormat Text\" if config.powershell_args !~ /[-\\/]OutputFormat/i\n\n            command = \"\\\"#{exec_path}\\\"#{args}\"\n            if File.extname(exec_path).downcase == \".ps1\"\n              command = \"powershell #{shell_args.to_s} -file #{command}\"\n            else\n              command = \"cmd /q /c #{command}\"\n            end\n\n            # Append the environment\n            if !env.empty?\n              command = \"#{env}; #{command}\"\n            end\n\n            if config.name\n              @machine.ui.detail(I18n.t(\"vagrant.provisioners.shell.running\",\n                                      script: \"script: #{config.name}\"))\n            elsif config.path\n              @machine.ui.detail(I18n.t(\"vagrant.provisioners.shell.runningas\",\n                                      local: config.path.to_s, remote: exec_path))\n            else\n              @machine.ui.detail(I18n.t(\"vagrant.provisioners.shell.running\",\n                                      script: \"inline PowerShell script\"))\n            end\n\n            # Execute it with sudo\n            begin\n              outputs, handler = build_outputs\n              comm.sudo(command,\n                elevated: config.privileged,\n                interactive: config.powershell_elevated_interactive,\n                &handler\n              )\n            ensure\n              outputs.values.map(&:close)\n            end\n          end\n        end\n      end\n\n      # Quote and escape strings for shell execution, thanks to Capistrano.\n      def quote_and_escape(text, quote = '\"')\n        \"#{quote}#{text.gsub(/#{quote}/) { |m| \"#{m}\\\\#{m}#{m}\" }}#{quote}\"\n      end\n\n      def add_extension(path, ext)\n        return path if !File.extname(path.to_s).empty?\n        path + ext\n      end\n\n      def get_windows_ext(path)\n        remote_ext = File.extname(upload_path.to_s)\n        if remote_ext.empty?\n          remote_ext = File.extname(path.to_s)\n          if remote_ext.empty?\n            remote_ext = @machine.config.winssh.shell == \"cmd\" ? CMD_WINDOWS_SHELL_EXT : DEFAULT_WINDOWS_SHELL_EXT\n          end\n        end\n        remote_ext\n      end\n\n      # This method yields the path to a script to upload and execute\n      # on the remote server. This method will properly clean up the\n      # script file if needed.\n      def with_script_file\n        ext    = nil\n        script = nil\n\n        if config.remote?\n          download_path = @machine.env.tmp_path.join(\n            \"#{@machine.id}-remote-script\")\n          download_path.delete if download_path.file?\n\n          begin\n            Vagrant::Util::Downloader.new(\n              config.path,\n              download_path,\n              md5: config.md5,\n              sha1: config.sha1,\n              sha256: config.sha256,\n              sha384: config.sha384,\n              sha512: config.sha512\n            ).download!\n            ext    = File.extname(config.path)\n            script = download_path.read\n          ensure\n            download_path.delete if download_path.file?\n          end\n        elsif config.path\n          # Just yield the path to that file...\n          root_path = @machine.env.root_path\n          ext    = File.extname(config.path)\n          script = Pathname.new(config.path).expand_path(root_path).read\n        else\n          script = config.inline\n        end\n\n        # Replace Windows line endings with Unix ones unless binary file\n        # or we're running on Windows.\n        if !config.binary && @machine.config.vm.guest != :windows\n          begin\n            script = script.gsub(/\\r\\n?$/, \"\\n\")\n          rescue ArgumentError\n            script = script.force_encoding(\"ASCII-8BIT\").gsub(/\\r\\n?$/, \"\\n\")\n          end\n        end\n\n        # Otherwise we have an inline script, we need to Tempfile it,\n        # and handle it specially...\n        file = Tempfile.new(['vagrant-shell', ext])\n\n        # Unless you set binmode, on a Windows host the shell script will\n        # have CRLF line endings instead of LF line endings, causing havoc\n        # when the guest executes it. This fixes [GH-1181].\n        file.binmode\n\n        begin\n          file.write(script)\n          file.fsync\n          file.close\n          yield file.path\n        ensure\n          file.close\n          file.unlink\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/atlas/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module AtlasPush\n    class Config < Vagrant.plugin(\"2\", :config)\n      # The address of the Atlas server to upload to. By default this will\n      # be the public Atlas server.\n      #\n      # @return [String]\n      attr_accessor :address\n\n      # The Atlas token to use. If the user has run `vagrant login`, this will\n      # use that token. If the environment variable `ATLAS_TOKEN` is set, the\n      # uploader will use this value. By default, this is nil.\n      #\n      # @return [String, nil]\n      attr_accessor :token\n\n      # The name of the application to push to. This will be created (with\n      # user confirmation) if it doesn't already exist.\n      #\n      # @return [String]\n      attr_accessor :app\n\n      # The base directory with file contents to upload. By default this\n      # is the same directory as the Vagrantfile, but you can specify this\n      # if you have a `src` folder or `bin` folder or some other folder\n      # you want to upload.\n      #\n      # @return [String]\n      attr_accessor :dir\n\n      # Lists of files to include/exclude in what is uploaded. Exclude is\n      # always the last run filter, so if a file is matched in both include\n      # and exclude, it will be excluded.\n      #\n      # The value of the array elements should be a simple file glob relative\n      # to the directory being packaged.\n      #\n      # @return [Array<String>]\n      attr_accessor :includes\n      attr_accessor :excludes\n\n      # If set to true, Vagrant will automatically use VCS data to determine\n      # the files to upload. As a caveat: uncommitted changes will not be\n      # deployed.\n      #\n      # @return [Boolean]\n      attr_accessor :vcs\n\n      # The path to the uploader binary to shell out to. This usually\n      # is only set for debugging/development. If not set, the uploader\n      # will be looked for within the Vagrant installer dir followed by\n      # the PATH.\n      #\n      # @return [String]\n      attr_accessor :uploader_path\n\n      def initialize\n        @address = UNSET_VALUE\n        @token = UNSET_VALUE\n        @app = UNSET_VALUE\n        @dir = UNSET_VALUE\n        @vcs = UNSET_VALUE\n        @includes = []\n        @excludes = []\n        @uploader_path = UNSET_VALUE\n      end\n\n      def merge(other)\n        super.tap do |result|\n          result.includes = self.includes.dup.concat(other.includes).uniq\n          result.excludes = self.excludes.dup.concat(other.excludes).uniq\n        end\n      end\n\n      def finalize!\n        @address = nil if @address == UNSET_VALUE\n        @token = nil if @token == UNSET_VALUE\n        @token = ENV[\"ATLAS_TOKEN\"] if !@token && ENV[\"ATLAS_TOKEN\"] != \"\"\n        @app = nil if @app == UNSET_VALUE\n        @dir = \".\" if @dir == UNSET_VALUE\n        @uploader_path = nil if @uploader_path == UNSET_VALUE\n        @vcs = true if @vcs == UNSET_VALUE\n      end\n\n      def validate(machine)\n        errors = _detected_errors\n\n        if missing?(@token)\n          token = token_from_vagrant_login(machine.env)\n          if missing?(token)\n            errors << I18n.t(\"atlas_push.errors.missing_token\")\n          else\n            @token = token\n          end\n        end\n\n        if missing?(@app)\n          errors << I18n.t(\"atlas_push.errors.missing_attribute\",\n            attribute: \"app\",\n          )\n        end\n\n        if missing?(@dir)\n          errors << I18n.t(\"atlas_push.errors.missing_attribute\",\n            attribute: \"dir\",\n          )\n        end\n\n        { \"Atlas push\" => errors }\n      end\n\n      # Add the filepath to the list of includes\n      # @param [String] filepath\n      def include(filepath)\n        @includes << filepath\n      end\n      alias_method :include=, :include\n\n      # Add the filepath to the list of excludes\n      # @param [String] filepath\n      def exclude(filepath)\n        @excludes << filepath\n      end\n      alias_method :exclude=, :exclude\n\n      private\n\n      # Determine if the given string is \"missing\" (blank)\n      # @return [true, false]\n      def missing?(obj)\n        obj.to_s.strip.empty?\n      end\n\n      # Attempt to load the token from disk using the vagrant-login plugin. If\n      # the constant is not defined, that means the user is operating in some\n      # bespoke and unsupported Ruby environment.\n      #\n      # @param [Vagrant::Environment] env\n      #\n      # @return [String, nil]\n      #   the token, or nil if it does not exist\n      def token_from_vagrant_login(env)\n        client = VagrantPlugins::LoginCommand::Client.new(env)\n        client.token\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/atlas/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module AtlasPush\n    module Errors\n      class Error < Vagrant::Errors::VagrantError\n        error_namespace(\"atlas_push.errors\")\n      end\n\n      class UploaderNotFound < Error\n        error_key(:uploader_not_found)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/atlas/locales/en.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  atlas_push:\n    errors:\n      missing_attribute: |-\n        Missing required attribute '%{attribute}'. The Vagrant Atlas Push plugin\n        requires you set this attribute. Please set this attribute in your\n        Vagrantfile, for example:\n\n            config.push.define \"atlas\" do |push|\n              push.%{attribute} = \"...\"\n            end\n      missing_token: |-\n        Missing required configuration parameter 'token'. This is required for\n        Vagrant to securely communicate with your Atlas account.\n\n        To generate an access token, run 'vagrant login'.\n      uploader_not_found: |-\n        Vagrant was unable to find the Atlas uploader CLI. If your Vagrantfile\n        specifies the path explicitly with \"uploader_path\", then make sure that\n        path is valid. Otherwise, make sure that you have a valid install of\n        Vagrant. If you installed Vagrant outside of the official installers,\n        the \"atlas-upload\" binary must exist on your PATH.\n"
  },
  {
    "path": "plugins/pushes/atlas/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module AtlasPush\n    autoload :Errors, File.expand_path(\"../errors\", __FILE__)\n\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"atlas\"\n      description <<-DESC\n      Deploy using HashiCorp's Atlas service.\n      DESC\n\n      config(:atlas, :push) do\n        require_relative \"config\"\n        init!\n        Config\n      end\n\n      push(:atlas) do\n        require_relative \"push\"\n        init!\n        Push\n      end\n\n      protected\n\n      def self.init!\n        return if defined?(@_init)\n        I18n.load_path << File.expand_path(\"../locales/en.yml\", __FILE__)\n        I18n.reload!\n        @_init = true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/atlas/push.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/safe_exec\"\nrequire \"vagrant/util/subprocess\"\nrequire \"vagrant/util/which\"\n\nmodule VagrantPlugins\n  module AtlasPush\n    class Push < Vagrant.plugin(\"2\", :push)\n      UPLOADER_BIN = \"atlas-upload\".freeze\n\n      def push\n        uploader = self.uploader_path\n\n        # If we didn't find the uploader binary it is a critical error\n        raise Errors::UploaderNotFound if !uploader\n\n        # We found it. Build up the command and the args.\n        execute(uploader)\n        return 0\n      end\n\n      # Executes the uploader with the proper flags based on the configuration.\n      # This function shouldn't return since it will exec, but might return\n      # if we're on a system that doesn't support exec, so handle that properly.\n      def execute(uploader)\n        cmd = []\n        cmd << \"-debug\" if !Vagrant.log_level.nil?\n        cmd << \"-vcs\" if config.vcs\n        cmd += config.includes.map { |v| [\"-include\", v] }\n        cmd += config.excludes.map { |v| [\"-exclude\", v] }\n        cmd += metadata.map { |k,v| [\"-metadata\", \"#{k}=#{v}\"] }\n        cmd += [\"-address\", config.address] if config.address\n        cmd += [\"-token\", config.token] if config.token\n        cmd << config.app\n        cmd << File.expand_path(config.dir, env.root_path)\n        Vagrant::Util::SafeExec.exec(uploader, *cmd.flatten)\n      end\n\n      # This returns the path to the uploader binary, or nil if it can't\n      # be found.\n      #\n      # @return [String]\n      def uploader_path\n        # Determine the uploader path\n        if uploader = config.uploader_path\n          return uploader\n        end\n\n        if Vagrant.in_installer?\n          path = File.join(\n            Vagrant.installer_embedded_dir, \"bin\", UPLOADER_BIN)\n          return path if File.file?(path)\n        end\n\n        return Vagrant::Util::Which.which(UPLOADER_BIN)\n      end\n\n      # The metadata command for this push.\n      #\n      # @return [Array<String>]\n      def metadata\n        box     = env.vagrantfile.config.vm.box\n        box_url = env.vagrantfile.config.vm.box_url\n\n        result = {}\n\n        if !box.nil? && !box.empty?\n          result[\"box\"] = box\n        end\n\n        if !box_url.nil? && !box_url.empty?\n          result[\"box_url\"] = Array(box_url).first\n        end\n\n        return result\n      end\n\n      include Vagrant::Util::CommandDeprecation::Complete\n\n      def deprecation_command_name\n        \"push (atlas strategy)\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/ftp/adapter.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nmodule VagrantPlugins\n  module FTPPush\n    class Adapter\n      attr_reader :host\n      attr_reader :port\n      attr_reader :username\n      attr_reader :password\n      attr_reader :options\n      attr_reader :server\n\n      def initialize(host, username, password, options = {})\n        @host, @port = parse_host(host)\n        @username = username\n        @password = password\n        @options  = options\n        @server   = nil\n      end\n\n      # Parse the host into it's url and port parts.\n      # @return [Array]\n      def parse_host(host)\n        if host.include?(\":\")\n          split = host.split(\":\", 2)\n          [split[0], split[1].to_i]\n        else\n          [host, default_port]\n        end\n      end\n\n      def default_port\n        raise NotImplementedError\n      end\n\n      def connect(&block)\n        raise NotImplementedError\n      end\n\n      def upload(local, remote)\n        raise NotImplementedError\n      end\n    end\n\n    #\n    # The FTP Adapter\n    #\n    class FTPAdapter < Adapter\n      def initialize(*)\n        require \"net/ftp\"\n        super\n      end\n\n      def default_port\n        21\n      end\n\n      def connect(&block)\n        @server = Net::FTP.new\n        @server.passive = options.fetch(:passive, true)\n        @server.connect(host, port)\n        @server.login(username, password)\n\n        begin\n          yield self\n        ensure\n          @server.close\n        end\n      end\n\n      def upload(local, remote)\n        parent   = File.dirname(remote)\n        fullpath = Pathname.new(File.expand_path(parent, pwd))\n\n        # Create the parent directories if they does not exist (naive mkdir -p)\n        fullpath.descend do |path|\n          if !directory_exists?(path.to_s)\n            @server.mkdir(path.to_s)\n          end\n        end\n\n        # Upload the file\n        @server.putbinaryfile(local, remote)\n      end\n\n      def directory_exists?(path)\n        begin\n          @server.chdir(path)\n          return true\n        rescue Net::FTPPermError\n          return false\n        end\n      end\n\n      private\n\n      def pwd\n        @pwd ||= @server.pwd\n      end\n    end\n\n    #\n    # The SFTP Adapter\n    #\n    class SFTPAdapter < Adapter\n      def initialize(*)\n        require \"net/sftp\"\n        super\n        @dirs = {}\n      end\n\n      def default_port\n        22\n      end\n\n      def connect(&block)\n        Net::SFTP.start(@host, @username, password: @password, port: @port) do |server|\n          @server = server\n          yield self\n        end\n      end\n\n      def upload(local, remote)\n        dir = File.dirname(remote)\n\n        fullpath = Pathname.new(dir)\n        fullpath.descend do |path|\n          if @dirs[path.to_s].nil?\n            begin\n              @server.mkdir!(path.to_s)\n\n              # Cache visited directories in a list to avoid duplicate requests\n              @dirs[path.to_s] = true\n            rescue Net::SFTP::StatusException => e\n              # Directory exists, skip...\n            end\n          end\n        end\n\n        @server.upload!(local, remote)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/ftp/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module FTPPush\n    class Config < Vagrant.plugin(\"2\", :config)\n      # The (S)FTP host to use.\n      # @return [String]\n      attr_accessor :host\n\n      # The username to use for authentication with the (S)FTP server.\n      # @return [String]\n      attr_accessor :username\n\n      # The password to use for authentication with the (S)FTP server.\n      # @return [String]\n      attr_accessor :password\n\n      # Use passive FTP (default is true).\n      # @return [true, false]\n      attr_accessor :passive\n\n      # Use secure (SFTP) (default is false).\n      # @return [true, false]\n      attr_accessor :secure\n\n      # The root destination on the target system to sync the files (default is\n      # /).\n      # @return [String]\n      attr_accessor :destination\n\n      # Lists of files to include/exclude in what is uploaded. Exclude is\n      # always the last run filter, so if a file is matched in both include\n      # and exclude, it will be excluded.\n      #\n      # The value of the array elements should be a simple file glob relative\n      # to the directory being packaged.\n      # @return [Array<String>]\n      attr_accessor :includes\n      attr_accessor :excludes\n\n      # The base directory with file contents to upload. By default this\n      # is the same directory as the Vagrantfile, but you can specify this\n      # if you have a `src` folder or `bin` folder or some other folder\n      # you want to upload.\n      # @return [String]\n      attr_accessor :dir\n\n      def initialize\n        @host = UNSET_VALUE\n        @username = UNSET_VALUE\n        @password = UNSET_VALUE\n        @passive = UNSET_VALUE\n        @secure = UNSET_VALUE\n        @destination = UNSET_VALUE\n\n        @includes = []\n        @excludes = []\n\n        @dir = UNSET_VALUE\n      end\n\n      def merge(other)\n        super.tap do |result|\n          result.includes = self.includes.dup.concat(other.includes).uniq\n          result.excludes = self.excludes.dup.concat(other.excludes).uniq\n        end\n      end\n\n      def finalize!\n        @host = nil if @host == UNSET_VALUE\n        @username = nil if @username == UNSET_VALUE\n        @password = nil if @password == UNSET_VALUE\n        @passive = true if @passive == UNSET_VALUE\n        @secure = false if @secure == UNSET_VALUE\n        @destination = \"/\" if @destination == UNSET_VALUE\n        @dir = \".\" if @dir == UNSET_VALUE\n      end\n\n      def validate(machine)\n        errors = _detected_errors\n\n        if missing?(@host)\n          errors << I18n.t(\"ftp_push.errors.missing_attribute\",\n            attribute: \"host\",\n          )\n        end\n\n        if missing?(@username)\n          errors << I18n.t(\"ftp_push.errors.missing_attribute\",\n            attribute: \"username\",\n          )\n        end\n\n        if missing?(@destination)\n          errors << I18n.t(\"ftp_push.errors.missing_attribute\",\n            attribute: \"destination\",\n          )\n        end\n\n        if missing?(@dir)\n          errors << I18n.t(\"ftp_push.errors.missing_attribute\",\n            attribute: \"dir\",\n          )\n        end\n\n        { \"FTP push\" => errors }\n      end\n\n      # Add the filepath to the list of includes\n      # @param [String] filepath\n      def include(filepath)\n        @includes << filepath\n      end\n      alias_method :include=, :include\n\n      # Add the filepath to the list of excludes\n      # @param [String] filepath\n      def exclude(filepath)\n        @excludes << filepath\n      end\n      alias_method :exclude=, :exclude\n\n      private\n\n      # Determine if the given string is \"missing\" (blank)\n      # @return [true, false]\n      def missing?(obj)\n        obj.to_s.strip.empty?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/ftp/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module FTPPush\n    module Errors\n      class Error < Vagrant::Errors::VagrantError\n        error_namespace(\"ftp_push.errors\")\n      end\n\n      class TooManyFiles < Error\n        error_key(:too_many_files)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/ftp/locales/en.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  ftp_push:\n    errors:\n      missing_attribute: |-\n        Missing required attribute '%{attribute}'. The Vagrant FTP Push plugin\n        requires you set this attribute. Please set this attribute in your\n        Vagrantfile, for example:\n\n            config.push.define \"ftp\" do |push|\n              push.%{attribute} = \"...\"\n            end\n      too_many_files: |-\n        The configured directory for Vagrant FTP push contains too many files\n        to successfully complete the command. This can be resolved by either\n        removing extraneous files from the configured directory, or updating\n        the `dir` configuration option to a subdirectory.\n"
  },
  {
    "path": "plugins/pushes/ftp/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module FTPPush\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"ftp\"\n      description <<-DESC\n      Deploy to a remote FTP or SFTP server.\n      DESC\n\n      config(:ftp, :push) do\n        require File.expand_path(\"../config\", __FILE__)\n        init!\n        Config\n      end\n\n      push(:ftp) do\n        require File.expand_path(\"../push\", __FILE__)\n        init!\n        Push\n      end\n\n      protected\n\n      def self.init!\n        return if defined?(@_init)\n        I18n.load_path << File.expand_path(\"../locales/en.yml\", __FILE__)\n        I18n.reload!\n        @_init = true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/ftp/push.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"net/ftp\"\nrequire \"pathname\"\n\nrequire_relative \"adapter\"\nrequire_relative \"errors\"\n\nmodule VagrantPlugins\n  module FTPPush\n    class Push < Vagrant.plugin(\"2\", :push)\n      IGNORED_FILES = %w(. ..).freeze\n      DEFAULT_EXCLUDES = %w(.git .hg .svn .vagrant).freeze\n\n      def initialize(*)\n        super\n        @logger = Log4r::Logger.new(\"vagrant::pushes::ftp\")\n      end\n\n      def push\n        # Grab files early so if there's an exception or issue, we don't have to\n        # wait and close the (S)FTP connection as well\n        files = nil\n        begin\n          files = Hash[*all_files.flat_map do |file|\n              relative_path = relative_path_for(file, base_dir)\n              destination = File.join(config.destination, relative_path)\n              file = File.expand_path(file, env.root_path)\n              [file, destination]\n            end]\n        rescue SystemStackError\n          raise Errors::TooManyFiles\n        end\n\n        ftp = \"#{config.username}@#{config.host}:#{config.destination}\"\n        env.ui.info \"Uploading #{files.length} files to #{env.root_path} to #{ftp}\"\n\n        connect do |ftp|\n          files.each do |local, remote|\n            env.ui.info \"Uploading #{local} => #{remote}\"\n            ftp.upload(local, remote)\n          end\n        end\n      end\n\n      # Helper method for creating the FTP or SFTP connection.\n      # @yield [Adapter]\n      def connect(&block)\n        klass = config.secure ? SFTPAdapter : FTPAdapter\n        ftp = klass.new(config.host, config.username, config.password,\n          passive: config.passive)\n        ftp.connect(&block)\n      end\n\n      # The list of all files that should be pushed by this push. This method\n      # only returns **files**, not folders or symlinks!\n      # @return [Array<String>]\n      def all_files\n        files = glob(\"#{base_dir}/**/*\") + includes_files\n        filter_excludes!(files, config.excludes)\n        files.reject! { |f| !File.file?(f) }\n        files\n      end\n\n      # The list of files to include in addition to those specified in `dir`.\n      # @return [Array<String>]\n      def includes_files\n        includes = config.includes.flat_map do |i|\n          path = absolute_path_for(i, base_dir)\n          [path, \"#{path}/**/*\"]\n        end\n\n        glob(\"{#{includes.join(\",\")}}\")\n      end\n\n      # Filter the excludes out of the given list. This method modifies the\n      # given list in memory!\n      #\n      # @param [Array<String>] list\n      #   the filepaths\n      # @param [Array<String>] excludes\n      #   the exclude patterns or files\n      def filter_excludes!(list, excludes)\n        excludes = Array(excludes)\n        excludes = excludes + DEFAULT_EXCLUDES\n        excludes = excludes.flat_map { |e| [e, \"#{e}/*\"] }\n\n        list.reject! do |file|\n          basename = relative_path_for(file, config.dir)\n\n          # Handle the special case where the file is outside of the working\n          # directory...\n          if basename.start_with?(\"../\")\n            basename = file\n          end\n\n          excludes.any? { |e| File.fnmatch?(e, basename, File::FNM_DOTMATCH) }\n        end\n      end\n\n      # Get the list of files that match the given pattern.\n      # @return [Array<String>]\n      def glob(pattern)\n        Dir.glob(pattern, File::FNM_DOTMATCH).sort.reject do |file|\n          IGNORED_FILES.include?(File.basename(file))\n        end\n      end\n\n      # The absolute path to the given `path` and `parent`, unless the given\n      # path is absolute.\n      # @return [String]\n      def absolute_path_for(path, parent)\n        path = Pathname.new(path)\n        return path if path.absolute?\n        File.expand_path(path, parent)\n      end\n\n      # The relative path from the given `parent`. If files exist on another\n      # device, this will probably blow up.\n      # @return [String]\n      def relative_path_for(path, parent)\n        Pathname.new(path).relative_path_from(Pathname.new(parent)).to_s\n      rescue ArgumentError\n        return path\n      end\n\n      def base_dir\n        if Pathname.new(config.dir).absolute?\n          config.dir\n        else\n          File.join(File.expand_path(env.root_path), config.dir)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/heroku/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HerokuPush\n    class Config < Vagrant.plugin(\"2\", :config)\n      # The name of the Heroku application to push to.\n      # @return [String]\n      attr_accessor :app\n\n      # The base directory with file contents to upload. By default this\n      # is the same directory as the Vagrantfile, but you can specify this\n      # if you have a `src` folder or `bin` folder or some other folder\n      # you want to upload. This directory must be a git repository.\n      # @return [String]\n      attr_accessor :dir\n\n      # The path to the git binary to shell out to. This usually is only set for\n      # debugging/development. If not set, the git bin will be searched for\n      # in the PATH.\n      # @return [String]\n      attr_accessor :git_bin\n\n      # The Git remote to push to (default: \"heroku\").\n      # @return [String]\n      attr_accessor :remote\n\n      def initialize\n        @app = UNSET_VALUE\n        @dir = UNSET_VALUE\n\n        @git_bin = UNSET_VALUE\n        @remote = UNSET_VALUE\n      end\n\n      def finalize!\n        @app = nil if @app == UNSET_VALUE\n        @dir = \".\" if @dir == UNSET_VALUE\n\n        @git_bin = \"git\" if @git_bin == UNSET_VALUE\n        @remote = \"heroku\" if @remote == UNSET_VALUE\n      end\n\n      def validate(machine)\n        errors = _detected_errors\n\n        if missing?(@dir)\n          errors << I18n.t(\"heroku_push.errors.missing_attribute\",\n            attribute: \"dir\",\n          )\n        end\n\n        if missing?(@git_bin)\n          errors << I18n.t(\"heroku_push.errors.missing_attribute\",\n            attribute: \"git_bin\",\n          )\n        end\n\n        if missing?(@remote)\n          errors << I18n.t(\"heroku_push.errors.missing_attribute\",\n            attribute: \"remote\",\n          )\n        end\n\n        { \"Heroku push\" => errors }\n      end\n\n      private\n\n      # Determine if the given string is \"missing\" (blank)\n      # @return [true, false]\n      def missing?(obj)\n        obj.to_s.strip.empty?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/heroku/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module HerokuPush\n    module Errors\n      class Error < Vagrant::Errors::VagrantError\n        error_namespace(\"heroku_push.errors\")\n      end\n\n      class CommandFailed < Error\n        error_key(:command_failed)\n      end\n\n      class GitNotFound < Error\n        error_key(:git_not_found)\n      end\n\n      class NotAGitRepo < Error\n        error_key(:not_a_git_repo)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/heroku/locales/en.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  heroku_push:\n    errors:\n      command_failed: |-\n        The following command exited with a non-zero exit status:\n\n            %{cmd}\n\n        stdout: %{stdout}\n        stderr: %{stderr}\n      git_not_found: |-\n        The Git binary '%{bin}' could not be found. Please ensure you\n        have downloaded and installed the latest version of Git:\n\n            https://git-scm.com/downloads\n      missing_attribute: |-\n        Missing required attribute '%{attribute}'. The Vagrant Heroku Push\n        plugin requires you set this attribute. Please set this attribute in\n        your Vagrantfile, for example:\n\n            config.push.define \"heroku\" do |push|\n              push.%{attribute} = \"...\"\n            end\n      not_a_git_repo: |-\n        The following path is not a valid Git repository:\n\n            %{path}\n\n        Please ensure you are working in the correct directory. In order to use\n        the Vagrant Heroku Push plugin, you must have a git repository.\n"
  },
  {
    "path": "plugins/pushes/heroku/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module HerokuPush\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"heroku\"\n      description <<-DESC\n      Deploy to a Heroku\n      DESC\n\n      config(:heroku, :push) do\n        require File.expand_path(\"../config\", __FILE__)\n        init!\n        Config\n      end\n\n      push(:heroku) do\n        require File.expand_path(\"../push\", __FILE__)\n        init!\n        Push\n      end\n\n      protected\n\n      def self.init!\n        return if defined?(@_init)\n        I18n.load_path << File.expand_path(\"../locales/en.yml\", __FILE__)\n        I18n.reload!\n        @_init = true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/heroku/push.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant/util/subprocess\"\nrequire \"vagrant/util/which\"\n\nrequire_relative \"errors\"\n\nmodule VagrantPlugins\n  module HerokuPush\n    class Push < Vagrant.plugin(\"2\", :push)\n      def push\n        # Expand any paths relative to the root\n        dir = File.expand_path(config.dir, env.root_path)\n\n        # Verify git is installed\n        verify_git_bin!(config.git_bin)\n\n        # Verify we are operating in a git repo\n        verify_git_repo!(dir)\n\n        # Get the current branch\n        branch = git_branch(dir)\n\n        # Get the name of the app\n        app = config.app || interpret_app(dir)\n\n        # Check if we need to add the git remote\n        if !has_git_remote?(config.remote, dir)\n          add_heroku_git_remote(config.remote, app, dir)\n        end\n\n        # Push to Heroku\n        git_push_heroku(config.remote, branch, dir)\n      end\n\n      # Verify that git is installed.\n      # @raise [Errors::GitNotFound]\n      def verify_git_bin!(path)\n        if Vagrant::Util::Which.which(path).nil?\n          raise Errors::GitNotFound, bin: path\n        end\n      end\n\n      # Verify that the given path is a git directory.\n      # @raise [Errors::NotAGitRepo]\n      # @param [String]\n      def verify_git_repo!(path)\n        if !File.directory?(git_dir(path))\n          raise Errors::NotAGitRepo, path: path\n        end\n      end\n\n      # Interpret the name of the Heroku application from the given path.\n      # @param [String] path\n      # @return [String]\n      def interpret_app(path)\n        File.basename(path)\n      end\n\n      # The git directory for the given path.\n      # @param [String] path\n      # @return [String]\n      def git_dir(path)\n        \"#{path}/.git\"\n      end\n\n      # The name of the current git branch.\n      # @param [String] path\n      # @return [String]\n      def git_branch(path)\n        result = execute!(\"git\",\n          \"--git-dir\", git_dir(path),\n          \"--work-tree\", path,\n          \"symbolic-ref\",\n          \"HEAD\",\n        )\n\n        # Returns something like \"* master\"\n        result.stdout.sub(\"*\", \"\").strip\n      end\n\n      # Push to the Heroku remote.\n      # @param [String] remote\n      # @param [String] branch\n      def git_push_heroku(remote, branch, path)\n        execute!(\"git\",\n          \"--git-dir\", git_dir(path),\n          \"--work-tree\", path,\n          \"push\", remote, \"#{branch}:master\",\n        )\n      end\n\n      # Check if the git remote has the given remote.\n      # @param [String] remote\n      # @return [true, false]\n      def has_git_remote?(remote, path)\n        result = execute!(\"git\",\n          \"--git-dir\", git_dir(path),\n          \"--work-tree\", path,\n          \"remote\",\n        )\n        remotes = result.stdout.split(/\\r?\\n/).map(&:strip)\n        remotes.include?(remote.to_s)\n      end\n\n      # Add the Heroku to the current repository.\n      # @param [String] remote\n      # @param [String] app\n      def add_heroku_git_remote(remote, app, path)\n        execute!(\"git\",\n          \"--git-dir\", git_dir(path),\n          \"--work-tree\", path,\n          \"remote\", \"add\", remote, heroku_git_url(app),\n        )\n      end\n\n      # The URL for this project on Heroku.\n      # @return [String]\n      def heroku_git_url(app)\n        \"git@heroku.com:#{app}.git\"\n      end\n\n      # Execute the command, raising an exception if it fails.\n      # @return [Vagrant::Util::Subprocess::Result]\n      def execute!(*cmd)\n        subproccmd = cmd.dup << { notify: [:stdout, :stderr] }\n        result = Vagrant::Util::Subprocess.execute(*subproccmd) do |type, data|\n          if type == :stdout\n            @env.ui.info(data, new_line: false)\n          elsif type == :stderr\n            @env.ui.warn(data, new_line: false)\n          end\n        end\n\n        if result.exit_code != 0\n          raise Errors::CommandFailed,\n            cmd:    cmd.join(\" \"),\n            stdout: result.stdout,\n            stderr: result.stderr\n        end\n\n        result\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/local-exec/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module LocalExecPush\n    class Config < Vagrant.plugin(\"2\", :config)\n      # The path (relative to the machine root) to a local script that will be\n      # executed.\n      # @return [String]\n      attr_accessor :script\n\n      # The command (as a string) to execute.\n      # @return [String]\n      attr_accessor :inline\n\n      # The arguments to provide when executing the script.\n      # @return [Array<String>]\n      attr_accessor :args\n\n      def initialize\n        @script = UNSET_VALUE\n        @inline = UNSET_VALUE\n        @args   = UNSET_VALUE\n      end\n\n      def finalize!\n        @script = nil if @script == UNSET_VALUE\n        @inline = nil if @inline == UNSET_VALUE\n        @args   = nil if @args == UNSET_VALUE\n\n        if @args && args_valid?\n          @args = @args.is_a?(Array) ? @args.map { |a| a.to_s } : @args.to_s\n        end\n      end\n\n      def validate(machine)\n        errors = _detected_errors\n\n        if missing?(@script) && missing?(@inline)\n          errors << I18n.t(\"local_exec_push.errors.missing_attribute\",\n            attribute: \"script\",\n          )\n        end\n\n        if !missing?(@script) && !missing?(@inline)\n          errors << I18n.t(\"local_exec_push.errors.cannot_specify_script_and_inline\")\n        end\n\n        if !args_valid?\n          errors << I18n.t(\"local_exec_push.errors.args_bad_type\")\n        end\n\n        { \"Local Exec push\" => errors }\n      end\n\n      private\n\n      # Determine if the given string is \"missing\" (blank)\n      # @return [true, false]\n      def missing?(obj)\n        obj.to_s.strip.empty?\n      end\n\n      # Args are optional, but if they're provided we only support them as a\n      # string or as an array.\n      def args_valid?\n        return true if !args\n        return true if args.is_a?(String)\n        return true if args.is_a?(Integer)\n        if args.is_a?(Array)\n          args.each do |a|\n            return false if !a.kind_of?(String) && !a.kind_of?(Integer)\n          end\n\n          return true\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/local-exec/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module LocalExecPush\n    module Errors\n      class Error < Vagrant::Errors::VagrantError\n        error_namespace(\"local_exec_push.errors\")\n      end\n\n      class CommandFailed < Error\n        error_key(:command_failed)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/local-exec/locales/en.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  local_exec_push:\n    errors:\n      cannot_specify_script_and_inline: |-\n        You have specified both the 'script' and 'inline' attributes for the\n        Vagrant Local Exec Push plugin. You may only specify one of these\n        attributes.\n      command_failed: |-\n        The following command exited with a non-zero exit status:\n\n            %{cmd}\n\n        stdout: %{stdout}\n        stderr: %{stderr}\n      missing_attribute: |-\n        Missing required attribute '%{attribute}'. The Vagrant Local Exec Push\n        plugin requires you set this attribute. Please set this attribute in\n        your Vagrantfile, for example:\n\n            config.push.define \"local-exec\" do |push|\n              push.%{attribute} = \"...\"\n            end\n      args_bad_type: \"Local-exec push `args` must be a string or array.\""
  },
  {
    "path": "plugins/pushes/local-exec/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module LocalExecPush\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"local-exec\"\n      description <<-DESC\n      Run a local command or script to push\n      DESC\n\n      config(:\"local-exec\", :push) do\n        require File.expand_path(\"../config\", __FILE__)\n        init!\n        Config\n      end\n\n      push(:\"local-exec\") do\n        require File.expand_path(\"../push\", __FILE__)\n        init!\n        Push\n      end\n\n      protected\n\n      def self.init!\n        return if defined?(@_init)\n        I18n.load_path << File.expand_path(\"../locales/en.yml\", __FILE__)\n        I18n.reload!\n        @_init = true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/local-exec/push.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\nrequire \"tempfile\"\nrequire \"vagrant/util/safe_exec\"\n\nrequire_relative \"errors\"\n\nmodule VagrantPlugins\n  module LocalExecPush\n    class Push < Vagrant.plugin(\"2\", :push)\n      @@logger = Log4r::Logger.new(\"vagrant::push::local_exec\")\n\n      def push\n        if config.inline\n          execute_inline!(config.inline, config.args)\n        else\n          execute_script!(config.script, config.args)\n        end\n      end\n\n      # Execute the inline script by writing it to a tempfile and executing.\n      def execute_inline!(inline, args)\n        script = Tempfile.new([\"vagrant-local-exec-script\", \".sh\"])\n        script.write(inline)\n        script.rewind\n        script.close\n\n        execute_script!(script.path, args)\n      ensure\n        if script\n          script.close\n          script.unlink\n        end\n      end\n\n      # Execute the script, expanding the path relative to the current env root.\n      def execute_script!(path, args)\n        path = File.expand_path(path, env.root_path)\n        FileUtils.chmod(\"+x\", path)\n\n        if args.is_a?(String)\n          args = \" #{args.to_s}\"\n        elsif args.is_a?(Array)\n          args = args.map { |a| quote_and_escape(a) }\n          args = \" #{args.join(\" \")}\"\n        end\n\n        execute!(\"#{path}#{args}\")\n      end\n\n      # Execute the script, raising an exception if it fails.\n      def execute!(*cmd)\n        if Vagrant::Util::Platform.windows?\n          execute_subprocess!(*cmd)\n        else\n          execute_exec!(*cmd)\n        end\n      end\n\n      private\n\n      # Quote and escape strings for shell execution, thanks to Capistrano.\n      def quote_and_escape(text, quote = '\"')\n        \"#{quote}#{text.gsub(/#{quote}/) { |m| \"#{m}\\\\#{m}#{m}\" }}#{quote}\"\n      end\n\n      # Run the command as exec (unix).\n      def execute_exec!(*cmd)\n        @@logger.debug(\"executing command via exec: #{cmd.inspect}\")\n        Vagrant::Util::SafeExec.exec(cmd[0], *cmd[1..-1])\n      end\n\n      # Run the command as a subprocess (windows).\n      def execute_subprocess!(*cmd)\n        @@logger.debug(\"executing command via subprocess: #{cmd.inspect}\")\n        cmd = cmd.dup << { notify: [:stdout, :stderr] }\n        result = Vagrant::Util::Subprocess.execute(*cmd) do |type, data|\n          if type == :stdout\n            @env.ui.info(data, new_line: false)\n          elsif type == :stderr\n            @env.ui.warn(data, new_line: false)\n          end\n        end\n\n        Kernel.exit(result.exit_code)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/noop/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module NoopDeploy\n    class Config < Vagrant.plugin(\"2\", :config)\n      def initialize\n      end\n\n      def finalize!\n      end\n\n      def validate(machine)\n        errors = _detected_errors\n        { \"Noop push\" => errors }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/noop/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module NoopDeploy\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"noop\"\n      description <<-DESC\n      Literally do nothing\n      DESC\n\n      config(:noop, :push) do\n        require File.expand_path(\"../config\", __FILE__)\n        Config\n      end\n\n      push(:noop) do\n        require File.expand_path(\"../push\", __FILE__)\n        Push\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/pushes/noop/push.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module NoopDeploy\n    class Push < Vagrant.plugin(\"2\", :push)\n      def push\n        puts \"pushed\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/nfs/action_cleanup.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nmodule VagrantPlugins\n  module SyncedFolderNFS\n    class ActionCleanup\n      def initialize(app, env)\n        @app    = app\n        @logger = Log4r::Logger.new(\"vagrant::synced_folders::nfs\")\n      end\n\n      def call(env)\n        if !env[:nfs_valid_ids]\n          @logger.warn(\"nfs_valid_ids not set, cleanup cannot occur\")\n          return @app.call(env)\n        end\n\n        if !env[:machine].env.host.capability?(:nfs_prune)\n          @logger.info(\"Host doesn't support pruning NFS. Skipping.\")\n          return @app.call(env)\n        end\n\n        @logger.info(\"NFS pruning. Valid IDs: #{env[:nfs_valid_ids].inspect}\")\n        env[:machine].env.host.capability(\n          :nfs_prune, env[:machine].ui, env[:nfs_valid_ids])\n        @app.call(env)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/nfs/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module SyncedFolderNFS\n    class Config < Vagrant.plugin(\"2\", :config)\n      attr_accessor :functional\n      attr_accessor :map_uid\n      attr_accessor :map_gid\n      attr_accessor :verify_installed\n\n      def initialize\n        super\n\n        @functional = UNSET_VALUE\n        @map_uid    = UNSET_VALUE\n        @map_gid    = UNSET_VALUE\n        @verify_installed = UNSET_VALUE\n      end\n\n      def finalize!\n        @functional = true if @functional == UNSET_VALUE\n        @map_uid = :auto if @map_uid == UNSET_VALUE\n        @map_gid = :auto if @map_gid == UNSET_VALUE\n        @verify_installed = true if @verify_installed == UNSET_VALUE\n      end\n\n      def to_s\n        \"NFS\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/nfs/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module SyncedFolderNFS\n    # This plugin implements NFS synced folders. In order to take advantage\n    # of NFS synced folders, some provider-specific assistance is required.\n    # Within the middleware sequences, some data must be put into the\n    # environment state bag:\n    #\n    #   * `nfs_host_ip` (string) - The IP of the host machine that the NFS\n    #     client in the machine should talk to.\n    #   * `nfs_machine_ip` (string) - The IP of the guest machine that the NFS\n    #     server should serve the folders to.\n    #   * `nfs_valid_ids` (array of strings) - A list of IDs that are \"valid\"\n    #     and should not be pruned. The synced folder implementation will\n    #     regularly prune NFS exports of invalid IDs.\n    #\n    # If any of these variables are not set, an internal exception will be\n    # raised.\n    #\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"NFS synced folders\"\n      description <<-EOF\n      The NFS synced folders plugin enables you to use NFS as a synced folder\n      implementation.\n      EOF\n\n      config(\"nfs\") do\n        require_relative \"config\"\n        Config\n      end\n\n      synced_folder(\"nfs\", 5) do\n        require_relative \"synced_folder\"\n        SyncedFolder\n      end\n\n      action_hook(\"nfs_cleanup\") do |hook|\n        require_relative \"action_cleanup\"\n        hook.before(\n          Vagrant::Action::Builtin::SyncedFolderCleanup,\n          ActionCleanup)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/nfs/synced_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'fileutils'\nrequire 'thread'\nrequire 'zlib'\n\nrequire \"log4r\"\n\nrequire \"vagrant/util/platform\"\n\nmodule VagrantPlugins\n  module SyncedFolderNFS\n    # This synced folder requires that two keys be set on the environment\n    # within the middleware sequence:\n    #\n    #   - `:nfs_host_ip` - The IP of where to mount the NFS folder from.\n    #   - `:nfs_machine_ip` - The IP of the machine where the NFS folder\n    #     will be mounted.\n    #\n    class SyncedFolder < Vagrant.plugin(\"2\", :synced_folder)\n      @@lock = Mutex.new\n\n      def initialize(*args)\n        super\n\n        @logger = Log4r::Logger.new(\"vagrant::synced_folders::nfs\")\n      end\n\n      def usable?(machine, raise_error=false)\n        # If the machine explicitly said NFS is not supported, then\n        # it isn't supported.\n        if !machine.config.nfs.functional\n          return false\n        end\n        if machine.env.host.capability?(:nfs_installed)\n          return true if machine.env.host.capability(:nfs_installed)\n        end\n        return false if !raise_error\n        raise Vagrant::Errors::NFSNotSupported\n      end\n\n      def prepare(machine, folders, opts)\n        # Nothing is necessary to do before VM boot.\n      end\n\n      def enable(machine, folders, nfsopts)\n        raise Vagrant::Errors::NFSNoHostIP if !nfsopts[:nfs_host_ip]\n        raise Vagrant::Errors::NFSNoGuestIP if !nfsopts[:nfs_machine_ip]\n\n        if machine.config.nfs.verify_installed\n          if machine.guest.capability?(:nfs_client_installed)\n            installed = machine.guest.capability(:nfs_client_installed)\n            if !installed\n              can_install = machine.guest.capability?(:nfs_client_install)\n              raise Vagrant::Errors::NFSClientNotInstalledInGuest if !can_install\n              machine.ui.info I18n.t(\"vagrant.actions.vm.nfs.installing\")\n              machine.guest.capability(:nfs_client_install)\n            end\n          end\n        end\n\n        machine_ip = nfsopts[:nfs_machine_ip]\n        machine_ip = [machine_ip] if !machine_ip.is_a?(Array)\n\n        # Prepare the folder, this means setting up various options\n        # and such on the folder itself.\n        folders.each { |id, opts| prepare_folder(machine, opts) }\n\n        # Determine what folders we'll export\n        export_folders = folders.dup\n        export_folders.keys.each do |id|\n          opts = export_folders[id]\n          if opts.key?(:nfs_export) && !opts[:nfs_export]\n            export_folders.delete(id)\n          end\n        end\n\n        # Update the exports when there are actually exports [GH-4148]\n        if !export_folders.empty?\n          # Export the folders. We do this with a class-wide lock because\n          # NFS exporting often requires sudo privilege and we don't want\n          # overlapping input requests. [GH-2680]\n          @@lock.synchronize do\n            begin\n              machine.env.lock(\"nfs-export\") do\n                machine.ui.info I18n.t(\"vagrant.actions.vm.nfs.exporting\")\n                machine.env.host.capability(\n                  :nfs_export,\n                  machine.ui, machine.id, machine_ip, export_folders)\n              end\n            rescue Vagrant::Errors::EnvironmentLockedError\n              sleep 1\n              retry\n            end\n          end\n        end\n\n        # Mount\n        machine.ui.info I18n.t(\"vagrant.actions.vm.nfs.mounting\")\n\n        # Only mount folders that have a guest path specified.\n        mount_folders = {}\n        folders.each do |id, opts|\n          mount_folders[id] = opts.dup if opts[:guestpath]\n\n          machine.ui.detail(I18n.t(\"vagrant.actions.vm.nfs.mounting_entry\",\n            guestpath: opts[:guestpath],\n            hostpath: opts[:hostpath]\n          ))\n        end\n\n        # Mount them!\n        if machine.guest.capability?(:nfs_pre)\n          machine.guest.capability(:nfs_pre)\n        end\n\n        machine.guest.capability(:mount_nfs_folder,\n          nfsopts[:nfs_host_ip], mount_folders)\n\n        if machine.guest.capability?(:nfs_post)\n          machine.guest.capability(:nfs_post)\n        end\n      end\n\n      def cleanup(machine, opts)\n        ids = opts[:nfs_valid_ids]\n        raise Vagrant::Errors::NFSNoValidIds if !ids\n\n        # Prune any of the unused machines\n        @logger.info(\"NFS pruning. Valid IDs: #{ids.inspect}\")\n        machine.env.host.capability(:nfs_prune, machine.ui, ids)\n      end\n\n      protected\n\n      def prepare_folder(machine, opts)\n        opts[:map_uid] = prepare_permission(machine, :uid, opts)\n        opts[:map_gid] = prepare_permission(machine, :gid, opts)\n        opts[:nfs_version] ||= 3\n        if !opts.key?(:nfs_udp)\n          opts[:nfs_udp] = !opts[:nfs_version].to_s.start_with?('4')\n        end\n\n        if opts[:nfs_version].to_s.start_with?('4') && opts[:nfs_udp]\n          machine.ui.info I18n.t(\"vagrant.actions.vm.nfs.v4_with_udp_warning\")\n        end\n\n        # We use a CRC32 to generate a 32-bit checksum so that the\n        # fsid is compatible with both old and new kernels.\n        opts[:uuid] = Zlib.crc32(opts[:hostpath]).to_s\n      end\n\n      # Prepares the UID/GID settings for a single folder.\n      def prepare_permission(machine, perm, opts)\n        key = \"map_#{perm}\".to_sym\n        return nil if opts.key?(key) && opts[key].nil?\n\n        # The options on the hash get priority, then the default\n        # values\n        value = opts.key?(key) ? opts[key] : machine.config.nfs.send(key)\n        return value if value != :auto\n\n        # Get UID/GID from folder if we've made it this far\n        # (value == :auto)\n        stat = File.stat(opts[:hostpath])\n        return stat.send(perm)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/rsync/command/rsync.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'optparse'\n\nrequire \"vagrant/action/builtin/mixin_synced_folders\"\n\nrequire_relative \"../helper\"\n\nmodule VagrantPlugins\n  module SyncedFolderRSync\n    module Command\n      class Rsync < Vagrant.plugin(\"2\", :command)\n        include Vagrant::Action::Builtin::MixinSyncedFolders\n\n        def self.synopsis\n          \"syncs rsync synced folders to remote machine\"\n        end\n\n        def execute\n          options = {}\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant rsync [vm-name]\"\n            o.separator \"\"\n            o.separator \"This command forces any synced folders with type 'rsync' to sync.\"\n            o.separator \"RSync is not an automatic sync so a manual command is used.\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n            o.on(\"--[no-]rsync-chown\", \"Use rsync to modify ownership\") do |chown|\n              options[:rsync_chown] = chown\n            end\n          end\n\n          # Parse the options and return if we don't have any target.\n          argv = parse_options(opts)\n          return if !argv\n\n          # Go through each machine and perform the rsync\n          error = false\n          with_target_vms(argv) do |machine|\n            if machine.provider.capability?(:proxy_machine)\n              proxy = machine.provider.capability(:proxy_machine)\n              if proxy\n                machine.ui.warn(I18n.t(\n                  \"vagrant.rsync_proxy_machine\",\n                  name: machine.name.to_s,\n                  provider: machine.provider_name.to_s))\n\n                machine = proxy\n              end\n            end\n\n            if !machine.communicate.ready?\n              machine.ui.error(I18n.t(\"vagrant.rsync_communicator_not_ready\"))\n              error = true\n              next\n            end\n\n            # Determine the rsync synced folders for this machine\n            folders = synced_folders(machine, cached: true)[:rsync]\n            next if !folders || folders.empty?\n\n            # Get the SSH info for this machine so we can access it\n            ssh_info = machine.ssh_info\n\n            # Sync them!\n            folders.each do |id, folder_opts|\n              if options.has_key?(:rsync_chown)\n                folder_opts = folder_opts.merge(rsync_ownership: options[:rsync_chown])\n              end\n              RsyncHelper.rsync_single(machine, ssh_info, folder_opts)\n            end\n          end\n\n          return error ? 1 : 0\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/rsync/command/rsync_auto.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\nrequire 'optparse'\nrequire \"thread\"\n\nrequire \"vagrant/action/builtin/mixin_synced_folders\"\nrequire \"vagrant/util/busy\"\nrequire \"vagrant/util/platform\"\n\nrequire_relative \"../helper\"\n\nrequire \"listen\"\n\nmodule VagrantPlugins\n  module SyncedFolderRSync\n    module Command\n      class RsyncAuto < Vagrant.plugin(\"2\", :command)\n        include Vagrant::Action::Builtin::MixinSyncedFolders\n\n        def self.synopsis\n          \"syncs rsync synced folders automatically when files change\"\n        end\n\n        def execute\n          @logger = Log4r::Logger.new(\"vagrant::commands::rsync-auto\")\n\n          options = {}\n          opts = OptionParser.new do |o|\n            o.banner = \"Usage: vagrant rsync-auto [vm-name]\"\n            o.separator \"\"\n            o.separator \"Options:\"\n            o.separator \"\"\n\n            o.on(\"--[no-]poll\", \"Force polling filesystem (slow)\") do |poll|\n              options[:poll] = poll\n            end\n\n            o.on(\"--[no-]rsync-chown\", \"Use rsync to modify ownership\") do |chown|\n              options[:rsync_chown] = chown\n            end\n          end\n\n          # Parse the options and return if we don't have any target.\n          argv = parse_options(opts)\n          return if !argv\n\n          # Build up the paths that we need to listen to.\n          paths = {}\n          ignores = []\n          with_target_vms(argv) do |machine|\n            next if machine.state.id == :not_created\n            cwd = machine.env.cwd.to_s\n\n            if machine.provider.capability?(:proxy_machine)\n              proxy = machine.provider.capability(:proxy_machine)\n              if proxy\n                machine.ui.warn(I18n.t(\n                  \"vagrant.rsync_proxy_machine\",\n                  name: machine.name.to_s,\n                  provider: machine.provider_name.to_s))\n\n                machine = proxy\n              end\n            end\n\n            cached = synced_folders(machine, cached: true)\n            fresh  = synced_folders(machine)\n            diff   = synced_folders_diff(cached, fresh)\n            if !diff[:added].empty?\n              machine.ui.warn(I18n.t(\"vagrant.rsync_auto_new_folders\"))\n            end\n\n            folders = cached[:rsync]\n            next if !folders || folders.empty?\n\n            # NOTE: This check is required with boot2docker since all containers\n            # share the same virtual machine. This prevents rsync-auto from\n            # syncing all known containers with rsync to the boot2docker vm\n            # and only syncs the current working dirs folders.\n            sync_folders = {}\n            # Still sync existing synced folders from vagrantfile\n            config_synced_folders = machine.config.vm.synced_folders.values.map { |x| x[:hostpath] }\n            config_synced_folders.map! { |x| File.expand_path(x, machine.env.root_path) }\n            folders.each do |id, folder_opts|\n              if cwd != folder_opts[:hostpath] &&\n                  !config_synced_folders.include?(folder_opts[:hostpath])\n\n                machine.ui.info(I18n.t(\"vagrant.rsync_auto_remove_folder\",\n                                    folder: folder_opts[:hostpath]))\n              else\n                if options.has_key?(:rsync_chown)\n                  folder_opts = folder_opts.merge(rsync_ownership: options[:rsync_chown])\n                end\n                sync_folders[id] = folder_opts\n              end\n            end\n            folders = sync_folders\n\n            # Get the SSH info for this machine so we can do an initial\n            # sync to the VM.\n            ssh_info = machine.ssh_info\n            if ssh_info\n              machine.ui.info(I18n.t(\"vagrant.rsync_auto_initial\"))\n              folders.each do |id, folder_opts|\n                RsyncHelper.rsync_single(machine, ssh_info, folder_opts)\n              end\n            end\n\n            folders.each do |id, folder_opts|\n              # If we marked this folder to not auto sync, then\n              # don't do it.\n              next if folder_opts.key?(:auto) && !folder_opts[:auto]\n\n              hostpath = folder_opts[:hostpath]\n              hostpath = File.expand_path(hostpath, machine.env.root_path)\n              paths[hostpath] ||= []\n              paths[hostpath] << {\n                id: id,\n                machine: machine,\n                opts:    folder_opts,\n              }\n\n              if folder_opts[:exclude]\n                Array(folder_opts[:exclude]).each do |pattern|\n                  ignores << RsyncHelper.exclude_to_regexp(pattern.to_s)\n                end\n              end\n\n              # Always ignore Vagrant\n              ignores << /.vagrant\\//\n              ignores.uniq!\n            end\n          end\n\n          # Exit immediately if there is nothing to watch\n          if paths.empty?\n            @env.ui.info(I18n.t(\"vagrant.rsync_auto_no_paths\"))\n            return 1\n          end\n\n          # Output to the user what paths we'll be watching\n          paths.keys.sort.each do |path|\n            paths[path].each do |path_opts|\n              path_opts[:machine].ui.info(I18n.t(\n                \"vagrant.rsync_auto_path\",\n                path: path.to_s,\n              ))\n            end\n          end\n\n          @logger.info(\"Listening to paths: #{paths.keys.sort.inspect}\")\n          @logger.info(\"Ignoring #{ignores.length} paths:\")\n          ignores.each do |ignore|\n            @logger.info(\"  -- #{ignore.to_s}\")\n          end\n          @logger.info(\"Listening via: #{Listen::Adapter.select.inspect}\")\n          callback = method(:callback).to_proc.curry[paths]\n          listopts = { ignore: ignores, force_polling: !!options[:poll] }\n          listener = Listen.to(*paths.keys, listopts, &callback)\n\n          # Create the callback that lets us know when we've been interrupted\n          queue    = Queue.new\n          callback = lambda do\n            # This needs to execute in another thread because Thread\n            # synchronization can't happen in a trap context.\n            Thread.new { queue << true }\n          end\n\n          # Run the listener in a busy block so that we can cleanly\n          # exit once we receive an interrupt.\n          Vagrant::Util::Busy.busy(callback) do\n            listener.start\n            queue.pop\n            listener.stop if listener.state != :stopped\n          end\n\n          0\n        end\n\n        # This is the callback that is called when any changes happen\n        def callback(paths, modified, added, removed)\n          @logger.info(\"File change callback called!\")\n          @logger.info(\"  - Modified: #{modified.inspect}\")\n          @logger.info(\"  - Added: #{added.inspect}\")\n          @logger.info(\"  - Removed: #{removed.inspect}\")\n\n          tosync = []\n          paths.each do |hostpath, folders|\n            # Find out if this path should be synced\n            found = catch(:done) do\n              [modified, added, removed].each do |changed|\n                changed.each do |listenpath|\n                  throw :done, true if listenpath.start_with?(hostpath)\n                end\n              end\n\n              # Make sure to return false if all else fails so that we\n              # don't sync to this machine.\n              false\n            end\n\n            # If it should be synced, store it for later\n            tosync << folders if found\n          end\n\n          # Sync all the folders that need to be synced\n          tosync.each do |folders|\n            folders.each do |opts|\n              # Reload so we get the latest ID\n              opts[:machine].reload\n              if !opts[:machine].id || opts[:machine].id == \"\"\n                # Skip since we can't get SSH info without an ID\n                next\n              end\n\n              ssh_info = opts[:machine].ssh_info\n              begin\n                start = Time.now\n                RsyncHelper.rsync_single(opts[:machine], ssh_info, opts[:opts])\n                finish = Time.now\n                @logger.info(\"Time spent in rsync: #{finish-start} (in seconds)\")\n              rescue Vagrant::Errors::MachineGuestNotReady\n                # Error communicating to the machine, probably a reload or\n                # halt is happening. Just notify the user but don't fail out.\n                opts[:machine].ui.error(I18n.t(\n                  \"vagrant.rsync_communicator_not_ready_callback\"))\n              rescue Vagrant::Errors::RSyncPostCommandError => e\n                # Error executing rsync chown command\n                opts[:machine].ui.error(I18n.t(\n                  \"vagrant.rsync_auto_post_command_error\", message: e.to_s))\n              rescue Vagrant::Errors::RSyncError => e\n                # Error executing rsync, so show an error\n                opts[:machine].ui.error(I18n.t(\n                  \"vagrant.rsync_auto_rsync_error\", message: e.to_s))\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/rsync/default_unix_cap.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"shellwords\"\n\nmodule VagrantPlugins\n  module SyncedFolderRSync\n    # This module provides default rsync capabilities for\n    # unix type operating systems.\n    module DefaultUnixCap\n\n      def rsync_installed(machine)\n        machine.communicate.test(\"which rsync\")\n      end\n\n      def rsync_command(machine)\n        \"sudo rsync\"\n      end\n\n      def rsync_pre(machine, opts)\n        guest_path = Shellwords.escape(opts[:guestpath])\n        machine.communicate.sudo(\"mkdir -p #{guest_path}\")\n      end\n\n      def rsync_post(machine, opts)\n        if opts.key?(:chown) && !opts[:chown]\n          return\n        end\n\n        machine.communicate.sudo(build_rsync_chown(opts))\n      end\n\n      def build_rsync_chown(opts)\n        guest_path = Shellwords.escape(opts[:guestpath])\n        if(opts[:exclude] && !Array(opts[:exclude]).empty?)\n          exclude_base = Pathname.new(opts[:guestpath])\n          exclusions = Array(opts[:exclude]).map do |ex_path|\n            ex_path = ex_path.slice(1, ex_path.size) if ex_path.start_with?(File::SEPARATOR)\n            \"-path #{Shellwords.escape(exclude_base.join(ex_path))} -prune\"\n          end.join(\" -o \") + \" -o \"\n        end\n        \"find #{guest_path} #{exclusions}\" \\\n          \"'!' -type l -a \" \\\n          \"'(' ! -user #{opts[:owner]} -or ! -group #{opts[:group]} ')' -exec \" \\\n          \"chown #{opts[:owner]}:#{opts[:group]} '{}' +\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/rsync/helper.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\nrequire \"ipaddr\"\nrequire \"shellwords\"\nrequire \"tmpdir\"\n\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/subprocess\"\n\nmodule VagrantPlugins\n  module SyncedFolderRSync\n    # This is a helper that abstracts out the functionality of rsyncing\n    # folders so that it can be called from anywhere.\n    class RsyncHelper\n      # rsync version requirement to support chown argument\n      RSYNC_CHOWN_REQUIREMENT = Gem::Requirement.new(\">= 3.1.0\").freeze\n\n      # This converts an rsync exclude pattern to a regular expression\n      # we can send to Listen.\n      #\n      # Note: Listen expects a path relative to the parameter passed into the\n      # Listener, not a fully qualified path\n      #\n      # @param [String]  - exclude path\n      # @return [Regexp] - A regex of the path, modified, to exclude\n      def self.exclude_to_regexp(exclude)\n        start_anchor = false\n\n        if exclude.start_with?(\"/\")\n          start_anchor = true\n        end\n\n        exclude = \"#{exclude}/\" if !exclude.end_with?(\"/\") if start_anchor\n        exclude = \"^#{exclude}\" if start_anchor\n        exclude += \".*\" if (!start_anchor && !exclude.end_with?(\"*\"))\n\n        # This is not an ideal solution, but it's a start. We can improve and\n        # keep unit tests passing in the future.\n        exclude = exclude.gsub(\"**\", \"|||GLOBAL|||\")\n        exclude = exclude.gsub(\"|||GLOBAL|||\", \".*\")\n\n        Regexp.new(exclude)\n      end\n\n      def self.rsync_single(machine, ssh_info, opts)\n        # Folder info\n        guestpath = opts[:guestpath]\n        hostpath  = opts[:hostpath]\n        hostpath  = File.expand_path(hostpath, machine.env.root_path)\n        hostpath  = Vagrant::Util::Platform.fs_real_path(hostpath).to_s\n\n        # if the guest has a guest path scrubber capability, use it\n        if machine.guest.capability?(:rsync_scrub_guestpath)\n          guestpath = machine.guest.capability(:rsync_scrub_guestpath, opts)\n        end\n\n        # Shellescape\n        guestpath = Shellwords.escape(guestpath)\n\n        if Vagrant::Util::Platform.windows?\n          # rsync for Windows expects cygwin style paths, always.\n          hostpath = Vagrant::Util::Platform.cygwin_path(hostpath)\n        end\n\n        # Make sure the host path ends with a \"/\" to avoid creating\n        # a nested directory...\n        if !hostpath.end_with?(\"/\")\n          hostpath += \"/\"\n        end\n\n        # Folder options\n        opts[:owner] ||= ssh_info[:username]\n        opts[:group] ||= ssh_info[:username]\n\n        # set log level\n        log_level = ssh_info[:log_level] || \"FATAL\"\n\n        # Connection information\n        # make it better match lib/vagrant/util/ssh.rb command_options style and logic\n        username = ssh_info[:username]\n        host     = ssh_info[:host]\n        proxy_command = \"\"\n        if ssh_info[:proxy_command]\n          proxy_command = \"-o ProxyCommand='#{ssh_info[:proxy_command]}' \"\n        end\n\n        ssh_config_file = \"\"\n        if ssh_info[:config]\n          ssh_config_file = \"-F #{ssh_info[:config]}\"\n        end\n\n        # Create the path for the control sockets. We used to do this\n        # in the machine data dir but this can result in paths that are\n        # too long for unix domain sockets.\n        control_options = \"\"\n        unless Vagrant::Util::Platform.windows?\n          controlpath = Dir.mktmpdir(\"vagrant-rsync-\")\n          control_options = \"-o ControlMaster=auto -o ControlPath=#{controlpath} -o ControlPersist=10m \"\n        end\n\n        # rsh cmd option\n        rsh = [\n          \"ssh\", \"-p\", \"#{ssh_info[:port]}\",\n          \"-o\", \"LogLevel=#{log_level}\",\n          proxy_command,\n          ssh_config_file,\n          control_options,\n        ]\n        rsh += ssh_info[:extra_args] if ssh_info[:extra_args]\n\n        # Solaris/OpenSolaris/Illumos uses SunSSH which doesn't support the\n        # IdentitiesOnly option. Also, we don't enable it if keys_only is false\n        # so that SSH properly searches our identities and tries to do it itself.\n        if !Vagrant::Util::Platform.solaris? && ssh_info[:keys_only]\n          rsh += [\"-o\", \"IdentitiesOnly=yes\"]\n        end\n\n        # no strict hostkey checking unless paranoid\n        if ssh_info[:verify_host_key] == :never || !ssh_info[:verify_host_key]\n          rsh += [\n            \"-o\", \"StrictHostKeyChecking=no\",\n            \"-o\", \"UserKnownHostsFile=/dev/null\"]\n        end\n\n        # If specified, attach the private key paths.\n        if ssh_info[:private_key_path]\n          rsh += ssh_info[:private_key_path].map { |p| \"-i '#{p}'\" }\n        end\n\n        # Exclude some files by default, and any that might be configured\n        # by the user.\n        excludes = ['.vagrant/']\n        excludes += Array(opts[:exclude]).map(&:to_s) if opts[:exclude]\n        excludes.uniq!\n\n        # Get the command-line arguments\n        args = nil\n        args = Array(opts[:args]).dup if opts[:args]\n        args ||= [\"--verbose\", \"--archive\", \"--delete\", \"-z\", \"--copy-links\"]\n\n        # On Windows, we have to set a default chmod flag to avoid permission issues\n        if Vagrant::Util::Platform.windows? && !args.any? { |arg| arg.start_with?(\"--chmod=\") }\n          # Ensures that all non-masked bits get enabled\n          args << \"--chmod=ugo=rwX\"\n\n          # Remove the -p option if --archive is enabled (--archive equals -rlptgoD)\n          # otherwise new files will not have the destination-default permissions\n          args << \"--no-perms\" if args.include?(\"--archive\") || args.include?(\"-a\")\n        end\n\n        if opts[:rsync_ownership] && rsync_chown_support?(machine)\n          # Allow rsync to map ownership\n          args << \"--chown=#{opts[:owner]}:#{opts[:group]}\"\n          # Notify rsync post capability not to chown\n          opts[:chown] = false\n        else\n          # Disable rsync's owner/group preservation (implied by --archive) unless\n          # specifically requested, since we adjust owner/group to match shared\n          # folder setting ourselves.\n          args << \"--no-owner\" unless args.include?(\"--owner\") || args.include?(\"-o\")\n          args << \"--no-group\" unless args.include?(\"--group\") || args.include?(\"-g\")\n        end\n\n        # Tell local rsync how to invoke remote rsync with sudo\n        rsync_path = opts[:rsync_path]\n        if !rsync_path && machine.guest.capability?(:rsync_command)\n          rsync_path = machine.guest.capability(:rsync_command)\n        end\n        if rsync_path\n          args << \"--rsync-path\"<< rsync_path\n        end\n\n        # If the remote host is an IPv6 address reformat\n        begin\n          if IPAddr.new(host).ipv6?\n            host = \"[#{host}]\"\n          end\n        rescue IPAddr::Error\n          # Ignore\n        end\n\n        # Build up the actual command to execute\n        command = [\n          \"rsync\",\n          args,\n          \"-e\", rsh.flatten.join(\" \"),\n          excludes.map { |e| [\"--exclude\", e] },\n          hostpath,\n          \"#{username}@#{host}:#{guestpath}\",\n        ].flatten\n\n        # The working directory should be the root path\n        command_opts = {}\n        command_opts[:workdir] = machine.env.root_path.to_s\n\n        machine.ui.info(I18n.t(\n          \"vagrant.rsync_folder\", guestpath: guestpath, hostpath: hostpath))\n        if excludes.length > 1\n          machine.ui.info(I18n.t(\n            \"vagrant.rsync_folder_excludes\", excludes: excludes.inspect))\n        end\n        if opts.include?(:verbose)\n          machine.ui.info(I18n.t(\"vagrant.rsync_showing_output\"));\n        end\n\n        # If we have tasks to do before rsyncing, do those.\n        if machine.guest.capability?(:rsync_pre)\n          machine.guest.capability(:rsync_pre, opts)\n        end\n\n        if opts.include?(:verbose)\n          command_opts[:notify] = [:stdout, :stderr]\n          r = Vagrant::Util::Subprocess.execute(*(command + [command_opts])) {\n            |io_name,data| data.each_line { |line|\n              machine.ui.info(\"rsync[#{io_name}] -> #{line}\") }\n          }\n        else\n          r = Vagrant::Util::Subprocess.execute(*(command + [command_opts]))\n        end\n\n        if r.exit_code != 0\n          raise Vagrant::Errors::RSyncError,\n            command: command.map(&:inspect).join(\" \"),\n            guestpath: guestpath,\n            hostpath: hostpath,\n            stderr: r.stderr\n        end\n\n        # If we have tasks to do after rsyncing, do those.\n        if machine.guest.capability?(:rsync_post)\n          begin\n            machine.guest.capability(:rsync_post, opts)\n          rescue Vagrant::Errors::VagrantError => err\n            raise Vagrant::Errors::RSyncPostCommandError,\n              guestpath: guestpath,\n              hostpath: hostpath,\n              message: err.to_s\n          end\n        end\n      ensure\n        FileUtils.remove_entry_secure(controlpath, true) if controlpath\n      end\n\n      # Check if rsync versions support using chown option\n      #\n      # @param [Vagrant::Machine] machine The remote machine\n      # @return [Boolean]\n      def self.rsync_chown_support?(machine)\n        if !RSYNC_CHOWN_REQUIREMENT.satisfied_by?(Gem::Version.new(local_rsync_version))\n          return false\n        end\n        mrv = machine_rsync_version(machine)\n        if mrv && !RSYNC_CHOWN_REQUIREMENT.satisfied_by?(Gem::Version.new(mrv))\n          return false\n        end\n        true\n      end\n\n      # @return [String, nil] version of remote rsync\n      def self.machine_rsync_version(machine)\n        if machine.guest.capability?(:rsync_command)\n          rsync_path = machine.guest.capability(:rsync_command)\n        else\n          rsync_path = \"rsync\"\n        end\n        output = \"\"\n        machine.communicate.execute(rsync_path + \" --version\") { |_, data| output << data }\n        vmatch = output.match(/version\\s+(?<version>[\\d.]+)\\s/)\n        if vmatch\n          vmatch[:version]\n        end\n      end\n\n      # @return [String, nil] version of local rsync\n      def self.local_rsync_version\n        if !@_rsync_version\n          r = Vagrant::Util::Subprocess.execute(\"rsync\", \"--version\")\n          vmatch = r.stdout.to_s.match(/version\\s+(?<version>[\\d.]+)\\s/)\n          if vmatch\n            @_rsync_version = vmatch[:version]\n          end\n        end\n        @_rsync_version\n      end\n\n      # @private\n      # Reset the cached values for helper. This is not considered a public\n      # API and should only be used for testing.\n      def self.reset!\n        instance_variables.each(&method(:remove_instance_variable))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/rsync/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module SyncedFolderRSync\n    # This plugin implements synced folders via rsync.\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"RSync synced folders\"\n      description <<-EOF\n      The Rsync synced folder plugin will sync folders via rsync.\n      EOF\n\n      command(\"rsync\", primary: false) do\n        require_relative \"command/rsync\"\n        Command::Rsync\n      end\n\n      command(\"rsync-auto\", primary: false) do\n        require_relative \"command/rsync_auto\"\n        Command::RsyncAuto\n      end\n\n      synced_folder(\"rsync\", 5) do\n        require_relative \"synced_folder\"\n        SyncedFolder\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/rsync/synced_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"log4r\"\n\nrequire \"vagrant/util/subprocess\"\nrequire \"vagrant/util/which\"\n\nrequire_relative \"helper\"\n\nmodule VagrantPlugins\n  module SyncedFolderRSync\n    class SyncedFolder < Vagrant.plugin(\"2\", :synced_folder)\n      include Vagrant::Util\n\n      def initialize(*args)\n        super\n\n        @logger = Log4r::Logger.new(\"vagrant::synced_folders::rsync\")\n      end\n\n      def usable?(machine, raise_error=false)\n        rsync_path = Which.which(\"rsync\")\n        return true if rsync_path\n        return false if !raise_error\n        raise Vagrant::Errors::RSyncNotFound\n      end\n\n      def prepare(machine, folders, opts)\n        # Nothing is necessary to do before VM boot.\n      end\n\n      def enable(machine, folders, opts)\n        if machine.guest.capability?(:rsync_installed)\n          installed = machine.guest.capability(:rsync_installed)\n          if !installed\n            can_install = machine.guest.capability?(:rsync_install)\n            raise Vagrant::Errors::RSyncNotInstalledInGuest if !can_install\n            machine.ui.info I18n.t(\"vagrant.rsync_installing\")\n            machine.guest.capability(:rsync_install)\n          end\n        end\n\n        ssh_info = machine.ssh_info\n\n        if ssh_info[:private_key_path].empty? && ssh_info[:password]\n          machine.ui.warn(I18n.t(\"vagrant.rsync_ssh_password\"))\n        end\n\n        folders.each do |id, folder_opts|\n          RsyncHelper.rsync_single(machine, ssh_info, folder_opts)\n        end\n      end\n\n      # Enable rsync synced folders within WSL when in use\n      # on non-DrvFs file systems\n      def self.wsl_allow_non_drvfs?\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/smb/cap/default_fstab_modification.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module SyncedFolderSMB\n    module Cap\n      module DefaultFstabModification\n        def self.default_fstab_modification(machine)\n          return false\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/smb/cap/mount_options.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../unix_mount_helpers\"\n\nmodule VagrantPlugins\n  module SyncedFolderSMB\n    module Cap\n      module MountOptions\n        extend VagrantPlugins::SyncedFolder::UnixMountHelpers\n\n        MOUNT_TYPE = \"cifs\".freeze\n\n        # Returns mount options for a smb synced folder\n        #\n        # @param [Machine] machine\n        # @param [String] name of mount\n        # @param [String] path of mount on guest\n        # @param [Hash] hash of mount options \n        def self.mount_options(machine, name, guest_path, options)\n          mount_options = options.fetch(:mount_options, [])\n          options[:smb_id] ||= name\n          detected_ids = detect_owner_group_ids(machine, guest_path, mount_options, options)\n          mount_uid = detected_ids[:uid]\n          mount_gid = detected_ids[:gid]\n\n          mnt_opts = []\n          if machine.env.host.capability?(:smb_mount_options)\n            mnt_opts += machine.env.host.capability(:smb_mount_options)\n          else\n            mnt_opts << \"sec=ntlmssp\"\n          end\n\n          mnt_opts << \"credentials=/etc/smb_creds_#{options[:smb_id]}\"\n          mnt_opts << \"uid=#{mount_uid}\"\n          mnt_opts << \"gid=#{mount_gid}\"\n          if !ENV['VAGRANT_DISABLE_SMBMFSYMLINKS']\n            mnt_opts << \"mfsymlinks\"\n          end\n          mnt_opts << \"_netdev\"\n          mnt_opts = merge_mount_options(mnt_opts, options[:mount_options] || [])\n          mount_options = mnt_opts.join(\",\")\n          return mount_options, mount_uid, mount_gid\n        end\n\n        def self.mount_type(machine)\n          return  MOUNT_TYPE\n        end\n\n        def self.mount_name(machine, name, data)\n          candidate_ips = machine.env.host.capability(:configured_ip_addresses)\n          data[:smb_host] ||= machine.guest.capability(\n            :choose_addressable_ip_addr, candidate_ips)\n          \"//#{data[:smb_host]}/#{data[:smb_id]}\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/smb/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module SyncedFolderSMB\n    class Config < Vagrant.plugin(\"2\", :config)\n      attr_accessor :functional\n\n      def initialize\n        super\n\n        @functional = UNSET_VALUE\n      end\n\n      def finalize!\n        @functional = true if @functional == UNSET_VALUE\n      end\n\n      def to_s\n        \"SMB\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/smb/errors.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantPlugins\n  module SyncedFolderSMB\n    module Errors\n      # A convenient superclass for all our errors.\n      class SMBError < Vagrant::Errors::VagrantError\n        error_namespace(\"vagrant_sf_smb.errors\")\n      end\n\n      class SMBNotSupported < SMBError\n        error_key(:not_supported)\n      end\n\n      class SMBStartFailed < SMBError\n        error_key(:start_failed)\n      end\n\n      class SMBCredentialsMissing < SMBError\n        error_key(:credentials_missing)\n      end\n\n      class SMBListFailed < SMBError\n        error_key(:list_failed)\n      end\n\n      class SMBNameError < SMBError\n        error_key(:name_error)\n      end\n\n      class CredentialsRequestError < SMBError\n        error_key(:credentials_request_error)\n      end\n\n      class DefineShareFailed < SMBError\n        error_key(:define_share_failed)\n      end\n\n      class PruneShareFailed < SMBError\n        error_key(:prune_share_failed)\n      end\n\n      class NoHostIPAddr < SMBError\n        error_key(:no_routable_host_addr)\n      end\n\n      class PowershellError < SMBError\n        error_key(:powershell_error)\n      end\n\n      class PowershellVersion < SMBError\n        error_key(:powershell_version)\n      end\n\n      class WindowsHostRequired < SMBError\n        error_key(:windows_host_required)\n      end\n\n      class WindowsAdminRequired < SMBError\n        error_key(:windows_admin_required)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/smb/plugin.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\n\nmodule VagrantPlugins\n  module SyncedFolderSMB\n    autoload :Errors, File.expand_path(\"../errors\", __FILE__)\n\n    # This plugin implements SMB synced folders.\n    class Plugin < Vagrant.plugin(\"2\")\n      name \"SMB synced folders\"\n      description <<-EOF\n      The SMB synced folders plugin enables you to use SMB folders on\n      Windows or macOS and share them to guest machines.\n      EOF\n\n      config(\"smb\") do\n        require_relative \"config\"\n        Config\n      end\n\n      synced_folder(\"smb\", 7) do\n        require_relative \"synced_folder\"\n        init!\n        SyncedFolder\n      end\n\n      synced_folder_capability(\"smb\", \"default_fstab_modification\") do\n        require_relative \"cap/default_fstab_modification\"\n        Cap::DefaultFstabModification\n      end\n\n      synced_folder_capability(\"smb\", \"mount_options\") do\n        require_relative \"cap/mount_options\"\n        Cap::MountOptions\n      end\n\n      synced_folder_capability(\"smb\", \"mount_name\") do\n        require_relative \"cap/mount_options\"\n        Cap::MountOptions\n      end\n\n      synced_folder_capability(\"smb\", \"mount_type\") do\n        require_relative \"cap/mount_options\"\n        Cap::MountOptions\n      end\n\n      protected\n\n      def self.init!\n        return if defined?(@_init)\n        I18n.load_path << File.expand_path(\n          \"templates/locales/synced_folder_smb.yml\", Vagrant.source_root)\n        I18n.reload!\n        @_init = true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/smb/synced_folder.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"digest/md5\"\nrequire \"json\"\n\nrequire \"log4r\"\n\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/powershell\"\n\nrequire_relative \"errors\"\n\nmodule VagrantPlugins\n  module SyncedFolderSMB\n    class SyncedFolder < Vagrant.plugin(\"2\", :synced_folder)\n\n      # Maximum number of times to retry requesting username/password\n      CREDENTIAL_RETRY_MAX = 5\n\n      def initialize(*args)\n        super\n\n        @logger = Log4r::Logger.new(\"vagrant::synced_folders::smb\")\n      end\n\n      def usable?(machine, raise_error=false)\n        # If the machine explicitly states SMB is not supported, then\n        # believe it\n        return false if !machine.config.smb.functional\n        return true if machine.env.host.capability?(:smb_installed) &&\n          machine.env.host.capability(:smb_installed)\n        return false if !raise_error\n        raise Errors::SMBNotSupported\n      end\n\n      def prepare(machine, folders, opts)\n        machine.ui.output(I18n.t(\"vagrant_sf_smb.preparing\"))\n\n        smb_username = smb_password = nil\n\n        # If we need auth information, then ask the user.\n        have_auth = false\n        folders.each do |id, data|\n          smb_username = data[:smb_username] if data[:smb_username]\n          smb_password = data[:smb_password] if data[:smb_password]\n          if smb_username && smb_password\n            have_auth = true\n            break\n          end\n        end\n\n        modify_username = false\n        if !have_auth\n          machine.ui.detail(I18n.t(\"vagrant_sf_smb.warning_password\") + \"\\n \")\n          retries = 0\n          while retries < CREDENTIAL_RETRY_MAX do\n            if smb_username\n              username = machine.ui.ask(\"Username (#{smb_username}): \")\n              smb_username = username if username != \"\"\n              modify_username = true\n            else\n              smb_username = machine.ui.ask(\"Username (user[@domain]): \")\n            end\n\n            smb_password = machine.ui.ask(\"Password (will be hidden): \", echo: false)\n            auth_success = true\n\n            if machine.env.host.capability?(:smb_validate_password)\n              Vagrant::Util::CredentialScrubber.sensitive(smb_password)\n              auth_success = machine.env.host.capability(:smb_validate_password,\n                machine, smb_username, smb_password)\n            end\n\n            break if auth_success\n            machine.ui.output(I18n.t(\"vagrant_sf_smb.incorrect_credentials\") + \"\\n \")\n            retries += 1\n          end\n\n          if retries >= CREDENTIAL_RETRY_MAX\n            raise Errors::CredentialsRequestError\n          end\n        end\n\n        # Check if this host can start and SMB service\n        if machine.env.host.capability?(:smb_start)\n          machine.env.host.capability(:smb_start)\n        end\n\n        folders.each do |id, data|\n          if modify_username\n            # Only override original username if user requests to\n            data[:smb_username] = smb_username\n          else\n            data[:smb_username] ||= smb_username\n          end\n          data[:smb_password] ||= smb_password\n\n          # Register password as sensitive\n          Vagrant::Util::CredentialScrubber.sensitive(data[:smb_password])\n        end\n\n        machine.env.host.capability(:smb_prepare, machine, folders, opts)\n      end\n\n      def enable(machine, folders, opts)\n        machine.ui.output(I18n.t(\"vagrant_sf_smb.mounting\"))\n\n        # Make sure that this machine knows this dance\n        if !machine.guest.capability?(:mount_smb_shared_folder)\n          raise Vagrant::Errors::GuestCapabilityNotFound,\n            cap: \"mount_smb_shared_folder\",\n            guest: machine.guest.name.to_s\n        end\n\n        # Setup if we have it\n        if machine.guest.capability?(:smb_install)\n          machine.guest.capability(:smb_install)\n        end\n\n        # Detect the host IP for this guest if one wasn't specified\n        # for every folder.\n        host_ip = nil\n        need_host_ip = false\n        folders.each do |id, data|\n          if !data[:smb_host]\n            need_host_ip = true\n            break\n          end\n        end\n\n        if need_host_ip\n          candidate_ips = machine.env.host.capability(:configured_ip_addresses)\n          @logger.debug(\"Potential host IPs: #{candidate_ips.inspect}\")\n          host_ip = machine.guest.capability(\n            :choose_addressable_ip_addr, candidate_ips)\n          if !host_ip\n            raise Errors::NoHostIPAddr\n          end\n        end\n\n        # This is used for defaulting the owner/group\n        ssh_info = machine.ssh_info\n\n        folders.each do |id, data|\n          data[:smb_host] ||= host_ip\n\n          # Default the owner/group of the folder to the SSH user\n          data[:owner] ||= ssh_info[:username]\n          data[:group] ||= ssh_info[:username]\n\n          machine.ui.detail(I18n.t(\n            \"vagrant_sf_smb.mounting_single\",\n            host: data[:hostpath].to_s,\n            guest: data[:guestpath].to_s))\n          machine.guest.capability(\n            :mount_smb_shared_folder, data[:smb_id], data[:guestpath], data)\n\n          clean_folder_configuration(data)\n        end\n      end\n\n      # Nothing to do here but ensure folder options are scrubbed\n      def disable(machine, folders, opts)\n        folders.each do |_, data|\n          clean_folder_configuration(data)\n        end\n      end\n\n      def cleanup(machine, opts)\n        if machine.env.host.capability?(:smb_cleanup)\n          machine.env.host.capability(:smb_cleanup, machine, opts)\n        end\n      end\n\n      protected\n\n      # Remove data that should not be persisted within folder\n      # specific configuration\n      #\n      # @param [Hash] data Folder configuration\n      def clean_folder_configuration(data)\n        return if !data.is_a?(Hash)\n        data.delete(:smb_password)\n        nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "plugins/synced_folders/unix_mount_helpers.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"shellwords\"\nrequire \"vagrant/util/retryable\"\n\nmodule VagrantPlugins\n  module SyncedFolder\n    module UnixMountHelpers\n\n      def self.extended(klass)\n        if !klass.class_variable_defined?(:@@logger)\n          klass.class_variable_set(:@@logger, Log4r::Logger.new(klass.name.downcase))\n        end\n        klass.extend Vagrant::Util::Retryable\n      end\n\n      def detect_owner_group_ids(machine, guest_path, mount_options, options)\n        mount_uid = find_mount_options_id(\"uid\", mount_options)\n        mount_gid = find_mount_options_id(\"gid\", mount_options)\n\n        if mount_uid.nil?\n          if options[:owner].to_i.to_s == options[:owner].to_s\n            mount_uid = options[:owner]\n            self.class_variable_get(:@@logger).debug(\"Owner user ID (provided): #{mount_uid}\")\n          else\n            output = {stdout: '', stderr: ''}\n            uid_command = \"id -u #{options[:owner]}\"\n            machine.communicate.execute(uid_command,\n              error_class: Vagrant::Errors::VirtualBoxMountFailed,\n              error_key: :virtualbox_mount_failed,\n              command: uid_command,\n              output: output[:stderr]\n            ) { |type, data| output[type] << data if output[type] }\n            mount_uid = output[:stdout].chomp\n            self.class_variable_get(:@@logger).debug(\"Owner user ID (lookup): #{options[:owner]} -> #{mount_uid}\")\n          end\n        else\n          machine.ui.warn \"Detected mount owner ID within mount options. (uid: #{mount_uid} guestpath: #{guest_path})\"\n        end\n\n        if mount_gid.nil?\n          if options[:group].to_i.to_s == options[:group].to_s\n            mount_gid = options[:group]\n            self.class_variable_get(:@@logger).debug(\"Owner group ID (provided): #{mount_gid}\")\n          else\n            begin\n              output = {stdout: '', stderr: ''}\n              gid_command = \"getent group #{options[:group]}\"\n              machine.communicate.execute(gid_command,\n                error_class: Vagrant::Errors::VirtualBoxMountFailed,\n                error_key: :virtualbox_mount_failed,\n                command: gid_command,\n                output: output[:stderr]\n              ) { |type, data| output[type] << data if output[type] }\n              mount_gid = output[:stdout].split(':').at(2).to_s.chomp\n              self.class_variable_get(:@@logger).debug(\"Owner group ID (lookup): #{options[:group]} -> #{mount_gid}\")\n            rescue Vagrant::Errors::VirtualBoxMountFailed\n              if options[:owner] == options[:group]\n                self.class_variable_get(:@@logger).debug(\"Failed to locate group `#{options[:group]}`. Group name matches owner. Fetching effective group ID.\")\n                output = {stdout: ''}\n                result = machine.communicate.execute(\"id -g #{options[:owner]}\",\n                  error_check: false\n                ) { |type, data| output[type] << data if output[type] }\n                mount_gid = output[:stdout].chomp if result == 0\n                self.class_variable_get(:@@logger).debug(\"Owner group ID (effective): #{mount_gid}\")\n              end\n              raise unless mount_gid\n            end\n          end\n        else\n          machine.ui.warn \"Detected mount group ID within mount options. (gid: #{mount_gid} guestpath: #{guest_path})\"\n        end\n        {:gid => mount_gid, :uid => mount_uid}\n      end\n\n      def find_mount_options_id(id_name, mount_options)\n        id_line = mount_options.detect{|line| line.include?(\"#{id_name}=\")}\n        if id_line\n          match = id_line.match(/,?#{Regexp.escape(id_name)}=(?<option_id>\\d+),?/)\n          found_id = match[\"option_id\"]\n          updated_id_line = [\n            match.pre_match,\n            match.post_match\n          ].find_all{|string| !string.empty?}.join(',')\n          if updated_id_line.empty?\n            mount_options.delete(id_line)\n          else\n            idx = mount_options.index(id_line)\n            mount_options.delete(idx)\n            mount_options.insert(idx, updated_id_line)\n          end\n        end\n        found_id\n      end\n\n      def emit_upstart_notification(machine, guest_path)\n        # Emit an upstart event if we can\n        machine.communicate.sudo <<-EOH.gsub(/^ {12}/, \"\")\n            if test -x /sbin/initctl && command -v /sbin/init && /sbin/init 2>/dev/null --version | grep upstart; then\n              /sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guest_path}\n            fi\n          EOH\n      end\n\n      def merge_mount_options(base, overrides)\n        base = base.join(\",\").split(\",\")\n        overrides = overrides.join(\",\").split(\",\")\n        b_kv = Hash[base.map{|item| item.split(\"=\", 2) }]\n        o_kv = Hash[overrides.map{|item| item.split(\"=\", 2) }]\n        merged = {}.tap do |opts|\n          (b_kv.keys + o_kv.keys).uniq.each do |key|\n            opts[key] = o_kv.fetch(key, b_kv[key])\n          end\n        end\n        merged.map do |key, value|\n          [key, value].compact.join(\"=\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "scripts/install_rvm",
    "content": "#!/usr/bin/env bash\n\nexport DEBIAN_FRONTEND=noninteractive\n\nif ! rvm --version\nthen\n  # https://github.com/rvm/ubuntu_rvm#install\n  apt-add-repository -y ppa:rael-gc/rvm &&\n    apt-get update -y &&\n    apt-get install -y rvm || {\n      echo 'Failed to install rvm' >&2\n      exit 1\n    }\nfi\n\nusermod -a -G rvm vagrant || {\n  echo 'Failed to add vagrant to the rvm group' >&2\n  exit 1\n}\n"
  },
  {
    "path": "scripts/setup_tests",
    "content": "#!/usr/bin/env bash\n\nrvm --version || {\n  echo 'rvm not installed' >&2\n  exit 1\n}\n\n# bsdtar is required for a small subset of the tests\nif ! bsdtar --version\nthen\n  apt-get install -y bsdtar &&\n    bsdtar --version || {\n      echo 'Failed to install bsdtar' >&2\n      exit 1\n    }\nfi\n\n# Install next-to-last Ruby that complies with Vagrant's version\n# constraint\nRUBY_VER_REQ=$(\n  awk '\n    $1 == \"s.required_ruby_version\" { print $4 }\n  ' /vagrant/vagrant.gemspec |\n    tr -d '\"')\nRUBY_VER=$(sudo -u vagrant -i rvm list known |\n             tr '[]-' ' ' |\n             awk \"/^ ruby  ${RUBY_VER_REQ:0:1}\\./ { print \\$2 }\" |\n             sort -r |\n             sed -n '2p')\nsudo -u vagrant -i rvm install \"${RUBY_VER}\"\nsudo -u vagrant -i rvm --default use \"${RUBY_VER}\"\n\n# Output the Ruby version (for sanity)\nsudo -u vagrant -i ruby --version\n\n# Upgrade Rubygems\nsudo -u vagrant -i rvm \"${RUBY_VER}\" do gem update --system\n\n# Prepare to run unit tests\nsudo -u vagrant -i bash -c 'cd /vagrant; bundle install'\n\n# Automatically move into the shared folder, but only add the command if\n# it's not already there.\nif ! grep -q 'cd /vagrant' /home/vagrant/.bash_profile\nthen\n  cat <<EOF >> /home/vagrant/.bash_profile\ncd /vagrant\nEOF\nfi\n"
  },
  {
    "path": "scripts/sign.sh",
    "content": "#!/bin/bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nset -e\n\n# Get the parent directory of where this script is.\nSOURCE=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$SOURCE\" ] ; do SOURCE=\"$(readlink \"$SOURCE\")\"; done\nDIR=\"$( cd -P \"$( dirname \"$SOURCE\" )/..\" && pwd )\"\n\n# Change into that dir because we expect that\ncd $DIR\n\n# Get the version from the command line\nVERSION=$1\nif [ -z $VERSION ]; then\n    echo \"Please specify a version.\"\n    exit 1\nfi\n\n# Make the checksums\npushd ./pkg/dist\nshasum -a256 * > ./vagrant_${VERSION}_SHA256SUMS\n# if [ -z $NOSIGN ]; then\n#   echo \"==> Signing...\"\n#   gpg --default-key 348FFC4C --detach-sig ./vagrant_${VERSION}_SHA256SUMS\n# fi\npopd\n"
  },
  {
    "path": "scripts/website_push_www.sh",
    "content": "#!/bin/bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\n# Set the tmpdir\nif [ -z \"$TMPDIR\" ]; then\n  TMPDIR=\"/tmp\"\nfi\n\n# Create a temporary build dir and make sure we clean it up. For\n# debugging, comment out the trap line.\nDEPLOY=`mktemp -d /tmp/vagrant-www-XXXXXX`\ntrap \"rm -rf $DEPLOY\" INT TERM EXIT\n\n# Get the parent directory of where this script is.\nSOURCE=\"${BASH_SOURCE[0]}\"\nwhile [ -h \"$SOURCE\" ] ; do SOURCE=\"$(readlink \"$SOURCE\")\"; done\nDIR=\"$( cd -P \"$( dirname \"$SOURCE\" )/..\" && pwd )\"\n\n# Copy into tmpdir\nshopt -s dotglob\ncp -R $DIR/website/www/* $DEPLOY/\n\n# Change into that directory\ncd $DEPLOY\n\n# Ignore some stuff\ntouch .gitignore\necho \".sass-cache\" >> .gitignore\necho \"build\" >> .gitignore\necho \"vendor\" >> .gitignore\n\n# Add everything\ngit init .\ngit add .\ngit commit -q -m \"Deploy by $USER\"\n\ngit remote add heroku git@heroku.com:vagrantup-www-2.git\ngit push -f heroku main\n\n# Cleanup the deploy\nrm -rf $DEPLOY\n"
  },
  {
    "path": "tasks/acceptance.rake",
    "content": "namespace :acceptance do\n  desc \"shows components that can be tested\"\n  task :components do\n    exec(\"vagrant-spec components --config=vagrant-spec.config.rb\")\n  end\n\n  desc \"runs acceptance tests\"\n  task :run do\n    args = [\n      \"--config=vagrant-spec.config.rb\",\n    ]\n\n    if ENV[\"COMPONENTS\"]\n      args << \"--components=\\\"#{ENV[\"COMPONENTS\"]}\\\"\"\n    end\n\n    command = \"vagrant-spec test #{args.join(\" \")}\"\n    puts command\n    puts\n    exec(command)\n  end\nend\n"
  },
  {
    "path": "tasks/bundler.rake",
    "content": "# This installs the tasks that help with gem creation and\n# publishing.\nBundler::GemHelper.install_tasks\n"
  },
  {
    "path": "tasks/test.rake",
    "content": "require 'rake/testtask'\nrequire 'rspec/core/rake_task'\n\nnamespace :test do\n  RSpec::Core::RakeTask.new(:unit) do |t|\n    t.pattern = \"test/unit/**/*_test.rb\"\n  end\nend\n"
  },
  {
    "path": "templates/commands/init/Vagrantfile.erb",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\n# All Vagrant configuration is done below. The \"2\" in Vagrant.configure\n# configures the configuration version (we support older styles for\n# backwards compatibility). Please don't change it unless you know what\n# you're doing.\nVagrant.configure(\"2\") do |config|\n  # The most common configuration options are documented and commented below.\n  # For a complete reference, please see the online documentation at\n  # https://docs.vagrantup.com.\n\n  # Every Vagrant development environment requires a box. You can search for\n  # boxes at https://vagrantcloud.com/search.\n  config.vm.box = \"<%= box_name %>\"\n  <% if box_version -%>\n  config.vm.box_version = \"<%= box_version %>\"\n  <% end -%>\n\n  <% if box_url -%>\n  # The url from where the 'config.vm.box' box will be fetched if it\n  # doesn't already exist on the user's system.\n  config.vm.box_url = \"<%= box_url %>\"\n  <% else -%>\n  # Disable automatic box update checking. If you disable this, then\n  # boxes will only be checked for updates when the user runs\n  # `vagrant box outdated`. This is not recommended.\n  # config.vm.box_check_update = false\n  <% end -%>\n\n  # Create a forwarded port mapping which allows access to a specific port\n  # within the machine from a port on the host machine. In the example below,\n  # accessing \"localhost:8080\" will access port 80 on the guest machine.\n  # NOTE: This will enable public access to the opened port\n  # config.vm.network \"forwarded_port\", guest: 80, host: 8080\n\n  # Create a forwarded port mapping which allows access to a specific port\n  # within the machine from a port on the host machine and only allow access\n  # via 127.0.0.1 to disable public access\n  # config.vm.network \"forwarded_port\", guest: 80, host: 8080, host_ip: \"127.0.0.1\"\n\n  # Create a private network, which allows host-only access to the machine\n  # using a specific IP.\n  # config.vm.network \"private_network\", ip: \"192.168.33.10\"\n\n  # Create a public network, which generally matched to bridged network.\n  # Bridged networks make the machine appear as another physical device on\n  # your network.\n  # config.vm.network \"public_network\"\n\n  # Share an additional folder to the guest VM. The first argument is\n  # the path on the host to the actual folder. The second argument is\n  # the path on the guest to mount the folder. And the optional third\n  # argument is a set of non-required options.\n  # config.vm.synced_folder \"../data\", \"/vagrant_data\"\n\n  # Disable the default share of the current code directory. Doing this\n  # provides improved isolation between the vagrant box and your host\n  # by making sure your Vagrantfile isn't accessible to the vagrant box.\n  # If you use this you may want to enable additional shared subfolders as\n  # shown above.\n  # config.vm.synced_folder \".\", \"/vagrant\", disabled: true\n\n  # Provider-specific configuration so you can fine-tune various\n  # backing providers for Vagrant. These expose provider-specific options.\n  # Example for VirtualBox:\n  #\n  # config.vm.provider \"virtualbox\" do |vb|\n  #   # Display the VirtualBox GUI when booting the machine\n  #   vb.gui = true\n  #\n  #   # Customize the amount of memory on the VM:\n  #   vb.memory = \"1024\"\n  # end\n  #\n  # View the documentation for the provider you are using for more\n  # information on available options.\n\n  # Enable provisioning with a shell script. Additional provisioners such as\n  # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the\n  # documentation for more information about their specific syntax and use.\n  # config.vm.provision \"shell\", inline: <<-SHELL\n  #   apt-get update\n  #   apt-get install -y apache2\n  # SHELL\nend\n"
  },
  {
    "path": "templates/commands/init/Vagrantfile.min.erb",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"<%= box_name %>\"\n  <% if box_version -%>\n  config.vm.box_version = \"<%= box_version %>\"\n  <% end -%>\n  <% if box_url -%>\n  config.vm.box_url = \"<%= box_url %>\"\n  <% end -%>\nend\n"
  },
  {
    "path": "templates/commands/ssh_config/config.erb",
    "content": "Host <%= host_key %>\n<% if config -%>\n  Include <%= config %>\n<% end -%>\n  HostName <%= ssh_host %>\n  User <%= ssh_user %>\n  Port <%= ssh_port %>\n<% if ! verify_host_key || verify_host_key == :never -%>\n  UserKnownHostsFile /dev/null\n  StrictHostKeyChecking no\n<% end -%>\n  PasswordAuthentication no\n<% if private_key_path -%>\n<% private_key_path.each do |path| %>\n<% if path.include?(\" \") -%>\n  IdentityFile \"<%= path %>\"\n<% else -%>\n  IdentityFile <%= path %>\n<% end -%>\n<% end -%>\n<% end -%>\n<% if keys_only -%>\n  IdentitiesOnly yes\n<% end -%>\n<% if log_level -%>\n  LogLevel <%= log_level %>\n<% else -%>\n  LogLevel FATAL\n<% end -%>\n<% if forward_agent -%>\n  ForwardAgent yes\n<% end -%>\n<% if forward_x11 -%>\n  ForwardX11 yes\n<% end -%>\n<% if proxy_command -%>\n  ProxyCommand <%= proxy_command %>\n<% end -%>\n<% if !disable_deprecated_algorithms -%>\n  PubkeyAcceptedKeyTypes +ssh-rsa\n  HostKeyAlgorithms +ssh-rsa\n<% end -%>\n"
  },
  {
    "path": "templates/commands/winrm_config/config.erb",
    "content": "Host <%= host_key %>\n  HostName <%= winrm_host %>\n  User <%= winrm_user %>\n  Password <%= winrm_password %>\n  Port <%= winrm_port %>\n  <% if rdp_port -%>\n  RDPHostName <%= rdp_host %>\n  RDPPort <%= rdp_port %>\n  RDPUser <%= rdp_user %>\n  RDPPassword <%= rdp_pass %>\n  <% end -%>\n"
  },
  {
    "path": "templates/config/messages.erb",
    "content": "<% if !warnings.empty? -%>\nWarnings:\n<% warnings.each do |warning| -%>\n* <%= warning %>\n<% end -%>\n\n<% end -%>\n<% if !errors.empty? -%>\nErrors:\n<% errors.each do |error| -%>\n* <%= error %>\n<% end -%>\n\n<% end -%>\n"
  },
  {
    "path": "templates/config/validation_failed.erb",
    "content": "<% errors.each do |section, list| -%>\n<%= section %>:\n<% list.each do |error| -%>\n* <%= error %>\n<% end -%>\n\n<% end -%>\n"
  },
  {
    "path": "templates/guests/alpine/network_dhcp.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nauto eth<%= options[:interface] %>\niface eth<%= options[:interface] %> inet dhcp\n<% if !options[:use_dhcp_assigned_default_route] %>\n    post-up route del default dev $IFACE || true\n<% else %>\n    # We need to disable eth0, see GH-2648\n    post-up route del default dev eth0\n\n    pre-down route add default dev eth0\n<% end %>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/alpine/network_static.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nauto eth<%= options[:interface] %>\niface eth<%= options[:interface] %> inet static\n      address <%= options[:ip] %>\n      netmask <%= options[:netmask] %>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/alt/network_dhcp.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nTYPE=eth\nNM_CONTROLLED=<%= options.fetch(:nm_controlled, \"no\") %>\nBOOTPROTO=dhcp\nONBOOT=yes\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/alt/network_ipv4address.erb",
    "content": "#VAGRANT-BEGIN\n<%= options[:ip] %>/<%= options[:netmask] %>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/alt/network_ipv4route.erb",
    "content": "#VAGRANT-BEGIN\n<% if options[:gateway] %>\ndefault via <%= options[:gateway] %>\n<% end %>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/alt/network_static.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nTYPE=eth\nNM_CONTROLLED=<%= options.fetch(:nm_controlled, \"no\") %>\nBOOTPROTO=static\nONBOOT=yes\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/arch/default_network/network_dhcp.erb",
    "content": "Description='A basic dhcp ethernet connection'\nInterface=<%= options[:device] %>\nConnection=ethernet\nIP=dhcp\n"
  },
  {
    "path": "templates/guests/arch/default_network/network_static.erb",
    "content": "Connection=ethernet\nDescription='A basic static ethernet connection'\nInterface=<%= options[:device] %>\nIP=static\nAddress=('<%= options[:ip]%>/<%= options[:netmask] %>')\n<% if options[:gateway] -%>\nGateway='<%= options[:gateway] %>'\n<% end -%>\n"
  },
  {
    "path": "templates/guests/arch/default_network/network_static6.erb",
    "content": "Connection=ethernet\nDescription='A basic IPv6 ethernet connection'\nInterface=<%= options[:device] %>\nIP6=static\nAddress6=('<%= options[:ip]%>/<%= options[:netmask] %>')\n<% if options[:gateway] -%>\nGateway6='<%= options[:gateway] %>'\n<% end -%>\n"
  },
  {
    "path": "templates/guests/arch/systemd_networkd/network_dhcp.erb",
    "content": "[Match]\nName=<%= options[:device] %>\n\n[Network]\nDescription=A basic DHCP ethernet connection\nDHCP=ipv4\n"
  },
  {
    "path": "templates/guests/arch/systemd_networkd/network_static.erb",
    "content": "[Match]\nName=<%= options[:device] %>\n\n[Network]\nDescription=A basic static ethernet connection\nAddress=<%= options[:ip]%>/<%= options[:netmask] %>\n<% if options[:gateway] -%>\nGateway=<%= options[:gateway] %>\n<% end -%>\n"
  },
  {
    "path": "templates/guests/arch/systemd_networkd/network_static6.erb",
    "content": "[Match]\nName=<%= options[:device] %>\n\n[Network]\nDescription=A basic static ethernet connection\nAddress=<%= options[:ip]%>/<%= options[:netmask] %>\n<% if options[:gateway] -%>\nGateway=<%= options[:gateway] %>\n<% end -%>\n"
  },
  {
    "path": "templates/guests/coreos/etcd.service.erb",
    "content": "[Unit]\nDescription=Clustered etcd\n#After=docker.service\n\n[Service]\nRestart=always\nExecStart=/usr/bin/etcd -c 4001 -s 7001 -h <%= options[:my_ip] %> <% if options[:connection_string] %>-C <%= options[:connection_string] %><% end %> -d /home/core/etcd\n\n[Install]\nWantedBy=local.target\n"
  },
  {
    "path": "templates/guests/debian/network_dhcp.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nauto <%= options[:device] %>\niface <%= options[:device] %> inet dhcp\n<% if !options[:use_dhcp_assigned_default_route] %>\n    post-up ip route del default dev $IFACE || true\n<% else %>\n    # We need to disable eth0, see GH-2648\n    post-up ip route del default dev <%= options[:root_device] %> || true\n    post-up dhclient $IFACE\n    pre-down ip route add default dev <%= options[:root_device] %>\n<% end %>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/debian/network_static.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nauto <%= options[:device] %>\niface <%= options[:device] %> inet static\n      address <%= options[:ip] %>\n      netmask <%= options[:netmask] %>\n<% if options[:gateway] %>\n      gateway <%= options[:gateway] %>\n<% end %>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/debian/network_static6.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nauto <%= options[:device] %>\niface <%= options[:device] %> inet6 static\n      address <%= options[:ip] %>\n      netmask <%= options[:netmask] %>\n<% if options[:gateway] %>\n      gateway <%= options[:gateway] %>\n<% end %>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/freebsd/network_dhcp.erb",
    "content": "#VAGRANT-BEGIN\nifconfig_<%= options[:device] %>=\"DHCP\"\nsynchronous_dhclient=\"YES\"\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/freebsd/network_static.erb",
    "content": "#VAGRANT-BEGIN\nifconfig_<%= options[:device] %>=\"inet <%= options[:ip] %> netmask <%= options[:netmask] %>\"\n<% if options[:gateway] %>\ndefaultrouter=\"<%= options[:gateway] %>\"\n<% end %>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/freebsd/network_static6.erb",
    "content": "#VAGRANT-BEGIN\nifconfig_<%= options[:device] %>_ipv6=\"inet6 <%= options[:ip] %> prefixlen <%= options[:netmask] %>\"\n<% if options[:gateway] %>\nipv6_defaultrouter=\"<%= options[:gateway] %>\"\n<% end %>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/funtoo/network_dhcp.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\ntemplate='dhcp'\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/funtoo/network_static.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\ntemplate='interface'\nipaddr='<%= options[:ip] %>/<%= options[:netmask] %>'\n<% [:gateway, :nameservers, :domain, :route, :gateway6, :route6, :mtu].each do |key| %>\n<% if options[key] %>\n<%= key %>='<%= options[key] %>'\n<% end %>\n<% end %>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/funtoo/network_static6.erb",
    "content": "#VAGRANT-BEGIN\ntemplate='interface'\nipaddr='<%= options[:ip] %>/<%= options[:netmask] %>'\n<% [:gateway, :nameservers, :domain, :route, :gateway6, :route6, :mtu].each do |key| %>\n<% if options[key] %>\n<%= key %>='<%= options[key] %>'\n<% end %>\n<% end %>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/gentoo/network_dhcp.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nconfig_<%= options[:device] %>=\"dhcp\"\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/gentoo/network_static.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nconfig_<%= options[:device] %>=(\"<%= options[:ip] %> netmask <%= options[:netmask] %>\")\nmodules_<%= options[:device] %>=(\"!plug\")\n<% if options[:gateway] -%>\ngateways_<%= options[:device] %>=\"<%= options[:gateway] %>\"\n<% end -%>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/gentoo/network_static6.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nconfig_<%= options[:device] %>=\"<%= options[:ip] %>/<%= options[:netmask] %>\"\nmodules_<%= options[:device] %>=\"!plug\"\n<% if options[:gateway] -%>\ngateways_<%= options[:device] %>=\"<%= options[:gateway] %>\"\n<% end -%>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/gentoo/network_systemd.erb",
    "content": "[Match]\nName=<%= networks[0][:device] %>\n\n[Network]\n<%- gateway = nil %>\n<%- networks.each do |net| %>\n<%-   if net[:type] == 'dhcp' %>\nDHCP=yes\n<%-   elsif net[:ip] %>\nAddress=<%= net[:ip] %>/<%= net[:netmask] %>\n<%-   end %>\n<%-   gateway ||= net[:gateway] %>\n<%- end %>\n<%- if gateway %>\nGateway=<%= gateway %>\n<%- end %>\n"
  },
  {
    "path": "templates/guests/linux/etc_fstab.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\n<% folders.each do |opts| %>\n<%= opts[:name] %> <%= opts[:mount_point] %> <%= opts[:mount_type] %> <%= opts[:mount_options] || 'default' %> <%= opts[:dump] || 0 %> <%= opts[:fsck] || 0 %>\n<%end%>\n#VAGRANT-END"
  },
  {
    "path": "templates/guests/netbsd/network_dhcp.erb",
    "content": "#VAGRANT-BEGIN\nifconfig_wm<%= options[:interface] %>=dhcp\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/netbsd/network_static.erb",
    "content": "#VAGRANT-BEGIN\nifconfig_wm<%= options[:interface] %>=\"media autoselect up;inet <%= options[:ip] %> netmask <%= options[:netmask] %>\"\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/nixos/hostname.erb",
    "content": "{ config, pkgs, ... }:\n{\n  <% if name %>\n  networking.hostName = \"<%= name %>\";\n  <% end %>\n}\n"
  },
  {
    "path": "templates/guests/nixos/network.erb",
    "content": "{ config, pkgs, ... }:\n{\n  networking.interfaces = {\n    <% networks.select {|n| n[:device]}.each do |network| %>\n    <%= network[:device] %>.ipv4.addresses = [{\n      <% if network[:type] == :static %>\n      address = \"<%= network[:ip] %>\";\n      <% end %>\n      <% if network[:prefix_length] %>\n      prefixLength = <%= network[:prefix_length] %>;\n      <% end %>\n    }];\n    <% end %>\n  };\n}\n"
  },
  {
    "path": "templates/guests/openbsd/network_dhcp.erb",
    "content": "dhcp\n"
  },
  {
    "path": "templates/guests/openbsd/network_static.erb",
    "content": "inet <%= options[:ip] %> <%= options[:netmask] %> NONE\n"
  },
  {
    "path": "templates/guests/openbsd/network_static6.erb",
    "content": "inet6  <%= options[:ip] %> <%= options[:netmask] %> NONE\n"
  },
  {
    "path": "templates/guests/redhat/network_dhcp.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nBOOTPROTO=dhcp\nONBOOT=yes\nDEVICE=<%= options[:device] %>\nNM_CONTROLLED=<%= options.fetch(:nm_controlled, \"no\") %>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/redhat/network_static.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nNM_CONTROLLED=<%= options.fetch(:nm_controlled, \"no\") %>\nBOOTPROTO=none\nONBOOT=yes\nIPADDR=<%= options[:ip] %>\nNETMASK=<%= options[:netmask] %>\nDEVICE=<%= options[:device] %>\n<% if options[:gateway] %>\nGATEWAY=<%= options[:gateway] %>\n<% end %>\n<% if options[:mac_address] %>\nHWADDR=<%= options[:mac_address] %>\n<% end %>\nPEERDNS=no\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/redhat/network_static6.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nNM_CONTROLLED=<%= options.fetch(:nm_controlled, \"no\") %>\nBOOTPROTO=static\nONBOOT=yes\nDEVICE=<%= options[:device] %>\nIPV6INIT=yes\nIPV6ADDR=<%= options[:ip] %>/<%= options[:netmask] %>\n<% if options[:gateway] -%>\nIPV6_DEFAULTGW=<%= options[:gateway] %>\n<% end %>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/slackware/network_dhcp.erb",
    "content": "#VAGRANT-BEGIN\n# Config for eth<%= i %>\nUSE_DHCP[<%= i %>]=\"yes\"\nDHCP_HOSTNAME[<%= i %>]=\"\"\n\n<% if options[:gateway] -%>\nGATEWAY=\"<%= options[:gateway] %>\"\n<% end -%>\n\nDEBUG_ETH_UP=\"no\"\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/slackware/network_static.erb",
    "content": "#VAGRANT-BEGIN\n# Config for eth<%= i %>\nIPADDR[<%= i %>]=\"<%= options[:ip] %>\"\nNETMASK[<%= i %>]=\"<%= options[:ip] %>\"\nUSE_DHCP[<%= i %>]=\"\"\nDHCP_HOSTNAME[<%= i %>]=\"\"\n\n<% if options[:gateway] -%>\nGATEWAY=\"<%= options[:gateway] %>\"\n<% end -%>\n\nDEBUG_ETH_UP=\"no\"\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/suse/network_dhcp.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nBOOTPROTO='dhcp'\nSTARTMODE='auto'\nDEVICE='<%= options[:device] %>'\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/suse/network_static.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nBOOTPROTO='static'\nIPADDR='<%= options[:ip] %>'\nNETMASK='<%= options[:netmask] %>'\nDEVICE='<%= options[:device] %>'\n<% if options[:gateway] -%>\nGATEWAY='<%= options[:gateway] %>'\n<% end -%>\nPEERDNS='no'\nSTARTMODE='auto'\nUSERCONTROL='no'\n#VAGRANT-END\n"
  },
  {
    "path": "templates/guests/suse/network_static6.erb",
    "content": "#VAGRANT-BEGIN\n# The contents below are automatically generated by Vagrant. Do not modify.\nSTARTMODE='auto'\nBOOTPROTO='static'\nIPADDR=<%= options[:ip] %>\n<% if options[:netmask] -%>\nNETMASK=<%= options[:netmask] %>\n<% end -%>\nDEVICE=<%= options[:device] %>\n<% if options[:gateway] -%>\nGATEWAY=<%= options[:gateway] %>\n<% end -%>\n<% if options[:prefix_length] -%>\nPREFIXLEN=<%= options[:prefix_length] %>\n<% end -%>\n#VAGRANT-END\n"
  },
  {
    "path": "templates/license/license.html.tmpl",
    "content": "<html>\n  <body>\n    <p>\n      License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.\n      \"Business Source License\" is a trademark of MariaDB Corporation Ab.\n    </p>\n    <p>\n      Parameters\n    </p>\n    <ul>\n      <li>\n        Licensor: HashiCorp, Inc.\n      </li>\n      <li>\n        Licensed Work: Vagrant 2.4.3 or later. The Licensed Work is (c) 2025 HashiCorp, Inc.\n      </li>\n      <li>\n        <p>\n          Additional Use Grant: You may make production use of the Licensed Work, provided\n          Your use does not include offering the Licensed Work to third\n          parties on a hosted or embedded basis in order to compete with\n          HashiCorp's paid version(s) of the Licensed Work. For purposes\n          of this license:\n        </p>\n        <p>\n          A \"competitive offering\" is a Product that is offered to third\n          parties on a paid basis, including through paid support\n          arrangements, that significantly overlaps with the capabilities\n          of HashiCorp's paid version(s) of the Licensed Work. If Your\n          Product is not a competitive offering when You first make it\n          generally available, it will not become a competitive offering\n          later due to HashiCorp releasing a new version of the Licensed\n          Work with additional capabilities. In addition, Products that\n          are not provided on a paid basis are not competitive.\n        </p>\n        <p>\n          \"Product\" means software that is offered to end users to manage\n          in their own environments or offered as a service on a hosted\n          basis.\n        </p>\n        <p>\n          \"Embedded\" means including the source code or executable code\n          from the Licensed Work in a competitive offering. \"Embedded\"\n          also means packaging the competitive offering in such a way\n          that the Licensed Work must be accessed or downloaded for the\n          competitive offering to operate.\n        </p>\n        <p>\n          Hosting or using the Licensed Work(s) for internal purposes\n          within an organization is not considered a competitive\n          offering. HashiCorp considers your organization to include all\n          of your affiliates under common control.\n        </p>\n        <p>\n          For binding interpretive guidance on using HashiCorp products\n          under the Business Source License, please visit our FAQ.\n          (https://www.hashicorp.com/license-faq)\n        </p>\n      </li>\n      <li>\n        Change Date: Four years from the date the Licensed Work is published.\n      </li>\n      <li>\n        Change License: MPL 2.0\n      </li>\n    </ul>\n    <p>\n      For information about alternative licensing arrangements for the Licensed Work, please contact licensing@hashicorp.com.\n    </p>\n    <p>\n      Notice\n    </p>\n    <p>\n      Business Source License 1.1\n    </p>\n    <p>\n      Terms\n    </p>\n    <p>\n      The Licensor hereby grants you the right to copy, modify, create derivative\n      works, redistribute, and make non-production use of the Licensed Work. The\n      Licensor may make an Additional Use Grant, above, permitting limited production use.\n    </p>\n    <p>\n      Effective on the Change Date, or the fourth anniversary of the first publicly\n      available distribution of a specific version of the Licensed Work under this\n      License, whichever comes first, the Licensor hereby grants you rights under\n      the terms of the Change License, and the rights granted in the paragraph\n      above terminate.\n    </p>\n    <p>\n      If your use of the Licensed Work does not comply with the requirements\n      currently in effect as described in this License, you must purchase a\n      commercial license from the Licensor, its affiliated entities, or authorized\n      resellers, or you must refrain from using the Licensed Work.\n    </p>\n    <p>\n      All copies of the original and modified Licensed Work, and derivative works\n      of the Licensed Work, are subject to this License. This License applies\n      separately for each version of the Licensed Work and the Change Date may vary\n      for each version of the Licensed Work released by Licensor.\n    </p>\n    <p>\n      You must conspicuously display this License on each original or modified copy\n      of the Licensed Work. If you receive the Licensed Work in original or\n      modified form from a third party, the terms and conditions set forth in this\n      License apply to your use of that work.\n    </p>\n    <p>\n      Any use of the Licensed Work in violation of this License will automatically\n      terminate your rights under this License for the current and all other\n      versions of the Licensed Work.\n    </p>\n    <p>\n      This License does not grant you any right in any trademark or logo of\n      Licensor or its affiliates (provided that you may use a trademark or logo of\n      Licensor as expressly required by this License).\n    </p>\n    <p>\n      TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON\n      AN \"AS IS\" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,\n      EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF\n      MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND\n      TITLE.\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "templates/license/license.tmpl",
    "content": "License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.\n\"Business Source License\" is a trademark of MariaDB Corporation Ab.\n\nParameters\n\nLicensor:             HashiCorp, Inc.\nLicensed Work:        Vagrant 2.4.3 or later. The Licensed Work is (c) 2024\n                      HashiCorp, Inc.\nAdditional Use Grant: You may make production use of the Licensed Work, provided\n                      Your use does not include offering the Licensed Work to third\n                      parties on a hosted or embedded basis in order to compete with\n                      HashiCorp’s paid version(s) of the Licensed Work. For purposes\n                      of this license:\n\n                      A \"competitive offering\" is a Product that is offered to third\n                      parties on a paid basis, including through paid support\n                      arrangements, that significantly overlaps with the capabilities\n                      of HashiCorp's paid version(s) of the Licensed Work. If Your\n                      Product is not a competitive offering when You first make it\n                      generally available, it will not become a competitive offering\n                      later due to HashiCorp releasing a new version of the Licensed\n                      Work with additional capabilities. In addition, Products that\n                      are not provided on a paid basis are not competitive.\n\n                      \"Product\" means software that is offered to end users to manage\n                      in their own environments or offered as a service on a hosted\n                      basis.\n\n                      \"Embedded\" means including the source code or executable code\n                      from the Licensed Work in a competitive offering. \"Embedded\"\n                      also means packaging the competitive offering in such a way\n                      that the Licensed Work must be accessed or downloaded for the\n                      competitive offering to operate.\n\n                      Hosting or using the Licensed Work(s) for internal purposes\n                      within an organization is not considered a competitive\n                      offering. HashiCorp considers your organization to include all\n                      of your affiliates under common control.\n\n                      For binding interpretive guidance on using HashiCorp products\n                      under the Business Source License, please visit our FAQ.\n                      (https://www.hashicorp.com/license-faq)\nChange Date:          Four years from the date the Licensed Work is published.\nChange License:       MPL 2.0\n\nFor information about alternative licensing arrangements for the Licensed Work,\nplease contact licensing@hashicorp.com.\n\nNotice\n\nBusiness Source License 1.1\n\nTerms\n\nThe Licensor hereby grants you the right to copy, modify, create derivative\nworks, redistribute, and make non-production use of the Licensed Work. The\nLicensor may make an Additional Use Grant, above, permitting limited production use.\n\nEffective on the Change Date, or the fourth anniversary of the first publicly\navailable distribution of a specific version of the Licensed Work under this\nLicense, whichever comes first, the Licensor hereby grants you rights under\nthe terms of the Change License, and the rights granted in the paragraph\nabove terminate.\n\nIf your use of the Licensed Work does not comply with the requirements\ncurrently in effect as described in this License, you must purchase a\ncommercial license from the Licensor, its affiliated entities, or authorized\nresellers, or you must refrain from using the Licensed Work.\n\nAll copies of the original and modified Licensed Work, and derivative works\nof the Licensed Work, are subject to this License. This License applies\nseparately for each version of the Licensed Work and the Change Date may vary\nfor each version of the Licensed Work released by Licensor.\n\nYou must conspicuously display this License on each original or modified copy\nof the Licensed Work. If you receive the Licensed Work in original or\nmodified form from a third party, the terms and conditions set forth in this\nLicense apply to your use of that work.\n\nAny use of the Licensed Work in violation of this License will automatically\nterminate your rights under this License for the current and all other\nversions of the Licensed Work.\n\nThis License does not grant you any right in any trademark or logo of\nLicensor or its affiliates (provided that you may use a trademark or logo of\nLicensor as expressly required by this License).\n\nTO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON\nAN \"AS IS\" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,\nEXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND\nTITLE.\n"
  },
  {
    "path": "templates/locales/comm_winrm.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  vagrant_winrm:\n    errors:\n      authentication_failed: |-\n        An authorization error occurred while connecting to WinRM.\n\n        User: %{user}\n        Endpoint: %{endpoint}\n        Message: %{message}\n      winrm_bad_exit_status: |-\n        The following WinRM command responded with a non-zero exit status.\n        Vagrant assumes that this means the command failed!\n\n        %{command}\n\n        Stdout from the command:\n\n        %{stdout}\n\n        Stderr from the command:\n\n        %{stderr}\n      execution_error: |-\n        An error occurred executing a remote WinRM command.\n\n        Shell: %{shell}\n        Command: %{command}\n        Message: %{message}\n      invalid_shell: |-\n        %{shell} is not a supported type of Windows shell.\n      invalid_transport: |-\n        %{transport} is not a supported WinRM transport.\n      ssl_error: |-\n        An SSL error occurred while connecting to WinRM. This usually\n        occurs when you are using a self-signed certificate and have\n        not set the WinRM `ssl_peer_verification` config setting to false.\n\n        Message: %{message}\n\n      winrm_not_ready: |-\n        The box is not able to report an address for WinRM to connect to yet.\n        WinRM cannot access this Vagrant environment. Please wait for the\n        Vagrant environment to be running and try again.\n      winrm_file_transfer_error: |-\n        Failed to transfer a file between the host and guest\n\n        From: %{from}\n        To: %{to}\n        Message: %{message}\n\n      connection_refused: |-\n        WinRM connection was refused! This usually happens if the VM failed to\n        boot properly. Some steps to try to fix this: First, try reloading your\n        VM with `vagrant reload`, since a simple restart sometimes fixes things.\n        If that doesn't work, destroy your VM and recreate it with a `vagrant destroy`\n        followed by a `vagrant up`. If that doesn't work, contact a Vagrant\n        maintainer (support channels listed on the website) for more assistance.\n      connection_reset: |-\n        WinRM connection was reset! This usually happens when the machine is\n        taking too long to reboot. First, try reloading your machine with\n        `vagrant reload`, since a simple restart sometimes fixes things.\n        If that doesn't work, destroy your machine and recreate it with\n        a `vagrant destroy` followed by a `vagrant up`. If that doesn't work,\n        contact support.\n      connection_timeout: |-\n        Vagrant timed out while attempting to connect via WinRM. This usually\n        means that the VM booted, but there are issues with the WinRM configuration\n        or network connectivity issues. Please try to `vagrant reload` or\n        `vagrant up` again.\n      disconnected: |-\n        The WinRM connection was unexpectedly closed by the remote end. This\n        usually indicates that WinRM within the guest machine was unable to\n        properly start up. Please boot the VM in GUI mode to check whether\n        it is booting properly.\n      no_route: |-\n        While attempting to connect with WinRM, a \"no route to host\" (EHOSTUNREACH)\n        error was received. Please verify your network settings are correct\n        and try again.\n      host_down: |-\n        While attempting to connect with WinRM, a \"host is down\" (EHOSTDOWN)\n        error was received. Please verify your WinRM settings are correct\n        and try again.\n"
  },
  {
    "path": "templates/locales/command_ps.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  vagrant_ps:\n    detecting: |-\n      Detecting if a remote PowerShell connection can be made with the guest...\n    resetting: |-\n      Resetting WinRM TrustedHosts to their original value.\n\n    errors:\n      elevated_no_command: |-\n        A command must be provided when the --elevated flag is provided for\n        the powershell command. Please provide a command when using the\n        --elevated flag and try again.\n\n      host_unsupported: |-\n        Your host does not support PowerShell. A remote PowerShell connection\n        can only be made from a windows host.\n\n      ps_remoting_undetected: |-\n        Unable to establish a remote PowerShell connection with the guest.\n        Check if the firewall rules on the guest allow connections to the\n        Windows remote management service.\n\n      powershell_error: |-\n        An error occurred while executing a PowerShell script. This error\n        is shown below. Please read the error message and see if this is\n        a configuration error with your system. If it is not, then please\n        report a bug.\n\n        Script: %{script}\n        Error:\n\n        %{stderr}\n"
  },
  {
    "path": "templates/locales/command_rdp.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  vagrant_rdp:\n    detecting: |-\n      Detecting RDP info...\n    connecting: |-\n      Vagrant will now launch your RDP client with the connection parameters\n      above. If the connection fails, verify that the information above is\n      correct. Additionally, make sure the RDP server is configured and\n      running in the guest machine (it is disabled by default on Windows).\n      Also, verify that the firewall is open to allow RDP connections.\n\n    errors:\n      host_unsupported: |-\n        Vagrant doesn't support running an RDP client on your\n        host OS.\n\n        If you wish for the OS you're running to support launching\n        an RDP client, please contribute this functionality back\n        into Vagrant. At the very least, open an issue on how it\n        could be done and we can handle the integration.\n      rdp_undetected: |-\n        RDP connection information for this machine could not be\n        detected. This is typically caused when we can't find the IP\n        or port to connect to for RDP. Please verify you're forwarding\n        an RDP port and that your machine is accessible.\n"
  },
  {
    "path": "templates/locales/en.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  vagrant:\n    alert: |-\n      [%{date}]:\n        %{message}\n\n        - %{url}\n    boot_completed: |-\n      Machine booted and ready!\n    boot_waiting: |-\n      Waiting for machine to boot. This may take a few minutes...\n    box_auto_adding: |-\n      Box '%{name}' could not be found. Attempting to find and install...\n    box_add_choose_provider: |-\n      This box can work with multiple providers! The providers that it\n      can work with are listed below. Please review the list and choose\n      the provider you will be working with.\n\n      %{options}\n\n      Enter your choice:\n    box_add_choose_provider_again: |-\n      Invalid choice. Try again:\n    box_add_with_version: |-\n      Adding box '%{name}' (v%{version}) for provider: %{providers}\n    box_added: |-\n      Successfully added box '%{name}' (v%{version}) for '%{provider}'!\n    box_adding_direct: |-\n      Box file was not detected as metadata. Adding it directly...\n    box_add_url_warn: |-\n      It looks like you attempted to add a box with a URL for the name...\n      Instead, use box_url instead of box for box URLs.\n    box_downloading: |-\n      Downloading: %{url}\n    box_download_error: |-\n      Error downloading: %{message}\n    box_unpacking: |-\n      Unpacking necessary files from: %{url}\n    box_expanding_url: |-\n      URL: %{url}\n    box_loading_metadata: |-\n      Loading metadata for box '%{name}'\n    box_malformed_continue_on_update: |-\n      Could not determine box updates because box metadata was malformed.\n      Vagrant will continue on...\n    box_outdated: |-\n      * '%{name}' for '%{provider}' is outdated! Current: %{current}. Latest: %{latest}\n    box_outdated_checking_with_refresh: |-\n      Checking if box '%{name}' version '%{version}' is up to date...\n    box_outdated_local: |-\n      A newer version of the box '%{name}' is available and already\n      installed, but your Vagrant machine is running against\n      version '%{old}'. To update to version '%{new}',\n      destroy and recreate your machine.\n    box_outdated_metadata_download_error: |-\n      There was a problem while downloading the metadata for your box\n      to check for updates. This is not an error, since it is usually due\n      to temporary network problems. This is just a warning. The problem\n      encountered was:\n\n      %{message}\n\n      If you want to check for box updates, verify your network connection\n      is valid and try again.\n    box_outdated_metadata_error_single: |-\n      Error loading box metadata while attempting to check for\n      updates: %{message}\n    box_outdated_single: |-\n      A newer version of the box '%{name}' for provider '%{provider}' is\n      available! You currently have version '%{current}'. The latest is version\n      '%{latest}'. Run `vagrant box update` to update.\n    box_outdated_metadata_error: |-\n      * '%{name}' for '%{provider}': Error loading metadata: %{message}\n    box_outdated_no_metadata: |-\n      * '%{name}' for '%{provider}' wasn't added from a catalog, no version information\n    box_updating: |-\n      Updating '%{name}' with provider '%{provider}' from version\n      '%{old}' to '%{new}'...\n    box_update_checking: |-\n      Checking for updates to '%{name}'\n    box_up_to_date: |-\n      * '%{name}' for '%{provider}' (v%{version}) is up to date\n    box_up_to_date_single: |-\n      Box '%{name}' (v%{version}) is running the latest version.\n    box_version_malformed:\n      Invalid version '%{version}' for '%{box_name}', ignoring...\n    cfengine_bootstrapping: |-\n      Bootstrapping CFEngine with policy server: %{policy_server}...\n    cfengine_bootstrapping_policy_hub: |-\n      Performing additional bootstrap for policy hubs...\n    cfengine_cant_detect: |-\n      Vagrant doesn't support detecting whether CFEngine is installed\n      for the guest OS running in the machine. Vagrant will assume it is\n      installed and attempt to continue.\n    cfengine_detected_ip: |-\n      Detected policy server IP address: %{address}...\n    cfengine_installing: |-\n      Installing CFEngine onto machine...\n    cfengine_installing_files_path: |-\n      Copying the 'files_path' files...\n    cfengine_no_bootstrap: |-\n      CFEngine doesn't require bootstrap. Not bootstrapping.\n    cfengine_single_run: |-\n      CFEngine running in \"single run\" mode. Will execute one file.\n    cfengine_single_run_execute: |-\n      Executing run file for CFEngine...\n    chef_cant_detect: |-\n      Vagrant does not support detecting whether Chef is installed\n      for the guest OS running in the machine. Vagrant will assume it is\n      installed and attempt to continue.\n    chef_already_installed: |-\n      Detected Chef (%{version}) is already installed\n    chef_installing: |-\n      Installing Chef (%{version})...\n    chef_client_cleanup_failed: |-\n      Cleaning up the '%{deletable}' for Chef failed. The stdout and\n      stderr are shown below. Vagrant will continue destroying the machine,\n      so please clean up these resources manually.\n\n      stdout: %{stdout}\n\n      stderr: %{stderr}\n    chef_config_knife_not_found: |-\n      The `knife` application was not found! This is required by Vagrant\n      to automatically delete Chef nodes and clients.\n    chef_run_list_empty: |-\n      Warning: Chef run list is empty. This may not be what you want.\n    cli_interrupt: |-\n      Exiting due to interrupt.\n    cloud_init_waiting: Waiting for cloud init to finish running\n    container_pulling_single: |-\n      -- Image: %{name}\n    container_building_single: |-\n      -- Path: %{path}\n    container_running: |-\n      -- Container: %{name}\n    container_restarting_container_args: |-\n      -- Detected changes to container '%{name}' args, restarting...\n    container_restarting_container_image: |-\n      -- Detected newer image for container '%{name}', restarting...\n    docker_auto_start_not_available: |-\n      Unable to configure automatic restart of Docker containers on\n      the guest machine\n    docker_cant_detect: |-\n      Vagrant doesn't support detecting whether Docker is installed\n      for the guest OS running in the machine. Vagrant will assume it is\n      installed and attempt to continue.\n    docker_configure_autostart: |-\n      Configuring Docker to autostart containers...\n    docker_installing: |-\n      Installing Docker onto machine...\n    docker_pulling_images:\n      Pulling Docker images...\n    docker_pulling_single: |-\n      -- Image: %{name}\n    docker_building_single: |-\n      -- Path: %{path}\n    docker_building_images:\n      Building Docker images...\n    docker_running: |-\n      -- Container: %{name}\n    docker_restarting_container_args: |-\n      -- Detected changes to container '%{name}' args, restarting...\n    docker_restarting_container_image: |-\n      -- Detected newer image for container '%{name}', restarting...\n    docker_starting_containers: |-\n      Starting Docker containers...\n    hyperv_enable_enhanced_session: |-\n      Setting VM Enhanced session transport type to HvSocket\n    hyperv_disable_enhanced_session: |-\n      Setting VM Enhanced session transport type to disabled/default (VMBus)\n    inserted_key: |-\n      Key inserted! Disconnecting and reconnecting using new SSH key...\n    inserting_insecure_detected: |-\n      Vagrant insecure key detected. Vagrant will automatically replace\n      this with a newly generated keypair for better security.\n    inserting_random_key: |-\n      Inserting generated public key within guest...\n    inserting_remove_key: |-\n      Removing insecure key from the guest if it's present...\n    installing_provider: |-\n      Provider '%{provider}' not found. We'll automatically install it now...\n    installing_provider_detail: |-\n      The installation process will start below. Human interaction may be\n      required at some points. If you're uncomfortable with automatically\n      installing this provider, you can safely Ctrl-C this process and install\n      it manually.\n    list_commands: |-\n      Below is a listing of all available Vagrant commands and a brief\n      description of what they do.\n\n      %{list}\n    moved_cwd: |-\n      This machine used to live in %{old_wd} but it's now at %{current_wd}.\n      Depending on your current provider you may need to change the name of\n      the machine to run it as a different machine.\n    guest_deb_installing_smb: |-\n      Installing SMB \"mount.cifs\"...\n    global_status_footer: |-\n      The above shows information about all known Vagrant environments\n      on this machine. This data is cached and may not be completely\n      up-to-date (use \"vagrant global-status --prune\" to prune invalid\n      entries). To interact with any of the machines, you can go to that\n      directory and run Vagrant, or you can use the ID directly with\n      Vagrant commands from any directory. For example:\n      \"vagrant destroy 1a2b3c4d\"\n    global_status_none: |-\n      There are no active Vagrant environments on this computer! Or,\n      you haven't destroyed and recreated Vagrant environments that were\n      started with an older version of Vagrant.\n    plugin_needs_reinstall: |-\n      The following plugins were installed with a version of Vagrant\n      that had different versions of underlying components. Because\n      these component versions were changed (which rarely happens),\n      the plugins must be uninstalled and reinstalled.\n\n      To ensure that all the dependencies are properly updated as well\n      it is _highly recommended_ to do a `vagrant plugin uninstall`\n      prior to reinstalling.\n\n      This message will not go away until all the plugins below are\n      either uninstalled or uninstalled then reinstalled.\n\n      The plugins below will not be loaded until they're uninstalled\n      and reinstalled:\n\n      %{names}\n    post_up_message: |-\n      Machine '%{name}' has a post `vagrant up` message. This is a message\n      from the creator of the Vagrantfile, and not from Vagrant itself:\n\n      %{message}\n    provisioner_cleanup: |-\n      Running cleanup tasks for '%{name}' provisioner...\n    rsync_auto_initial: |-\n      Doing an initial rsync...\n    rsync_auto_new_folders: |-\n      New synced folders were added to the Vagrantfile since running\n      `vagrant reload`. If these new synced folders are backed by rsync,\n      they won't be automatically synced until a `vagrant reload` is run.\n    rsync_auto_no_paths: |-\n      There are no paths to watch! This is either because you have no\n      synced folders using rsync, or any rsync synced folders you have\n      have specified `rsync_auto` to be false.\n    rsync_auto_path: |-\n      Watching: %{path}\n    rsync_auto_remove_folder: |-\n      Not syncing %{folder} as it is not part of the current working directory.\n    rsync_auto_rsync_error: |-\n      There was an error while executing rsync. The error is shown below.\n      This may not be critical since rsync sometimes fails, but if this message\n      repeats, then please fix the issue:\n\n      %{message}\n    rsync_auto_post_command_error: |-\n      There was an error while executing the rsync post command. This error is\n      shown below. This may not be critical but if this message repeats please\n      fix the issue:\n\n      %{message}\n    rsync_communicator_not_ready: |-\n      The machine is reporting that it is not ready for rsync to\n      communicate with it. Verify that this machine is properly running.\n    rsync_communicator_not_ready_callback: |-\n      Failed to connect to remote machine. This is usually caused by the\n      machine rebooting or being halted. Please make sure the machine is\n      running, and modify a file to try again.\n    rsync_folder: |-\n      Rsyncing folder: %{hostpath} => %{guestpath}\n    rsync_folder_excludes: \"  - Exclude: %{excludes}\"\n    rsync_installing: \"Installing rsync to the VM...\"\n    rsync_proxy_machine: |-\n      The provider ('%{provider}') for the machine '%{name}' is\n      using a proxy machine. RSync will sync to this proxy\n      instead of directly to the environment itself.\n    rsync_showing_output: \"Showing rsync output...\"\n    rsync_ssh_password: |-\n      The machine you're rsyncing folders to is configured to use\n      password-based authentication. Vagrant can't script rsync to automatically\n      enter this password, so you'll likely be prompted for a password\n      shortly.\n\n      If you don't want to have to do this, please enable automatic\n      key insertion using `config.ssh.insert_key`.\n    ssh_exec_password: |-\n      The machine you're attempting to SSH into is configured to use\n      password-based authentication. Vagrant can't script entering the\n      password for you. If you're prompted for a password, please enter\n      the same password you have configured in the Vagrantfile.\n    stdin_cant_hide_input: |-\n      Error! Your console doesn't support hiding input. We'll ask for\n      input again below, but we WILL NOT be able to hide input. If this\n      is a problem for you, ctrl-C to exit and fix your stdin.\n    up_no_machines: |-\n      No machines to bring up. This is usually because all machines are\n      set to `autostart: false`, which means you have to explicitly specify\n      the name of the machine to bring up.\n    upgrading_home_path_v1_5: |-\n      Vagrant is upgrading some internal state for the latest version.\n      Please do not quit Vagrant at this time. While upgrading, Vagrant\n      will need to copy all your boxes, so it will use a considerable\n      amount of disk space. After it is done upgrading, the temporary disk\n      space will be freed.\n\n      Press ctrl-c now to exit if you want to remove some boxes or free\n      up some disk space.\n\n      Press the Enter or Return key to continue.\n\n    trigger:\n      on_error_continue: |-\n        Trigger configured to continue on error...\n      abort: |-\n        Vagrant has been configured to abort. Terminating now...\n      abort_threaded: |-\n        Vagrant has been configured to abort. Vagrant will terminate\n        after remaining running actions have completed...\n      start: |-\n        Running %{type} triggers %{stage} %{name} ...\n      fire_with_name: |-\n        Running trigger: %{name}...\n      fire: |-\n        Running trigger...\n      run:\n        inline: |-\n          Running local: Inline script\n          %{command}\n        script: |-\n          Running local script: %{path}\n\n\n    version_current: |-\n      Installed Version: %{version}\n    version_latest: |-\n      Latest Version: %{version}\n    version_latest_installed: |-\n      You're running an up-to-date version of Vagrant!\n    version_no_checkpoint: |-\n      Vagrant was unable to check for the latest version of Vagrant.\n      Please check manually at https://www.vagrantup.com\n    version_upgrade_available: |-\n      A new version of Vagrant is available: %{latest_version} (installed version: %{installed_version})!\n      To upgrade visit: https://www.vagrantup.com/downloads.html\n\n    version_upgrade_howto: |-\n      To upgrade to the latest version, visit the downloads page and\n      download and install the latest version of Vagrant from the URL\n      below:\n\n        https://www.vagrantup.com/downloads.html\n\n      If you're curious what changed in the latest release, view the\n      CHANGELOG below:\n\n        https://github.com/hashicorp/vagrant/blob/v%{version}/CHANGELOG.md\n\n    cfengine_config:\n      classes_array: |-\n        The 'classes' configuration must be an array.\n      files_path_not_directory: |-\n        The 'files_path' must point to a valid directory.\n      invalid_mode: |-\n        The mode must be 'bootstrap' or 'single_run'\n      policy_server_address: |-\n        The policy server address must be set for bootstrapping.\n      run_file_not_found: |-\n        The 'run_file' specified could not be found.\n\n    virtualbox:\n      checking_guest_additions: |-\n        Checking for guest additions in VM...\n      network_adapter: |-\n        Adapter %{adapter}: %{type}%{extra}\n      config:\n        id_in_pre_import: |-\n          The ':id' parameter is not available in \"pre-import\" customizations.\n        intnet_on_bad_type: |-\n          VirtualBox internal networks can only be enabled on \"private_network\"\n        invalid_event: |-\n          %{event} is not a valid event for customization. Valid events\n          are: %{valid_events}\n      warning:\n        shared_folder_symlink_create: |-\n          Vagrant is currently configured to create VirtualBox synced folders with\n          the `SharedFoldersEnableSymlinksCreate` option enabled. If the Vagrant\n          guest is not trusted, you may want to disable this option. For more\n          information on this option, please refer to the VirtualBox manual:\n\n            https://www.virtualbox.org/manual/ch04.html#sharedfolders\n\n          This option can be disabled globally with an environment variable:\n\n            VAGRANT_DISABLE_VBOXSYMLINKCREATE=1\n\n          or on a per folder basis within the Vagrantfile:\n\n            config.vm.synced_folder '/host/path', '/guest/path', SharedFoldersEnableSymlinksCreate: false\n    general:\n      batch_notify_error: |-\n        An error occurred. The error will be shown after all tasks complete.\n      batch_unexpected_error: |-\n        An unexpected error occurred when executing the action on the\n        '%{machine}' machine. Please report this as a bug:\n\n        %{message}\n      batch_vagrant_error: |-\n        An error occurred while executing the action on the '%{machine}'\n        machine. Please handle this error then try again:\n\n        %{message}\n      config_upgrade_messages: |-\n        There were warnings and/or errors while loading your Vagrantfile\n        for the machine '%{name}'.\n\n        Your Vagrantfile was written for an earlier version of Vagrant,\n        and while Vagrant does the best it can to remain backwards\n        compatible, there are some cases where things have changed\n        significantly enough to warrant a message. These messages are\n        shown below.\n\n        %{output}\n      experimental:\n        all: |-\n          You have enabled the experimental flag with all features enabled.\n          Please use with caution, as some of the features may not be fully\n          functional yet.\n        features: |-\n          You have requested to enable the experimental flag with the following features:\n\n          Features:  %{features}\n\n          Please use with caution, as some of the features may not be fully\n          functional yet.\n      not_in_installer: |-\n        You appear to be running Vagrant outside of the official installers.\n        Note that the installers are what ensure that Vagrant has all required\n        dependencies. Vagrant has detected that the following  executables are\n        currently unavailable:\n\n          %{tools}\n      upgraded_v1_dotfile: |-\n        A Vagrant 1.0.x state file was found for this environment. Vagrant has\n        gone ahead and auto-upgraded this to the latest format. Everything\n        should continue working as normal. Beware, however, that older versions\n        of Vagrant may no longer be used with this environment.\n\n        However, in case anything went wrong, the old dotfile was backed up\n        to the location below. If everything is okay, it is safe to remove\n        this backup.\n\n        Backup: %{backup_path}\n\n    plugins:\n      local:\n        uninstalled_plugins: |-\n          Vagrant has detected project local plugins configured for this\n          project which are not installed.\n\n            %{plugins}\n        request_plugin_install: |-\n          Install local plugins (Y/N)\n        install_rerun_command: |-\n          Vagrant has completed installing local plugins for the current Vagrant\n          project directory. Please run the requested command again.\n\n        install_all: |-\n          Vagrant will now install the following plugins to the local project\n          which have been defined in current Vagrantfile:\n\n            %{plugins}\n\n          Press ctrl-c to cancel...\n#-------------------------------------------------------------------------------\n# Translations for exception classes\n#-------------------------------------------------------------------------------\n    errors:\n      active_machine_with_different_provider: |-\n        An active machine was found with a different provider. Vagrant\n        currently allows each machine to be brought up with only a single\n        provider at a time. A future version will remove this limitation.\n        Until then, please destroy the existing machine to up with a new\n        provider.\n\n        Machine name: %{name}\n        Active provider: %{active_provider}\n        Requested provider: %{requested_provider}\n      alias_invalid_error: |-\n        The defined alias is not valid. Please review the information below\n        to help resolve the issue:\n\n        Alias: %{alias}\n        Message: %{message}\n      batch_multi_error: |-\n        An error occurred while executing multiple actions in parallel.\n        Any errors that occurred are shown below.\n\n        %{message}\n      box_add_no_matching_provider: |-\n        The box you're attempting to add doesn't support the provider\n        you requested. Please find an alternate box or use an alternate\n        provider. Double-check your requested provider to verify you didn't\n        simply misspell it.\n\n        If you're adding a box from HashiCorp's Vagrant Public Registry, make sure the box is\n        released.\n\n        Name: %{name}\n        Address: %{url}\n        Requested provider: %{requested}\n      box_add_no_architecture_support: |-\n        The box you're attempting to add doesn't support the requested\n        architecture. Please find an alternate box that support the\n        requested architecture.\n\n        Box: %{name}\n        Address: %{url}\n        Architecture: %{architecture}\n      box_add_no_matching_architecture: |-\n        The box you're attempting to add doesn't support the requested\n        architecture with the current provider. The following providers\n        support the requested architecture for this box:\n\n          %{supported_providers}\n\n        If the above providers cannot be used, please find and alternate\n        box that supports the requested architecture.\n\n        Box: %{name}\n        Address: %{url}\n        Architecture: %{architecture}\n        Provider: %{provider}\n      box_add_no_matching_provider_version: |-\n        The box you're attempting to add has no available version that\n        matches the constraints you requested with support for the\n        required provider and architecture. Versions of the box that\n        support the required provider and architecture are listed\n        below.\n\n        Box: %{name}\n        Address: %{url}\n        Constraints: %{constraints}\n        Architecture: %{architecture}\n        Provider: %{provider}\n        Supported versions: %{versions}\n      box_add_no_matching_version: |-\n        The box you're attempting to add has no available version that\n        matches the constraints you requested. Please double-check your\n        settings. Also verify that if you specified version constraints,\n        that the provider you wish to use is available for these constraints.\n\n        Box: %{name}\n        Address: %{url}\n        Constraints: %{constraints}\n        Available versions: %{versions}\n      box_add_short_not_found: |-\n        The box '%{name}' could not be found or could not be accessed in the remote catalog. \n        If this is a private box on the HashiCorp Vagrant Public Registry, please verify \n        you're logged in via `vagrant cloud auth login`. Also, please double-check the name. \n        The expanded URL and error message are shown below:\n\n        URL: %{url}\n        Error: %{error}\n      boot_bad_state: |-\n        The guest machine entered an invalid state while waiting for it\n        to boot. Valid states are '%{valid}'. The machine is in the\n        '%{invalid}' state. Please verify everything is configured\n        properly and try again.\n\n        If the provider you're using has a GUI that comes with it,\n        it is often helpful to open that and watch the machine, since the\n        GUI often has more helpful error messages than Vagrant can retrieve.\n        For example, if you're using VirtualBox, run `vagrant up` while the\n        VirtualBox GUI is open.\n\n        The primary issue for this error is that the provider you're using\n        is not properly configured. This is very rarely a Vagrant issue.\n      boot_timeout: |-\n        Timed out while waiting for the machine to boot. This means that\n        Vagrant was unable to communicate with the guest machine within\n        the configured (\"config.vm.boot_timeout\" value) time period.\n\n        If you look above, you should be able to see the error(s) that\n        Vagrant had when attempting to connect to the machine. These errors\n        are usually good hints as to what may be wrong.\n\n        If you're using a custom box, make sure that networking is properly\n        working and you're able to connect to the machine. It is a common\n        problem that networking isn't setup properly in these boxes.\n        Verify that authentication configurations are also setup properly,\n        as well.\n\n        If the box appears to be booting properly, you may want to increase\n        the timeout (\"config.vm.boot_timeout\") value.\n      box_add_exists: |-\n        The box you're attempting to add already exists. Remove it before\n        adding it again or add it with the `--force` flag.\n\n        Name: %{name}\n        Provider: %{provider}\n        Version: %{version}\n      box_add_direct_version: |-\n        You specified a box version constraint with a direct box file\n        path. Box version constraints only work with boxes from Vagrant\n        Cloud or a custom box host. Please remove the version constraint\n        and try again.\n      box_add_metadata_multi_url: |-\n        Multiple URLs for a box can't be specified when adding\n        versioned boxes. Please specify a single URL to the box\n        metadata (JSON) information. The full list of URLs you\n        specified is shown below:\n\n        %{urls}\n      box_add_name_mismatch: |-\n        The box you're adding has a name different from the name you\n        requested. For boxes with metadata, you cannot override the name.\n        If you're adding a box using `vagrant box add`, don't specify\n        the `--name` parameter. If the box is being added via a Vagrantfile,\n        change the `config.vm.box` value to match the name below.\n\n        Requested name: %{requested_name}\n        Actual name: %{actual_name}\n      box_add_name_required: |-\n        A name is required when adding a box file directly. Please pass\n        the `--name` parameter to `vagrant box add`. See\n        `vagrant box add -h` for more help.\n      box_checksum_invalid_type: |-\n        The specified checksum type is not supported by Vagrant: %{type}.\n        Vagrant supports the following checksum types:\n\n        %{types}\n      box_checksum_mismatch: |-\n        The checksum of the downloaded box did not match the expected\n        value. Please verify that you have the proper URL setup and that\n        you're downloading the proper file.\n\n        Expected: %{expected}\n        Received: %{actual}\n      box_config_changing_box: |-\n        While loading the Vagrantfile, the provider override specified\n        a new box. This box, in turn, specified a different box. This isn't\n        allowed, as it could lead to infinite recursion of Vagrant configuration\n        loading. Please fix this.\n      box_file_not_exist: |-\n        The file you are attempting to upload does not exist. Please recheck\n        that the file exists and was passed in correctly.\n\n        File: %{file}\n      box_metadata_corrupted: |-\n        The metadata associated with the box '%{name}' appears corrupted.\n        This is most often caused by a disk issue or system crash. Please\n        remove the box, re-add it, and try again.\n      box_metadata_missing_required_fields: |-\n        The metadata associated with the box '%{name}' appears to be missing\n        the required field '%{required_field}'. Please ensure `metadata.json`\n        has all required fields.\n\n        Required fields: %{all_fields}\n      box_metadata_download_error: |-\n        There was an error while downloading the metadata for this box.\n        The error message is shown below:\n\n        %{message}\n      box_metadata_file_not_found: |-\n        The \"metadata.json\" file for the box '%{name}' was not found.\n        Boxes require this file in order for Vagrant to determine the\n        provider it was made for. If you made the box, please add a\n        \"metadata.json\" file to it. If someone else made the box, please\n        notify the box creator that the box is corrupt. Documentation for\n        box file format can be found at the URL below:\n\n        https://www.vagrantup.com/docs/boxes/format.html\n      box_metadata_malformed: |-\n        The metadata for the box was malformed. The exact error\n        is shown below. Please contact the maintainer of the box so\n        that this issue can be fixed.\n\n        %{error}\n      box_metadata_malformed_version: |-\n        A version of the box you're loading is formatted in a way that\n        Vagrant cannot parse: '%{version}'. Please reformat the version\n        to be properly formatted. It should be in the format of\n        X.Y.Z.\n      box_not_found: |-\n        The box '%{name}' does not exist. Please double check and\n        try again. You can see the boxes that are installed with\n        `vagrant box list`.\n      box_not_found_with_provider: |-\n        The box '%{name}' isn't installed for the provider '%{provider}'.\n        Please double-check and try again. The installed providers for\n        the box are shown below:\n\n        %{providers}\n      box_not_found_with_provider_architecture: |-\n        The box '%{name}' for the provider '%{provider}' isn't installed\n        for the architecture '%{architecture}'. Please double-check and\n        try again. The installed architectures for the box are shown\n        below:\n\n        %{architectures}\n      box_not_found_with_provider_and_version: |-\n        The box '%{name}' (v%{version}) with provider '%{provider}'\n        could not be found. Please double check and try again. You\n        can see all the boxes that are installed with `vagrant box list`.\n      box_provider_doesnt_match: |-\n        The box you attempted to add doesn't match the provider you specified.\n\n        Provider expected: %{expected}\n        Provider of box: %{actual}\n      box_remove_multi_provider: |-\n        You requested to remove the box '%{name}' version '%{version}'. This\n        box has multiple providers. You must explicitly select a single provider to\n        remove with `--provider` or specify the `--all-providers` flag to remove\n        all providers.\n\n        Available providers: %{providers}\n      box_remove_multi_version: |-\n        You requested to remove the box '%{name}'. This box has multiple\n        versions. You must explicitly specify which version you want to\n        remove with the `--box-version` flag or specify the `--all` flag\n        to remove all versions. The available versions for this box are:\n\n        %{versions}\n      box_remove_not_found: |-\n        The box you requested to be removed could not be found. No\n        boxes named '%{name}' could be found.\n      box_remove_provider_not_found: |-\n        You requested to remove the box '%{name}' version '%{version}'\n        with provider '%{provider}'. The box '%{name}' exists but not\n        with the provider specified. Please double-check and try again.\n\n        The providers for this are: %{providers}\n      box_remove_version_not_found: |-\n        You requested to remove the box '%{name}' version '%{version}',\n        but that specific version of the box is not installed. Please\n        double-check and try again. The available versions for this box\n        are:\n\n        %{versions}\n      box_remove_multi_architecture: |-\n        You requested to remove the box '%{name}' version '%{version}'\n        with provider '%{provider}'. This box has multiple architectures.\n        You must explicitly select a single architecture to remove with\n        `--architecture` or specify the `--all-architectures` flag to\n        remove all architectures. The available architectures for this\n        box are:\n\n        %{architectures}\n      box_remove_architecture_not_found: |-\n        You requested to remove the box '%{name}' version '%{version}'\n        with provider '%{provider}' and architecture '%{architecture}' but\n        that specific architecture is not installed. Please double-check\n        and try again. The available architectures are:\n\n        %{architectures}\n      box_server_not_set: |-\n        A URL to a Vagrant Cloud server is not set, so boxes cannot be added with a\n        shorthand (\"mitchellh/precise64\") format. You may also be seeing this\n        error if you meant to type in a path to a box file which doesn't exist\n        locally on your system.\n\n        To set a URL to a Vagrant Cloud server, set the `VAGRANT_SERVER_URL`\n        environmental variable. Or, if you meant to use a file path, make sure\n        the path to the file is valid.\n      box_update_multi_provider: |-\n        You requested to update the box '%{name}'. This box has\n        multiple providers. You must explicitly select a single\n        provider to update with `--provider`.\n\n        Available providers: %{providers}\n      box_update_multi_architecture: |-\n        You requested to update the box '%{name}' (v%{version}) with provider\n        '%{provider}'. This box has multiple architectures. You must explicitly\n        select a single architecture to update with `--architecture`.\n\n        Available architectures: %{architectures}\n      box_update_no_box: |-\n        Box '%{name}' not installed, can't check for updates.\n      box_update_no_metadata: |-\n        The box '%{name}' is not a versioned box. The box was added\n        directly instead of from a box catalog. Vagrant can only\n        check the versions of boxes that were added from a catalog\n        such as from the public Vagrant Server.\n      box_update_no_name: |-\n        This machine doesn't have a box. Won't update anything.\n      box_version_invalid: |-\n        The format of box version provided (%{version}) is incorrect. The\n        version must follow the semantic versioning format or semantic\n        versioning compatible constraint format. Examples of valid values:\n\n          2.0\n          2.1.4\n          >= 2\n          < 3.0.0\n      bundler_disabled: |-\n        Vagrant's built-in bundler management mechanism is disabled because\n        Vagrant is running in an external bundler environment. In these\n        cases, plugin management does not work with Vagrant. To install\n        plugins, use your own Gemfile. To load plugins, either put the\n        plugins in the `plugins` group in your Gemfile or manually require\n        them in a Vagrantfile.\n      bundler_error: |-\n        Vagrant failed to properly resolve required dependencies. These\n        errors can commonly be caused by misconfigured plugin installations\n        or transient network issues. The reported error is:\n\n        %{message}\n      source_spec_not_found: |-\n        Vagrant failed to properly initialize its internal library\n        dependencies. Please try running the command again. If this\n        error persists, please report a bug.\n      cant_read_mac_addresses: |-\n        The provider you are using  ('%{provider}') doesn't support the\n        \"nic_mac_addresses\" provider capability which is required\n        for advanced networking to work with this guest OS. Please inform\n        the author of the provider to add this feature.\n\n        Until then, you must remove any networking configurations other\n        than forwarded ports from your Vagrantfile for Vagrant to continue.\n      capability_host_explicit_not_detected: |-\n        The explicit capability host specified of '%{value}' could not be\n        found.\n\n        This is an internal error that users should never see. Please report\n        a bug.\n      capability_host_not_detected: |-\n        The capability host could not be detected. This is an internal error\n        that users should never see. Please report a bug.\n      capability_invalid: |-\n        The capability '%{cap}' is invalid. This is an internal error that\n        users should never see. Please report a bug.\n      capability_not_found: |-\n        The capability '%{cap}' could not be found. This is an internal error\n        that users should never see. Please report a bug.\n\n      cfengine_bootstrap_failed: |-\n        Failed to bootstrap CFEngine. Please see the output above to\n        see what went wrong and address the issue.\n      cfengine_cant_autodetect_ip: |-\n        Vagrant was unable to automatically detect the IP address of the\n        running machine to use as the policy server address. Please specify\n        the policy server address manually, or verify that the networks are\n        configured properly internally.\n      cfengine_install_failed: |-\n        After installing CFEngine, Vagrant still can't detect a proper\n        CFEngine installation. Please verify that CFEngine was properly\n        installed, as the installation may have failed.\n      cfengine_not_installed: |-\n        CFEngine appears not to be installed on the guest machine. Vagrant\n        can attempt to install CFEngine for you if you set the \"install\"\n        setting to \"true\", but this setting was not set. Please install\n        CFEngine on the guest machine or enable the \"install\" setting for\n        Vagrant to attempt to install it for you.\n      cli_invalid_options: |-\n        An invalid option was specified. The help for this command\n        is available below.\n\n        %{help}\n      cli_invalid_usage: |-\n        This command was not invoked properly. The help for this command is\n        available below.\n\n        %{help}\n      clone_not_found: |-\n        The specified Vagrantfile to clone from was not found. Please verify\n        the `config.vm.clone` setting points to a valid Vagrantfile.\n      clone_machine_not_found: |-\n        The clone environment hasn't been created yet. To clone from\n        another Vagrantfile, it must already be created with `vagrant up`.\n        It doesn't need to be running.\n\n        Additionally, the created environment must be started with a provider\n        matching this provider. For example, if you're using VirtualBox,\n        the clone environment must also be using VirtualBox.\n      cloud_init_not_found: |-\n        cloud-init is not found. Please ensure that cloud-init is installed and\n        available on path for guest '%{guest_name}'.\n      cloud_init_command_failed: |-\n        cloud init command '%{cmd}' failed on guest '%{guest_name}'.\n      command_deprecated: |-\n        The command 'vagrant %{name}' has been deprecated and is no longer functional\n        within Vagrant.\n      command_suspend_all_arguments: |-\n        The suspend command with the `--all-global` flag does not take any arguments.\n      command_unavailable: |-\n        The executable '%{file}' Vagrant is trying to run was not\n        found in the PATH variable. This is an error. Please verify\n        this software is installed and on the path.\n      command_unavailable_windows: |-\n        The executable '%{file}' Vagrant is trying to run was not\n        found in the %PATH% variable. This is an error. Please verify\n        this software is installed and on the path.\n      communicator_not_found: |-\n        The requested communicator '%{comm}' could not be found.\n        Please verify the name is correct and try again.\n      config_invalid: |-\n        There are errors in the configuration of this machine. Please fix\n        the following errors and try again:\n\n        %{errors}\n      config_upgrade_errors: |-\n        Because there were errors upgrading your Vagrantfiles, Vagrant\n        can no longer continue. Please fix the errors above and try again.\n      copy_private_key_failed: |-\n        Vagrant failed to copy the default insecure private key into your\n        home directory. This is usually caused by a permissions error.\n        Please make sure the permissions of the source is readable and\n        the destination is writable.\n\n        Source: %{source}\n        Destination: %{destination}\n      corrupt_machine_index: |-\n        The machine index which stores all required information about\n        running Vagrant environments has become corrupt. This is usually\n        caused by external tampering of the Vagrant data folder.\n\n        Vagrant cannot manage any Vagrant environments if the index is\n        corrupt. Please attempt to manually correct it. If you are unable\n        to manually correct it, then remove the data file at the path below.\n        This will leave all existing Vagrant environments \"orphaned\" and\n        they'll have to be destroyed manually.\n\n        Path: %{path}\n      create_iso_host_cap_not_found: |-\n        Vagrant cannot create an iso due to the host capability for creating isos not existing.\n        Vagrant will now exit.\n      darwin_mount_failed: |-\n        Failed to mount folders in Darwin guest. The command attempted was:\n\n        %{command}\n\n        The error output from the last command was:\n\n        %{output}\n      darwin_version_failed: |-\n        Failed to determine macOS version.\n\n          Version string: %{version}\n          Error information: %{error}\n      destroy_requires_force: |-\n        Destroy doesn't have a TTY to ask for confirmation. Please pass the\n        `--force` flag to force a destroy, otherwise attach a TTY so that\n        the destroy can be confirmed.\n      dotfile_upgrade_json_error: |-\n        A Vagrant 1.0.x local state file was found. Vagrant is able to upgrade\n        this to the latest format automatically, however various checks are\n        put in place to verify data isn't incorrectly deleted. In this case,\n        the old state file was not valid JSON. Vagrant 1.0.x would store state\n        as valid JSON, meaning that this file was probably tampered with or\n        manually edited. Vagrant's auto-upgrade process cannot continue in this\n        case.\n\n        In most cases, this can be resolve by simply removing the state file.\n        Note however though that if Vagrant was previously managing virtual\n        machines, they may be left in an \"orphan\" state. That is, if they are\n        running or exist, they'll have to manually be removed.\n\n        If you're unsure what to do, ask the Vagrant mailing list or contact\n        support.\n\n        State file path: %{state_file}\n      download_already_in_progress_error: |-\n        Download to global Vagrant location already in progress. This\n        may be caused by other Vagrant processes attempting to download\n        a file to the same location.\n\n        Download path: %{dest_path}\n        Lock file path: %{lock_file_path}\n      downloader_error: |-\n        An error occurred while downloading the remote file. The error\n        message, if any, is reproduced below. Please fix this error and try\n        again.\n\n        %{message}\n      downloader_interrupted: |-\n        The download was interrupted by an external signal. It did not\n        complete.\n      downloader_checksum_error: |-\n        The calculated checksum of the requested file does not match the expected\n        checksum!\n\n        File source:         %{source}\n        Checksum type:       %{type}\n        Expected checksum:   %{expected_checksum}\n        Calculated checksum: %{actual_checksum}\n      env_inval: |-\n        Vagrant received an \"EINVAL\" error while attempting to set some\n        environment variables. This is usually caused by the total size of your\n        environment variables being too large. Vagrant sets a handful of\n        environment variables to function and requires this to work. Please\n        delete some environment variables prior to executing Vagrant to\n        fix this.\n      environment_locked: |-\n        Vagrant attempted to acquire a lock named '%{name}', but this\n        lock is being held by another instance of Vagrant already. Please\n        wait and try again.\n      environment_non_existent_cwd: |-\n        The working directory for Vagrant doesn't exist! This is the\n        specified working directory:\n\n        %{cwd}\n      forward_port_adapter_not_found: |-\n        The adapter to attach a forwarded port to was not found. Please\n        verify that the given adapter is setup on the machine as a NAT\n        interface.\n\n        Host port: %{host}\n        Guest port: %{guest}\n        Adapter: %{adapter}\n      freebsd_nfs_whitespace: |-\n        FreeBSD hosts do not support sharing directories with whitespace in\n        their path. Please adjust your path accordingly.\n      guest_capability_invalid: |-\n        The registered guest capability '%{cap}' for the\n        detected guest OS '%{guest}' is invalid. The capability does\n        not implement the proper method. This is a bug with Vagrant or the\n        plugin that implements this capability. Please report a bug.\n      guest_capability_not_found: |-\n        Vagrant attempted to execute the capability '%{cap}'\n        on the detect guest OS '%{guest}', but the guest doesn't\n        support that capability. This capability is required for your\n        configuration of Vagrant. Please either reconfigure Vagrant to\n        avoid this capability or fix the issue by creating the capability.\n      guest_explicit_not_detected: |-\n        The guest implementation explicitly specified in your Vagrantfile\n        (\"%{value}\") could not be found. Please verify that the plugin is\n        installed which implements this guest and that the value you\n        used for `config.vm.guest` is correct.\n      guest_not_detected: |-\n        The guest operating system of the machine could not be detected!\n        Vagrant requires this knowledge to perform specific tasks such\n        as mounting shared folders and configuring networks. Please add\n        the ability to detect this guest operating system to Vagrant\n        by creating a plugin or reporting a bug.\n      home_dir_later_version: |-\n        It appears that a newer version of Vagrant was run on this machine\n        at some point. The current version of Vagrant is unable to read\n        the configuration structure of this newer version. Please upgrade to\n        the latest version of Vagrant.\n      home_dir_not_accessible: |-\n        The home directory you specified is not accessible. The home\n        directory that Vagrant uses must be both readable and writable.\n\n        You specified: %{home_path}\n      home_dir_unknown_version: |-\n        The Vagrant app data directory (%{path}) is in a\n        structure Vagrant doesn't understand. This is a rare exception.\n        Please report an issue or ask the mailing list for help.\n      host_explicit_not_detected: |-\n        The host implementation explicitly specified in your Vagrantfile\n        (\"%{value}\") could not be found. Please verify that the plugin is\n        installed which implements this host and that the value you used\n        for `config.vagrant.host` is correct.\n      iso_build_failed: |-\n        Failed to build iso image. The following command returned an error:\n\n        %{cmd}\n\n        Stdout from the command:\n\n        %{stdout}\n\n        Stderr from the command:\n\n        %{stderr}\n      hyperv_virtualbox_error: |-\n        Hyper-V and VirtualBox cannot be used together and will result in a\n        system crash. Vagrant will now exit. Please disable Hyper-V if you wish\n        to use VirtualBox.\n      interrupted: |-\n        Vagrant exited after cleanup due to external interrupt.\n      local_data_dir_not_accessible: |-\n        The directory Vagrant will use to store local environment-specific\n        state is not accessible. The directory specified as the local data\n        directory must be both readable and writable for the user that is\n        running Vagrant.\n\n        Local data directory: %{local_data_path}\n      linux_mount_failed: |-\n        Failed to mount folders in Linux guest. This is usually because\n        the \"vboxsf\" file system is not available. Please verify that\n        the guest additions are properly installed in the guest and\n        can work properly. The command attempted was:\n\n        %{command}\n\n        The error output from the last command was:\n\n        %{output}\n      linux_rdp_client_not_found: |-\n        An appropriate RDP client was not found. Vagrant requires either\n        `xfreerdp` or `rdesktop` in order to connect via RDP to the Vagrant\n        environment. Please ensure one of these applications is installed and\n        available on the path and try again.\n      machine_action_locked: |-\n        An action '%{action}' was attempted on the machine '%{name}',\n        but another process is already executing an action on the machine.\n        Vagrant locks each machine for access by only one process at a time.\n        Please wait until the other Vagrant process finishes modifying this\n        machine, then try again.\n\n        If you believe this message is in error, please check the process\n        listing for any \"ruby\" or \"vagrant\" processes and kill them. Then\n        try again.\n      machine_folder_not_accessible: |-\n        Vagrant attempted to clean the machine folder for the machine '%{name}'\n        but does not have permission to read the following path:\n\n        %{path}\n\n        Please ensure that Vagrant has the proper permissions to access the path\n        above. You may need to grant this permission to the terminal emulator\n        running Vagrant as well.\n      machine_guest_not_ready: |-\n        Guest-specific operations were attempted on a machine that is not\n        ready for guest communication. This should not happen and a bug\n        should be reported.\n      machine_locked: |-\n        Vagrant can't use the requested machine because it is locked! This\n        means that another Vagrant process is currently reading or modifying\n        the machine. Please wait for that Vagrant process to end and try\n        again. Details about the machine are shown below:\n\n        Name: %{name}\n        Provider: %{provider}\n      machine_not_found: |-\n        The machine with the name '%{name}' was not found configured for\n        this Vagrant environment.\n      machine_state_invalid: |-\n        An internal error has occurred! The provider of the machine you're\n        trying to work with reported an invalid state. This is a bug with\n        the provider you're using, and not with Vagrant itself or with\n        any configuration you may have done. Please report this bug to\n        the proper location.\n      multi_vm_target_required: |-\n        This command requires a specific VM name to target in a multi-VM environment.\n      netplan_no_available_renderers: |-\n        No renderers compatible with netplan are available on guest. Please install\n        a compatible renderer.\n      net_ssh_exception: |-\n        An error occurred in the underlying SSH library that Vagrant uses.\n        The error message is shown below. In many cases, errors from this\n        library are caused by ssh-agent issues. Try disabling your SSH\n        agent or removing some keys and try again.\n\n        If the problem persists, please report a bug to the net-ssh project.\n\n        %{message}\n      network_type_not_supported: |-\n        The %{type} network type is not supported for this box or guest.\n      network_address_invalid: |-\n        Network settings specified in your Vagrantfile define an invalid\n        IP address. Please review the error message below and update your\n        Vagrantfile network settings:\n\n          Address: %{address}\n          Netmask: %{mask}\n          Error: %{error}\n      network_manager_not_installed: |-\n        Vagrant was instructed to configure the %{device} network device to\n        be managed by NetworkManager. However, the configured guest VM does\n        not have NetworkManager installed. To fix this error please remove\n        the `nm_controlled` setting from local Vagrantfile. If NetworkManager\n        is required to manage the network devices, please use a box with\n        NetworkManager installed.\n      nfs_bad_exports: |-\n        NFS is reporting that your exports file is invalid. Vagrant does\n        this check before making any changes to the file. Please correct\n        the issues below and execute \"vagrant reload\":\n\n        %{output}\n      nfs_exports_failed: |-\n        Vagrant failed to install an updated NFS exports file. This may be\n        due to overly restrictive permissions on your NFS exports file. Please\n        validate them and try again.\n\n        command: %{command}\n        stdout: %{stdout}\n        stderr: %{stderr}\n      nfs_dupe_permissions: |-\n        You have attempted to export the same nfs host path at %{hostpath} with\n        different nfs permissions. Please pick one permission and reload your guest.\n      nfs_cant_read_exports: |-\n        Vagrant can't read your current NFS exports! The exports file should be\n        readable by any user. This is usually caused by invalid permissions\n        on your NFS exports file. Please fix them and try again.\n      nfs_mount_failed: |-\n        Mounting NFS shared folders failed. This is most often caused by the NFS\n        client software not being installed on the guest machine. Please verify\n        that the NFS client software is properly installed, and consult any resources\n        specific to the linux distro you're using for more information on how to\n        do this.\n      nfs_no_guest_ip: |-\n        No guest IP was given to the Vagrant core NFS helper. This is an\n        internal error that should be reported as a bug.\n      nfs_no_host_ip: |-\n        No host IP was given to the Vagrant core NFS helper. This is\n        an internal error that should be reported as a bug.\n      nfs_no_hostonly_network: |-\n        NFS requires a host-only network to be created.\n        Please add a host-only network to the machine (with either DHCP or a\n        static IP) for NFS to work.\n      nfs_no_valid_ids: |-\n        No valid IDs were given to the NFS synced folder implementation to\n        prune. This is an internal bug with Vagrant and an issue should be\n        filed.\n      nfs_not_supported: |-\n        It appears your machine doesn't support NFS, or there is not an\n        adapter to enable NFS on this machine for Vagrant. Please verify\n        that `nfsd` is installed on your machine, and try again. If you're\n        on Windows, NFS isn't supported. If the problem persists, please\n        contact Vagrant support.\n      nfs_client_not_installed_in_guest: |-\n        No NFS client was detected as installed in your guest machine. This\n        is required for NFS synced folders to work. In addition to this, Vagrant\n        doesn't know how to automatically install NFS for your machine, so\n        you must do this manually.\n\n        If this message is erroneous, you may disable this check by setting\n        `config.nfs.verify_installed` to `false` in your Vagrantfile.\n      no_default_provider: |-\n        No usable default provider could be found for your system.\n\n        Vagrant relies on interactions with 3rd party systems, known as\n        \"providers\", to provide Vagrant with resources to run development\n        environments. Examples are VirtualBox, VMware, Hyper-V.\n\n        The easiest solution to this message is to install VirtualBox, which\n        is available for free on all major platforms.\n\n        If you believe you already have a provider available, make sure it\n        is properly installed and configured. You can see more details about\n        why a particular provider isn't working by forcing usage with\n        `vagrant up --provider=PROVIDER`, which should give you a more specific\n        error message for that particular provider.\n      no_default_synced_folder_impl: |-\n        No synced folder implementation is available for your synced folders!\n        Please consult the documentation to learn why this may be the case.\n        You may force a synced folder implementation by specifying a \"type:\"\n        option for the synced folders. Available synced folder implementations\n        are listed below.\n\n        %{types}\n      no_env: |-\n        A Vagrant environment or target machine is required to run this\n        command. Run `vagrant init` to create a new Vagrant environment. Or,\n        get an ID of a target machine from `vagrant global-status` to run\n        this command on. A final option is to change to a directory with a\n        Vagrantfile and to try again.\n      oscdimg_command_missing: |-\n        Vagrant failed to locate the oscdimg.exe executable which is required\n        for creating ISO files. Please ensure the oscdimg.exe executable is\n        available on the configured PATH. If the oscdimg.exe executable is\n        not found on the local system, it can be installed with the Windows\n        Assessment and Deployment Kit:\n\n          https://go.microsoft.com/fwlink/?linkid=2196127\n      plugin_gem_not_found: |-\n        The plugin '%{name}' could not be installed because it could not\n        be found. Please double check the name and try again.\n      plugin_install_license_not_found: |-\n        The license file to install could not be found. Please verify\n        the path you gave is correct. The path to the license file given\n        was: '%{path}'\n      plugin_install_failed: |-\n        Vagrant was unable to install the requested plugin: '%{name}'\n        Please try to install this plugin again. If Vagrant is still unable\n        to install the requested plugin, please report this error.\n      plugin_install_space: |-\n        The directory where plugins are installed (the Vagrant home directory)\n        has a space in it. On Windows, there is a bug in Ruby when compiling\n        plugins into directories with spaces. Please move your Vagrant home\n        directory to a path without spaces and try again.\n      plugin_install_version_conflict: |-\n        The plugin(s) can't be installed due to the version conflicts below.\n        This means that the plugins depend on a library version that conflicts\n        with other plugins or Vagrant itself, creating an impossible situation\n        where Vagrant wouldn't be able to load the plugins.\n\n        You can fix the issue by either removing a conflicting plugin or\n        by contacting a plugin author to see if they can address the conflict.\n\n        %{conflicts}\n      plugin_init_error: |-\n        The plugins failed to initialize correctly. This may be due to manual\n        modifications made within the Vagrant home directory. Vagrant can\n        attempt to automatically correct this issue by running:\n\n          vagrant plugin repair\n\n        If Vagrant was recently updated, this error may be due to incompatible\n        versions of dependencies. To fix this problem please remove and re-install\n        all plugins. Vagrant can attempt to do this automatically by running:\n\n          vagrant plugin expunge --reinstall\n\n        Or you may want to try updating the installed plugins to their latest\n        versions:\n\n          vagrant plugin update\n\n        Error message given during initialization: %{message}\n      plugin_load_error: |-\n        The plugins failed to load properly. The error message given is\n        shown below.\n\n        %{message}\n      plugin_not_installed: |-\n        The plugin '%{name}' is not currently installed.\n      plugin_state_file_not_parsable: |-\n        Failed to parse the state file \"%{path}\":\n        %{message}\n\n        Please remove the file and reinstall the plugins.\n        If this error recurs, please report a bug.\n      plugin_uninstall_system: |-\n        The plugin you're attempting to uninstall ('%{name}') is a\n        system plugin. This means that the plugin is part of the installation\n        of Vagrant. These plugins cannot be removed.\n\n        You can however, install a plugin with the same name to replace\n        these plugins. User-installed plugins take priority over\n        system-installed plugins.\n      plugin_source_error: |-\n        Vagrant failed to load a configured plugin source. This can be caused\n        by a variety of issues including: transient connectivity issues, proxy\n        filtering rejecting access to a configured plugin source, or a configured\n        plugin source not responding correctly. Please review the error message\n        below to help resolve the issue:\n\n          %{error_msg}\n\n        Source: %{source}\n      plugin_no_local_error: |-\n        Vagrant is not currently working within a Vagrant project directory. Local\n        plugins are only supported within a Vagrant project directory.\n      plugin_missing_local_error: |-\n        Vagrant is missing plugins required by the currently loaded Vagrantfile.\n        Please install the configured plugins and run this command again. The\n        following plugins are currently missing:\n\n          %{plugins}\n\n        To install the plugins configured in the current Vagrantfile run the\n        following command:\n\n          vagrant plugin install --local\n      plugin_needs_developer_tools: |-\n        Vagrant failed to install the requested plugin because development tools\n        are required for installation but are not currently installed on this\n        machine. Please install development tools and then try this command\n        again.\n      plugin_missing_library: |-\n        Vagrant failed to install the requested plugin because it depends\n        on development files for a library which is not currently installed\n        on this system. The following library is required by the '%{name}'\n        plugin:\n\n          %{library}\n\n        If a package manager is used on this system, please install the development\n        package for the library. The name of the package will be similar to:\n\n          %{library}-dev or %{library}-devel\n\n        After the library and development files have been installed, please\n        run the command again.\n      plugin_missing_ruby_dev: |-\n        Vagrant failed to install the requested plugin because the Ruby header\n        files could not be found. Install the ruby development package for your\n        system and then run this command again.\n      powershell_not_found: |-\n        Failed to locate the powershell executable on the available PATH. Please\n        ensure powershell is installed and available on the local PATH, then\n        run the command again.\n      powershell_invalid_version: |-\n        The version of powershell currently installed on this host is less than\n        the required minimum version. Please upgrade the installed version of\n        powershell to the minimum required version and run the command again.\n\n          Installed version: %{installed_version}\n          Minimum required version: %{minimum_version}\n      pushes_not_defined: |-\n        The Vagrantfile does not define any 'push' strategies. In order to use\n        `vagrant push`, you must define at least one push strategy:\n\n            config.push.define \"ftp\" do |push|\n              # ... push-specific options\n            end\n      push_strategy_not_defined: |-\n        The push strategy '%{name}' is not defined in the Vagrantfile. Defined\n        strategy names are:\n\n            %{pushes}\n      push_strategy_not_loaded: |-\n        There are no push strategies named '%{name}'. Please make sure you\n        spelled it correctly. If you are using an external push strategy, you\n        may need to install a plugin. Loaded push strategies are:\n\n            %{pushes}\n      push_strategy_not_provided: |-\n        The Vagrantfile defines more than one 'push' strategy. Please specify a\n        strategy. Defined strategy names are:\n\n            %{pushes}\n      package_include_symlink: |-\n        A file or directory you're attempting to include with your packaged\n        box has symlinks in it. Vagrant cannot include symlinks in the\n        resulting package. Please remove the symlinks and try again.\n      package_invalid_info: |-\n        The information file that you've attempted to include doesn't exist or isn't a valid JSON file.\n        Please check that the file exists and is titled 'info.json' and try again.\n      provider_cant_install: |-\n        The provider '%{provider}' doesn't support automatic installation.\n        This is a limitation of this provider. Please report this as a feature\n        request to the provider in question. To install this provider, you'll\n        have to do so manually.\n      provider_checksum_mismatch: |-\n        The checksum of the downloaded provider '%{provider}' did not match the\n        expected value. If the problem persists, please install the provider\n        manually.\n\n        Expected: %{expected}\n        Received: %{actual}\n      provider_install_failed: |-\n        Installation of the provider '%{provider}' failed! The stdout\n        and stderr are shown below. Please read the error output, resolve it,\n        and try again. If problem persists, please install the provider\n        manually.\n\n        Stdout: %{stdout}\n        Stderr: %{stderr}\n      provider_not_found: |-\n        The provider '%{provider}' could not be found, but was requested to\n        back the machine '%{machine}'. Please use a provider that exists.\n\n        Vagrant knows about the following providers: %{providers}\n      provider_not_found_suggestion: |-\n        The provider '%{provider}' could not be found, but was requested to\n        back the machine '%{machine}'. Please use a provider that exists.\n\n        Did you mean '%{suggestion}'?\n\n        Vagrant knows about the following providers: %{providers}\n      provider_not_usable: |-\n        The provider '%{provider}' that was requested to back the machine\n        '%{machine}' is reporting that it isn't usable on this system. The\n        reason is shown below:\n\n        %{message}\n      provisioner_flag_invalid: |-\n        '%{name}' is not a known provisioner. Please specify a valid\n        provisioner.\n      provisioner_winrm_unsupported: |-\n        The provisioner '%{name}' doesn't support provisioning on\n        Windows guests via WinRM. This is likely not a limitation\n        of the provisioner itself but rather that Vagrant doesn't know\n        how to run this provisioner over WinRM.\n\n        If you'd like this provisioner to work over WinRM, please\n        take a look at the Vagrant source code linked below and try\n        to contribute back support. Thank you!\n\n        https://github.com/hashicorp/vagrant\n      rsync_error: |-\n        There was an error when attempting to rsync a synced folder.\n        Please inspect the error message below for more info.\n\n        Host path: %{hostpath}\n        Guest path: %{guestpath}\n        Command: %{command}\n        Error: %{stderr}\n      rsync_post_command_error: |-\n        There was an error while attempting to run the post rsync\n        command for a synced folder. Please inspect the error message\n        below for more info.\n\n        Host path: %{hostpath}\n        Guest path: %{guestpath}\n        Error: %{message}\n      rsync_guest_install_error: |-\n        Installation of rsync into the guest has failed! The stdout\n        and stderr are shown below. Please read the error output, resolve\n        it and try again. If the problem persists, please install rsync\n        manually within the guest.\n\n        Command: %{command}\n        Stdout: %{stdout}\n        Stderr: %{stderr}\n      rsync_not_found: |-\n        \"rsync\" could not be found on your PATH. Make sure that rsync\n        is properly installed on your system and available on the PATH.\n      rsync_not_installed_in_guest: |-\n        \"rsync\" was not detected as installed in your guest machine. This\n        is required for rsync synced folders to work. In addition to this,\n        Vagrant doesn't know how to automatically install rsync for your\n        machine, so you must do this manually.\n      scp_permission_denied: |-\n        Failed to upload a file to the guest VM via SCP due to a permissions\n        error. This is normally because the SSH user doesn't have permission\n        to write to the destination location. Alternately, the user running\n        Vagrant on the host machine may not have permission to read the file.\n\n        Source: %{from}\n        Dest: %{to}\n      scp_unavailable: |-\n        SSH server on the guest doesn't support SCP. Please install the necessary\n        software to enable SCP on your guest operating system.\n      shared_folder_create_failed: |-\n        Failed to create the following shared folder on the host system. This is\n        usually because Vagrant does not have sufficient permissions to create\n        the folder.\n\n        %{path}\n\n        Please create the folder manually or specify another path to share.\n      shell_expand_failed: |-\n        Vagrant failed to determine the shell expansion of a guest path\n        (probably for one of your shared folders). This is an extremely rare\n        error case and most likely indicates an unusual configuration of the\n        guest system. Please report a bug with your Vagrantfile and debug log.\n      snapshot_force: |-\n        You must include the `--force` option to replace an existing snapshot.\n      snapshot_not_supported: |-\n        This provider doesn't support snapshots.\n\n        This may be intentional or this may be a bug. If this provider\n        should support snapshots, then please report this as a bug to the\n        maintainer of the provider.\n      snapshot_not_found: |-\n        The snapshot name `%{snapshot_name}` was not found for the\n        virtual machine `%{machine}`.\n      ssh_authentication_failed: |-\n        SSH authentication failed! This is typically caused by the public/private\n        keypair for the SSH user not being properly set on the guest VM. Please\n        verify that the guest VM is setup with the proper public key, and that\n        the private key path for Vagrant is setup properly as well.\n      ssh_bad_exit_status: |-\n        The following SSH command responded with a non-zero exit status.\n        Vagrant assumes that this means the command failed!\n\n        %{command}\n\n        Stdout from the command:\n\n        %{stdout}\n\n        Stderr from the command:\n\n        %{stderr}\n      ssh_bad_exit_status_muted: |-\n        The SSH command responded with a non-zero exit status. Vagrant\n        assumes that this means the command failed. The output for this command\n        should be in the log above. Please read the output to determine what\n        went wrong.\n      ssh_channel_open_fail: |-\n        Failed to open an SSH channel on the remote end! This typically\n        means that the maximum number of active sessions was hit on the\n        SSH server. Please configure your remote SSH server to resolve\n        this issue.\n      ssh_connect_eacces: |-\n        SSH is getting permission denied errors when attempting to connect\n        to the IP for SSH. This is usually caused by network rules and not being\n        able to connect to the specified IP. Please try changing the IP on\n        which the guest machine binds to for SSH.\n      ssh_connection_refused: |-\n        SSH connection was refused! This usually happens if the VM failed to\n        boot properly. Some steps to try to fix this: First, try reloading your\n        VM with `vagrant reload`, since a simple restart sometimes fixes things.\n        If that doesn't work, destroy your VM and recreate it with a `vagrant destroy`\n        followed by a `vagrant up`. If that doesn't work, contact a Vagrant\n        maintainer (support channels listed on the website) for more assistance.\n      ssh_connection_aborted: |-\n        SSH connection was aborted! This usually happens when the machine is taking\n        too long to reboot or the SSH daemon is not properly configured on the VM.\n        First, try reloading your machine with `vagrant reload`, since a simple\n        restart sometimes fixes things. If that doesn't work, destroy your machine\n        and recreate it with a `vagrant destroy` followed by a `vagrant up`. If that\n        doesn't work, contact support.\n      ssh_connection_reset: |-\n        SSH connection was reset! This usually happens when the machine is\n        taking too long to reboot. First, try reloading your machine with\n        `vagrant reload`, since a simple restart sometimes fixes things.\n        If that doesn't work, destroy your machine and recreate it with\n        a `vagrant destroy` followed by a `vagrant up`. If that doesn't work,\n        contact support.\n      ssh_connection_timeout: |-\n        Vagrant timed out while attempting to connect via SSH. This usually\n        means that the VM booted, but there are issues with the SSH configuration\n        or network connectivity issues. Please try to `vagrant reload` or\n        `vagrant up` again.\n      ssh_disconnected: |-\n        The SSH connection was unexpectedly closed by the remote end. This\n        usually indicates that SSH within the guest machine was unable to\n        properly start up. Please boot the VM in GUI mode to check whether\n        it is booting properly.\n      ssh_no_exit_status: |-\n        The SSH command completed, but Vagrant did not receive an exit status.\n        This indicates that the command was terminated unexpectedly. Please\n        check that the VM has sufficient memory to run the command and that no\n        processes were killed by the guest operating system.\n      ssh_no_route: |-\n        While attempting to connect with SSH, a \"no route to host\" (EHOSTUNREACH)\n        error was received. Please verify your network settings are correct\n        and try again.\n      ssh_host_down: |-\n        While attempting to connect with SSH, a \"host is down\" (EHOSTDOWN)\n        error was received. Please verify your SSH settings are correct\n        and try again.\n      ssh_invalid_shell: |-\n        The configured shell (config.ssh.shell) is invalid and unable\n        to properly execute commands. The most common cause for this is\n        using a shell that is unavailable on the system. Please verify\n        you're using the full path to the shell and that the shell is\n        executable by the SSH user.\n      ssh_insert_key_unsupported: |-\n        Vagrant is configured to generate a random keypair and insert it\n        onto the guest machine, but it appears Vagrant doesn't know how to do\n        this with your guest OS. Please disable key insertion by setting\n        `config.ssh.insert_key = false` in the Vagrantfile.\n\n        After doing so, run `vagrant reload` for the setting to take effect.\n\n        If you'd like Vagrant to learn how to insert keys on this OS, please\n        open an issue with details about your environment.\n      ssh_is_putty_link: |-\n        The `ssh` executable found in the PATH is a PuTTY Link SSH client.\n        Vagrant is only compatible with OpenSSH SSH clients. Please install\n        an OpenSSH SSH client or manually SSH in using your existing client\n        using the information below.\n\n        Host: %{host}\n        Port: %{port}\n        Username: %{username}\n        Private key: %{key_path}\n      ssh_key_bad_owner: |-\n        The private key to connect to the machine via SSH must be owned\n        by the user running Vagrant. This is a strict requirement from\n        SSH itself. Please fix the following key to be owned by the user\n        running Vagrant:\n\n        %{key_path}\n      ssh_key_bad_permissions: |-\n        The private key to connect to this box via SSH has invalid permissions\n        set on it. The permissions of the private key should be set to 0600, otherwise SSH will\n        ignore the key. Vagrant tried to do this automatically for you but failed. Please set the\n        permissions on the following file to 0600 and then try running this command again:\n\n        %{key_path}\n\n        Note that this error occurs after Vagrant automatically tries to\n        do this for you. The likely cause of this error is a lack of filesystem\n        permissions or even filesystem functionality. For example, if your\n        Vagrant data is on a USB stick, a common case is that chmod is\n        not supported. The key will need to be moved to a filesystem that\n        supports chmod.\n      ssh_key_type_not_supported: |-\n        The private key you're attempting to use with this Vagrant box uses\n        an unsupported encryption type. The SSH library Vagrant uses does not support\n        this key type. Please use `ssh-rsa` or `ssh-dss` instead. Note that\n        sometimes keys in your ssh-agent can interfere with this as well,\n        so verify the keys are valid there in addition to standard\n        file paths.\n      ssh_key_type_not_supported_by_server: |-\n        The private key you are attempting to generate is not supported by\n        the guest SSH server. Please use one of the available key types defined\n        below that is supported by the guest SSH server.\n\n        Requested: %{requested_key_type}\n        Available: %{available_key_types}\n\n      ssh_not_ready: |-\n        The provider for this Vagrant-managed machine is reporting that it\n        is not yet ready for SSH. Depending on your provider this can carry\n        different meanings. Make sure your machine is created and running and\n        try again. Additionally, check the output of `vagrant status` to verify\n        that the machine is in the state that you expect. If you continue to\n        get this error message, please view the documentation for the provider\n        you're using.\n      ssh_run_requires_keys: |-\n        Using `vagrant ssh -c` requires key-based SSH authentication, but your\n        Vagrant environment is configured to use only password-based authentication.\n        Please configure your Vagrantfile with a private key to use this\n        feature.\n\n        Note that Vagrant can automatically insert a keypair and use that\n        keypair for you. Just set `config.ssh.insert_key = true` in your\n        Vagrantfile.\n      ssh_unavailable: \"`ssh` binary could not be found. Is an SSH client installed?\"\n      ssh_unavailable_windows: |-\n        `ssh` executable not found in any directories in the %PATH% variable. Is an\n        SSH client installed? Try installing Cygwin, MinGW or Git, all of which\n        contain an SSH client. Or use your favorite SSH client with the following\n        authentication information shown below:\n\n        Host: %{host}\n        Port: %{port}\n        Username: %{username}\n        Private key: %{key_path}\n      synced_folder_unusable: |-\n        The synced folder type '%{type}' is reporting as unusable for\n        your current setup. Please verify you have all the proper\n        prerequisites for using this shared folder type and try again.\n\n      test_key: |-\n        test value\n      triggers_run_fail: |-\n        Trigger run failed\n      triggers_guest_not_running: |-\n        Could not run remote script on %{machine_name} because its state is %{state}\n      triggers_guest_not_exist: |-\n        Could not run remote script on guest because it does not exist.\n      triggers_bad_exit_codes: |-\n        A script exited with an unacceptable exit code %{code}.\n      triggers_no_block_given: |-\n        There was an error parsing the Vagrantfile:\n        No config was given for the trigger(s) %{command}.\n      triggers_no_stage_given: |-\n        The incorrect stage was given to the trigger plugin:\n        Guest: %{guest_name}\n        Name:  %{name}\n        Type:  %{type}\n        Stage: %{stage}\n\n        This is an internal error that should be reported as a bug.\n\n      ui_expects_tty: |-\n        Vagrant is attempting to interface with the UI in a way that requires\n        a TTY. Most actions in Vagrant that require a TTY have configuration\n        switches to disable this requirement. Please do that or run Vagrant\n        with TTY.\n      unimplemented_provider_action: |-\n        Vagrant attempted to call the action '%{action}' on the provider\n        '%{provider}', but this provider doesn't support this action. This\n        is probably a bug in either the provider or the plugin calling this\n        action, and should be reported.\n      upload_invalid_compression_type: |-\n        The compression type requested for upload (`%{type}`) is not a\n        supported value. Try uploading again using a valid compression\n        type.\n\n          Supported types: %{valid_types}\n      upload_missing_extract_capability: |-\n        The guest does not provide extraction capability for the requested\n        compression type (`%{type}`). Try a different compression type or\n        upload without compression.\n      upload_missing_temp_capability: |-\n        The guest does not provide temporary path capabilities. Please\n        try the upload again without requesting a temporary path.\n      upload_source_missing: |-\n        The source path provided for upload cannot be found. Please validate\n        the source location for upload an try again.\n\n          Source Path: %{source}\n      uploader_error: |-\n        An error occurred while uploading the file. The error\n        message, if any, is reproduced below. Please fix this error and try\n        again.\n\n        exit code: %{exit_code}\n        %{message}\n      uploader_interrupted: |-\n        The upload was interrupted by an external signal. It did not\n        complete.\n      vagrant_locked: |-\n        The requested Vagrant action is locked. This may be caused\n        by other Vagrant processes attempting to do a similar action.\n\n        Lock file path: %{lock_file_path}\n      vagrantfile_exists: |-\n        `Vagrantfile` already exists in this directory. Remove it before\n        running `vagrant init`.\n      vagrantfile_load_error: |-\n        There was an error loading a Vagrantfile. The file being loaded\n        and the error message are shown below. This is usually caused by\n        a syntax error.\n\n        Path: %{path}\n        Line number: %{line}\n        Message: %{exception_class}: %{message}\n      vagrantfile_name_error: |-\n        There was an error loading a Vagrantfile. The file being loaded\n        and the error message are shown below. This is usually caused by\n        an invalid or undefined variable.\n\n        Path: %{path}\n        Line number: %{line}\n        Message: %{message}\n      vagrantfile_syntax_error: |-\n        There is a syntax error in the following Vagrantfile. The syntax error\n        message is reproduced below for convenience:\n\n        %{file}\n      vagrantfile_template_not_found_error: |-\n        The Vagrantfile template '%{path}' does not exist. Please double check\n        the template path and try again.\n      vagrantfile_write_error: |-\n        The user that is running Vagrant doesn't have the proper permissions\n        to write a Vagrantfile to the specified location. Please ensure that\n        you call `vagrant init` in a location where the proper permissions\n        are in place to create a Vagrantfile.\n      vagrant_version_bad: |-\n        This Vagrant environment has specified that it requires the Vagrant\n        version to satisfy the following version requirements:\n\n          %{requirements}\n\n        You are running Vagrant %{version}, which does not satisfy\n        these requirements. Please change your Vagrant version or update\n        the Vagrantfile to allow this Vagrant version. However, be warned\n        that if the Vagrantfile has specified another version, it probably has\n        good reason to do so, and changing that may cause the environment to\n        not function properly.\n      vboxmanage_error: |-\n        There was an error while executing `VBoxManage`, a CLI used by Vagrant\n        for controlling VirtualBox. The command and stderr is shown below.\n\n        Command: %{command}\n\n        Stderr: %{stderr}\n      vboxmanage_launch_error: |-\n        There was an error running VBoxManage. This is usually a permissions\n        problem or installation problem with VirtualBox itself, and not Vagrant.\n        Please note the error message below (if any), resolve the issue, and\n        try Vagrant again.\n\n        %{message}\n      vboxmanage_not_found_error: |-\n        The \"VBoxManage\" command or one of its dependencies could not\n        be found. Please verify VirtualBox is properly installed. You can verify\n        everything is okay by running \"VBoxManage --version\" and verifying\n        that the VirtualBox version is outputted.\n\n        If you're on Windows and just installed VirtualBox, you have to\n        log out and log back in for the new environmental variables to take\n        effect. If you're on Linux or Mac, verify your PATH contains the folder\n        that has VBoxManage in it.\n      virtualbox_broken_version_040214: |-\n        Vagrant detected you have VirtualBox 4.2.14 installed. VirtualBox\n        4.2.14 contains a critical bug which prevents it from working with\n        Vagrant. VirtualBox 4.2.16+ fixes this problem. Please upgrade\n        VirtualBox.\n      virtualbox_config_not_found: |-\n        Vagrant was unable to locate the configuration file for the requested\n        VirtualBox VM. Verify the requested VM exists and try again.\n\n          UUID provided: %{uuid}\n      virtualbox_disks_controller_not_found: |-\n        Vagrant expected to find a storage controller called '%{name}',\n        but there is no controller with this name attached to the current VM.\n        If you have changed or removed any storage controllers, please restore\n        them to their previous configuration.\n      virtualbox_disks_no_supported_controllers: |-\n        Vagrant was unable to detect a disk controller with any of the\n        following supported types:\n\n        %{supported_types}\n\n        Please add one of the supported controller types in order to use the\n        disk configuration feature.\n      virtualbox_disks_defined_exceed_limit: |-\n        VirtualBox only allows up to %{limit} disks to be attached to the\n        storage controller '%{name}'. Please remove some disks from your disk\n        configuration, or attach a new storage controller.\n      virtualbox_disks_primary_not_found: |-\n        Vagrant was not able to detect a primary disk attached to the main\n        controller. Vagrant Disks requires that the primary disk be attached\n        to the first port of the main controller in order to manage it.\n      virtualbox_disks_unsupported_controller: |-\n        An disk operation was attempted on the controller '%{controller_name}',\n        but Vagrant doesn't support this type of disk controller.\n      virtualbox_guest_property_not_found: |-\n        Could not find a required VirtualBox guest property:\n          %{guest_property}\n        This is an internal error that should be reported as a bug.\n      virtualbox_invalid_version: |-\n        Vagrant has detected that you have a version of VirtualBox installed\n        that is not supported by this version of Vagrant. Please install one of\n        the supported versions listed below to use Vagrant:\n\n        %{supported_versions}\n\n        A Vagrant update may also be available that adds support for the version\n        you specified. Please check www.vagrantup.com/downloads.html to download\n        the latest version.\n      virtualbox_kernel_module_not_loaded: |-\n        VirtualBox is complaining that the kernel module is not loaded. Please\n        run `VBoxManage --version` or open the VirtualBox GUI to see the error\n        message which should contain instructions on how to fix this error.\n      virtualbox_install_incomplete: |-\n        VirtualBox is complaining that the installation is incomplete. Please\n        run `VBoxManage --version` to see the error message which should contain\n        instructions on how to fix this error.\n      virtualbox_machine_folder_not_found: |-\n        Vagrant failed to determine the machine folder on this host from\n        the VirtualBox system information. Try running the command again.\n        If this error persists, please report this error as a bug.\n      virtualbox_mount_failed: |-\n        Vagrant was unable to mount VirtualBox shared folders. This is usually\n        because the filesystem \"vboxsf\" is not available. This filesystem is\n        made available via the VirtualBox Guest Additions and kernel module.\n        Please verify that these guest additions are properly installed in the\n        guest. This is not a bug in Vagrant and is usually caused by a faulty\n        Vagrant box. For context, the command attempted was:\n\n        %{command}\n\n        The error output from the command was:\n\n        %{output}\n      virtualbox_mount_not_supported_bsd: |-\n        Vagrant is not able to mount VirtualBox shared folders on BSD-based\n        guests. BSD-based guests do not support the VirtualBox filesystem at\n        this time.\n\n        To change the type of the default synced folder, specify the type as\n        rsync or nfs:\n\n            config.vm.synced_folder \".\", \"/vagrant\", type: \"nfs\" # or \"rsync\"\n\n        Alternatively, if you do not need to mount the default synced folder,\n        you can also disable it entirely:\n\n            config.vm.synced_folder \".\", \"/vagrant\", disabled: true\n\n        You can read more about Vagrant's synced folder types and the various\n        configuration options on the Vagrant website.\n\n        This is not a bug in Vagrant.\n      virtualbox_name_exists: |-\n        The name of your virtual machine couldn't be set because VirtualBox\n        is reporting another VM with that name already exists. Most of the\n        time, this is because of an error with VirtualBox not cleaning up\n        properly. To fix this, verify that no VMs with that name do exist\n        (by opening the VirtualBox GUI). If they don't, then look at the\n        folder in the error message from VirtualBox below and remove it\n        if there isn't any information you need in there.\n\n        VirtualBox error:\n\n        %{stderr}\n      virtualbox_no_name: |-\n        Vagrant was unable to determine the recommended name for your\n        VirtualBox VM. This is usually an issue with VirtualBox. The output\n        from VirtualBox is shown below, which may contain an error to fix.\n        The best way to fix this issue is usually to uninstall VirtualBox,\n        restart your computer, then reinstall VirtualBox.\n\n        VirtualBox output:\n\n        %{output}\n      virtualbox_no_room_for_high_level_network: |-\n        There is no available slots on the VirtualBox VM for the configured\n        high-level network interfaces. \"private_network\" and \"public_network\"\n        network configurations consume a single network adapter slot on the\n        VirtualBox VM. VirtualBox limits the number of slots to 8, and it\n        appears that every slot is in use. Please lower the number of used\n        network adapters.\n      virtualbox_not_detected: |-\n        Vagrant could not detect VirtualBox! Make sure VirtualBox is properly installed.\n        Vagrant uses the `VBoxManage` binary that ships with VirtualBox, and requires\n        this to be available on the PATH. If VirtualBox is installed, please find the\n        `VBoxManage` binary and add it to the PATH environmental variable.\n      virtualbox_user_mismatch: |-\n        The VirtualBox VM was created with a user that doesn't match the\n        current user running Vagrant. VirtualBox requires that the same user\n        be used to manage the VM that was created. Please re-run Vagrant with\n        that user. This is not a Vagrant issue.\n\n        The UID used to create the VM was: %{original_uid}\n        Your UID is: %{uid}\n      virtualbox_version_empty: |-\n        Vagrant detected that VirtualBox appears installed on your system,\n        but calls to detect the version are returning empty. This is often\n        indicative of installation issues with VirtualBox. Please verify\n        that VirtualBox is properly installed. As a final verification,\n        please run the following command manually and verify a version is\n        outputted:\n\n        %{vboxmanage} --version\n      virtualbox_invalid_host_subnet: |-\n        The IP address configured for the host-only network is not within the\n        allowed ranges. Please update the address used to be within the allowed\n        ranges and run the command again.\n\n          Address: %{address}\n          Ranges: %{ranges}\n\n        Valid ranges can be modified in the /etc/vbox/networks.conf file. For\n        more information including valid format see:\n\n          https://www.virtualbox.org/manual/ch06.html#network_hostonly\n      vm_creation_required: |-\n        VM must be created before running this command. Run `vagrant up` first.\n      vm_inaccessible: |-\n        Your VM has become \"inaccessible.\" Unfortunately, this is a critical error\n        with VirtualBox that Vagrant can not cleanly recover from. Please open VirtualBox\n        and clear out your inaccessible virtual machines or find a way to fix\n        them.\n      vm_name_exists: |-\n        A VirtualBox machine with the name '%{name}' already exists.\n        Please use another name or delete the machine with the existing\n        name, and try again.\n      vm_no_match: |-\n        No virtual machines matched the regular expression given.\n      vm_not_found: |-\n        A VM by the name of %{name} was not found.\n      vm_not_running: |-\n        VM must be running to open SSH connection. Run `vagrant up`\n        to start the virtual machine.\n      winrm_invalid_communicator: |-\n        The winrm command requires a WinRM communicator to be used when connecting\n        to the guest. Please update your configuration and try the command again.\n      wsl_vagrant_version_mismatch: |-\n        Vagrant cannot currently enable access to manage machines within the Windows\n        environment because the version of Vagrant installed on Windows does not\n        match this version of Vagrant running within the Windows Subsystem for Linux.\n        Please ensure both installation of Vagrant are the same. If you do not want\n        update your Vagrant installations you can disable Windows access by unsetting\n        the `VAGRANT_WSL_ACCESS_WINDOWS_USER` environment variable.\n\n          Windows Vagrant version: %{windows_version}\n          Windows Subsystem for Linux Vagrant version: %{wsl_version}\n      wsl_vagrant_access_error: |-\n        Vagrant will not operate outside the Windows Subsystem for Linux unless explicitly\n        instructed. Due to the inability to enforce expected Linux file ownership and\n        permissions on the Windows system, Vagrant will not make modifications to prevent\n        unexpected errors. To learn more about this, and the options that are available,\n        please refer to the Vagrant documentation:\n\n          https://www.vagrantup.com/docs/other/wsl.html\n      wsl_virtualbox_windows_access: |-\n        Vagrant is unable to use the VirtualBox provider from the Windows Subsystem for\n        Linux without access to the Windows environment. Enabling this access must be\n        done with caution and an understanding of the implications. For more information\n        on enabling Windows access and using VirtualBox from the Windows Subsystem for\n        Linux, please refer to the Vagrant documentation:\n\n          https://www.vagrantup.com/docs/other/wsl.html\n      wsl_rootfs_not_found_error: |-\n        Vagrant is unable to determine the location of this instance of the Windows\n        Subsystem for Linux. If this error persists it may be resolved by destroying\n        this subsystem and installing it again.\n#-------------------------------------------------------------------------------\n# Translations for config validation errors\n#-------------------------------------------------------------------------------\n    config:\n      disk:\n        dvd_type_file_required:\n          A 'file' option is required when defining a disk of type ':dvd' for guest '%{machine}'.\n        dvd_type_primary: |-\n          Disks of type ':dvd' cannot be defined as a primary disks. Please\n          remove the 'primary' argument for disk '%{name}' on guest '%{machine}'.\n        invalid_ext: |-\n          Disk type '%{ext}' is not a valid disk extension for '%{name}'. Please pick one of the following supported disk types: %{exts}\n        invalid_type: |-\n          Disk type '%{type}' is not a valid type. Please pick one of the following supported disk types: %{types}\n        invalid_size: |-\n          Config option 'size' for disk '%{name}' on guest '%{machine}' is not an integer\n        invalid_file_type: |-\n          Disk config option 'file' for '%{machine}' is not a string.\n        missing_file: |-\n          Disk file '%{file_path}' for disk '%{name}' on machine '%{machine}' does not exist.\n        missing_provider: |-\n          Guest '%{machine}' using provider '%{provider_name}' has provider specific config options for a provider other than '%{provider_name}'. These provider config options will be ignored for this guest\n        no_name_set: |-\n          A 'name' option is required when defining a disk for guest '%{machine}'.\n      common:\n        bad_field: \"The following settings shouldn't exist: %{fields}\"\n      chef:\n        cookbooks_path_empty: |-\n          Missing required value for `chef.cookbooks_path'.\n        nodes_path_empty: |-\n          Missing required value for `chef.nodes_path'.\n        environment_path_missing: |-\n          Environment path not found: %{path}\n        nodes_path_missing: |-\n          Path specified for `nodes_path` does not exist: %{path}\n        environment_path_required: |-\n          When 'environment' is specified, you must provide 'environments_path'.\n        cookbooks_path_missing: |-\n          Cookbook path doesn't exist: %{path}\n        custom_config_path_missing: |-\n          Path specified for \"custom_config_path\" does not exist.\n        server_url_empty: \"Chef Server URL must be populated.\"\n        validation_key_path: \"Validation key path must be valid path to your Chef Server validation key.\"\n      loader:\n        bad_v1_key: |-\n          Unknown configuration section '%{key}'. If this section was part of\n          a Vagrant 1.0.x plugin, note that 1.0.x plugins are incompatible with 1.1+.\n      root:\n        sensitive_bad_type: |-\n          Invalid type provided for `sensitive`. The sensitive option expects a string\n          or an array of strings.\n        plugins_invalid_format: |-\n          Invalid type provided for `plugins`.\n        plugins_bad_key: |-\n          Invalid plugin configuration detected for `%{plugin_name}` plugin.\n\n            Unknown keys: %{plugin_key}\n        bad_key: |-\n          Unknown configuration section '%{key}'.\n      ssh:\n        private_key_missing: \"`private_key_path` file must exist: %{path}\"\n        paranoid_deprecated: |-\n          The key `paranoid` is deprecated. Please use `verify_host_key`. Supported\n          values are exactly the same, only the name of the option has changed.\n        ssh_config_missing: \"`config` file must exist: %{path}\"\n        connect_timeout_invalid_type: |-\n          The `connect_timeout` key only accepts values of Integer type. Received\n          `%{given}` type which cannot be converted to an Integer type.\n        connect_timeout_invalid_value: |-\n          The `connect_timeout` key only accepts values greater than or equal to 1 (received `%{given}`)\n        connect_retries_invalid_type: |-\n          The `connect_retries` key only accepts values of Integer type. Received\n          `%{given}` type which cannot be converted to an Integer type.\n        connect_retries_invalid_value: |-\n          The `connect_retries` key only accepts values greater than or equal to 0 (received `%{given}`)\n        connect_retry_delay_invalid_type: |-\n          The `connect_retry_delay` key only accepts values of Numeric type. Received\n          `%{given}` type which cannot be converted to a Numeric type.\n        connect_retry_delay_invalid_value: |-\n          The `connect_retry_delay` key only accepts values greater than or equal to 0 (received `%{given}`)\n        connect_invalid_key_type: |-\n          Invalid SSH key type set ('%{given}'). Supported types: %{supported}\n      triggers:\n        bad_trigger_type: |-\n          The type '%{type}' defined for trigger '%{trigger}' is not valid. Must be one of the following types: '%{types}'\n        bad_command_warning: |-\n          The command '%{cmd}' was not found for this trigger.\n        name_bad_type: |-\n          Invalid type set for `name` on trigger for command '%{cmd}'. `name` should be a String.\n        info_bad_type: |-\n          Invalid type set for `info` on trigger for command '%{cmd}'. `info` should be a String.\n        warn_bad_type: |-\n          Invalid type set for `warn` on trigger for command '%{cmd}'. `warn` should be a String.\n        on_error_bad_type: |-\n          Invalid type set for `on_error` on trigger for command '%{cmd}'. `on_error` can\n          only be `:halt` (default) or `:continue`.\n        abort_bad_type: |-\n          Trigger option `abort` for command '%{cmd}' must be either set to\n          `true` (defaults to exit code 1) or an integer to use as an exit code.\n        abort_false_type: |-\n          `false` is not a valid option for the `abort` option for a trigger. This\n          will be ignored...\n        exit_codes_bad_type: |-\n          Invalid type set for `exit_codes` on trigger for command '%{cmd}'. `exit_codes` can\n          only be a single integer or an array of integers.\n        only_on_bad_type: |-\n          Invalid type found for `only_on`. All values must be a `String` or `Regexp`.\n        ruby_bad_type: |-\n          Invalid type for `ruby` option on trigger for command '%{cmd}'. Only `proc`\n          types are allowed.\n        privileged_ignored: |-\n          The `privileged` setting for option `run` for trigger command '%{command}' will be ignored and set to false.\n        powershell_args_ignored: |-\n          The setting `powershell_args` is not supported for the trigger option `run` and will be ignored.\n        run:\n          bad_type: |-\n            Invalid type set for `run` on trigger for command '%{cmd}'. `run`\n            must be a Hash.\n        run_remote:\n          bad_type: |-\n            Invalid type set for `run` on trigger for command '%{cmd}'. `run`\n            must be a Hash.\n\n      vm:\n        bad_version: |-\n          Invalid box version constraints: %{version}\n        box_download_ca_cert_not_found: |-\n          \"box_download_ca_cert\" file not found: %{path}\n        box_download_ca_path_not_found: |-\n          \"box_download_ca_path\" directory not found: %{path}\n        box_download_checksum_blank: |-\n          Checksum type specified but \"box_download_checksum\" is blank\n        box_download_checksum_notblank: |-\n          Checksum specified but must also specify \"box_download_checksum_type\"\n        box_empty: \"Box value for guest '%{machine_name}' is an empty string.\"\n        box_missing: \"A box must be specified.\"\n        box_download_options_type: |-\n          Found \"box_download_options\" specified as type '%{type}', should be a Hash\n        box_download_options_not_converted: |-\n          Something went wrong converting VM config `box_download_options`. Value for provided key '%{missing_key}' is invalid type. Should be String or Bool\n        clone_and_box: \"Only one of clone or box can be specified.\"\n        config_type: \"Found '%{option}' specified as type '%{given}', should be '%{required}'\"\n        hostname_invalid_characters: |-\n          The hostname set for the VM '%{name}' should only contain letters, numbers,\n          hyphens or dots. It cannot start with a hyphen or dot.\n        ignore_provider_config: |-\n          Ignoring provider config for validation...\n        multiple_primary_disks_error: |-\n          There are more than one primary disks defined for guest '%{name}'. Please ensure that only one disk has been defined as a primary disk.\n        multiple_disk_files_error: |-\n          The following disk files are used multiple times in the disk\n          configuration for the VM '%{name}':\n\n          %{disk_files}\n\n          Each disk file may only be attached to a VM once.\n        multiple_disk_names_error: |-\n          The following disks names are defined multiple times in the disk\n          configuration for the VM '%{name}':\n\n          %{disk_names}\n\n          Disk names must be unique.\n        multiple_networks_set_hostname: \"Multiple networks have set `:hostname`\"\n        name_invalid: |-\n          The sub-VM name '%{name}' is invalid. Please don't use special characters.\n        network_ip_ends_in_one: |-\n          You assigned a static IP ending in \".1\" or \":1\" to this machine.\n          This is very often used by the router and can cause the\n          network to not work properly. If the network doesn't work\n          properly, try changing this IP.\n        network_ip_required: |-\n          An IP is required for a private network.\n        network_fp_invalid_port: |-\n          Ports to forward must be 1 to 65535\n        network_fp_host_not_unique: |-\n          Forwarded port '%{host}' (host port) is declared multiple times\n          with the protocol '%{protocol}'.\n        network_fp_requires_ports: |-\n          Forwarded port definitions require a \"host\" and \"guest\" value\n        network_type_invalid: |-\n          Network type '%{type}' is invalid. Please use a valid network type.\n        network_with_hostname_must_set_ip: \"Network specified with `:hostname` must provide a static ip\"\n        provisioner_not_found: |-\n          The '%{name}' provisioner could not be found.\n        shared_folder_guestpath_duplicate: |-\n          A shared folder guest path is used multiple times. Shared\n          folders must all map to a unique guest path: %{path}\n        shared_folder_guestpath_relative: |-\n          The shared folder guest path must be absolute: %{path}\n        shared_folder_hostpath_missing: |-\n          The host path of the shared folder is missing: %{path}\n        shared_folder_invalid_option_type: |-\n          The type '%{type}' is not a valid synced folder type. If 'type' is not\n            specified, Vagrant will automatically choose the best synced folder\n            option for your guest. Otherwise, please pick from the following available options:\n\n              %{options}\n        shared_folder_nfs_owner_group: |-\n          Shared folders that have NFS enabled do not support owner/group\n          attributes. Host path: %{path}\n        shared_folder_mount_options_array: |-\n          Shared folder mount options specified by 'mount_options' must\n          be an array of options.\n        shared_folder_requires_guestpath_or_name: |-\n          Shared folder options must include a guestpath and/or name.\n        shared_folder_wsl_not_drvfs: |-\n          The host path of the shared folder is not supported from WSL. Host\n          path of the shared folder must be located on a file system with\n          DrvFs type. Host path: %{path}\n\n#-------------------------------------------------------------------------------\n# Translations for guests\n#-------------------------------------------------------------------------------\n    guests:\n      capabilities:\n        rebooting: |-\n          Waiting for machine to reboot...\n\n#-------------------------------------------------------------------------------\n# Translations for commands. e.g. `vagrant x`\n#-------------------------------------------------------------------------------\n    commands:\n      deprecated: |-\n        [DEPRECATION WARNING]: The Vagrant command 'vagrant %{name}' is scheduled be\n        deprecated in an upcoming Vagrant release.\n      common:\n        vm_already_running: |-\n          VirtualBox VM is already running.\n        vm_not_created: \"VM not created. Moving on...\"\n        vm_not_running: \"VM is not currently running. Please, first bring it up with `vagrant up` then run this command.\"\n      box:\n        no_installed_boxes: \"There are no installed boxes! Use `vagrant box add` to add some.\"\n        remove_in_use_query: |-\n          Box '%{name}' (v%{version}) with provider '%{provider}' and\n          architectures '%{architecture}' appears to still be in use by\n          at least one Vagrant environment. Removing the box could corrupt\n          the environment. We recommend destroying these environments first:\n\n          %{users}\n\n          Are you sure you want to remove this box? [y/N]\n        removing: |-\n          Removing box '%{name}' (v%{version}) with provider '%{provider}'...\n      destroy:\n        confirmation: \"Are you sure you want to destroy the '%{name}' VM? [y/N] \"\n        will_not_destroy: |-\n          The VM '%{name}' will not be destroyed, since the confirmation\n          was declined.\n        warning: |-\n          Destroying guests with `--parallel` automatically enables `--force`.\n          Press ctrl-c to cancel.\n      init:\n        success: |-\n          A `Vagrantfile` has been placed in this directory. You are now\n          ready to `vagrant up` your first virtual environment! Please read\n          the comments in the Vagrantfile as well as documentation on\n          `vagrantup.com` for more information on using Vagrant.\n      plugin:\n        expunge_confirm: |-\n\n          This command permanently deletes all currently installed user plugins. It\n          should only be used when a repair command is unable to properly fix the\n          system.\n\n          Continue?\n        expunge_request_reinstall: |-\n          Would you like Vagrant to attempt to reinstall current plugins?\n        expunge_complete: |-\n\n          All user installed plugins have been removed from this Vagrant environment!\n        expunge_reinstall: |-\n\n          Vagrant will now attempt to reinstall user plugins that were removed.\n        expunge_aborted: |-\n\n          Vagrant expunge has been declined. Skipping removal of plugins.\n        installed_license: |-\n          The license for '%{name}' was successfully installed!\n        installing_license: |-\n          Installing license for '%{name}'...\n        no_plugins: |-\n          No plugins installed.\n        plugin_require: \"  - Custom entrypoint: %{require}\"\n        plugin_version: \"  - Version Constraint: %{version}\"\n        installed: |-\n          Installed the plugin '%{name} (%{version})'!\n        installing: |-\n          Installing the '%{name}' plugin. This can take a few minutes...\n        uninstalling: |-\n          Uninstalling the '%{name}' plugin...\n        up_to_date: |-\n          All plugins are up to date.\n        updated: |-\n          Updated '%{name}' to version '%{version}'!\n        updating: |-\n          Updating installed plugins...\n        updating_specific: |-\n          Updating plugins: %{names}. This may take a few minutes...\n        post_install: |-\n          Post install message from the '%{name}' plugin:\n\n          %{message}\n        repairing: |-\n          Repairing currently installed global plugins. This may take a few minutes...\n        repairing_local: |-\n          Repairing currently installed local project plugins. This may take a few minutes...\n        repair_complete: |-\n          Installed plugins successfully repaired!\n        repair_local_complete: |-\n          Local project plugins successfully repaired!\n        repair_failed: |-\n          Failed to automatically repair installed Vagrant plugins. To fix this\n          problem remove all user installed plugins and reinstall. Vagrant can\n          do this for you automatically by running the following command:\n\n            vagrant plugin expunge --reinstall\n\n          Failure message received during repair:\n\n          %{message}\n      snapshot:\n        not_supported: |-\n          This provider doesn't support snapshots.\n\n          This may be intentional or this may be a bug. If this provider\n          should support snapshots, then please report this as a bug to the\n          maintainer of the provider.\n        no_push_snapshot: |-\n          No pushed snapshot found!\n\n          Use `vagrant snapshot push` to push a snapshot to restore to.\n        save:\n          vm_not_created: |-\n            Machine '%{name}' has not been created yet, and therefore cannot save snapshots. Skipping...\n      status:\n        aborted: |-\n          The VM is in an aborted state. This means that it was abruptly\n          stopped without properly closing the session. Run `vagrant up`\n          to resume this virtual machine. If any problems persist, you may\n          have to destroy and restart the virtual machine.\n        gurumeditation: |-\n          The VM is in the \"guru meditation\" state. This is a rare case which means\n          that an internal error in VirtualBox caused the VM to fail. This is always\n          the sign of a bug in VirtualBox. You can try to bring your VM back online\n          with a `vagrant up`.\n        inaccessible: |-\n          The VM is inaccessible! This is a rare case which means that VirtualBox\n          can't find your VM configuration. This usually happens when upgrading\n          VirtualBox, moving to a new computer, etc. Please consult VirtualBox\n          for how to handle this issue.\n        output: |-\n          Current machine states:\n\n          %{states}\n\n          %{message}\n        not_created: |-\n          The environment has not yet been created. Run `vagrant up` to\n          create the environment. If a machine is not created, only the\n          default provider will be shown. So if a provider is not listed,\n          then the machine is not created for that environment.\n        paused: |-\n          The VM is paused. This VM may have been paused via the VirtualBox\n          GUI or the VBoxManage command line interface. To unpause, please\n          use the VirtualBox GUI and/or VBoxManage command line interface so\n          that vagrant would be able to control the VM again.\n        poweroff: |-\n          The VM is powered off. To restart the VM, simply run `vagrant up`\n        stopping: |-\n          The VM is stopping.\n        running: |-\n          The VM is running. To stop this VM, you can run `vagrant halt` to\n          shut it down forcefully, or you can run `vagrant suspend` to simply\n          suspend the virtual machine. In either case, to restart it again,\n          simply run `vagrant up`.\n        saving: |-\n          The VM is currently saving its state. In a few moments this state\n          should transition to \"saved.\" Please run `vagrant status` again\n          in a few seconds.\n        saved: |-\n          To resume this VM, simply run `vagrant up`.\n        stuck: |-\n          The VM is \"stuck!\" This is a very rare state which means that\n          VirtualBox is unable to recover the current state of the VM.\n          The only known solution to this problem is to restart your\n          machine, sorry.\n        listing: |-\n          This environment represents multiple VMs. The VMs are all listed\n          above with their current state. For more information about a specific\n          VM, run `vagrant status NAME`.\n      up:\n        upping: |-\n          Bringing machine '%{name}' up with '%{provider}' provider...\n      upload:\n        compress: |-\n          Compressing %{source} to %{type} for upload...\n        complete: |-\n          Upload has completed successfully!\n\n            Source: %{source}\n            Destination: %{destination}\n        decompress: |-\n          Decompressing %{type} upload to %{destination}...\n        start: |-\n          Uploading %{source} to %{destination}\n      validate:\n        success: |-\n          Vagrantfile validated successfully.\n\n#-------------------------------------------------------------------------------\n# Translations for Vagrant middleware actions\n#-------------------------------------------------------------------------------\n    cap:\n      cleanup_disks:\n        disk_cleanup: |-\n          Disk '%{name}' no longer exists in Vagrant config. Removing and closing medium from guest...\n        disk_not_found: |-\n          Disk '%{name}' could not be found, and could not be properly removed. Please remove this disk manually if it still exists\n      configure_disks:\n        create_disk: |-\n          Disk '%{name}' not found in guest. Creating and attaching disk to guest...\n        floppy_not_supported: \"Floppy disk configuration not yet supported. Skipping disk '%{name}'...\"\n        shrink_size_not_supported: |-\n          VirtualBox does not support shrinking disk sizes. Cannot shrink '%{name}' disks size.\n        resize_disk: |-\n          Disk '%{name}' needs to be resized. Resizing disk...\n        recovery_from_resize: |-\n          Vagrant has encountered an exception while trying to resize a disk. It will now attempt to reattach the original disk, as to prevent any data loss.\n          The original disk is located at %{location}\n          If Vagrant fails to reattach the original disk, it is recommended that you open the VirtualBox GUI and navigate to the current guests settings for '%{name}' and look at the 'storage' section. Here is where you can reattach a missing disk if Vagrant fails to do so...\n        recovery_attached_disks: |-\n          Disk has been reattached. Vagrant will now continue on an raise the exception receieved\n        start: \"Configuring storage mediums...\"\n    cloud_init:\n      content_type_not_set: |-\n        'content_type' must be defined for a cloud_init config. Guest '%{machine}'\n        has not defined a 'content_type' for its cloud_init config. Valid options\n        supported by Vagrant are listed below:\n\n        %{accepted_types}\n      incorrect_content_type: |-\n        Content-type \"%{content_type}\" is not supported by Vagrant and\n        cloud-init on machine '%{machine}'. Valid options supported by Vagrant are\n        listed below:\n\n        %{accepted_types}\n      incorrect_inline_type: |-\n        The inline config cloud_init option for guest '%{machine}' is of type '%{type}', but\n        should be a String.\n      incorrect_path_type: |-\n        The path '%{path}' given for guest '%{machine}' is of type '%{type}'.\n        Config option 'path' must be a String.\n      incorrect_type_set: |-\n        The type '%{type}' defined for a cloud_init config with guest '%{machine}'\n        is not a valid type. Currently, Vagrant only supports type: '%{default_type}'.\n      path_and_inline_set: |-\n        Guest '%{machine}' defines both a 'path' and 'inline' for its cloud_init config,\n        however only one config option should be defined for cloud_init.\n      path_invalid: |-\n        The path '%{path}' defined for guest '%{machine}' cloud_init config was\n        not found on the system.\n    actions:\n      runner:\n        waiting_cleanup: \"Waiting for cleanup before exiting...\"\n        exit_immediately: \"Exiting immediately, without cleanup!\"\n      disk:\n        cleanup_provider_unsupported: |-\n          Guest provider '%{provider}' does not support the cleaning up disks, and will not attempt to clean up attached disks on the guest..\n        provider_unsupported: |-\n          Guest provider '%{provider}' does not support the disk feature, and will not use the disk configuration defined.\n      vm:\n        boot:\n          booting: Booting VM...\n        bridged_networking:\n          available: |-\n            Available bridged network interfaces:\n          bridging: |-\n            Bridging adapter #%{adapter} to '%{bridge}'\n          enabling: |-\n            Enabling bridged network...\n          preparing: |-\n            Preparing bridged networking...\n          choice_help: |-\n            When choosing an interface, it is usually the one that is\n            being used to connect to the internet.\n          select_interface: |-\n            Which interface should the network bridge to?\n          specific_not_found: |-\n            Specific bridge '%{bridge}' not found. You may be asked to specify\n            which network to bridge to.\n        check_box:\n          not_found: |-\n            Box '%{name}' was not found. Fetching box from specified URL for\n            the provider '%{provider}'. Note that if the URL does not have\n            a box for this provider, you should interrupt Vagrant now and add\n            the box yourself. Otherwise Vagrant will attempt to download the\n            full box prior to discovering this error.\n        check_guest_additions:\n          not_detected: |-\n            No guest additions were detected on the base box for this VM! Guest\n            additions are required for forwarded ports, shared folders, host only\n            networking, and more. If SSH fails on this machine, please install\n            the guest additions and repackage the box to continue.\n\n            This is not an error message; everything may continue to work properly,\n            in which case you may ignore this message.\n          version_mismatch: |-\n            The guest additions on this VM do not match the installed version of\n            VirtualBox! In most cases this is fine, but in rare cases it can\n            prevent things such as shared folders from working properly. If you see\n            shared folder errors, please make sure the guest additions within the\n            virtual machine match the version of VirtualBox you have installed on\n            your host and reload your VM.\n\n            Guest Additions Version: %{guest_version}\n            VirtualBox Version: %{virtualbox_version}\n        clear_forward_ports:\n          deleting: Clearing any previously set forwarded ports...\n        clear_network_interfaces:\n          deleting: Clearing any previously set network interfaces...\n        clear_shared_folders:\n          deleting: Cleaning previously set shared folders...\n        clone:\n          setup_master: Preparing master VM for linked clones...\n          setup_master_detail: |-\n            This is a one time operation. Once the master VM is prepared,\n            it will be used as a base for linked clones, making the creation\n            of new VMs take milliseconds on a modern system.\n          creating: Cloning VM...\n          failure: Creation of the linked clone failed.\n          create_master:\n            failure: |-\n              Failed to create lock-file for master VM creation for box %{box}.\n        cloud_init_user_data_setup: |-\n          Preparing user data for cloud-init...\n        customize:\n          failure: |-\n            A customization command failed:\n\n            %{command}\n\n            The following error was experienced:\n\n            %{error}\n\n            Please fix this customization and try again.\n          running: Running '%{event}' VM customizations...\n        destroy:\n          destroying: Destroying VM and associated drives...\n        destroy_network:\n          destroying: Destroying unused networking interface...\n        disable_networks:\n          disabling: Disabling host only networks...\n        discard_state:\n          discarding: Discarding saved state of VM...\n        export:\n          create_dir: Creating temporary directory for export...\n          exporting: Exporting VM...\n          power_off: \"The Vagrant virtual environment you are trying to package must be powered off.\"\n        forward_ports:\n          auto_empty: |-\n            Vagrant found a port collision for the specified port and virtual machine.\n            While this port was marked to be auto-corrected, the ports in the\n            auto-correction range are all also used.\n\n            VM: %{vm_name}\n            Forwarded port: %{guest_port} => %{host_port}\n          collision_error: |-\n            Vagrant cannot forward the specified ports on this VM, since they\n            would collide with some other application that is already listening\n            on these ports. The forwarded port to %{host_port} is already in use\n            on the host machine.\n\n            To fix this, modify your current project's Vagrantfile to use another\n            port. Example, where '1234' would be replaced by a unique host port:\n\n              config.vm.network :forwarded_port, guest: %{guest_port}, host: 1234\n\n            Sometimes, Vagrant will attempt to auto-correct this for you. In this\n            case, Vagrant was unable to. This is usually because the guest machine\n            is in a state which doesn't allow modifying port forwarding. You could\n            try 'vagrant reload' (equivalent of running a halt followed by an up)\n            so vagrant can attempt to auto-correct this upon booting. Be warned\n            that any unsaved work might be lost.\n\n          fixed_collision: |-\n            Fixed port collision for %{guest_port} => %{host_port}. Now on port %{new_port}.\n          forwarding: Forwarding ports...\n          forwarding_entry: |-\n            %{guest_port} (guest) => %{host_port} (host) (adapter %{adapter})\n          host_ip_not_found: |-\n            You are trying to forward a host IP that does not exist. Please set `host_ip`\n            to the address of an existing IPv4 network interface, or remove the option\n            from your port forward configuration.\n\n            VM: %{name}\n            Host IP: %{host_ip}\n          non_nat: |-\n            VirtualBox adapter #%{adapter} not configured as \"NAT\". Skipping port\n            forwards on this adapter.\n          privileged_ports: |-\n            You are trying to forward to privileged ports (ports <= 1024). Most\n            operating systems restrict this to only privileged process (typically\n            processes running as an administrative user). This is a warning in case\n            the port forwarding doesn't work. If any problems occur, please try a\n            port higher than 1024.\n        halt:\n          force: |-\n            Forcing shutdown of VM...\n          graceful: |-\n            Attempting graceful shutdown of VM...\n          guest_not_ready: |-\n            Guest communication could not be established! This is usually because\n            SSH is not running, the authentication information was changed,\n            or some other networking issue. Vagrant will force halt, if\n            capable.\n        hostname:\n          setting: \"Setting hostname...\"\n        import:\n          importing: Importing base box '%{name}'...\n          failure: |-\n            The VM import failed! Try running `VBoxManage import` on the box file\n            manually for more verbose error output.\n        match_mac:\n          matching: Matching MAC address for NAT networking...\n          generating: Generating MAC address for NAT networking...\n          no_base_mac: |-\n            No base MAC address was specified. This is required for the NAT networking\n            to work properly (and hence port forwarding, SSH, etc.). Specifying this\n            MAC address is typically up to the box and box maintainer. Please contact\n            the relevant person to solve this issue.\n        network:\n          configuring: |-\n            Configuring and enabling network interfaces...\n          dhcp_already_attached: |-\n            A host only network interface you're attempting to configure via DHCP\n            already has a conflicting host only adapter with DHCP enabled. The\n            DHCP on this adapter is incompatible with the DHCP settings. Two\n            host only network interfaces are not allowed to overlap, and each\n            host only network interface can have only one DHCP server. Please\n            reconfigure your host only network or remove the virtual machine\n            using the other host only network.\n          preparing: |-\n            Preparing network interfaces based on configuration...\n          cleanup_vbox_default_dhcp: |-\n            Found default DHCP server from initial VirtualBox install. Cleaning it up...\n        host_only_network:\n          collides: |-\n            The specified host network collides with a non-hostonly network!\n            This will cause your specified IP to be inaccessible. Please change\n            the IP or name of your host only network so that it no longer matches that of\n            a bridged or non-hostonly network.\n\n            Bridged Network Address: '%{netaddr}'\n            Host-only Network '%{interface_name}': '%{that_netaddr}'\n          creating: \"Creating new host only network for environment...\"\n          enabling: \"Enabling host only network...\"\n          not_found: |-\n            The specified host network could not be found: '%{name}.'\n            If the name specification is removed, Vagrant will create a new\n            host only network for you. Alternatively, please create the\n            specified network manually.\n          preparing: \"Preparing host only network...\"\n        nfs:\n          exporting: Exporting NFS shared folders...\n          installing: \"Installing NFS client...\"\n          mounting: Mounting NFS shared folders...\n          mounting_entry: \"%{hostpath} => %{guestpath}\"\n          v4_with_udp_warning: |-\n\n            WARNING: Invalid NFS settings detected!\n\n            Detected UDP enabled with NFSv4. NFSv4 does not support UDP.\n            If folder mount fails disable UDP using the following option:\n\n              nfs_udp: false\n\n            For more information see:\n            RFC5661: https://tools.ietf.org/html/rfc5661#section-2.9.1\n            RFC7530: https://tools.ietf.org/html/rfc7530#section-3.1\n        smb:\n          mfsymlink_warning: |-\n            Vagrant is currently configured to mount SMB folders with the\n            `mfsymlink` option enabled. This is equivalent to adding the\n            following to your Vagrantfile:\n\n              config.vm.synced_folder '/host/path', '/guest/path', type: \"smb\", mount_options: ['mfsymlink']\n\n            This option may be globally disabled with an environment variable:\n\n              VAGRANT_DISABLE_SMBMFSYMLINKS=1\n\n        provision:\n          beginning: \"Running provisioner: %{provisioner}...\"\n          disabled_by_config: |-\n            Machine not provisioned because `--no-provision` is specified.\n          disabled_by_sentinel: |-\n            Machine already provisioned. Run `vagrant provision` or use the `--provision`\n            flag to force provisioning. Provisioners marked to run always will still run.\n          file:\n            locations: \"%{src} => %{dst}\"\n        resume:\n          resuming: Resuming suspended VM...\n          unpausing: |-\n            Unpausing the VM...\n        share_folders:\n          creating: Creating shared folders metadata...\n          mounting: Mounting shared folders...\n          mounting_entry: \"%{hostpath} => %{guestpath}\"\n          nomount_entry: \"Automounting disabled: %{hostpath}\"\n        set_default_nic_type:\n          e1000_warning: |-\n            Vagrant has detected a configuration issue which exposes a\n            vulnerability with the installed version of VirtualBox. The\n            current guest is configured to use an E1000 NIC type for a\n            network adapter which is vulnerable in this version of VirtualBox.\n            Ensure the guest is trusted to use this configuration or update\n            the NIC type using one of the methods below:\n\n              https://www.vagrantup.com/docs/virtualbox/configuration.html#default-nic-type\n              https://www.vagrantup.com/docs/virtualbox/networking.html#virtualbox-nic-type\n        set_name:\n          setting_name: |-\n            Setting the name of the VM: %{name}\n        snapshot:\n          deleting: |-\n            Deleting the snapshot '%{name}'...\n          deleted: |-\n            Snapshot deleted!\n          list_none: |-\n            No snapshots have been taken yet!\n          list_none_detail: |-\n            You can take a snapshot using `vagrant snapshot save`. Note that\n            not all providers support this yet. Once a snapshot is taken, you\n            can list them using this command, and use commands such as\n            `vagrant snapshot restore` to go back to a certain snapshot.\n          restoring: |-\n            Restoring the snapshot '%{name}'...\n          saving: |-\n            Snapshotting the machine as '%{name}'...\n          saved: |-\n            Snapshot saved! You can restore the snapshot at any time by\n            using `vagrant snapshot restore`. You can delete it using\n            `vagrant snapshot delete`.\n        suspend:\n          suspending: Saving VM state and suspending execution...\n\n      box:\n        unpackage:\n          untar_failure: |-\n            The box failed to unpackage properly. Please verify that the box\n            file you're trying to add is not corrupted and that enough disk space\n            is available and then try again.\n            The output from attempting to unpackage (if any):\n\n            %{output}\n        add:\n          adding: |-\n            Extracting box...\n          checksumming: |-\n            Calculating and comparing box checksum...\n        destroy:\n          destroying: \"Deleting box '%{name}'...\"\n        download:\n          cleaning: \"Cleaning up downloaded box...\"\n          download_failed: |-\n            Download failed. Will try another box URL if there is one.\n          interrupted: \"Box download was interrupted. Exiting.\"\n          resuming: \"Box download is resuming from prior download progress\"\n        verify:\n          verifying: \"Verifying box...\"\n          failed: |-\n            The box file you're attempting to add is invalid. This can be\n            commonly attributed to typos in the path given to the box add\n            command. Another common case of this is invalid packaging of the\n            box itself.\n\n      general:\n        package:\n          packaging: \"Packaging additional file: %{file}\"\n          compressing: \"Compressing package to: %{fullpath}\"\n          box_folder: \"Creating new folder: %{folder_path}\"\n          output_exists: |-\n            The specified file '%{filename}' to save the package as already exists. Please\n            remove this file or specify a different file name for outputting.\n          output_is_directory: |-\n            The specified output is a directory. Please specify a path including\n            a filename.\n          requires_directory: |-\n            A directory was not specified to package. This should never happen\n            and is a result of an internal inconsistency. Please report a bug\n            on the Vagrant bug tracker.\n          include_file_missing: |-\n            Package include file doesn't exist: %{file}\n\n    hosts:\n      bsd:\n        nfs_export: |-\n          Preparing to edit /etc/exports. Administrator privileges will be required...\n        nfs_prune: |-\n          Pruning invalid NFS exports. Administrator privileges will be required...\n      darwin:\n        virtualbox_install_download: |-\n          Downloading VirtualBox %{version}...\n        virtualbox_install_detail: |-\n          This may not be the latest version of VirtualBox, but it is a version\n          that is known to work well. Over time, we'll update the version that\n          is installed.\n        virtualbox_install_install: |-\n          Installing VirtualBox. This will take a few minutes...\n        virtualbox_install_install_detail: |-\n          You may be asked for your administrator password during this time.\n          If you're uncomfortable entering your password here, please install\n          VirtualBox manually.\n        virtualbox_install_success: |-\n          VirtualBox has successfully been installed!\n      linux:\n        nfs_export: |-\n          Preparing to edit /etc/exports. Administrator privileges will be required...\n        nfs_prune: |-\n          Pruning invalid NFS exports. Administrator privileges will be required...\n      arch:\n        nfs_export:\n          prepare: \"Preparing to edit /etc/exports. Administrator privileges will be required...\"\n      windows:\n        virtualbox_install_download: |-\n          Downloading VirtualBox %{version}...\n        virtualbox_install_detail: |-\n          This may not be the latest version of VirtualBox, but it is a version\n          that is known to work well. Over time, we'll update the version that\n          is installed.\n        virtualbox_install_install: |-\n          Installing VirtualBox. This will take a few minutes...\n        virtualbox_install_install_detail: |-\n          A couple pop-ups will occur during this installation process to\n          ask for admin privileges as well as to install Oracle drivers.\n          Please say yes to both. If you're uncomfortable with this, please\n          install VirtualBox manually.\n        virtualbox_install_success: |-\n          VirtualBox has successfully been installed!\n\n    provisioners:\n      base:\n        both_before_after_set: |-\n          Dependency provisioners cannot currently set both `before` and `after` options.\n        dependency_provisioner_dependency: |-\n          Dependency provisioner \"%{name}\" relies on another dependency provisioner \"%{dep_name}\". This is currently not supported.\n        invalid_alias_value: |-\n          Provisioner option `%{opt}` is not set as a valid type. Must be a string, or one of the alias shortcuts: %{alias}\n        missing_provisioner_name: |-\n          Could not find provisioner name `%{name}` defined for machine `%{machine_name}` to run provisioner \"%{provisioner_name}\" `%{action}`.\n        wrong_type: |-\n          Provisioner option `%{opt}` is not set as a valid type. Must be a %{type}.\n      chef:\n        chef_not_detected: |-\n          The chef binary (either `chef-solo` or `chef-client`) was not found on\n          the VM and is required for chef provisioning. Please verify that chef\n          is installed and that the binary is available on the PATH.\n        cookbooks_folder_not_found_warning:\n          \"The cookbook path '%{path}' doesn't exist. Ignoring...\"\n        nodes_folder_not_found_warning:\n          \"The node path '%{path}' doesn't exist. Ignoring...\"\n        data_bags_folder_not_found_warning:\n          \"The databag path '%{path}' doesn't exist. Ignoring...\"\n        roles_folder_not_found_warning:\n          \"The role path '%{path}' doesn't exist. Ignoring...\"\n        environments_folder_not_found_warning:\n          \"The environment path '%{path}' doesn't exist. Ignoring...\"\n        json: \"Generating chef JSON and uploading...\"\n        client_key_folder: \"Creating folder to hold client key...\"\n        generating_node_name: |-\n          Auto-generating node name for Chef...\n        using_hostname_node_name: |-\n          Using hostname \"%{hostname}\" as node name for Chef...\n        install_failed: |-\n          Vagrant could not detect Chef on the guest! Even after Vagrant\n          attempted to install Chef, it could still not find Chef on the system.\n          Please make sure you are connected to the Internet and can access\n          Chef's package distribution servers. If you already have Chef\n          installed on this guest, you can disable the automatic Chef detection\n          by setting the 'install' option in the Chef configuration section of\n          your Vagrantfile:\n\n              chef.install = false\n        log_level_empty: |-\n          The Chef provisioner requires a log level. If you did not set a\n          log level, this is probably a bug and should be reported.\n        upload_validation_key: \"Uploading chef client validation key...\"\n        upload_encrypted_data_bag_secret_key: \"Uploading chef encrypted data bag secret key...\"\n        recipe_empty: |-\n          Chef Apply provisioning requires that the `config.chef.recipe` be set\n          to a string containing the recipe contents you want to execute on the\n          guest.\n        running_client: \"Running chef-client...\"\n        running_client_again: \"Running chef-client again (failed to converge)...\"\n        running_apply: \"Running chef-apply...\"\n        running_solo: \"Running chef-solo...\"\n        running_solo_again: \"Running chef-solo again (failed to converge)...\"\n        running_zero: \"Running chef-client (local-mode)...\"\n        running_zero_again: \"Running chef-client (local-mode) again (failed to converge)...\"\n        missing_shared_folders: |-\n          Shared folders that Chef requires are missing on the virtual machine.\n          This is usually due to configuration changing after already booting the\n          machine. The fix is to run a `vagrant reload` so that the proper shared\n          folders will be prepared and mounted on the VM.\n        no_convergence: |-\n          Chef never successfully completed! Any errors should be visible in the\n          output above. Please fix your recipes so that they properly complete.\n        not_detected: |-\n          The `%{binary}` binary appears not to be in the PATH of the guest. This\n          could be because the PATH is not properly setup or perhaps chef is not\n          installed on this guest. Chef provisioning can not continue without\n          chef properly installed.\n        server_url_required: |-\n          Chef server provisioning requires that the `config.chef.chef_server_url` be set to the\n          URL of your chef server. Examples include \"http://12.12.12.12:4000\" and\n          \"http://example.com:4000\" (the port of course can be different, but 4000 is the default)\n        server_validation_key_required: |-\n          Chef server provisioning requires that the `config.chef.validation_key_path` configuration\n          be set to a path on your local machine of the validation key used to register the\n          VM with the chef server.\n        server_validation_key_doesnt_exist: |-\n          The validation key set for `config.chef.validation_key_path` does not exist! This\n          file needs to exist so it can be uploaded to the virtual machine.\n        upload_path_empty: |-\n          The Chef Apply provisioner requires that the `config.chef.upload_path`\n          be set to a non-nil, non-empty value.\n        missing_node_name: |-\n          The Chef Client provisioner cannot delete the %{deletable} from\n          the Chef server because Vagrant does not know the `node_name'! You\n          need to manually delete the %{deletable} from the Chef server. You\n          can specify the `node_name' in the Chef configuration to prevent this\n          in the future.\n        deleting_from_server: \"Deleting %{deletable} \\\"%{name}\\\" from Chef server...\"\n\n      file:\n        no_dest_file: \"File destination must be specified.\"\n        no_source_file: \"File source must be specified.\"\n        path_invalid: \"File upload source file %{path} must exist\"\n\n      puppet:\n        not_detected: |-\n          The `%{binary}` binary appears not to be in the PATH of the guest. This\n          could be because the PATH is not properly setup or perhaps Puppet is not\n          installed on this guest. Puppet provisioning can not continue without\n          Puppet properly installed.\n        running_puppet: \"Running Puppet with %{manifest}...\"\n        running_puppet_env: \"Running Puppet with environment %{environment}...\"\n        manifest_missing: |-\n          The configured Puppet manifest is missing. Please specify a path to an\n          existing manifest:\n\n          %{manifest}\n        environment_missing: |-\n          The configured Puppet environment folder %{environment} was not found in the\n          specified environmentpath %{environmentpath}.\n          Please specify a path to an existing Puppet directory environment.\n        environment_path_missing: \"The environment path specified for Puppet does not exist: %{path}\"\n        manifests_path_missing: \"The manifests path specified for Puppet does not exist: %{path}\"\n        missing_shared_folders: |-\n          Shared folders that Puppet requires are missing on the virtual machine.\n          This is usually due to configuration changing after already booting the\n          machine. The fix is to run a `vagrant reload` so that the proper shared\n          folders will be prepared and mounted on the VM.\n        module_path_missing: \"The configured module path doesn't exist: %{path}\"\n\n      puppet_server:\n        cert_requires_node: |-\n          \"puppet_node\" is required when a client cert or key is specified\n        client_cert_and_private_key: |-\n          Both a client certificate and private key must be specified, if any\n        client_cert_not_found: |-\n          The specified client cert path could not be found\n        client_private_key_not_found: |-\n          The specified client private key path could not be found\n        not_detected: |-\n          The `%{binary}` binary appears not to be in the PATH of the guest. This\n          could be because the PATH is not properly setup or perhaps Puppet is not\n          installed on this guest. Puppet provisioning can not continue without\n          Puppet properly installed.\n        running_puppetd: \"Running Puppet agent...\"\n        uploading_client_cert: |-\n          Uploading client certificate and private key...\n\n      shell:\n        args_bad_type: \"Shell provisioner `args` must be a string or array.\"\n        invalid_encoding: |-\n          Invalid encoding '%{actual}' for script at '%{path}'.\n          Must be '%{default}' or UTF-8.\n        env_must_be_a_hash: |-\n          The shell provisioner's `env' option must be a hash.\n        no_path_or_inline: \"One of `path` or `inline` must be set.\"\n        path_and_inline_set: \"Only one of `path` or `inline` may be set.\"\n        path_invalid: \"`path` for shell provisioner does not exist on the host system: %{path}\"\n        running: \"Running: %{script}\"\n        runningas: \"Running: %{local} as %{remote}\"\n        upload_path_not_set: \"`upload_path` must be set for the shell provisioner.\"\n        interactive_not_elevated: \"To be interactive, it must also be privileged.\"\n\n      ansible:\n        ansible_host_pattern_detected: |-\n          Vagrant has detected a host range pattern in the `groups` option.\n          Vagrant doesn't fully check the validity of these parameters!\n\n          Please check https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#inventory-basics-formats-hosts-and-groups\n          for more information.\n        cannot_detect: |-\n          Vagrant does not support detecting whether Ansible is installed\n          for the guest OS running in the machine. Vagrant will assume it is\n          installed and attempt to continue.\n\n          If you'd like this provisioner to be improved, please\n          take a look at the Vagrant source code linked below and try\n          to contribute back support. Thank you!\n\n          https://github.com/hashicorp/vagrant\n        errors:\n          ansible_command_failed: |-\n            Ansible failed to complete successfully. Any error output should be\n            visible above. Please fix these errors and try again.\n          ansible_compatibility_mode_conflict: |-\n            The requested Ansible compatibility mode (%{compatibility_mode}) is in conflict with\n            the Ansible installation on your Vagrant %{system} system (currently: %{ansible_version}).\n            See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#compatibility_mode\n            for more information.\n          ansible_not_found_on_guest: |-\n            The Ansible software could not be found! Please verify\n            that Ansible is correctly installed on your guest system.\n\n            If you haven't installed Ansible yet, please install Ansible\n            on your Vagrant basebox, or enable the automated setup with the\n            ansible_local provisioner `install` option. Please check\n            https://docs.vagrantup.com/v2/provisioning/ansible_local.html#install\n            for more information.\n          ansible_not_found_on_host: |-\n            The Ansible software could not be found! Please verify\n            that Ansible is correctly installed on your host system.\n\n            If you haven't installed Ansible yet, please install Ansible\n            on your host system. Vagrant can't do this for you in a safe and\n            automated way.\n            Please check https://docs.ansible.com for more information.\n          ansible_programming_error: |-\n            Ansible Provisioner Programming Error:\n\n            %{message}\n\n            Internal Details:\n\n            %{details}\n\n            Sorry, but this Vagrant error should never occur.\n            Please check https://github.com/hashicorp/vagrant/issues for any\n            existing bug report. If needed, please create a new issue. Thank you!\n          cannot_support_pip_install: |-\n            Unfortunately Vagrant does not support yet installing Ansible\n            from pip for the guest OS running in the machine.\n\n            If you'd like this provisioner to be improved, please\n            take a look at the Vagrant source code linked below and try\n            to contribute back support. Thank you!\n\n            https://github.com/hashicorp/vagrant\n          ansible_version_mismatch: |-\n            The requested Ansible version (%{required_version}) was not found on the %{system}.\n            Please check the Ansible installation on your Vagrant %{system} system (currently: %{current_version}),\n            or adapt the provisioner `version` option in your Vagrantfile.\n            See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#version\n            for more information.\n          config_file_not_found: |-\n            `%{config_option}` does not exist on the %{system}: %{path}\n          extra_vars_invalid: |-\n            `extra_vars` must be a hash or a path to an existing file. Received: %{value} (as %{type})\n          no_compatibility_mode: |-\n            `compatibility_mode` must be a valid mode (possible values: %{valid_modes}).\n          no_playbook: |-\n            `playbook` file path must be set.\n          raw_arguments_invalid: |-\n            `raw_arguments` must be an array of strings. Received: %{value} (as %{type})\n          raw_ssh_args_invalid: |-\n            `raw_ssh_args` must be an array of strings. Received: %{value} (as %{type})\n        installing: \"Installing Ansible...\"\n        installing_pip: \"Installing pip... (for Ansible installation)\"\n        running_galaxy: \"Running ansible-galaxy...\"\n        running_playbook: \"Running ansible-playbook...\"\n        windows_not_supported_for_control_machine: |-\n          Windows is not officially supported for the Ansible Control Machine.\n          Please check https://docs.ansible.com/intro_installation.html#control-machine-requirements\n        compatibility_mode_not_detected: |-\n          Vagrant gathered an unknown Ansible version:\n\n          %{gathered_version}\n          and falls back on the compatibility mode '%{compatibility_mode}'.\n\n          Alternatively, the compatibility mode can be specified in your Vagrantfile:\n          https://www.vagrantup.com/docs/provisioning/ansible_common.html#compatibility_mode\n        compatibility_mode_warning: |-\n          Vagrant has automatically selected the compatibility mode '%{compatibility_mode}'\n          according to the Ansible version installed (%{ansible_version}).\n\n          Alternatively, the compatibility mode can be specified in your Vagrantfile:\n          https://www.vagrantup.com/docs/provisioning/ansible_common.html#compatibility_mode\n\n      docker:\n        wrong_provisioner: |-\n          The Docker post-install provisioner cannot also take a Docker post-install\n          provisioner\n        not_running: \"Docker is not running on the guest VM.\"\n        install_failed: \"Docker installation failed.\"\n\n      podman:\n        wrong_provisioner: |-\n          The Podman post-install provisioner cannot also take a Podman post-install\n          provisioner\n        install_failed: \"Podman installation failed.\"\n\n      salt:\n        minion_config_nonexist: |-\n          The specified minion_config '%{missing_config_file}' file could not be found.\n        master_config_nonexist: |-\n          The specified master_config '%{missing_config_file}' file could not be found.\n        grains_config_nonexist: |-\n          The specified grains_config file could not be found.\n        missing_key: |-\n          You must include both public and private keys.\n        must_accept_keys: |-\n          You must accept keys when running highstate with master!\n        args_array: |-\n          You must set `args_array` value as an array.\n        python_version: |-\n          You must set `python_version` as an integer or string that represents an integer.\n        version_type_missing: |-\n          You must set the option `install_type` when specifying a `version`.\n        salt_invalid_shasum_error: |-\n          The bootstrap-salt script downloaded from '%{source}' couldn't be verified. Expected SHA256 '%{expected_sha}', but computed '%{computed_sha}'\n\n    pushes:\n      file:\n        no_destination: \"File destination must be specified.\"\n\n    autocomplete:\n      installed: |-\n        Autocomplete installed at paths:\n        - %{paths}\n      not_installed: \"Autocomplete not installed\"\n"
  },
  {
    "path": "templates/locales/guest_windows.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  vagrant_windows:\n    errors:\n      public_key_directory_failure: |-\n        Vagrant failed to properly discover the correct paths for the\n        temporary directory and user profile directory on the Windows\n        guest. Please ensure the guest is properly configured.\n      network_winrm_required: |-\n        Configuring networks on Windows requires the communicator to be\n        set to WinRM. To do this, add the following to your Vagrantfile:\n\n          config.vm.communicator = \"winrm\"\n\n        Note that the Windows guest must be configured to accept insecure\n        WinRM connections, and the WinRM port must be forwarded properly from\n        the guest machine. This is not always done by default.\n      rename_computer_failed: |-\n        Renaming the Windows guest failed. Most often this is because you've\n        specified a FQDN instead of just a host name.\n\n        Ensure the new guest name is properly formatted. Standard names may\n        contain letters (a-z, A-Z), numbers (0-9), and hyphens (-), but no\n        spaces or periods (.). The name may not consist entirely of digits.\n"
  },
  {
    "path": "templates/locales/providers_docker.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  docker_provider:\n    already_built: |-\n      Image is already built from the Dockerfile. `vagrant reload` to rebuild.\n    build_image_destroy: |-\n      Removing built image...\n    build_image_destroy_in_use: |-\n      Build image couldn't be destroyed because the image is in use. The\n      image must be destroyed manually in the future if you want to remove\n      it.\n    build_image_invalid: |-\n      Build image no longer exists. Rebuilding...\n    building: |-\n      Building the container from a Dockerfile...\n    building_git_repo: |-\n      Building the container from the git repository: %{repo}...\n    building_named_dockerfile: |-\n      Building the container from the named Dockerfile: %{file}...\n    building_git_repo_named_dockerfile: |-\n      Building the container from the named Dockerfile: %{file} in the git repository: %{repo}...\n    creating: |-\n      Creating the container...\n    created: |-\n      Container created: %{id}\n    host_machine_disabling_folders: |-\n      Removing synced folders...\n    host_machine_forwarded_ports: |-\n      Warning: When using a remote Docker host, forwarded ports will NOT be\n      immediately available on your machine. They will still be forwarded on\n      the remote machine, however, so if you have a way to access the remote\n      machine, then you should be able to access those ports there. This is\n      not an error, it is only an informational message.\n    host_machine_needed: |-\n      Docker host is required. One will be created if necessary...\n    host_machine_ready: |-\n      Docker host VM is already ready.\n    host_machine_starting: |-\n      Vagrant will now create or start a local VM to act as the Docker\n      host. You'll see the output of the `vagrant up` for this VM below.\n    host_machine_syncing_folders: |-\n      Syncing folders to the host VM...\n    logging_in: |-\n      Logging in to Docker server...\n    logs_host_state_unknown: |-\n      This container requires a host VM, and the state of that VM\n      is unknown. Run `vagrant up` to verify that the container and\n      its host VM is running, then try again.\n    network_bridge_gateway_invalid: |-\n      The provided gateway IP address is invalid (%{gateway}). Please\n      provide a valid IP address.\n    network_bridge_gateway_outofbounds: |-\n      The provided gateway IP (%{gateway}) is not within the defined\n      subnet (%{subnet}). Please provide an IP address within the\n      defined subnet.\n    network_bridge_gateway_request: |-\n      Gateway IP address for %{interface} interface [%{default_gateway}]:\n    network_bridge_iprange_info: |-\n      When an explicit address is not provided to a container attached\n      to this bridged network, docker will supply an address to the\n      container. This is independent of the local DHCP service that\n      may be available on the network.\n    network_bridge_iprange_invalid: |-\n      The provided IP address range is invalid (%{range}). Please\n      provide a valid range.\n    network_bridge_iprange_outofbounds: |-\n      The provided IP address range (%{range}) is not within the\n      defined subnet (%{subnet}). Please provide an address range\n      within the defined subnet.\n    network_bridge_iprange_request: |-\n      Available address range for assignment on %{interface} interface [%{default_range}]:\n    network_create: |-\n      Creating and configuring docker networks...\n    network_connect: |-\n      Enabling network interfaces...\n    network_destroy: |-\n      Removing network %{network_name} ...\n    not_created_skip: |-\n      Container not created. Skipping.\n    not_docker_provider: |-\n      Not backed by Docker provider. Skipping.\n    pull: |-\n      Pulling image '%{image}'...\n    run_command_required: |-\n      `vagrant docker-run` requires a command to execute. This command\n      must be specified after a `--` in the command line. This is used\n      to separate possible machine names and options from the actual\n      command to execute. An example is shown below:\n\n        vagrant docker-run web -- rails new .\n\n    running: |-\n      Container is starting. Output will stream in below...\n    running_detached: |-\n      Container is started detached.\n    ssh_through_host_vm: |-\n      SSH will be proxied through the Docker virtual machine since we're\n      not running Docker natively. This is just a notice, and not an error.\n    subnet_exists: |-\n      A network called '%{network_name}' using subnet '%{subnet}' is already in use.\n      Using '%{network_name}' instead of creating a new network...\n    synced_folders_changed: |-\n      Vagrant has noticed that the synced folder definitions have changed.\n      With Docker, these synced folder changes won't take effect until you\n      destroy the container and recreate it.\n    waiting_for_running: |-\n      Waiting for container to enter \"running\" state...\n    volume_path_not_expanded: |-\n      Host path `%{host}` exists as a `volumes` key and is a folder on disk. Vagrant\n      will not expand this key like it used to and instead leave it as is defined.\n      If this folder is intended to be used, make sure its full path is defined\n      in your `volumes` config. More information can be found on volumes in the\n      docker compose documentation.\n\n    messages:\n      destroying: |-\n        Deleting the container...\n      not_created: |-\n        The container hasn't been created yet.\n      not_created_original: |-\n        The original container hasn't been created yet. Run `vagrant up`\n        for this machine first.\n      not_running: |-\n        The container is not currently running.\n      preparing: |-\n        Preparing to start the container...\n      provision_no_ssh: |-\n        Provisioners will not be run since container doesn't support SSH.\n      will_not_destroy: |-\n        The container will not be destroyed, since the confirmation was declined.\n      starting: |-\n        Starting container...\n      stopping: |-\n        Stopping container...\n      container_ready: |-\n        Container started and ready for use!\n      not_provisioning: |-\n        The following provisioners require a communicator, though none is available (this container does not support SSH).\n        Not running the following provisioners:\n        - %{provisioners}\n\n    status:\n      host_state_unknown: |-\n        The host VM for the Docker containers appears to not be running\n        or is currently inaccessible. Because of this, we can't determine\n        the state of the containers on that host. Run `vagrant up` to\n        bring up the host VM again.\n      not_created: |-\n        The environment has not yet been created. Run `vagrant up` to\n        create the environment. If a machine is not created, only the\n        default provider will be shown. So if a provider is not listed,\n        then the machine is not created for that environment.\n      preparing: |-\n        Vagrant is preparing to start this Docker container. Run `vagrant up`\n        to continue.\n      running: |-\n        The container is created and running. You can stop it using\n        `vagrant halt`, see logs with `vagrant docker-logs`, and\n        kill/destroy it with `vagrant destroy`.\n      stopped: |-\n        The container is created but not running. You can run it again\n        with `vagrant up`. If the container always goes to \"stopped\"\n        right away after being started, it is because the command being\n        run exits and doesn't keep running.\n\n    errors:\n      build_error: |-\n        Vagrant received unknown output from `docker build` while building a container: %{result}\n      compose_lock_timeout: |-\n        Vagrant encountered a timeout waiting for the docker compose driver\n        to become available. Please try to run your command again. If you\n        continue to experience this error it may be resolved by disabling\n        parallel execution.\n      docker_compose_not_installed: |-\n        Vagrant has been instructed to use to use the Compose driver for the\n        Docker plugin but was unable to locate the `docker-compose` executable.\n        Ensure that `docker-compose` is installed and available on the PATH.\n      not_created: |-\n        The container hasn't been created yet.\n      not_running: |-\n        The container is not currently running.\n      communicator_non_docker: |-\n        The \"docker_hostvm\" communicator was specified on a machine that\n        is not provided by the Docker provider. This is a bug with your\n        Vagrantfile. Please contact the creator of your Vagrant environment\n        and notify them to not use this communicator for anything except the\n        \"docker\" provider.\n      config:\n        both_build_and_image_and_git: |-\n          Only one of \"build_dir\", \"git_repo\" or \"image\" can be set\n        build_dir_invalid: |-\n          \"build_dir\" must exist and contain a Dockerfile\n        git_repo_invalid: |-\n          \"git_repo\" must be a valid repository URL\n        build_dir_or_image: |-\n          One of \"build_dir\", \"git_repo\" or \"image\" must be set\n        compose_configuration_hash: |-\n          \"compose_configuration\" must be a hash\n        compose_force_vm: |-\n          Docker compose is not currently supported from within proxy VM.\n        git_repo_invalid: |-\n          \"git_repo\" must be a valid git URL\n        create_args_array: |-\n          \"create_args\" must be an array\n        invalid_link: |-\n          Invalid link (should be 'name:alias'): \"%{link}\"\n        invalid_vagrantfile: |-\n          \"vagrant_vagrantfile\" must point to a Vagrantfile that exists.\n      docker_provider_nfs_without_privileged: |-\n        You've configured a NFS synced folder but didn't enable privileged\n        mode for the container. Please set the `privileged` option to true\n        on the provider block from your Vagrantfile, recreate the container\n        and try again.\n      docker_provider_image_not_configured: |-\n        The base Docker image has not been set for the '%{name}' VM!\n      execute_error: |-\n        A Docker command executed by Vagrant didn't complete successfully!\n        The command run along with the output from the command is shown\n        below.\n\n        Command: %{command}\n\n        Stderr: %{stderr}\n\n        Stdout: %{stdout}\n      exec_command_required: |-\n        The \"docker-exec\" command requires a command to execute. This command\n        must be specified after a \"--\" in the command line. This is used to\n        separate machine name and options from the actual command to execute.\n        An example is show below:\n\n          $ vagrant docker-exec -t nginx -- bash\n\n      host_vm_communicator_not_ready: |-\n        The Docker provider was able to bring up the host VM successfully\n        but the host VM is still reporting that SSH is unavailable. This\n        sometimes happens with certain providers due to bugs in the\n        underlying hypervisor, and can be fixed with a `vagrant reload`.\n        The ID for the host VM is shown below for convenience.\n\n        If this does not fix it, please verify that the host VM provider\n        is functional and properly configured.\n\n        Host VM ID: %{id}\n      network_address_invalid: |-\n        The configured network address is not valid within the configured\n        subnet of the defined network. Please update the network settings\n        and try again.\n\n          Configured address: %{address}\n          Network name:       %{network_name}\n      network_address_required: |-\n        An IP address is required if not using `type: \"dhcp\"` or not specifying a `subnet`.\n      network_invalid_option: |-\n        Invalid option given for docker network for guest \"%{container}\". Must specify either\n        a `subnet` or use `type: \"dhcp\"`.\n      network_name_missing: |-\n        The Docker provider is unable to connect the container to the\n        defined network due to a missing network name. Please validate\n        your configuration and try again.\n\n          Container: %{container}\n          Network Number: %{index}\n      network_name_undefined: |-\n        The Docker provider was unable to configure networking using the\n        provided network name `%{network_name}`. Please ensure the network\n        name is correct and exists, then try again.\n      network_no_interfaces: |-\n        The Docker provider was unable to list any available interfaces to bridge\n        the public network with.\n      network_subnet_invalid: |-\n        The configured network subnet is not valid for the defined network.\n        Please update the network settings and try again.\n\n          Configured subnet: %{subnet}\n          Network name:      %{network_name}\n      package_not_supported: |-\n        The \"package\" command is not supported with the Docker provider.\n        If you'd like to commit or push your Docker container, please SSH\n        into the host VM (if there is one), and run `docker commit` and\n        so on manually.\n      state_not_running: |-\n        The container never entered the \"running\" state, or entered it\n        briefly but reverted back to another state. Please verify that\n        the configuration of the container is correct.\n\n        If you meant for this container to not remain running, please\n        set the Docker provider configuration \"remains_running\" to \"false\":\n\n          config.vm.provider \"docker\" do |d|\n            d.remains_running = false\n          end\n\n      state_stopped: |-\n        The container started either never left the \"stopped\" state or\n        very quickly reverted to the \"stopped\" state. This is usually\n        because the container didn't execute a command that kept it running,\n        and usually indicates a misconfiguration.\n\n        If you meant for this container to not remain running, please\n        set the Docker provider configuration \"remains_running\" to \"false\":\n\n          config.vm.provider \"docker\" do |d|\n            d.remains_running = false\n          end\n\n      suspend_not_supported: |-\n        The \"suspend\" command is not supported with the Docker provider.\n        Docker containers don't natively support suspend. If you're using\n        a host machine, you can suspend the host machine by finding it\n        in `vagrant global-status` and using `vagrant suspend <id>`.\n      synced_folder_non_docker: |-\n        The \"docker\" synced folder type can't be used because the  provider\n        in use is not Docker. This synced folder type only works with the\n        Docker provider. The provider this machine is using is: %{provider}\n      vagrantfile_not_found: |-\n        The configured host VM Vagrantfile could not be found. Please fix\n        the Vagrantfile for this Docker environment to point to a valid\n        host VM.\n"
  },
  {
    "path": "templates/locales/providers_hyperv.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  vagrant_hyperv:\n    choose_switch: |-\n      Please choose a switch to attach to your Hyper-V instance.\n      If none of these are appropriate, please open the Hyper-V manager\n      to create a new virtual switch.\n    message_already_running: |-\n      Hyper-V instance already running.\n    message_not_created: |-\n      VM not created. Moving on...\n    message_not_running: |-\n      Hyper-V machine isn't running. Can't SSH in!\n\n    config:\n      invalid_auto_start_action: |-\n        The requested auto start action for the Hyper-V VM is not a\n        valid action. Please provide a valid action and run the command\n        again.\n\n          Received: %{action}\n          Allowed: %{allowed_actions}\n      invalid_auto_stop_action: |-\n        The requested auto stop action for the Hyper-V VM is not a\n        valid action. Please provide a valid action and run the command\n        again.\n\n          Received: %{action}\n          Allowed: %{allowed_actions}\n      invalid_integration_services_type: |-\n        Invalid type provided for `vm_integration_services`. Type received\n        is `%{received}` but `Hash` was expected.\n      invalid_integration_services_entry: |-\n        The `%{entry_name}` entry in the `vm_integration_services` is set\n        to an unexpected value.\n\n          Received: %{entry_value}\n          Allowed: true, false\n      differencing_disk_deprecation: |-\n        The `differencing_disk` configuration option is deprecated and should\n        no longer be used. The `linked_clone` configuration option should\n        be used instead.\n    errors:\n      admin_required: |-\n        The Hyper-V provider requires that Vagrant be run with\n        administrative privileges. This is a limitation of Hyper-V itself.\n        Hyper-V requires administrative privileges for management\n        commands. Please restart your console with administrative\n        privileges and try again.\n      box_invalid: |-\n        The box you're using with the Hyper-V provider ('%{name}')\n        is invalid. A Hyper-V box should contain both a\n        \"Virtual Machines\" and a \"Virtual Hard Disks\" folder that are\n        created as part of exporting a Hyper-V machine.\n\n        Within these directories, Vagrant expects to find the\n        virtual machine configuration as well as the root hard disk.\n\n        The box you're attempting to use is missing one or both of\n        these directories or does not contain the files expected. Verify\n        that you added the correct box. If this problem persists,\n        please contact the creator of the box for assistance.\n      ip_addr_timeout: |-\n        Hyper-V failed to determine your machine's IP address within the\n        configured timeout. Please verify the machine properly booted and\n        the network works. To do this, open the Hyper-V manager, find your\n        virtual machine, and connect to it.\n\n        The most common cause for this error is that the running virtual\n        machine doesn't have the latest Hyper-V integration drivers. Please\n        research for your operating system how to install these in order\n        for the VM to properly communicate its IP address to Hyper-V.\n      no_switches: |-\n        There are no virtual switches created for Hyper-V! Please open\n        the Hyper-V Manager, go to the \"Virtual Switch Manager\", and create\n        at least one virtual switch.\n\n        A virtual switch is required for Vagrant to create a Hyper-V\n        machine that is connected to a network so it can access it.\n\n        For more help, please see the documentation on the Vagrant website\n        for Hyper-V.\n      powershell_features_disabled: |-\n        The Hyper-V cmdlets for PowerShell are not available! Vagrant\n        requires these to control Hyper-V. Please enable them in the\n        \"Windows Features\" control panel and try again.\n      powershell_error: |-\n        An error occurred while executing a PowerShell script. This error\n        is shown below. Please read the error message and see if this is\n        a configuration error with your system. If it is not, then please\n        report a bug.\n\n        Script: %{script}\n        Error:\n\n        %{stderr}\n      powershell_required: |-\n        The Vagrant Hyper-V provider requires PowerShell to be available.\n        Please make sure \"powershell.exe\" is available on your PATH.\n      windows_required: |-\n        The Hyper-V provider only works on Windows. Please try to\n        use another provider.\n      system_access_required: |-\n        Hyper-V access check has failed for the configured destination. This\n        is usually caused by running on a non-system drive which is missing\n        required permissions. Running the following command may resolve the\n        problem:\n\n          icacls.exe %{root_dir} /T /Q /grant \"NT AUTHORITY\\SYSTEM:(IO)(CI)(F)\"\n"
  },
  {
    "path": "templates/locales/synced_folder_smb.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nen:\n  vagrant_sf_smb:\n    mounting: |-\n      Mounting SMB shared folders...\n    mounting_single: |-\n      %{host} => %{guest}\n    preparing: |-\n      Preparing SMB shared folders...\n    warning_password: |-\n      You will be asked for the username and password to use for the SMB\n      folders shortly. Please use the proper username/password of your\n      account.\n    incorrect_credentials: |-\n      Credentials incorrect. Please try again.\n\n    uac:\n      prune_warning: |-\n        Vagrant requires administrator access for pruning SMB shares and\n        may request access to complete removal of stale shares.\n      create_warning: |-\n        Vagrant requires administrator access to create SMB shares and\n        may request access to complete setup of configured shares.\n    errors:\n      not_supported: |-\n        It appears your machine doesn't support SMB, has not been\n        properly configured for SMB, or there is not an adapter to\n        enable SMB on this machine for Vagrant. Ensure SMB host\n        functionality is available on this machine and try again.\n      start_failed: |-\n        Vagrant failed to automatically start the SMB service. Ensure the\n        required services can be started and try again.\n\n        Command: %{command}\n\n        Stderr: %{stderr}\n\n        Stdout: %{stdout}\n      credentials_missing: |-\n        Vagrant SMB synced folders require the account password to be stored\n        in an NT compatible format. Please update your sharing settings to\n        enable a Windows compatible password and try again.\n      credentials_request_error: |-\n        Vagrant failed to receive credential information required for preparing\n        an SMB share.\n      define_share_failed: |-\n        Exporting an SMB share failed! Details about the failure are shown\n        below. Please inspect the error message and correct any problems.\n\n        Host path: %{host}\n\n        Stderr: %{stderr}\n\n        Stdout: %{stdout}\n      prune_share_failed: |-\n        Pruning an SMB share failed! Details about the failure are shown\n        below. Please inspect the error message and correct any problems.\n\n        Share name: %{name}\n\n        Stderr: %{stderr}\n\n        Stdout: %{stdout}\n      name_error: |-\n        Vagrant is unable to setup a requested SMB share. An SMB share already\n        exists with the given name.\n\n        Share name: %{name}\n\n        Current path: %{existing_path}\n\n        Requested path: %{path}\n      list_failed: |-\n        Vagrant failed to generate a list of local SMB shares. Please try\n        running the command again.\n      no_routable_host_addr: |-\n        We couldn't detect an IP address that was routable to this\n        machine from the guest machine! Please verify networking is properly\n        setup in the guest machine and that it is able to access this\n        host.\n\n        As another option, you can manually specify an IP for the machine\n        to mount from using the `smb_host` option to the synced folder.\n      powershell_version: |-\n        PowerShell version 3 or later is required for SMB synced folders\n        to work on Windows. You have version: '%{version}'. Please update\n        your PowerShell installation.\n      windows_admin_required: |-\n        SMB shared folders require running Vagrant with administrative\n        privileges. This is a limitation of Windows, since creating new\n        network shares requires admin privileges. Please try again in a\n        console with proper permissions or use another synced folder type.\n      windows_host_required: |-\n        SMB shared folders are only available when Vagrant is running\n        on Windows. The guest machine can be running non-Windows. Please use\n        another synced folder type.\n"
  },
  {
    "path": "templates/networking/network_manager/network_manager_device.erb",
    "content": "[connection]\nid=<%= options[:interface_name] %>\nuuid=<%= options[:uuid] %>\ntype=ethernet\nautoconnect-priority=-100\nautoconnect-retries=1\ninterface-name=<%= options[:interface_name] %>\n\n[ethernet]\n<% if options[:mac_address] -%>\nmac-address=<%= options[:mac_address] %>\n<% end -%>\n\n<% if options[:ipv4] -%>\n[ipv4]\n<% if options[:type] == \"dhcp\" -%>\ndhcp-timeout=90\nmethod=auto\nrequired-timeout=20000\n<% elsif options[:ipv4] -%>\nmethod=manual\naddresses=<%= options[:ipv4] %>/<%= options[:ipv4_mask] %>\ngateway=<%= options[:ipv4_gateway] %>\n<% end -%>\n<% end -%>\n\n<% if options[:ipv6] -%>\n[ipv6]\n<% if options[:type] == \"dhcp\" -%>\naddr-gen-mode=eui64\ndhcp-timeout=90\nmethod=auto\n<% elsif options[:ipv6] -%>\nmethod=manual\naddresses=<%= options[:ipv6] %>/<%= options[:ipv6_mask] %>\ngateway=<%= options[:ipv6_gateway] %>\n<% end -%>\n<% end -%>\n\n[user]\norg.freedesktop.NetworkManager.origin=vagrant\n"
  },
  {
    "path": "templates/nfs/exports_bsd.erb",
    "content": "# VAGRANT-BEGIN: <%= user %> <%= uuid %>\n<% folders.each do |dirs, opts| %>\n<%= dirs.map { |d| \"#{d}\" }.join(\" \") %> <%=opts[:bsd__compiled_nfs_options] %> <%= ips.join(\" \") %>\n<% end %>\n# VAGRANT-END: <%= user %> <%= uuid %>\n"
  },
  {
    "path": "templates/nfs/exports_darwin.erb",
    "content": "# VAGRANT-BEGIN: <%= user %> <%= uuid %>\n<% folders.each do |dirs, opts| %>\n<% dirs.each do |d| %>\n\"<%= d %>\" <%=opts[:bsd__compiled_nfs_options] %> <%= ips.join(\" \") %>\n<% end %>\n<% end %>\n# VAGRANT-END: <%= user %> <%= uuid %>\n"
  },
  {
    "path": "templates/nfs/exports_linux.erb",
    "content": "# VAGRANT-BEGIN: <%= user %> <%= uuid %>\n<% ips.each do |ip| %>\n<% folders.each do |name, opts| %>\n\"<%= opts[:hostpath] %>\" <%= ip %>(<%= opts[:linux__nfs_options].join(\",\") %>)\n<% end %>\n<% end %>\n# VAGRANT-END: <%= user %> <%= uuid %>\n"
  },
  {
    "path": "templates/package_Vagrantfile.erb",
    "content": "Vagrant::Config.run do |config|\n  # This Vagrantfile is auto-generated by `vagrant package` to contain\n  # the MAC address of the box. Custom configuration should be placed in\n  # the actual `Vagrantfile` in this box.\n  config.vm.base_mac = \"<%= base_mac %>\"\nend\n\n# Load include vagrant file if it exists after the auto-generated\n# so it can override any of the settings\ninclude_vagrantfile = File.expand_path(\"../include/_Vagrantfile\", __FILE__)\nload include_vagrantfile if File.exist?(include_vagrantfile)\n"
  },
  {
    "path": "templates/provisioners/chef_client/client.erb",
    "content": "log_level          <%= log_level.inspect %>\nlog_location       STDOUT\nverbose_logging    <%= verbose_logging.inspect %>\n<% if node_name %>\nnode_name          \"<%= node_name %>\"\n<% end %>\nssl_verify_mode    :verify_none\nchef_server_url    \"<%= chef_server_url %>\"\n\nvalidation_client_name \"<%= validation_client_name %>\"\nvalidation_key         \"<%= validation_key %>\"\nclient_key             \"<%= client_key %>\"\n\nencrypted_data_bag_secret <%= encrypted_data_bag_secret.inspect %>\n\n<% if environment %>\nenvironment        \"<%= environment %>\"\n<% end %>\n\nfile_cache_path    \"<%= file_cache_path %>\"\nfile_backup_path   \"<%= file_backup_path %>\"\n\nhttp_proxy <%= http_proxy.inspect %>\nhttp_proxy_user <%= http_proxy_user.inspect %>\nhttp_proxy_pass <%= http_proxy_pass.inspect %>\nhttps_proxy <%= https_proxy.inspect %>\nhttps_proxy_user <%= https_proxy_user.inspect %>\nhttps_proxy_pass <%= https_proxy_pass.inspect %>\nno_proxy <%= no_proxy.inspect %>\n\npid_file           \"/var/run/chef/chef-client.pid\"\n\nMixlib::Log::Formatter.show_time = true\n\n<% if formatter %>\nadd_formatter \"<%= formatter %>\"\n<% end %>\n\n<% if custom_configuration -%>\nChef::Config.from_file \"<%= custom_configuration %>\"\n<% end -%>\n"
  },
  {
    "path": "templates/provisioners/chef_solo/solo.erb",
    "content": "<% if node_name %>\nnode_name \"<%= node_name %>\"\n<% end %>\nfile_cache_path    \"<%= file_cache_path %>\"\nfile_backup_path   \"<%= file_backup_path %>\"\ncookbook_path <%= cookbooks_path.inspect %>\n<% if roles_path && !roles_path.empty? -%>\nrole_path <%= roles_path.size == 1 ? roles_path.first.inspect : roles_path.inspect %>\n<% end -%>\nlog_level <%= log_level.inspect %>\nverbose_logging <%= verbose_logging.inspect %>\n\nencrypted_data_bag_secret <%= encrypted_data_bag_secret.inspect %>\n\n<% if data_bags_path && !data_bags_path.empty? -%>\ndata_bag_path <%= data_bags_path.size == 1 ? data_bags_path.first.inspect : data_bags_path.inspect %>\n<% end %>\n\n<% if recipe_url -%>\nrecipe_url \"<%= recipe_url %>\"\n<% end -%>\n\n<% if environments_path && !environments_path.empty? -%>\nenvironment_path <%= environments_path.inspect %>\n<% end -%>\n\n<% if environment -%>\nenvironment \"<%= environment %>\"\n<% end -%>\n\n<% if local_mode -%>\nlocal_mode true\n<% end -%>\n<% if nodes_path && !nodes_path.empty? -%>\nnode_path <%= nodes_path.inspect %>\n<% end -%>\n\nhttp_proxy <%= http_proxy.inspect %>\nhttp_proxy_user <%= http_proxy_user.inspect %>\nhttp_proxy_pass <%= http_proxy_pass.inspect %>\nhttps_proxy <%= https_proxy.inspect %>\nhttps_proxy_user <%= https_proxy_user.inspect %>\nhttps_proxy_pass <%= https_proxy_pass.inspect %>\nno_proxy <%= no_proxy.inspect %>\n\n<% if formatter -%>\nadd_formatter \"<%= formatter %>\"\n<% end %>\n\n<% if custom_configuration -%>\nChef::Config.from_file \"<%= custom_configuration %>\"\n<% end -%>\n"
  },
  {
    "path": "templates/provisioners/chef_zero/zero.erb",
    "content": "<% if node_name %>\nnode_name \"<%= node_name %>\"\n<% end %>\nfile_cache_path    \"<%= file_cache_path %>\"\nfile_backup_path   \"<%= file_backup_path %>\"\ncookbook_path <%= cookbooks_path.inspect %>\n<% if roles_path %>\nrole_path <%= roles_path.size == 1 ? roles_path.first.inspect : roles_path.inspect %>\n<% end %>\nlog_level <%= log_level.inspect %>\nverbose_logging <%= verbose_logging.inspect %>\n\n<% if !enable_reporting %>\nenable_reporting <%= enable_reporting.inspect %>\n<% end %>\n\nencrypted_data_bag_secret <%= encrypted_data_bag_secret.inspect %>\n\n<% if data_bags_path -%>\ndata_bag_path <%= data_bags_path.size == 1 ? data_bags_path.first.inspect : data_bags_path.inspect %>\n<% end %>\n\n<% if environments_path %>\nenvironment_path <%= environments_path.inspect %>\n<% end -%>\n\n<% if environment %>\nenvironment \"<%= environment %>\"\n<% end -%>\n\n<% if local_mode -%>\nchef_zero.enabled true\nlocal_mode true\n<% end -%>\n<% if nodes_path -%>\nnode_path <%= nodes_path.inspect %>\n<% end -%>\n\n<% if formatter %>\nadd_formatter \"<%= formatter %>\"\n<% end %>\n\n<% if custom_configuration -%>\nChef::Config.from_file \"<%= custom_configuration %>\"\n<% end -%>\n"
  },
  {
    "path": "templates/rgloader.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# This file loads the proper rgloader/loader.rb file that comes packaged\n# with Vagrant so that encoded files can properly run with Vagrant.\n\nif ENV[\"VAGRANT_INSTALLER_EMBEDDED_DIR\"]\n  require File.expand_path(\n    \"rgloader/loader\", ENV[\"VAGRANT_INSTALLER_EMBEDDED_DIR\"])\nelse\n  raise \"Encoded files can't be read outside of the Vagrant installer.\"\nend\n"
  },
  {
    "path": "test/acceptance/base.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant-spec/acceptance\"\nrequire_relative \"shared/context_virtualbox\"\n"
  },
  {
    "path": "test/acceptance/provider-docker/lifecycle_spec.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# This tests the basic functionality of a provider: that it can run\n# a machine, provide SSH access, and destroy that machine.\nshared_examples \"provider/docker/lifecycle\" do |provider, options|\n  if provider != \"docker\"\n    raise ArgumentError, \n      \"provider must be docker to run tests\" \n  end\n\n  if !options[:image]\n    raise ArgumentError,\n      \"box option must be specified for provider: #{provider}\"\n  end\n\n  include_context \"acceptance\"\n\n  before do\n    environment.skeleton(\"basic_docker\")\n    ENV[\"VAGRANT_SPEC_DOCKER_IMAGE\"] = options[:image]\n  end\n\n  let(:opts) { options }\n\n  after do\n    # Just always do this just in case\n    execute(\"vagrant\", \"destroy\", \"--force\", log: false)\n  end\n\n  def assert_running\n    result = execute(\"docker\", \"ps\", \"--filter\", \"name=dockertest\")\n    expect(result).to exit_with(0)\n    expect(result.stdout).to match(/#{opts[:image]}/)\n  end\n\n  def assert_not_running\n    result = execute(\"docker\", \"ps\", \"--filter\", \"name=dockertest\")\n    expect(result).to exit_with(0)\n    # Check the output ends with the last column of the header from the `docker ps`\n    # command, indicating no images found.\n    expect(result.stdout).to match(/NAMES\\n$/)\n  end\n\n  context \"after an up\" do\n    before do\n      assert_execute(\"vagrant\", \"up\", \"--provider=#{provider}\")\n    end\n\n    after do\n      assert_execute(\"vagrant\", \"destroy\", \"--force\")\n    end\n\n    it \"can manage machine lifecycle\" do\n      status(\"Test: machine is running after up\")\n      assert_running\n\n      status(\"Test: halt\")\n      assert_execute(\"vagrant\", \"halt\")\n\n      status(\"Test: ssh doesn't work during halted state\")\n      assert_not_running\n\n      status(\"Test: up after halt\")\n      assert_execute(\"vagrant\", \"up\")\n      assert_running\n    end\n  end\nend\n"
  },
  {
    "path": "test/acceptance/provider-virtualbox/linked_clone_spec.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# This tests that VM is up as a linked clone\nshared_examples 'provider/linked_clone' do |provider, options|\n  if !options[:box]\n    raise ArgumentError,\n      \"box option must be specified for provider: #{provider}\"\n  end\n\n  include_context 'acceptance'\n\n  before do\n    environment.skeleton('linked_clone')\n    assert_execute('vagrant', 'box', 'add', 'box', options[:box])\n  end\n\n  after do\n    assert_execute('vagrant', 'destroy', '--force')\n  end\n\n  it 'creates machine as linked clone' do\n    status('Test: machine is created successfully')\n    result = execute('vagrant', 'up', \"--provider=#{provider}\")\n    expect(result).to exit_with(0)\n\n    status('Test: master VM is created')\n    expect(result.stdout).to match(/master VM/)\n\n    status('Test: machine is a master VM clone')\n    expect(result.stdout).to match(/Cloning/)\n\n    status('Test: machine is available by ssh')\n    result = execute('vagrant', 'ssh', '-c', 'echo foo')\n    expect(result).to exit_with(0)\n    expect(result.stdout).to match(/foo\\n$/)\n  end\nend\n"
  },
  {
    "path": "test/acceptance/provider-virtualbox/network_intnet_spec.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nshared_examples \"provider/network/intnet\" do |provider, options|\n  if !options[:box]\n    raise ArgumentError,\n      \"box option must be specified for provider: #{provider}\"\n  end\n\n  include_context \"acceptance\"\n\n  before do\n    environment.skeleton(\"network_intnet\")\n    assert_execute(\"vagrant\", \"box\", \"add\", \"box\", options[:box])\n    assert_execute(\"vagrant\", \"up\", \"--provider=#{provider}\")\n  end\n\n  after do\n    assert_execute(\"vagrant\", \"destroy\", \"--force\", log: false)\n  end\n\n  it \"properly configures an internal network\" do\n  end\nend\n"
  },
  {
    "path": "test/acceptance/shared/context_virtualbox.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nshared_context \"provider-context/virtualbox\" do\n  let(:extra_env) {{\n    \"VBOX_USER_HOME\" => \"{{homedir}}\",\n  }}\nend\n"
  },
  {
    "path": "test/acceptance/skeletons/basic_docker/Vagrantfile",
    "content": "Vagrant.configure(\"2\") do |config|\n  config.vm.define \"dockertest\" do |c|\n    c.vm.provider \"docker\" do |d|\n      d.image = ENV[\"VAGRANT_SPEC_DOCKER_IMAGE\"]\n      d.remains_running = true\n    end\n  end\nend\n"
  },
  {
    "path": "test/acceptance/skeletons/linked_clone/Vagrantfile",
    "content": "Vagrant.configure('2') do |config|\n  config.vm.box = 'basic'\n\n  config.vm.provider 'virtualbox' do |v|\n    v.linked_clone = true\n  end\nend\n"
  },
  {
    "path": "test/acceptance/skeletons/network_intnet/Vagrantfile",
    "content": "Vagrant.configure(\"2\") do |config|\n  config.vm.box = \"box\"\n  config.vm.network \"private_network\",\n    ip: \"192.168.50.4\",\n    virtualbox__intnet: true\nend\n"
  },
  {
    "path": "test/config/acceptance_boxes.yml",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# This is the list of required boxes for acceptance tests, and\n# their locations. The locations are used to download the boxes\n# on the fly, if necessary. Additionally, a SHA1 checksum is\n# given to determine if the box needs to be updated.\n- name: default\n  url: http://files.vagrantup.com/test/boxes/default.box\n  checksum: 1b0a7eb6e152c5b43f45485654ff4965a7f9f604\n"
  },
  {
    "path": "test/support/isolated_environment.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\nrequire \"pathname\"\nrequire \"tmpdir\"\n\nrequire \"log4r\"\n\nrequire \"vagrant/util/platform\"\n\n# This class manages an isolated environment for Vagrant to\n# run in. It creates a temporary directory to act as the\n# working directory as well as sets a custom home directory.\n#\n# This class also provides various helpers to create Vagrantfiles,\n# boxes, etc.\nclass IsolatedEnvironment\n  attr_reader :homedir\n  attr_reader :workdir\n\n  # Initializes an isolated environment. You can pass in some\n  # options here to configure running custom applications in place\n  # of others as well as specifying environmental variables.\n  #\n  # @param [Hash] apps A mapping of application name (such as \"vagrant\")\n  #   to an alternate full path to the binary to run.\n  # @param [Hash] env Additional environmental variables to inject\n  #   into the execution environments.\n  def initialize\n    @logger = Log4r::Logger.new(\"test::isolated_environment\")\n\n    # Create a temporary directory for our work\n    @tempdir = Vagrant::Util::Platform.fs_real_path(Dir.mktmpdir(\"vagrant-iso-env\"))\n    @logger.info(\"Initialize isolated environment: #{@tempdir}\")\n\n    # Setup the home and working directories\n    @homedir = Pathname.new(File.join(@tempdir, \"home\"))\n    @workdir = Pathname.new(File.join(@tempdir, \"work\"))\n\n    @homedir.mkdir\n    @workdir.mkdir\n  end\n\n  # This closes the environment by cleaning it up.\n  def close\n    @logger.info(\"Removing isolated environment: #{@tempdir}\")\n    FileUtils.rm_rf(@tempdir)\n  end\nend\n"
  },
  {
    "path": "test/unit/base.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tmpdir\"\nrequire \"rubygems\"\n\n# Gems\nrequire \"checkpoint\"\nrequire \"rspec/its\"\n\n# Require Vagrant itself so we can reference the proper\n# classes to test.\nrequire \"vagrant\"\nrequire \"vagrant/util/platform\"\n\n# Include patches for fake ftp\nrequire \"vagrant/patches/fake_ftp\"\n\n# Add the test directory to the load path\n$:.unshift File.expand_path(\"../../\", __FILE__)\n\n# Load in helpers\nrequire \"unit/support/dummy_communicator\"\nrequire \"unit/support/dummy_provider\"\nrequire \"unit/support/shared/base_context\"\nrequire \"unit/support/shared/action_synced_folders_context\"\nrequire \"unit/support/shared/capability_helpers_context\"\nrequire \"unit/support/shared/plugin_command_context\"\nrequire \"unit/support/shared/virtualbox_context\"\n\n# Do not buffer output\n$stdout.sync = true\n$stderr.sync = true\n\n# Create a temporary directory where test vagrant will run. The reason we save\n# this to a constant is so we can clean it up later.\nVAGRANT_TEST_CWD = Dir.mktmpdir(\"vagrant-test-cwd\")\n\n# Configure RSpec\nRSpec.configure do |c|\n  c.formatter = :progress\n  c.color_mode = :on\n\n  if Vagrant::Util::Platform.windows?\n    c.filter_run_excluding :skip_windows\n  else\n    c.filter_run_excluding :windows\n  end\n\n  if !Vagrant::Util::Which.which(\"bsdtar\")\n    c.filter_run_excluding :bsdtar\n  end\n\n  c.after(:suite) do\n    FileUtils.rm_rf(VAGRANT_TEST_CWD)\n  end\nend\n\n# Configure VAGRANT_CWD so that the tests never find an actual\n# Vagrantfile anywhere, or at least this minimizes those chances.\nENV[\"VAGRANT_CWD\"] = VAGRANT_TEST_CWD\n\n# Set the dummy provider to the default for tests\nENV[\"VAGRANT_DEFAULT_PROVIDER\"] = \"dummy\"\n\n# Unset all host plugins so that we aren't executing subprocess things\n# to detect a host for every test.\nVagrant.plugin(\"2\").manager.registered.dup.each do |plugin|\n  if plugin.components.hosts.to_hash.length > 0\n    Vagrant.plugin(\"2\").manager.unregister(plugin)\n  end\nend\n\n# Disable checkpoint\nCheckpoint.disable!\n"
  },
  {
    "path": "test/unit/bin/vagrant_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\ndescribe \"vagrant bin\" do\n  include_context \"unit\"\n\n  let(:env) { isolated_environment }\n  let(:ui) { double(:ui) }\n  let(:argv) { [] }\n  let(:exit_code) { 0 }\n\n  let(:run_vagrant) {\n    lambda {\n      begin\n        instance_eval(\n          File.read(\n            File.expand_path(\n              \"../../../../bin/vagrant\", __FILE__)))\n      rescue SystemExit => e\n        e.status\n      end\n    }.call\n  }\n\n  before do\n    allow(env).to receive(:ui).and_return(ui)\n    allow(ARGV).to receive(:dup).and_return(argv)\n    allow(env).to receive(:unload)\n    allow(env).to receive(:cli).and_return(exit_code)\n    allow(Kernel).to receive(:at_exit)\n    allow(Kernel).to receive(:exit)\n    allow(Vagrant::Environment).to receive(:new).and_return(env)\n    allow(Vagrant).to receive(:in_installer?).and_return(true)\n    allow(self).to receive(:require_relative)\n  end\n\n  after { expect(run_vagrant).to eq(exit_code) }\n\n  it \"should run the CLI and exit successfully\" do\n    expect(env).to receive(:cli).with(argv).and_return(exit_code)\n    expect(run_vagrant).to eq(exit_code)\n  end\n\n  context \"with flag\" do\n    describe \"--version\" do\n      let(:argv) { [\"--version\"] }\n      before { allow(self).to receive(:require_relative).with(/version/) }\n\n      it \"should output the current version\" do\n        expect($stdout).to receive(:puts).with(/#{Regexp.escape(Vagrant::VERSION.to_s)}/)\n      end\n    end\n\n    describe \"--timestamp\" do\n      let(:argv) { [\"--timestamp\"] }\n\n      it \"should enable timestamps on logs\" do\n        expect(ENV).to receive(:[]=).with(\"VAGRANT_LOG_TIMESTAMP\", \"1\")\n      end\n    end\n\n    describe \"--debug-timestamp\" do\n      let(:argv) { [\"--debug-timestamp\"] }\n\n      it \"should enable debugging and log timestamps\" do\n        expect(ENV).to receive(:[]=).with(\"VAGRANT_LOG_TIMESTAMP\", \"1\")\n        expect(ENV).to receive(:[]=).with(\"VAGRANT_LOG\", \"debug\")\n      end\n    end\n\n    describe \"--no-color\" do\n      let(:argv) { [\"--no-color\"] }\n\n      it \"should remove flag from argv\" do\n        expect(env).to receive(:cli).with([]).and_return(exit_code)\n      end\n\n      it \"should pass a Basic UI instance\" do\n        expect(Vagrant::Environment).to receive(:new).\n          with(hash_including(ui_class: Vagrant::UI::Basic))\n      end\n    end\n\n    describe \"--color\" do\n      let(:argv) { [\"--color\"] }\n\n      it \"should remove flag from argv\" do\n        expect(env).to receive(:cli).with([]).and_return(exit_code)\n      end\n\n      it \"should pass a Colored UI instance\" do\n        expect(Vagrant::Environment).to receive(:new).\n          with(hash_including(ui_class: Vagrant::UI::Colored))\n      end\n    end\n\n    describe \"--no-tty\" do\n      let(:argv) { [\"--no-tty\"] }\n\n      it \"should enable less verbose progress output\" do\n        expect(Vagrant::Environment).to receive(:new).\n          with(hash_including(ui_class: Vagrant::UI::NonInteractive))\n      end\n    end\n  end\n\n  context \"default CLI flags\" do\n    let(:argv) { [\"--help\"] }\n\n    before do\n      allow(env).to receive(:ui).and_return(ui)\n      allow(ARGV).to receive(:dup).and_return(argv)\n      allow(Kernel).to receive(:at_exit)\n      allow(Kernel).to receive(:exit)\n      allow(Vagrant::Environment).to receive(:new).and_call_original\n\n      # Include this to intercept checkpoint instance setup\n      # since it is a singleton\n      allow(Vagrant::Util::CheckpointClient).\n        to receive_message_chain(:instance, :setup, :check)\n    end\n\n    it \"should include default CLI flags in command help output\" do\n      expect($stdout).to receive(:puts).with(/--debug/)\n    end\n  end\n\n  context \"when not in installer\" do\n    let(:warning) { \"INSTALLER WARNING\" }\n\n    before do\n      expect(Vagrant).to receive(:in_installer?).and_return(false)\n      allow(I18n).to receive(:t).with(/not_in_installer/, any_args).and_return(warning)\n    end\n\n    context \"when tool is missing\" do\n      before { expect(Vagrant).to receive(:detect_missing_tools).and_return([\"tool\"]) }\n\n      it \"should output a warning\" do\n        expect(env.ui).to receive(:warn).with(/#{warning}/, any_args)\n      end\n    end\n\n    context \"when tool is not missing\" do\n      before { expect(Vagrant).to receive(:detect_missing_tools).and_return([]) }\n\n      it \"should not output a warning\" do\n        expect(env.ui).not_to receive(:warn).with(/#{warning}/, any_args)\n      end\n    end\n  end\n\n  context \"plugin commands\" do\n    let(:argv) { [\"plugin\"] }\n\n    before do\n      allow(ENV).to receive(:[]=)\n      allow(ENV).to receive(:[])\n    end\n\n    it \"should unset vagrantfile\" do\n      expect(Vagrant::Environment).to receive(:new).\n        with(hash_including(vagrantfile_name: \"\")).and_return(env)\n    end\n\n    it \"should set the no plugins environment variable\" do\n      expect(ENV).to receive(:[]=).with(\"VAGRANT_NO_PLUGINS\", \"1\")\n    end\n\n    it \"should set the disable plugin init environment variable\" do\n      expect(ENV).to receive(:[]=).with(\"VAGRANT_DISABLE_PLUGIN_INIT\", \"1\")\n    end\n\n    context \"list\" do\n      let(:argv) { [\"plugin\", \"list\"] }\n\n      it \"should not set the disable plugin init environment variable\" do\n        expect(ENV).not_to receive(:[]=).with(\"VAGRANT_DISABLE_PLUGIN_INIT\", \"1\")\n      end\n    end\n\n    context \"--local\" do\n      let(:argv) { [\"plugin\", \"install\", \"--local\"] }\n\n      it \"should not unset vagrantfile\" do\n        expect(Vagrant::Environment).to receive(:new).\n          with(hash_excluding(vagrantfile_name: \"\")).and_return(env)\n      end\n    end\n\n    context \"with VAGRANT_LOCAL_PLUGINS_LOAD enabled\" do\n      before { expect(ENV).to receive(:[]).with(\"VAGRANT_LOCAL_PLUGINS_LOAD\").and_return(\"1\") }\n\n      it \"should not unset vagrantfile\" do\n        expect(Vagrant::Environment).to receive(:new).\n          with(hash_excluding(vagrantfile_name: \"\")).and_return(env)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/autocomplete/commands/install_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/autocomplete/command/install\")\n\ndescribe VagrantPlugins::CommandAutocomplete::Command::Install do\n  include_context \"unit\"\n\n  let(:argv)     { [] }\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:homedir) { Dir.mktmpdir(\"homedir\") }\n\n  before {\n    allow(Dir).to receive(:home) { homedir }\n  }\n\n  after {\n    FileUtils.rm_rf(homedir)\n  }\n\n  subject { described_class.new(argv, iso_env) }\n\n  let(:action_runner) { double(\"action_runner\") }\n\n  before do\n    allow(iso_env).to receive(:action_runner).and_return(action_runner)\n  end\n\n  context \"with no arguments\" do\n    it \"returns without errors\" do\n      expect(subject.execute).to be(0)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/box/command/add_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/box/command/add\")\n\ndescribe VagrantPlugins::CommandBox::Command::Add do\n  include_context \"unit\"\n\n  let(:argv)     { [] }\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  subject { described_class.new(argv, iso_env) }\n\n  let(:action_runner) { double(\"action_runner\") }\n\n  before do\n    allow(iso_env).to receive(:action_runner).and_return(action_runner)\n  end\n\n  context \"with no arguments\" do\n    it \"shows help\" do\n      expect { subject.execute }.\n        to raise_error(Vagrant::Errors::CLIInvalidUsage)\n    end\n  end\n\n  context \"with one argument\" do\n    let(:argv) { [\"foo\"] }\n\n    it \"executes the runner with the proper actions\" do\n      expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n        expect(opts[:box_name]).to be_nil\n        expect(opts[:box_url]).to eq(\"foo\")\n        true\n      }\n\n      subject.execute\n    end\n  end\n\n  context \"with two arguments\" do\n    let(:argv) { [\"foo\", \"bar\"] }\n\n    it \"executes the runner with the proper actions\" do\n      expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n        expect(opts[:box_name]).to eq(\"foo\")\n        expect(opts[:box_url]).to eq(\"bar\")\n        true\n      }\n\n      subject.execute\n    end\n  end\n\n  context \"with more than two arguments\" do\n    let(:argv) { [\"one\", \"two\", \"three\"] }\n\n    it \"shows help\" do\n      expect { subject.execute }.\n        to raise_error(Vagrant::Errors::CLIInvalidUsage)\n    end\n  end\n\n  context \"with architecture flag\" do\n    let(:argv) { [\"foo\", \"--architecture\", \"test-arch\"] }\n\n    it \"executes the runner with box architecture set\" do\n      expect(action_runner).to receive(:run) do |_, opts|\n        expect(opts[:box_architecture]).to eq(\"test-arch\")\n      end\n\n      subject.execute\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/box/command/outdated_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/box/command/outdated\")\n\ndescribe VagrantPlugins::CommandBox::Command::Outdated do\n  include_context \"unit\"\n\n  let(:argv)     { [] }\n  let(:iso_env) do\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  subject { described_class.new(argv, iso_env) }\n\n  let(:action_runner) { double(\"action_runner\") }\n\n  before do\n    allow(iso_env).to receive(:action_runner).and_return(action_runner)\n  end\n\n  context \"with force argument\" do\n    let(:argv) { [\"--force\"] }\n\n    it \"passes along the force update option\" do\n      expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n        expect(opts[:box_outdated_force]).to be_truthy\n        true\n      }\n      subject.execute\n    end\n  end\n\n  context \"with global argument\" do\n    let(:argv) { [\"--global\"] }\n\n    it \"calls outdated_global\" do\n      expect(subject).to receive(:outdated_global)\n\n      subject.execute\n    end\n\n    describe \".outdated_global\" do\n      let(:test_iso_env) { isolated_environment }\n\n      let(:md) {\n        md = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW))\n      {\n        \"name\": \"foo\",\n        \"versions\": [\n          {\n            \"version\": \"1.0\"\n          },\n          {\n            \"version\": \"1.1\",\n            \"providers\": [\n              {\n                \"name\": \"virtualbox\",\n                \"url\": \"bar\"\n              }\n            ]\n          },\n          {\n            \"version\": \"1.2\",\n            \"providers\": [\n              {\n                \"name\": \"vmware\",\n                \"url\": \"baz\"\n              }\n            ]\n          }\n        ]\n      }\n        RAW\n      }\n\n      let(:collection) do\n        collection = double(\"collection\")\n        allow(collection).to receive(:all).and_return([box])\n        allow(collection).to receive(:find).and_return(box)\n        collection\n      end\n\n      context \"when latest version is available for provider\" do\n        let(:box) do\n          box_dir = test_iso_env.box3(\"foo\", \"1.0\", :vmware)\n          box = Vagrant::Box.new(\n            \"foo\", :vmware, \"1.0\", box_dir, metadata_url: \"foo\")\n          allow(box).to receive(:load_metadata).and_return(md)\n          box\n        end\n\n        it \"displays the latest version\" do\n          allow(iso_env).to receive(:boxes).and_return(collection)\n\n          expect(I18n).to receive(:t).with(/box_outdated$/, hash_including(latest: \"1.2\"))\n\n          subject.outdated_global({})\n        end\n      end\n\n      context \"when latest version isn't available for provider\" do\n        let(:box) do\n          box_dir = test_iso_env.box3(\"foo\", \"1.0\", :virtualbox)\n          box = Vagrant::Box.new(\n            \"foo\", :virtualbox, \"1.0\", box_dir, metadata_url: \"foo\")\n          allow(box).to receive(:load_metadata).and_return(md)\n          box\n        end\n\n        it \"displays the latest version for that provider\" do\n          allow(iso_env).to receive(:boxes).and_return(collection)\n\n          expect(I18n).to receive(:t).with(/box_outdated$/, hash_including(latest: \"1.1\"))\n\n          subject.outdated_global({})\n        end\n      end\n\n      context \"when no versions are available for provider\" do\n        let(:box) do\n          box_dir = test_iso_env.box3(\"foo\", \"1.0\", :libvirt)\n          box = Vagrant::Box.new(\n            \"foo\", :libvirt, \"1.0\", box_dir, metadata_url: \"foo\")\n          allow(box).to receive(:load_metadata).and_return(md)\n          box\n        end\n\n        it \"displays up to date message\" do\n          allow(iso_env).to receive(:boxes).and_return(collection)\n\n          expect(I18n).to receive(:t).with(/box_up_to_date$/, hash_including(version: \"1.0\"))\n\n          subject.outdated_global({})\n        end\n      end\n\n      context \"with architectures\" do\n        let(:md) {\n          md = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW))\n        {\n          \"name\": \"foo\",\n          \"versions\": [\n            {\n              \"version\": \"1.0\",\n              \"providers\": [\n                {\n                  \"name\": \"vmware\",\n                  \"architecture\": \"amd64\",\n                  \"url\": \"bar\"\n                },\n                {\n                  \"name\": \"virtualbox\",\n                  \"architecture\": \"unknown\",\n                  \"url\": \"foo\"\n                }\n              ]\n            },\n            {\n              \"version\": \"1.1\",\n              \"providers\": [\n                {\n                  \"name\": \"vmware\",\n                  \"architecture\": \"amd64\",\n                  \"url\": \"bar\"\n                },\n                {\n                  \"name\": \"virtualbox\",\n                  \"architecture\": \"unknown\",\n                  \"url\": \"foo\"\n                },\n                {\n                  \"name\": \"docker\",\n                  \"architecture\": \"amd64\",\n                  \"url\": \"foo\"\n                }\n              ]\n            },\n            {\n              \"version\": \"1.2\",\n              \"providers\": [\n                {\n                  \"name\": \"vmware\",\n                  \"architecture\": \"arm64\",\n                  \"url\": \"baz\"\n                },\n                {\n                  \"name\": \"virtualbox\",\n                  \"architecture\": \"unknown\",\n                  \"url\": \"bat\"\n                },\n                {\n                  \"name\": \"docker\",\n                  \"architecture\": \"unknown\",\n                  \"url\": \"foo\"\n                }\n              ]\n            }\n          ]\n        }\n          RAW\n        }\n\n\n        context \"when latest version is available for provider with unknown architecture\" do\n          let(:box) do\n            box_dir = test_iso_env.box3(\"foo\", \"1.0\", :virtualbox)\n            box = Vagrant::Box.new(\n              \"foo\", :virtualbox, \"1.0\", box_dir, metadata_url: \"foo\")\n            allow(box).to receive(:load_metadata).and_return(md)\n            box\n          end\n\n          it \"displays the latest version\" do\n            allow(iso_env).to receive(:boxes).and_return(collection)\n\n            expect(I18n).to receive(:t).with(/box_outdated$/, hash_including(latest: \"1.2\"))\n\n            subject.outdated_global({})\n          end\n        end\n\n\n        context \"when latest version isn't available for provider with explicit architecture\" do\n          let(:box) do\n            box_dir = test_iso_env.box3(\"foo\", \"1.0\", :vmware, architecture: \"amd64\")\n            box = Vagrant::Box.new(\n              \"foo\", :vmware, \"1.0\", box_dir, metadata_url: \"foo\", architecture: \"amd64\")\n            allow(box).to receive(:load_metadata).and_return(md)\n            box\n          end\n\n          it \"displays the latest version\" do\n            allow(iso_env).to receive(:boxes).and_return(collection)\n\n            expect(I18n).to receive(:t).with(/box_outdated$/, hash_including(latest: \"1.1\"))\n\n            subject.outdated_global({})\n          end\n        end\n\n        context \"when no versions are available provider with explicit architecture\" do\n          let(:box) do\n            box_dir = test_iso_env.box3(\"foo\", \"1.1\", :vmware)\n            box = Vagrant::Box.new(\n              \"foo\", :vmware, \"1.1\", box_dir, metadata_url: \"foo\", architecture: \"amd64\")\n            allow(box).to receive(:load_metadata).and_return(md)\n            box\n          end\n\n          it \"displays up to date message\" do\n            allow(iso_env).to receive(:boxes).and_return(collection)\n\n            expect(I18n).to receive(:t).with(/box_up_to_date$/, hash_including(version: \"1.1\"))\n\n            subject.outdated_global({})\n          end\n        end\n\n        context \"when newer version does not have an explicit architecture\" do\n          let(:box) do\n            box_dir = test_iso_env.box3(\"foo\", \"1.1\", :docker)\n            box = Vagrant::Box.new(\n              \"foo\", :docker, \"1.1\", box_dir, metadata_url: \"foo\", architecture: :auto)\n            allow(box).to receive(:load_metadata).and_return(md)\n            box\n          end\n\n          it \"displays up to date message\" do\n            allow(iso_env).to receive(:boxes).and_return(collection)\n\n            expect(I18n).to receive(:t).with(/box_up_to_date$/, hash_including(version: \"1.1\"))\n\n            subject.outdated_global({})\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/box/command/prune_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/box/command/prune\")\n\ndescribe VagrantPlugins::CommandBox::Command::Prune do\n  include_context \"unit\"\n  include_context \"command plugin helpers\"\n\n  let(:entry_klass) { Vagrant::MachineIndex::Entry }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    isolated_environment.tap do |env|\n      env.vagrantfile(\"\")\n    end\n  end\n\n  let(:iso_vagrant_env) { iso_env.create_vagrant_env }\n\n  let(:argv) { [] }\n\n  # Seems this way of providing a box version triggers box in use.\n  def new_entry(name, box_name, box_provider, version)\n    entry_klass.new.tap do |e|\n      e.name = name\n      e.vagrantfile_path = \"/bar\"\n      e.extra_data[\"box\"] = {\n          \"name\" => box_name,\n          \"provider\" => box_provider,\n          \"version\" => version,\n      }\n    end\n  end\n\n  subject { described_class.new(argv, iso_vagrant_env) }\n\n  describe \"execute\" do\n    context \"with no args\" do\n      it \"removes the old version and keeps the current one\" do\n\n        # Let's put some things in the index\n        iso_env.box3(\"foobox\", \"1.0\", :virtualbox);\n        iso_env.box3(\"foobox\", \"1.1\", :virtualbox);\n        iso_env.box3(\"barbox\", \"1.0\", :vmware);\n        iso_env.box3(\"barbox\", \"1.1\", :vmware);\n\n        iso_vagrant_env.machine_index.set(new_entry(\"foo\", \"foobox\", \"virtualbox\", 1))\n\n        output = \"\"\n        allow(iso_vagrant_env.ui).to receive(:info) do |data|\n          output << data\n        end\n        expect(iso_vagrant_env.boxes.all.count).to eq(4)\n        expect(subject.execute).to eq(0)\n        expect(iso_vagrant_env.boxes.all.count).to eq(2)\n\n        expect(output).to include(\"barbox (vmware, 1.1)\")\n        expect(output).to include(\"Removing box 'barbox' (v1.0) with provider 'vmware'...\")\n        expect(output).to include(\"foobox (virtualbox, 1.1)\")\n        expect(output).to include(\"Removing box 'foobox' (v1.0) with provider 'virtualbox'...\")\n      end\n\n      it \"removes nothing\" do\n        # Let's put some things in the index\n        iso_env.box3(\"foobox\", \"1.0\", :virtualbox);\n        iso_env.box3(\"barbox\", \"1.0\", :vmware);\n\n        iso_vagrant_env.machine_index.set(new_entry(\"foo\", \"foobox\", \"virtualbox\", 1))\n\n        allow(iso_vagrant_env.ui).to receive(:info).and_call_original\n        expect(iso_vagrant_env.ui).to receive(:info).with(/No old versions of boxes/).\n          and_call_original\n        expect(iso_vagrant_env.boxes.all.count).to eq(2)\n        expect(subject.execute).to eq(0)\n        expect(iso_vagrant_env.boxes.all.count).to eq(2)\n      end\n    end\n\n    context \"with --provider\" do\n      let(:argv) { [\"--provider\", \"virtualbox\"] }\n\n      it \"removes the old versions of the specified provider\" do\n\n        # Let's put some things in the index\n        iso_env.box3(\"foobox\", \"1.0\", :virtualbox);\n        iso_env.box3(\"foobox\", \"1.1\", :virtualbox);\n        iso_env.box3(\"barbox\", \"1.0\", :vmware);\n        iso_env.box3(\"barbox\", \"1.1\", :vmware);\n\n        iso_vagrant_env.machine_index.set(new_entry(\"foo\", \"foobox\", \"virtualbox\", 1))\n\n        output = \"\"\n        allow(iso_vagrant_env.ui).to receive(:info) do |data|\n          output << \"\\n\" + data\n        end\n\n        expect(iso_vagrant_env.boxes.all.count).to eq(4)\n        expect(subject.execute).to eq(0)\n        expect(iso_vagrant_env.boxes.all.count).to eq(3)\n\n        expect(output).to include(\"foobox (virtualbox, 1.1)\")\n        expect(output).to include(\"Removing box 'foobox' (v1.0) with provider 'virtualbox'...\")\n\n      end\n    end\n\n    context \"with --dry-run\" do\n      let(:argv) { [\"--dry-run\"] }\n\n      it \"removes the old versions of the specified provider\" do\n\n        # Let's put some things in the index\n        iso_env.box3(\"foobox\", \"1.0\", :virtualbox);\n        iso_env.box3(\"foobox\", \"1.1\", :virtualbox);\n\n        iso_vagrant_env.machine_index.set(new_entry(\"foo\", \"foobox\", \"virtualbox\", 1))\n\n        output = \"\"\n        allow(iso_vagrant_env.ui).to receive(:info) do |data|\n          output << \"\\n\" + data\n        end\n\n        expect(iso_vagrant_env.boxes.all.count).to eq(2)\n        expect(subject.execute).to eq(0)\n        expect(iso_vagrant_env.boxes.all.count).to eq(2)\n\n\n        expect(output).to include(\"foobox (virtualbox, 1.1)\")\n        expect(output).to include(\"Would remove foobox virtualbox 1.0\")\n\n\n      end\n    end\n\n    context \"with --name\" do\n      let(:argv) { [\"--name\", \"barbox\"] }\n\n      it \"removes the old versions of the specified provider\" do\n\n        # Let's put some things in the index\n        iso_env.box3(\"foobox\", \"1.0\", :virtualbox);\n        iso_env.box3(\"foobox\", \"1.1\", :virtualbox);\n        iso_env.box3(\"barbox\", \"1.0\", :vmware);\n        iso_env.box3(\"barbox\", \"1.1\", :vmware);\n\n        iso_vagrant_env.machine_index.set(new_entry(\"foo\", \"foobox\", \"virtualbox\", 1))\n\n        output = \"\"\n        allow(iso_vagrant_env.ui).to receive(:info) do |data|\n          output << \"\\n\" + data\n        end\n\n        expect(iso_vagrant_env.boxes.all.count).to eq(4)\n        expect(subject.execute).to eq(0)\n        expect(iso_vagrant_env.boxes.all.count).to eq(3)\n\n        expect(output).to include(\"barbox (vmware, 1.1)\")\n        expect(output).to include(\"Removing box 'barbox' (v1.0) with provider 'vmware'...\")\n      end\n    end\n\n\n    context \"with --name and --provider\" do\n      let(:argv) { [\"--name\", \"foobox\", \"--provider\", \"virtualbox\"] }\n\n      it \"removed the old versions of that name and provider only\" do\n        # Let's put some things in the index\n        iso_env.box3(\"foobox\", \"1.0\", :virtualbox);\n        iso_env.box3(\"foobox\", \"1.1\", :virtualbox);\n        iso_env.box3(\"foobox\", \"1.0\", :vmware);\n        iso_env.box3(\"foobox\", \"1.1\", :vmware);\n        iso_env.box3(\"barbox\", \"1.0\", :vmware);\n        iso_env.box3(\"barbox\", \"1.1\", :vmware);\n\n        iso_vagrant_env.machine_index.set(new_entry(\"foo\", \"foobox\", \"virtualbox\", 1))\n\n        output = \"\"\n        allow(iso_vagrant_env.ui).to receive(:info) do |data|\n          output << \"\\n\" + data\n        end\n\n        expect(iso_vagrant_env.boxes.all.count).to eq(6)\n        expect(subject.execute).to eq(0)\n        expect(iso_vagrant_env.boxes.all.count).to eq(5)\n\n        expect(output).to include(\"Removing box 'foobox' (v1.0) with provider 'virtualbox'...\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/box/command/remove_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/box/command/remove\")\n\ndescribe VagrantPlugins::CommandBox::Command::Remove do\n  include_context \"unit\"\n\n  let(:argv)     { [] }\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  subject { described_class.new(argv, iso_env) }\n\n  let(:action_runner) { double(\"action_runner\") }\n\n  before do\n    allow(iso_env).to receive(:action_runner).and_return(action_runner)\n  end\n\n  context \"with no arguments\" do\n    it \"shows help\" do\n      expect { subject.execute }.\n        to raise_error(Vagrant::Errors::CLIInvalidUsage)\n    end\n  end\n\n  context \"with one argument\" do\n    let(:argv) { [\"foo\"] }\n\n    it \"invokes the action runner\" do\n      expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n        expect(opts[:box_name]).to eq(\"foo\")\n        expect(opts[:force_confirm_box_remove]).to be(false)\n        true\n      }\n\n      subject.execute\n    end\n\n    context \"with --force\" do\n      let(:argv) { super() + [\"--force\"] }\n\n      it \"invokes the action runner with force option\" do\n        expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n          expect(opts[:box_name]).to eq(\"foo\")\n          expect(opts[:force_confirm_box_remove]).to be(true)\n          true\n        }\n\n        subject.execute\n      end\n    end\n  end\n\n  context \"with two arguments\" do\n    let(:argv) { [\"foo\", \"bar\"] }\n\n    it \"uses the 2nd arg as a provider\" do\n      expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n        expect(opts[:box_name]).to eq(\"foo\")\n        expect(opts[:box_provider]).to eq(\"bar\")\n        true\n      }\n\n      subject.execute\n    end\n  end\n\n  context \"with more than two arguments\" do\n    let(:argv) { [\"one\", \"two\", \"three\"] }\n\n    it \"shows help\" do\n      expect { subject.execute }.\n        to raise_error(Vagrant::Errors::CLIInvalidUsage)\n    end\n  end\n\n  context \"with architecture flag\" do\n    let(:argv) { [\"foo\", \"--architecture\", \"test-arch\"] }\n\n    it \"should execute runner with box architecture set\" do\n      expect(action_runner).to receive(:run) do |_, opts|\n        expect(opts[:box_architecture]).to eq(\"test-arch\")\n      end\n\n      subject.execute\n    end\n  end\n\n  context \"with all providers flag\" do\n    let(:argv) { [\"foo\", \"--all-providers\"] }\n\n    it \"should execute runner with all providers enabled\" do\n      expect(action_runner).to receive(:run) do |_, opts|\n        expect(opts[:box_remove_all_providers]).to be(true)\n      end\n\n      subject.execute\n    end\n  end\n\n  context \"with all architectures flag\" do\n    let(:argv) { [\"foo\", \"--all-architectures\"] }\n\n    it \"should execute runner with all architectures enabled\" do\n      expect(action_runner).to receive(:run) do |_, opts|\n        expect(opts[:box_remove_all_architectures]).to be(true)\n      end\n\n      subject.execute\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/box/command/repackage_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/box/command/repackage\")\n\ndescribe VagrantPlugins::CommandBox::Command::Repackage do\n  include_context \"unit\"\n\n  let(:argv)     { [] }\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  subject { described_class.new(argv, iso_env) }\n\n  let(:action_runner) { double(\"action_runner\") }\n\n  before do\n    allow(iso_env).to receive(:action_runner).and_return(action_runner)\n  end\n\n  context \"with no arguments\" do\n    it \"shows help\" do\n      expect { subject.execute }.\n        to raise_error(Vagrant::Errors::CLIInvalidUsage)\n    end\n  end\n\n  context \"with one argument\" do\n    let(:argv) { [\"one\"] }\n\n    it \"shows help\" do\n      expect { subject.execute }.\n        to raise_error(Vagrant::Errors::CLIInvalidUsage)\n    end\n  end\n\n  context \"with two arguments\" do\n    let(:argv) { [\"one\", \"two\"] }\n\n    it \"shows help\" do\n      expect { subject.execute }.\n        to raise_error(Vagrant::Errors::CLIInvalidUsage)\n    end\n  end\n\n  context \"with three arguments\" do\n    it \"repackages the box with the given provider\"\n  end\n\n  context \"with more than three arguments\" do\n    let(:argv) { [\"one\", \"two\", \"three\", \"four\"] }\n\n    it \"shows help\" do\n      expect { subject.execute }.\n        to raise_error(Vagrant::Errors::CLIInvalidUsage)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/box/command/update_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tmpdir\"\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/box/command/update\")\n\ndescribe VagrantPlugins::CommandBox::Command::Update do\n  include_context \"unit\"\n\n  let(:argv)     { [] }\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    test_iso_env.vagrantfile(\"\")\n    test_iso_env.create_vagrant_env\n  end\n  let(:test_iso_env) { isolated_environment }\n\n  let(:action_runner) { double(\"action_runner\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:download_options) { [\"--insecure\",\n                            \"--cacert\", \"foo\",\n                            \"--capath\", \"bar\",\n                            \"--cert\", \"baz\"] }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(iso_env).to receive(:action_runner).and_return(action_runner)\n    machine.config.vm.box = \"foo\"\n  end\n\n  describe \"execute\" do\n    context \"updating specific box\" do\n      let(:argv) { [\"--box\", \"foo\"] }\n      let(:scratch) { Dir.mktmpdir(\"vagrant-test-command-box-update-execute\") }\n      let(:metadata_url) { Pathname.new(scratch).join(\"metadata.json\") }\n      let(:box_args) { [\"foo\", \"1.0\", :virtualbox] }\n      let(:box_opts) { {metadata_url: metadata_url.to_s} }\n      before do\n        metadata_url.open(\"w\") do |f|\n          f.write(\"\")\n        end\n\n        test_iso_env.box3(*box_args, **box_opts)\n      end\n\n      after do\n        FileUtils.rm_rf(scratch)\n      end\n\n      it \"doesn't update if they're up to date\" do\n        called = false\n        allow(action_runner).to receive(:run) do |callable, opts|\n          if opts[:box_provider]\n            called = true\n          end\n\n          opts\n        end\n\n        subject.execute\n\n        expect(called).to be(false)\n      end\n\n      it \"does the correct update if there is an update\" do\n        metadata_url.open(\"w\") do |f|\n          f.write(\n           {\n             name: \"foo\",\n             versions: [\n              {\n                version: \"1.0\"\n              },\n              {\n                version: \"1.8\",\n                providers: [\n                 {\n                   name: \"virtualbox\",\n                   url: \"bar\"\n                 }\n                ]\n              },\n              {\n                version: \"1.10\",\n                providers: [\n                 {\n                   name: \"virtualbox\",\n                   url: \"bar\"\n                 }\n                ]\n              },\n              {\n                version: \"1.11\",\n                providers: [\n                 {\n                   name: \"virtualbox\",\n                   url: \"bar\"\n                 }\n                ]\n              }\n             ]\n           }.to_json\n          )\n        end\n\n        action_called = false\n        allow(action_runner).to receive(:run) do |action, opts|\n          if opts[:box_provider]\n            action_called = true\n            expect(opts[:box_force]).to eq(nil)\n            expect(opts[:box_url]).to eq(metadata_url.to_s)\n            expect(opts[:box_provider]).to eq(\"virtualbox\")\n            expect(opts[:box_version]).to eq(\"1.11\")\n            expect(opts[:box_download_ca_path]).to be_nil\n            expect(opts[:box_download_ca_cert]).to be_nil\n            expect(opts[:box_download_client_cert]).to be_nil\n            expect(opts[:box_download_insecure]).to be_nil\n          end\n\n          opts\n        end\n\n        subject.execute\n\n        expect(action_called).to be(true)\n      end\n\n      it \"raises an error if there are multiple providers\" do\n        test_iso_env.box3(\"foo\", \"1.0\", :vmware)\n\n        expect(action_runner).to receive(:run).never\n\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::BoxUpdateMultiProvider)\n      end\n\n      context \"with multiple providers and specifying the provider\" do\n        let(:argv) { [\"--box\", \"foo\", \"--provider\", \"vmware\"] }\n\n        it \"updates the proper box\" do\n          metadata_url.open(\"w\") do |f|\n            f.write(\n             {\n               name: \"foo\",\n               versions: [\n                {\n                  version: \"1.0\"\n                },\n                {\n                  version: \"1.1\",\n                  providers: [\n                   {\n                     name: \"vmware\",\n                     url: \"bar\"\n                   }\n                  ]\n                }\n               ]\n             }.to_json\n            )\n          end\n\n          test_iso_env.box3(\"foo\", \"1.0\", :vmware)\n\n          action_called = false\n          allow(action_runner).to receive(:run) do |action, opts|\n            if opts[:box_provider]\n              action_called = true\n              expect(opts[:box_url]).to eq(metadata_url.to_s)\n              expect(opts[:box_provider]).to eq(\"vmware\")\n              expect(opts[:box_version]).to eq(\"1.1\")\n            end\n\n            opts\n          end\n\n          subject.execute\n\n          expect(action_called).to be(true)\n        end\n\n        it \"raises an error if that provider doesn't exist\" do\n          expect(action_runner).to receive(:run).never\n\n          expect { subject.execute }.\n            to raise_error(Vagrant::Errors::BoxNotFoundWithProvider)\n        end\n      end\n\n      context \"download options are specified\" do\n        let(:argv) { [\"--box\", \"foo\" ].concat(download_options) }\n\n        it \"passes down download options\" do\n          metadata_url.open(\"w\") do |f|\n            f.write(\n             {\n               name: \"foo\",\n               versions: [\n                {\n                  version: \"1.0\"\n                },\n                {\n                  version: \"1.1\",\n                  providers: [\n                   {\n                     name: \"virtualbox\",\n                     url: \"bar\"\n                   }\n                  ]\n                }\n               ]\n             }.to_json\n            )\n          end\n\n          action_called = false\n          allow(action_runner).to receive(:run) do |action, opts|\n            if opts[:box_provider]\n              action_called = true\n              expect(opts[:box_download_ca_cert]).to eq(\"foo\")\n              expect(opts[:box_download_ca_path]).to eq(\"bar\")\n              expect(opts[:box_download_client_cert]).to eq(\"baz\")\n              expect(opts[:box_download_insecure]).to be(true)\n            end\n\n            opts\n          end\n\n          subject.execute\n          expect(action_called).to be(true)\n        end\n      end\n\n      context \"with a box that doesn't exist\" do\n        let(:argv) { [\"--box\", \"nope\"] }\n\n        it \"raises an exception\" do\n          expect(action_runner).to receive(:run).never\n\n          expect { subject.execute }.\n            to raise_error(Vagrant::Errors::BoxNotFound)\n        end\n      end\n\n      context \"with architecture\" do\n        let(:box_opts) { {metadata_url: metadata_url.to_s, architecture: \"test-arch\"} }\n\n        it \"doesn't update if they're up to date\" do\n          called = false\n          allow(action_runner).to receive(:run) do |callable, opts|\n            if opts[:box_provider]\n              called = true\n            end\n\n            opts\n          end\n\n          subject.execute\n\n          expect(called).to be(false)\n        end\n\n        it \"does the correct update if there is an update\" do\n          metadata_url.open(\"w\") do |f|\n            f.write(\n             {\n               name: \"foo\",\n               versions: [\n                {\n                  version: \"1.0\"\n                },\n                {\n                  version: \"1.8\",\n                  providers: [\n                   {\n                     name: \"virtualbox\",\n                     url: \"bar\",\n                     architecture: \"test-arch\",\n                     default_architecture: true\n                   }\n                  ]\n                },\n                {\n                  version: \"1.10\",\n                  providers: [\n                   {\n                     name: \"virtualbox\",\n                     url: \"bar\",\n                     architecture: \"test-arch\",\n                     default_architecture: true\n                   }\n                  ]\n                },\n                {\n                  version: \"1.11\",\n                  providers: [\n                   {\n                     name: \"virtualbox\",\n                     url: \"bar\",\n                     architecture: \"test-arch\",\n                     default_architecture: true\n                   }\n                  ]\n                }\n               ]\n             }.to_json\n            )\n          end\n\n          action_called = false\n          allow(action_runner).to receive(:run) do |action, opts|\n            if opts[:box_provider]\n              action_called = true\n              expect(opts[:box_force]).to eq(nil)\n              expect(opts[:box_url]).to eq(metadata_url.to_s)\n              expect(opts[:box_provider]).to eq(\"virtualbox\")\n              expect(opts[:box_version]).to eq(\"1.11\")\n              expect(opts[:box_architecture]).to eq(\"test-arch\")\n              expect(opts[:box_download_ca_path]).to be_nil\n              expect(opts[:box_download_ca_cert]).to be_nil\n              expect(opts[:box_download_client_cert]).to be_nil\n              expect(opts[:box_download_insecure]).to be_nil\n            end\n\n            opts\n          end\n\n          subject.execute\n\n          expect(action_called).to be(true)\n        end\n\n        it \"raises an error if there are multiple providers\" do\n          test_iso_env.box3(\"foo\", \"1.0\", :vmware)\n\n          expect(action_runner).to receive(:run).never\n\n          expect { subject.execute }.\n            to raise_error(Vagrant::Errors::BoxUpdateMultiProvider)\n        end\n\n        it \"raises an error if there are multiple architectures\" do\n          test_iso_env.box3(\"foo\", \"1.0\", :virtualbox, architecture: \"other-arch\")\n\n          expect(action_runner).to receive(:run).never\n\n          expect { subject.execute }.\n            to raise_error(Vagrant::Errors::BoxUpdateMultiArchitecture)\n        end\n\n        context \"with multiple architectures and specifying the architecture\" do\n          let(:argv) { [\"--box\", \"foo\", \"--architecture\", \"other-arch\"] }\n\n          it \"updates the proper box\" do\n            metadata_url.open(\"w\") do |f|\n              f.write(\n                {\n                  name: \"foo\",\n                  versions: [\n                   {\n                     version: \"1.0\"\n                   },\n                   {\n                     version: \"1.1\",\n                     providers: [\n                      {\n                        name: \"virtualbox\",\n                        url: \"bar\",\n                        architecture: \"test-arch\",\n                      },\n                      {\n                        name: \"virtualbox\",\n                        url: \"bar\",\n                        architecture: \"other-arch\",\n                      }\n                     ]\n                   }\n                  ]\n                }.to_json\n               )\n            end\n\n            test_iso_env.box3(\"foo\", \"1.0\", :virtualbox, architecture: \"other-arch\")\n\n            action_called = false\n            allow(action_runner).to receive(:run) do |action, opts|\n              if opts[:box_provider]\n                action_called = true\n                expect(opts[:box_url]).to eq(metadata_url.to_s)\n                expect(opts[:box_provider]).to eq(\"virtualbox\")\n                expect(opts[:box_version]).to eq(\"1.1\")\n                expect(opts[:box_architecture]).to eq(\"other-arch\")\n              end\n\n              opts\n            end\n\n            subject.execute\n\n            expect(action_called).to be(true)\n          end\n\n          it \"raises an error if that provider doesn't exist\" do\n            expect(action_runner).to receive(:run).never\n\n            expect { subject.execute }.\n              to raise_error(Vagrant::Errors::BoxNotFoundWithProviderArchitecture)\n          end\n        end\n\n      end\n    end\n\n    context \"updating environment machines\" do\n      before do\n        allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n      end\n\n      let(:box) do\n        box_dir = test_iso_env.box3(\"foo\", \"1.0\", :virtualbox)\n        box = Vagrant::Box.new(\n          \"foo\", :virtualbox, \"1.0\", box_dir, metadata_url: \"foo\")\n        allow(box).to receive(:has_update?).and_return(nil)\n        box\n      end\n\n      it \"ignores machines without boxes\" do\n        expect(action_runner).to receive(:run).never\n\n        subject.execute\n      end\n\n      it \"doesn't update boxes if they're up-to-date\" do\n        allow(machine).to receive(:box).and_return(box)\n        expect(box).to receive(:has_update?).\n          with(machine.config.vm.box_version,\n               {download_options:\n                 {ca_cert: nil, ca_path: nil, client_cert: nil,\n                  insecure: false}}).\n          and_return(nil)\n\n        expect(action_runner).to receive(:run).never\n\n        subject.execute\n      end\n\n      context \"boxes have an update\" do\n        let(:md) {\n          Vagrant::BoxMetadata.new(\n           StringIO.new(\n            {\n              name: \"foo\",\n              versions: [\n               {\n                 version: \"1.0\"\n               },\n               {\n                 version: \"1.1\",\n                 providers: [\n                  {\n                    name: \"virtualbox\",\n                    url: \"bar\"\n                  }\n                 ]\n               }\n              ]\n            }.to_json\n           )\n          )\n        }\n\n        before { allow(machine).to receive(:box).and_return(box) }\n\n        it \"updates boxes\" do\n          expect(box).to receive(:has_update?).\n            with(machine.config.vm.box_version,\n                 {download_options:\n                   {ca_cert: nil, ca_path: nil, client_cert: nil,\n                    insecure: false}}).\n            and_return([md, md.version(\"1.1\"), md.version(\"1.1\").provider(\"virtualbox\")])\n\n          expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n            expect(opts[:box_url]).to eq(box.metadata_url)\n            expect(opts[:box_provider]).to eq(\"virtualbox\")\n            expect(opts[:box_version]).to eq(\"1.1\")\n            expect(opts[:ui]).to equal(machine.ui)\n            true\n          }\n\n          subject.execute\n        end\n\n        context \"when box version is updated but previous box exists\" do\n\n          let(:collection) { double(\"collection\") }\n\n          it \"updates the box\" do\n            # First call gets nil result to for lookup\n            expect(machine).to receive(:box).and_return(nil)\n            expect(Vagrant::BoxCollection).to receive(:new).and_return(collection)\n            expect(collection).to receive(:find).and_return(box)\n\n            expect(box).to receive(:has_update?).\n              with(machine.config.vm.box_version,\n              {download_options:\n                {ca_cert: nil, ca_path: nil, client_cert: nil,\n                  insecure: false}}).\n              and_return([md, md.version(\"1.1\"), md.version(\"1.1\").provider(\"virtualbox\")])\n\n            expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n              expect(opts[:box_url]).to eq(box.metadata_url)\n              expect(opts[:box_provider]).to eq(\"virtualbox\")\n              expect(opts[:box_version]).to eq(\"1.1\")\n              expect(opts[:ui]).to equal(machine.ui)\n              true\n            }\n\n            subject.execute\n          end\n        end\n\n        context \"machine has download options\" do\n          before do\n            machine.config.vm.box_download_ca_cert = \"oof\"\n            machine.config.vm.box_download_ca_path = \"rab\"\n            machine.config.vm.box_download_client_cert = \"zab\"\n            machine.config.vm.box_download_insecure = false\n          end\n\n          it \"uses download options from machine\" do\n            expect(box).to receive(:has_update?).\n              with(machine.config.vm.box_version,\n                   {download_options:\n                     {ca_cert: \"oof\", ca_path: \"rab\", client_cert: \"zab\",\n                      insecure: false}}).\n              and_return([md, md.version(\"1.1\"), md.version(\"1.1\").provider(\"virtualbox\")])\n\n            expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n              expect(opts[:box_download_ca_cert]).to eq(\"oof\")\n              expect(opts[:box_download_ca_path]).to eq(\"rab\")\n              expect(opts[:box_download_client_cert]).to eq(\"zab\")\n              expect(opts[:box_download_insecure]).to be(false)\n              true\n            }\n\n            subject.execute\n          end\n\n          context \"download options are specified on the command line\" do\n            let(:argv) { download_options }\n\n            it \"overrides download options from machine with options from CLI\" do\n              expect(box).to receive(:has_update?).\n                with(machine.config.vm.box_version,\n                     {download_options:\n                       {ca_cert: \"foo\", ca_path: \"bar\", client_cert: \"baz\",\n                        insecure: true}}).\n                and_return([md, md.version(\"1.1\"),\n                            md.version(\"1.1\").provider(\"virtualbox\")])\n\n              expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n                expect(opts[:box_download_ca_cert]).to eq(\"foo\")\n                expect(opts[:box_download_ca_path]).to eq(\"bar\")\n                expect(opts[:box_download_client_cert]).to eq(\"baz\")\n                expect(opts[:box_download_insecure]).to be(true)\n                true\n              }\n\n              subject.execute\n            end\n          end\n\n          context \"ignoring boxes with no metadata\" do\n            before do\n              allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n            end\n\n            let(:box) do\n              box_dir = test_iso_env.box3(\"foo\", \"1.0\", :virtualbox)\n              box = Vagrant::Box.new(\n                \"foo\", :virtualbox, \"1.0\", box_dir, metadata_url: \"foo\")\n              allow(box).to receive(:has_update?).and_raise(Vagrant::Errors::BoxUpdateNoMetadata, name: \"foo\")\n              box\n            end\n\n            it \"continues to update the rest of the boxes in the environment\" do\n              subject.execute\n            end\n          end\n\n          context \"force flag is specified on the command line\" do\n            let(:argv) { [\"--force\"].concat(download_options) }\n\n            it \"passes force through to action_box_add as true\" do\n              expect(box).to receive(:has_update?).\n                with(machine.config.vm.box_version,\n                     {download_options:\n                       {ca_cert: \"foo\", ca_path: \"bar\", client_cert: \"baz\",\n                        insecure: true}}).\n                and_return([md, md.version(\"1.1\"),\n                            md.version(\"1.1\").provider(\"virtualbox\")])\n\n              expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n                expect(opts[:box_force]).to be(true)\n                true\n              }\n\n              subject.execute\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cap/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/cap/command\")\n\ndescribe VagrantPlugins::CommandCap::Command do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:argv)     { [] }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n  end\n\n  describe \"execute\" do\n    context \"--check provider foo (exists)\" do\n      let(:argv) { [\"--check\", \"provider\", \"foo\"] }\n      let(:cap) { Class.new }\n\n      before do\n        register_plugin do |p|\n          p.provider_capability(:dummy, :foo) { cap }\n        end\n      end\n\n      it \"exits with 0 if it exists\" do\n        expect(subject.execute).to eq(0)\n      end\n    end\n\n    context \"--check provider foo (doesn't exists)\" do\n      let(:argv) { [\"--check\", \"provider\", \"foo\"] }\n\n      it \"exits with 1\" do\n        expect(subject.execute).to eq(1)\n      end\n    end\n\n    context \"runs against target vm\" do\n      let(:argv) { [\"provider\", \"foo\", \"--target-guest=dummy\"] }\n      let(:cap) {\n        Class.new do\n          def self.foo(m)\n            true\n          end\n        end\n      }\n\n      before do\n        register_plugin do |p|\n          p.provider_capability(:dummy, :foo) { cap }\n        end\n      end\n\n      it \"exits with 0 if it exists\" do\n        expect(subject.execute).to eq(0)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/auth/login_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/auth/login\")\n\ndescribe VagrantPlugins::CloudCommand::AuthCommand::Command::Login do\n  include_context \"unit\"\n\n  let(:argv) { [] }\n  let(:env)  { isolated_environment.create_vagrant_env }\n  let(:action_runner) { double(\"action_runner\") }\n  let(:client) { double(\"client\", logged_in?: logged_in) }\n  let(:logged_in) { true }\n\n  before do\n    allow(env).to receive(:action_runner).\n      and_return(action_runner)\n    allow(VagrantPlugins::CloudCommand::Client).\n      to receive(:new).and_return(client)\n  end\n\n  subject { described_class.new(argv, env) }\n\n  describe \"#execute_check\" do\n    context \"when user is logged in\" do\n      let(:logged_in) { true }\n\n      it \"should output a success message\" do\n        expect(env.ui).to receive(:success)\n        subject.execute_check(client)\n      end\n\n      it \"should return zero value\" do\n        expect(subject.execute_check(client)).to eq(0)\n      end\n    end\n\n    context \"when user is not logged in\" do\n      let(:logged_in) { false }\n\n      it \"should output an error message\" do\n        expect(env.ui).to receive(:error)\n        subject.execute_check(client)\n      end\n\n      it \"should return a non-zero value\" do\n        r = subject.execute_check(client)\n        expect(r).not_to eq(0)\n        expect(r).to be_a(Integer)\n      end\n    end\n  end\n\n  describe \"#execute_token\" do\n    let(:token) { double(\"token\") }\n\n    before { allow(client).to receive(:store_token) }\n\n    it \"should store the token\" do\n      expect(client).to receive(:store_token).with(token)\n      subject.execute_token(client, token)\n    end\n\n    context \"when token is valid\" do\n      let(:logged_in) { true }\n\n      it \"should output a success message\" do\n        expect(env.ui).to receive(:success).twice\n        subject.execute_token(client, token)\n      end\n\n      it \"should return a zero value\" do\n        expect(subject.execute_token(client, token)).to eq(0)\n      end\n    end\n\n    context \"when token is invalid\" do\n      let(:logged_in) { false }\n\n      it \"should output an error message\" do\n        expect(env.ui).to receive(:error)\n        subject.execute_token(client, token)\n      end\n\n      it \"should return a non-zero value\" do\n        r = subject.execute_token(client, token)\n        expect(r).not_to eq(0)\n        expect(r).to be_a(Integer)\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    before do\n      allow(client).to receive(:username_or_email=)\n      allow(client).to receive(:store_token)\n    end\n\n    context \"when arguments are passed\" do\n      before { argv << \"argument\" }\n\n      it \"should print help\" do\n        expect { subject.execute }.to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"when --check flag is used\" do\n      before { argv << \"--check\" }\n\n      it \"should run login check\" do\n        expect(subject).to receive(:execute_check).with(client)\n        subject.execute\n      end\n\n      it \"should return the value of the check execution\" do\n        result = double(\"result\")\n        expect(subject).to receive(:execute_check).with(client).and_return(result)\n        expect(subject.execute).to eq(result)\n      end\n    end\n\n    context \"when --token flag is used\" do\n      let(:new_token) { \"NEW-TOKEN\" }\n\n      before { argv.push(\"--token\").push(new_token) }\n\n      it \"should execute the token action\" do\n        expect(subject).to receive(:execute_token).with(client, new_token)\n        subject.execute\n      end\n\n      it \"should return value of token action\" do\n        result = double(\"result\")\n        expect(subject).to receive(:execute_token).with(client, new_token).and_return(result)\n        expect(subject.execute).to eq(result)\n      end\n\n      it \"should store the new token\" do\n        expect(client).to receive(:store_token).with(new_token)\n        subject.execute\n      end\n    end\n\n    context \"when user is logged in\" do\n      let(:logged_in) { true }\n\n      it \"should output success message\" do\n        expect(env.ui).to receive(:success)\n        subject.execute\n      end\n\n      it \"should return a zero value\" do\n        expect(subject.execute).to eq(0)\n      end\n    end\n\n    context \"when user is not logged in\" do\n      let(:logged_in) { false }\n\n      it \"should run the client login\" do\n        expect(subject).to receive(:client_login)\n        subject.execute\n      end\n\n      context \"when username and description flags are supplied\" do\n        let(:username) { \"my-username\" }\n        let(:description) { \"my-description\" }\n\n        before { argv.push(\"--username\").push(username).push(\"--description\").push(description) }\n\n        it \"should include login and description to login\" do\n          expect(subject).to receive(:client_login).with(env, hash_including(login: username, description: description))\n          subject.execute\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/auth/logout_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/auth/logout\")\n\ndescribe VagrantPlugins::CloudCommand::AuthCommand::Command::Logout do\n  include_context \"unit\"\n\n  let(:argv)     { [] }\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n  let(:client) { double(\"client\") }\n\n  subject { described_class.new(argv, iso_env) }\n\n  let(:action_runner) { double(\"action_runner\") }\n\n  before do\n    allow(VagrantPlugins::CloudCommand::Client).to receive(:new).and_return(client)\n    allow(iso_env).to receive(:action_runner).and_return(action_runner)\n  end\n\n  context \"with any arguments\" do\n    let (:argv) { [\"stuff\", \"things\"] }\n\n    it \"shows the help\" do\n      expect { subject.execute }.\n        to raise_error(Vagrant::Errors::CLIInvalidUsage)\n    end\n  end\n\n  context \"with no arguments\" do\n    it \"logs you out\" do\n      expect(client).to receive(:clear_token)\n      expect(subject.execute).to eq(0)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/auth/middleware/add_authentication_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/auth/middleware/add_authentication\")\n\ndescribe VagrantPlugins::CloudCommand::AddAuthentication do\n  include_context \"unit\"\n\n  let(:app) { lambda { |env| } }\n  let(:ui) { Vagrant::UI::Silent.new }\n  let(:env) { {\n    env: iso_env,\n    ui: ui\n  } }\n\n  let(:iso_env) { isolated_environment.create_vagrant_env }\n  let(:server_url) { \"http://vagrantcloud.com\" }\n  let(:client) { double(\"client\", token: token) }\n  let(:token) { \"TEST_TOKEN\" }\n\n  subject { described_class.new(app, env) }\n\n  before do\n    allow(Vagrant).to receive(:server_url).and_return(server_url)\n    allow(VagrantPlugins::CloudCommand::Client).to receive(:new).\n      with(iso_env).and_return(client)\n    stub_env(\"ATLAS_TOKEN\" => nil)\n  end\n\n  describe \"#call\" do\n    it \"does nothing if we have no server set\" do\n      allow(Vagrant).to receive(:server_url).and_return(nil)\n\n      original = [token, \"#{server_url}/bar\"]\n      env[:box_urls] = original.dup\n\n      subject.call(env)\n\n      expect(env[:box_urls]).to eq(original)\n    end\n\n    it \"does nothing if we aren't logged in\" do\n      original = [\"foo\", \"#{server_url}/bar\"]\n      env[:box_urls] = original.dup\n\n      subject.call(env)\n\n      expect(env[:box_urls]).to eq(original)\n    end\n\n    context \"when urls are set\" do\n      it \"does not modify urls\" do\n        original = [\"https://example.com/boxes/test.box\",\n          \"file://C:/my/box/path/local.box\"]\n        env[:box_urls] = original.dup\n        subject.call(env)\n        expect(env[:box_urls]).to eq(original)\n      end\n\n      it \"should remove access_token parameters when found\" do\n        env[:box_urls] = [\"https://example.com/boxes/test.box?access_token=TEST\",\n          \"file://C:/my/box/path/local.box\"]\n        subject.call(env)\n        expect(env[:box_urls]).to eq([\n          \"https://example.com/boxes/test.box\",\n          \"file://C:/my/box/path/local.box\"])\n      end\n    end\n\n    context \"with VAGRANT_SERVER_ACCESS_TOKEN_BY_URL set\" do\n\n      before { stub_env(\"VAGRANT_SERVER_ACCESS_TOKEN_BY_URL\" => \"1\") }\n\n      it \"appends the access token to the URL of server URLs\" do\n        original = [\n          \"http://example.com/box.box\",\n          \"#{server_url}/foo.box\",\n          \"#{server_url}/bar.box?arg=true\",\n        ]\n\n        expected = original.dup\n        expected[1] = \"#{original[1]}?access_token=#{token}\"\n        expected[2] = \"#{original[2]}&access_token=#{token}\"\n\n        env[:box_urls] = original.dup\n        subject.call(env)\n\n        expect(env[:box_urls]).to eq(expected)\n      end\n\n      it \"does not append the access token to vagrantcloud.com URLs if Atlas\" do\n        server_url = \"https://atlas.hashicorp.com\"\n        allow(Vagrant).to receive(:server_url).and_return(server_url)\n        allow(subject).to receive(:sleep)\n\n        original = [\n          \"http://example.com/box.box\",\n          \"http://vagrantcloud.com/foo.box\",\n          \"http://vagrantcloud.com/bar.box?arg=true\",\n        ]\n\n        expected = original.dup\n\n        env[:box_urls] = original.dup\n        subject.call(env)\n\n        expect(env[:box_urls]).to eq(expected)\n      end\n\n      it \"warns when adding token to custom server\" do\n        server_url = \"https://example.com\"\n        allow(Vagrant).to receive(:server_url).and_return(server_url)\n\n        original = [\n          \"http://example.org/box.box\",\n          \"http://vagrantcloud.com/foo.box\",\n          \"http://example.com/bar.box\",\n          \"http://example.com/foo.box\"\n        ]\n\n        expected = original.dup\n        expected[2] = expected[2] + \"?access_token=#{token}\"\n        expected[3] = expected[3] + \"?access_token=#{token}\"\n\n        expect(subject).to receive(:sleep).once\n        expect(ui).to receive(:warn).once.and_call_original\n\n        env[:box_urls] = original.dup\n        subject.call(env)\n\n        expect(env[:box_urls]).to eq(expected)\n      end\n\n      it \"ignores urls that it cannot parse\" do\n        bad_url = \"this is not a valid url\"\n        # Ensure the bad URL does cause an exception\n        expect{ URI.parse(bad_url) }.to raise_error URI::Error\n        env[:box_urls] = [bad_url]\n        subject.call(env)\n        expect(env[:box_urls].first).to eq(bad_url)\n      end\n\n      it \"does not append multiple access_tokens\" do\n        original = [\n          \"#{server_url}/foo.box?access_token=existing\",\n          \"#{server_url}/bar.box?arg=true\",\n        ]\n\n        env[:box_urls] = original.dup\n        subject.call(env)\n\n        expect(env[:box_urls][0]).to eq(\"#{server_url}/foo.box?access_token=existing\")\n        expect(env[:box_urls][1]).to eq(\"#{server_url}/bar.box?arg=true&access_token=#{token}\")\n      end\n\n\n      context \"when token is not set\" do\n        let(:token) { nil }\n\n        it \"modifies host URL to target if authorized host\" do\n          originals = VagrantPlugins::CloudCommand::AddAuthentication::\n            REPLACEMENT_HOSTS.map{ |h| \"http://#{h}/box.box\" }\n          expected = \"http://#{VagrantPlugins::CloudCommand::AddAuthentication::TARGET_HOST}/box.box\"\n          env[:box_urls] = originals\n          subject.call(env)\n          env[:box_urls].each do |url|\n            expect(url).to eq(expected)\n          end\n        end\n\n        it \"returns original urls when not modified\" do\n          to_persist = \"file:////path/to/box.box\"\n          to_change = VagrantPlugins::CloudCommand::AddAuthentication::\n            REPLACEMENT_HOSTS.map{ |h| \"http://#{h}/box.box\" }.first\n          expected = \"http://#{VagrantPlugins::CloudCommand::AddAuthentication::TARGET_HOST}/box.box\"\n          env[:box_urls] = [to_persist, to_change]\n          subject.call(env)\n          check_persist, check_change = env[:box_urls]\n          expect(check_change).to eq(expected)\n          expect(check_persist).to eq(to_persist)\n          # NOTE: The behavior of URI.parse changes on Ruby 2.5 to produce\n          # the same string value. To make the test worthwhile in checking\n          # for the same value, check that the object IDs are still the same.\n          expect(check_persist.object_id).to eq(to_persist.object_id)\n        end\n      end\n    end\n\n\n    context \"with VAGRANT_SERVER_ACCESS_TOKEN_BY_URL unset\" do\n\n      before { stub_env(\"VAGRANT_SERVER_ACCESS_TOKEN_BY_URL\" => nil) }\n\n      it \"returns the original urls\" do\n        box1 = \"http://vagrantcloud.com/box.box\"\n        box2 = \"http://app.vagrantup.com/box.box\"\n\n        env = {\n          box_urls: [\n            box1.dup,\n            box2.dup\n          ]\n        }\n        subject.call(env)\n\n        expect(env[:box_urls]).to eq([box1, box2])\n      end\n\n      it \"removes access_token parameters if set\" do\n        box1 = \"http://vagrantcloud.com/box.box\"\n        box2 = \"http://app.vagrantup.com/box.box\"\n        box3 = \"http://app.vagrantup.com/box.box?arg1=value1\"\n\n        env = {\n          box_urls: [\n            \"#{box1}?access_token=TEST_TOKEN\",\n            box2.dup,\n            \"#{box3}&access_token=TEST_TOKEN\"\n          ]\n        }\n        subject.call(env)\n\n        expect(env[:box_urls]).to eq([box1, box2, box3])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/auth/middleware/add_downloader_authentication_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/auth/middleware/add_downloader_authentication\")\nrequire \"vagrant/util/downloader\"\n\ndescribe VagrantPlugins::CloudCommand::AddDownloaderAuthentication do\n  include_context \"unit\"\n\n  let(:app) { lambda { |env| } }\n  let(:ui) { Vagrant::UI::Silent.new }\n  let(:env) { {\n    env: iso_env,\n    ui: ui\n  } }\n\n  let(:iso_env) { isolated_environment.create_vagrant_env }\n  let(:server_url) { \"http://vagrantcloud.com/box.box\" }\n  let(:dwnloader) { Vagrant::Util::Downloader.new(server_url, \"/some/path\", {}) }\n\n  subject { described_class.new(app, env) }\n\n  before do\n    allow(Vagrant).to receive(:server_url).and_return(server_url)\n    stub_env(\"ATLAS_TOKEN\" => nil)\n    stub_env(\"VAGRANT_SERVER_ACCESS_TOKEN_BY_URL\" => nil)\n    VagrantPlugins::CloudCommand::Client.class_variable_set(:@@client, nil)\n  end\n\n  describe \"#call\" do\n    context \"non full paths\" do\n      let(:server_url) { \"http://vagrantcloud.com\" }\n      let(:dwnloader) { Vagrant::Util::Downloader.new(server_url, \"/some/path\", {}) }\n\n      it \"does nothing if we have no server set\" do\n        allow(Vagrant).to receive(:server_url).and_return(nil)\n        VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(\"fooboohoo\")\n\n        env[:downloader] = dwnloader\n        subject.call(env)\n        expect(env[:downloader].headers.empty?).to eq(true)\n      end\n\n      it \"does nothing if we aren't logged in\" do\n        env[:downloader] = dwnloader\n        subject.call(env)\n        expect(env[:downloader].headers.empty?).to eq(true)\n      end\n    end\n\n    context \"custom server\" do\n      let(:server_url) { \"http://surprise.com/box.box\" }\n      let(:dwnloader) { Vagrant::Util::Downloader.new(server_url, \"/some/path\", {}) }\n\n      it \"warns when adding token to custom server\" do\n        server_url = \"https://surprise.com\"\n        allow(Vagrant).to receive(:server_url).and_return(server_url)\n\n        token = \"foobarbaz\"\n        VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(token)\n\n        expect(subject).to receive(:sleep).once\n        expect(ui).to receive(:warn).once.and_call_original\n\n        env[:downloader] = dwnloader\n        subject.call(env)\n\n        expect(env[:downloader].headers).to eq([\"Authorization: Bearer #{token}\"])\n      end\n    end\n\n    context \"replacement hosts\" do\n      let(:dwnloader) { Vagrant::Util::Downloader.new(\"https://app.vagrantup.com\", \"/some/path\", {}) }\n\n      it \"modifies host URL to target if authorized host\" do\n        token = \"foobarbaz\"\n        VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(token)\n        env[:downloader] = dwnloader\n        subject.call(env)\n        expect(env[:downloader].headers).to eq([\"Authorization: Bearer #{token}\"])\n        expect(URI.parse(env[:downloader].source).host).to eq(VagrantPlugins::CloudCommand::AddDownloaderAuthentication::TARGET_HOST)\n      end\n    end\n\n    context \"malformed url\" do\n      let(:bad_url) { \"this is not a valid url\" }\n      let(:dwnloader) { Vagrant::Util::Downloader.new(bad_url, \"/some/path\", {}) }\n\n      it \"ignores urls that it cannot parse\" do\n        # Ensure the bad URL does cause an exception\n        expect{ URI.parse(bad_url) }.to raise_error URI::Error\n        env[:downloader] = dwnloader\n        subject.call(env)\n        expect(env[:downloader].source).to eq(bad_url)\n      end\n    end\n\n    context \"with an headers already added\" do\n      let(:auth_header) { \"Authorization Bearer: token\" }\n      let(:other_header) {\"some: thing\"}\n      let(:dwnloader) { Vagrant::Util::Downloader.new(server_url, \"/some/path\", {headers: [other_header]}) }\n\n      it \"appends the auth header\" do\n        token = \"foobarbaz\"\n        VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(token)\n\n        env[:downloader] = dwnloader\n        subject.call(env)\n\n        expect(env[:downloader].headers).to eq([other_header, \"Authorization: Bearer #{token}\"])\n      end\n\n      context \"with local file path\" do\n        let(:file_path) { \"file:////path/to/box.box\" }\n        let(:dwnloader) { Vagrant::Util::Downloader.new(file_path, \"/some/path\", {}) }\n\n        it \"returns original urls when not modified\" do\n          env[:downloader] = dwnloader\n          subject.call(env)\n\n          expect(env[:downloader].source).to eq(file_path)\n          expect(env[:downloader].headers.empty?).to eq(true)\n        end\n      end\n\n      it \"does not append multiple access_tokens\" do\n        dwnloader.headers << auth_header\n        token = \"foobarbaz\"\n        VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(token)\n\n        env[:downloader] = dwnloader\n        subject.call(env)\n\n        expect(env[:downloader].headers).to eq([other_header, auth_header])\n      end\n    end\n\n    it \"adds a token to the headers\" do\n      token = \"foobarbaz\"\n      VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(token)\n      env[:downloader] = dwnloader\n      subject.call(env)\n      expect(env[:downloader].headers).to eq([\"Authorization: Bearer #{token}\"])\n    end\n\n    it \"does not append the access token to vagrantcloud.com URLs if Atlas\" do\n      server_url = \"https://atlas.hashicorp.com\"\n      allow(Vagrant).to receive(:server_url).and_return(server_url)\n      allow(subject).to receive(:sleep)\n      token = \"foobarbaz\"\n      VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(token)\n      env[:downloader] = dwnloader\n      subject.call(env)\n      expect(env[:downloader].headers.empty?).to eq(true)\n    end\n\n    context \"with VAGRANT_SERVER_ACCESS_TOKEN_BY_URL environment variable set\" do\n      before do\n        stub_env(\"VAGRANT_SERVER_ACCESS_TOKEN_BY_URL\" => \"1\")\n      end\n\n      it \"does not add a token to the headers\" do\n        token = \"foobarbaz\"\n        VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(token)\n        env[:downloader] = dwnloader\n        subject.call(env)\n        expect(env[:downloader].headers).to eq([])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/auth/whoami_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/auth/whoami\")\n\ndescribe VagrantPlugins::CloudCommand::AuthCommand::Command::Whoami do\n  include_context \"unit\"\n\n  let(:argv)     { [] }\n  let(:env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n  let(:client) { double(\"client\", token: token) }\n  let(:token) { double(\"token\") }\n  let(:account_username) { \"account-username\" }\n  let(:account) { double(\"account\", username: account_username) }\n  let(:action_runner) { double(\"action_runner\") }\n\n  subject { described_class.new(argv, env) }\n\n  before do\n    allow(env).to receive(:action_runner).and_return(action_runner)\n    allow(VagrantPlugins::CloudCommand::Client).to receive(:new).and_return(client)\n    allow(VagrantCloud::Account).to receive(:new).and_return(account)\n  end\n\n  describe \"whoami\" do\n    context \"when token is unset\" do\n      let(:token) { \"\" }\n\n      it \"should output an error\" do\n        expect(env.ui).to receive(:error)\n        subject.whoami(token)\n      end\n\n      it \"should return non-zero\" do\n        r = subject.whoami(token)\n        expect(r).not_to eq(0)\n        expect(r).to be_a(Integer)\n      end\n    end\n\n    context \"when token is set\" do\n      let(:token) { \"my-token\" }\n\n      it \"should load an account to validate\" do\n        expect(VagrantCloud::Account).to receive(:new).\n          with(hash_including(access_token: token)).and_return(account)\n        subject.whoami(token)\n      end\n\n      it \"should output the account username\" do\n        expect(env.ui).to receive(:success).with(/#{account_username}/)\n        subject.whoami(token)\n      end\n\n      it \"should return zero value\" do\n        expect(subject.whoami(token)).to eq(0)\n      end\n\n      context \"when error is encountered\" do\n        before { allow(VagrantCloud::Account).to receive(:new).and_raise(VagrantCloud::Error::ClientError) }\n\n        it \"should output an error\" do\n          expect(env.ui).to receive(:error).twice\n          subject.execute\n        end\n\n        it \"should return a non-zero value\" do\n          r = subject.execute\n          expect(r).not_to eq(0)\n          expect(r).to be_a(Integer)\n        end\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    before do\n      allow(subject).to receive(:whoami)\n    end\n\n    context \"with too many arguments\" do\n      let(:argv) { [\"token\", \"token\", \"token\"] }\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with no argument\" do\n      it \"should use stored token via client\" do\n        expect(subject).to receive(:whoami).with(token)\n        subject.execute\n      end\n    end\n\n    context \"with token argument\" do\n      let(:token_arg) { \"TOKEN_ARG\" }\n      let(:argv) { [token_arg] }\n\n      it \"should use the passed token\" do\n        expect(subject).to receive(:whoami).with(token_arg)\n        subject.execute\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/box/create_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/box/create\")\n\ndescribe VagrantPlugins::CloudCommand::BoxCommand::Command::Create do\n  include_context \"unit\"\n\n  let(:access_token) { double(\"token\") }\n  let(:org_name) { \"my-org\" }\n  let(:box_name) { \"my-box\" }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\") }\n\n  describe \"#create_box\" do\n    let(:options) { {} }\n    let(:env) { double(\"env\", ui: ui) }\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:argv) { [] }\n\n    before do\n      allow(env).to receive(:ui).and_return(ui)\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: access_token).\n        and_return(account)\n      allow(account).to receive(:organization).with(name: org_name).\n        and_return(organization)\n      allow(subject).to receive(:format_box_results).with(box, env)\n      allow(organization).to receive(:add_box).and_return(box)\n      allow(box).to receive(:save)\n    end\n\n    subject { described_class.new(argv, env) }\n\n    it \"should add a new box to the organization\" do\n      expect(organization).to receive(:add_box).with(box_name).\n        and_return(box)\n      subject.create_box(org_name, box_name, access_token, options)\n    end\n\n    it \"should save the new box\" do\n      expect(box).to receive(:save)\n      subject.create_box(org_name, box_name, access_token, options)\n    end\n\n    it \"should return a zero value on success\" do\n      expect(subject.create_box(org_name, box_name, access_token, options)).\n        to eq(0)\n    end\n\n    it \"should return a non-zero value on error\" do\n      expect(box).to receive(:save).and_raise(VagrantCloud::Error)\n      result = subject.create_box(org_name, box_name, access_token, options)\n      expect(result).not_to eq(0)\n      expect(result).to be_a(Integer)\n    end\n\n    context \"with option set\" do\n      let(:options) { {short: short, description: description, private: priv} }\n      let(:short) { double(\"short\") }\n      let(:description) { double(\"description\") }\n      let(:priv) { double(\"private\") }\n\n      it \"should set info into box\" do\n        expect(box).to receive(:short_description=).with(short)\n        expect(box).to receive(:description=).with(description)\n        expect(box).to receive(:private=).with(priv)\n        subject.create_box(org_name, box_name, access_token, options)\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    let(:argv)     { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n\n    let(:client) { double(\"client\", token: access_token) }\n    let(:box) { double(\"box\") }\n\n    before do\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).and_return(client)\n      allow(subject).to receive(:format_box_results)\n      allow(subject).to receive(:create_box)\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with box name argument\" do\n      let(:argv) { [\"#{org_name}/#{box_name}\"] }\n\n      it \"should create the box\" do\n        expect(subject).to receive(:create_box).with(org_name, box_name, any_args)\n        subject.execute\n      end\n\n      context \"when description flag is provided\" do\n        let(:description) { \"my-description\" }\n\n        before { argv.push(\"--description\").push(description) }\n\n        it \"should create box with given description\" do\n          expect(subject).to receive(:create_box).\n            with(org_name, box_name, access_token, hash_including(description: description))\n          subject.execute\n        end\n      end\n\n      context \"when short flag is provided\" do\n        let(:description) { \"my-description\" }\n\n        before { argv.push(\"--short\").push(description) }\n\n        it \"should create box with given short description\" do\n          expect(subject).to receive(:create_box).\n            with(org_name, box_name, access_token, hash_including(short: description))\n          subject.execute\n        end\n      end\n\n      context \"when private flag is provided\" do\n        before { argv.push(\"--private\") }\n\n        it \"should create box as private\" do\n          expect(subject).to receive(:create_box).\n            with(org_name, box_name, access_token, hash_including(private: true))\n          subject.execute\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/box/delete_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/box/delete\")\n\ndescribe VagrantPlugins::CloudCommand::BoxCommand::Command::Delete do\n  include_context \"unit\"\n\n  let(:access_token) { double(\"token\") }\n  let(:org_name) { \"my-org\" }\n  let(:box_name) { \"my-box\" }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\") }\n\n  describe \"#delete_box\" do\n    let(:options) { {} }\n    let(:env) { double(\"env\", ui: ui) }\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:argv) { [] }\n\n    before do\n      allow(env).to receive(:ui).and_return(ui)\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: access_token).\n        and_return(account)\n      allow(subject).to receive(:with_box).with(account: account, org: org_name, box: box_name).\n        and_yield(box)\n      allow(account).to receive(:organization).with(name: org_name).\n        and_return(organization)\n      allow(box).to receive(:delete)\n    end\n\n    subject { described_class.new(argv, env) }\n\n    it \"should return 0 on success\" do\n      expect(subject.delete_box(org_name, box_name, access_token)).to eq(0)\n    end\n\n    it \"should delete the box\" do\n      expect(box).to receive(:delete)\n      subject.delete_box(org_name, box_name, access_token)\n    end\n\n    it \"should return non-zero on error\" do\n      expect(box).to receive(:delete).and_raise(VagrantCloud::Error)\n      result = subject.delete_box(org_name, box_name, access_token)\n      expect(result).not_to eq(0)\n      expect(result).to be_a(Integer)\n    end\n  end\n\n  describe \"#execute\" do\n\n    let(:argv) { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n\n    let(:client) { double(\"client\", token: access_token) }\n    let(:box) { double(\"box\") }\n\n    before do\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).\n        and_return(client)\n      allow(iso_env.ui).to receive(:ask).\n        and_return(\"y\")\n      allow(subject).to receive(:delete_box)\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with box name argument\" do\n      let (:argv) { [\"#{org_name}/#{box_name}\"] }\n\n      it \"should delete the box\" do\n        expect(subject).to receive(:delete_box).\n          with(org_name, box_name, access_token)\n        subject.execute\n      end\n\n      it \"should prompt for confirmation\" do\n        expect(iso_env.ui).to receive(:ask).and_return(\"y\")\n        subject.execute\n      end\n\n      context \"with force flag\" do\n        before { argv.push(\"--force\") }\n\n        it \"should not prompt for confirmation\" do\n          expect(iso_env.ui).not_to receive(:ask)\n          subject.execute\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/box/show_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/box/show\")\n\ndescribe VagrantPlugins::CloudCommand::BoxCommand::Command::Show do\n  include_context \"unit\"\n\n  let(:access_token) { double(\"token\") }\n  let(:org_name) { \"my-org\" }\n  let(:box_name) { \"my-box\" }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\") }\n\n  describe \"#show_box\" do\n    let(:options) { {} }\n    let(:env) { double(\"env\", ui: ui) }\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:argv) { [] }\n\n    before do\n      allow(env).to receive(:ui).and_return(ui)\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: access_token).\n        and_return(account)\n      allow(subject).to receive(:with_box).with(account: account, org: org_name, box: box_name).\n        and_yield(box)\n      allow(account).to receive(:organization).with(name: org_name).\n        and_return(organization)\n      allow(subject).to receive(:format_box_results)\n    end\n\n    subject { described_class.new(argv, env) }\n\n    it \"should return 0 on success\" do\n      expect(subject.show_box(org_name, box_name, access_token, options)).to eq(0)\n    end\n\n    it \"should display the box results\" do\n      expect(subject).to receive(:format_box_results).with(box, env, {})\n      subject.show_box(org_name, box_name, access_token, options)\n    end\n\n    it \"should return non-zero on error\" do\n      expect(subject).to receive(:with_box).and_raise(VagrantCloud::Error)\n      result = subject.show_box(org_name, box_name, access_token, options)\n      expect(result).not_to eq(0)\n      expect(result).to be_a(Integer)\n    end\n\n    context \"with version defined\" do\n      let(:options) { {versions: [version]} }\n      let(:box_version) { double(\"box_version\", version: version) }\n      let(:box_versions) { [box_version] }\n      let(:version) { double(\"version\") }\n\n      before do\n        allow(box).to receive(:versions).and_return(box_versions)\n      end\n\n      it \"should print the version details\" do\n        expect(subject).to receive(:format_box_results).with(box_version, env, {})\n        subject.show_box(org_name, box_name, access_token, options)\n      end\n\n      context \"when version is not found\" do\n        let(:box_versions) { [] }\n\n        it \"should return non-zero\" do\n          result = subject.show_box(org_name, box_name, access_token, options)\n          expect(result).not_to eq(0)\n          expect(result).to be_a(Integer)\n        end\n\n        it \"should not print any box information\" do\n          expect(subject).not_to receive(:format_box_results)\n          subject.show_box(org_name, box_name, access_token, options)\n        end\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    let(:argv)     { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n    let(:client) { double(\"client\", token: access_token) }\n\n    before do\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).\n        and_return(client)\n      allow(subject).to receive(:show_box)\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with box name argument\" do\n      let (:argv) { [\"#{org_name}/#{box_name}\"] }\n\n      it \"should show the box\" do\n        expect(subject).to receive(:show_box).with(org_name, box_name, any_args)\n        subject.execute\n      end\n\n      it \"should create the client login quietly\" do\n        expect(subject).to receive(:client_login).with(iso_env, hash_including(quiet: true))\n        subject.execute\n      end\n\n      context \"with auth flag\" do\n        before { argv.push(\"--auth\") }\n\n        it \"should set quiet option to false when creating client\" do\n          expect(subject).to receive(:client_login).with(iso_env, hash_including(quiet: false))\n          subject.execute\n        end\n      end\n\n      context \"with versions flag set\" do\n        let(:version_option) { \"1.0.0\" }\n\n        before { argv.push(\"--versions\").push(version_option) }\n\n        it \"should show box with version option set\" do\n          expect(subject).to receive(:show_box).\n            with(org_name, box_name, access_token, hash_including(versions: [version_option]))\n          subject.execute\n        end\n      end\n\n      context \"with architectures flag set\" do\n        let(:arch_option) { \"test-arch\" }\n\n        before { argv.push(\"--architectures\").push(arch_option) }\n\n        it \"shouild show box with architectures option set\" do\n          expect(subject).to receive(:show_box) do |*_, opts|\n            expect(opts[:architectures]).to eq([arch_option])\n          end\n\n          subject.execute\n        end\n      end\n\n      context \"with providers flag set\" do\n        let(:provider_option) { \"test-provider\" }\n\n        before { argv.push(\"--providers\").push(provider_option) }\n\n        it \"shilud show box with providers option set\" do\n          expect(subject).to receive(:show_box) do |*_, opts|\n            expect(opts[:providers]).to eq([provider_option])\n          end\n\n          subject.execute\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/box/update_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/box/update\")\n\ndescribe VagrantPlugins::CloudCommand::BoxCommand::Command::Update do\n  include_context \"unit\"\n\n  let(:access_token) { double(\"token\") }\n  let(:org_name) { \"my-org\" }\n  let(:box_name) { \"my-box\" }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\") }\n\n  describe \"#update_box\" do\n    let(:options) { {} }\n    let(:env) { double(\"env\", ui: ui) }\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:argv) { [] }\n\n    before do\n      allow(env).to receive(:ui).and_return(ui)\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: access_token).\n        and_return(account)\n      allow(subject).to receive(:with_box).with(account: account, org: org_name, box: box_name).\n        and_yield(box)\n      allow(account).to receive(:organization).with(name: org_name).\n        and_return(organization)\n      allow(subject).to receive(:format_box_results)\n      allow(box).to receive(:save)\n    end\n\n    subject { described_class.new(argv, env) }\n\n    it \"should save the box\" do\n      expect(box).to receive(:save)\n      subject.update_box(org_name, box_name, access_token, options)\n    end\n\n    it \"should return 0 on success\" do\n      result = subject.update_box(org_name, box_name, access_token, options)\n      expect(result).to eq(0)\n    end\n\n    it \"should return non-zero on error\" do\n      expect(box).to receive(:save).and_raise(VagrantCloud::Error)\n      result = subject.update_box(org_name, box_name, access_token, options)\n      expect(result).not_to eq(0)\n      expect(result).to be_a(Integer)\n    end\n\n    it \"should display the box information\" do\n      expect(subject).to receive(:format_box_results).with(box, env)\n      subject.update_box(org_name, box_name, access_token, options)\n    end\n\n    context \"with options set\" do\n      let(:options) { {short: short, description: description, private: priv} }\n      let(:short) { double(\"short\") }\n      let(:description) { double(\"description\") }\n      let(:priv) { double(\"private\") }\n\n      it \"should set box info\" do\n        expect(box).to receive(:short_description=).with(short)\n        expect(box).to receive(:description=).with(description)\n        expect(box).to receive(:private=).with(priv)\n        subject.update_box(org_name, box_name, access_token, options)\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    let(:argv) { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n    let(:client) { double(\"client\", token: access_token) }\n\n    before do\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).and_return(client)\n      allow(subject).to receive(:update_box)\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with box name argument\" do\n      let(:argv) { [\"#{org_name}/#{box_name}\"] }\n\n      it \"should show help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n\n      context \"with description flag set\" do\n        let(:description) { \"my-description\" }\n\n        before { argv.push(\"--description\").push(description) }\n\n        it \"should update box with description\" do\n          expect(subject).to receive(:update_box).\n            with(org_name, box_name, access_token, hash_including(description: description))\n          subject.execute\n        end\n      end\n\n      context \"with short flag set\" do\n        let(:description) { \"my-description\" }\n\n        before { argv.push(\"--short-description\").push(description) }\n\n        it \"should update box with short description\" do\n          expect(subject).to receive(:update_box).\n            with(org_name, box_name, access_token, hash_including(short: description))\n          subject.execute\n        end\n      end\n\n      context \"with private flag set\" do\n        before { argv.push(\"--private\") }\n\n        it \"should update box with private\" do\n          expect(subject).to receive(:update_box).\n            with(org_name, box_name, access_token, hash_including(private: true))\n          subject.execute\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/client_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/client/client\")\n\ndescribe VagrantPlugins::CloudCommand::Client do\n  include_context \"unit\"\n\n  let(:env) { isolated_environment.create_vagrant_env }\n  let(:token) { nil }\n  let(:vc_client) { double(\"vagrantcloud-client\", access_token: token) }\n\n  subject(:client) { described_class.new(env) }\n\n  before(:all) do\n    I18n.load_path << Vagrant.source_root.join(\"plugins/commands/cloud/locales/en.yml\")\n    I18n.reload!\n  end\n\n  before do\n    stub_env(\"ATLAS_TOKEN\" => nil)\n    stub_env(\"VAGRANT_CLOUD_TOKEN\" => nil)\n    allow(VagrantCloud::Client).to receive(:new).and_return(vc_client)\n    allow(Vagrant::Util::CredentialScrubber).to receive(:sensitive)\n  end\n\n  after do\n    described_class.reset!\n    Vagrant::Util::CredentialScrubber.reset!\n  end\n\n  describe \"#logged_in?\" do\n    before { allow(subject).to receive(:token).and_return(token) }\n\n    context \"when token is not set\" do\n      it \"should return false\" do\n        expect(subject.logged_in?).to be_falsey\n      end\n    end\n\n    context \"when token is set\" do\n      let(:token) { double(\"token\") }\n\n      before do\n        allow(vc_client).to receive(:authentication_token_validate)\n      end\n\n      it \"should return true when token is valid\" do\n        expect(subject.logged_in?).to be_truthy\n      end\n\n      it \"should validate the set token\" do\n        expect(vc_client).to receive(:authentication_token_validate)\n        subject.logged_in?\n      end\n\n      it \"should return false when token does not validate\" do\n        expect(vc_client).to receive(:authentication_token_validate).\n          and_raise(Excon::Error::Unauthorized.new(StandardError.new))\n        expect(subject.logged_in?).to be_falsey\n      end\n\n      it \"should add token to scrubber\" do\n        expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(token)\n        subject.logged_in?\n      end\n    end\n  end\n\n  describe \"#login\" do\n    let(:new_token) { double(\"new-token\") }\n    let(:result) { {token: new_token} }\n    let(:password) { double(\"password\") }\n    let(:username) { double(\"username\") }\n\n    before do\n      subject.username_or_email = username\n      subject.password = password\n      allow(vc_client).to receive(:authentication_token_create).\n        and_return(result)\n    end\n\n    it \"should add password to scrubber\" do\n      expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(password)\n      subject.login\n    end\n\n    it \"should create an authentication token\" do\n      expect(vc_client).to receive(:authentication_token_create).\n        and_return(result)\n      subject.login\n    end\n\n    it \"should wrap remote request to handle errors\" do\n      expect(subject).to receive(:with_error_handling)\n      subject.login\n    end\n\n    it \"should add new token to scrubber\" do\n      expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(new_token)\n      subject.login\n    end\n\n    it \"should create a new internal client\" do\n      expect(VagrantCloud::Client).to receive(:new).with(access_token: new_token, url_base: anything)\n      subject.login\n    end\n\n    it \"should create authentication token using username and password\" do\n      expect(vc_client).to receive(:authentication_token_create).\n        with(hash_including(username: username, password: password)).and_return(result)\n      subject.login\n    end\n\n    it \"should return the new token\" do\n      expect(subject.login).to eq(new_token)\n    end\n\n    context \"with description and code\" do\n      let(:description) { double(\"description\") }\n      let(:code) { double(\"code\") }\n\n      it \"should create authentication token using description and code\" do\n        expect(vc_client).to receive(:authentication_token_create).with(\n          hash_including(username: username, password: password,\n            description: description, code: code))\n        subject.login(description: description, code: code)\n      end\n    end\n  end\n\n  describe \"#request_code\" do\n    let(:password) { double(\"password\") }\n    let(:username) { double(\"username\") }\n    let(:delivery_method) { double(\"delivery-method\", upcase: nil) }\n    let(:result) { {two_factor: two_factor} }\n    let(:two_factor) { {obfuscated_destination: obfuscated_destination} }\n    let(:obfuscated_destination) { double(\"obfuscated-destination\", to_s: \"2FA_DESTINATION\") }\n\n    before do\n      subject.password = password\n      subject.username_or_email = username\n      allow(vc_client).to receive(:authentication_request_2fa_code).and_return(result)\n    end\n\n    it \"should add password to scrubber\" do\n      expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(password)\n      subject.request_code(delivery_method)\n    end\n\n    it \"should request the code\" do\n      expect(vc_client).to receive(:authentication_request_2fa_code).with(\n        hash_including(username: username, password: password, delivery_method: delivery_method))\n      subject.request_code(delivery_method)\n    end\n\n    it \"should print the destination\" do\n      expect(env.ui).to receive(:success).with(/2FA_DESTINATION/)\n      subject.request_code(delivery_method)\n    end\n  end\n\n  describe \"#store_token\" do\n    let(:token_path) { double(\"token-path\") }\n    let(:new_token) { double(\"new-token\") }\n\n    before do\n      allow(subject).to receive(:token_path).and_return(token_path)\n      allow(token_path).to receive(:open)\n    end\n\n    it \"should add token to scrubber\" do\n      expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(new_token)\n      subject.store_token(new_token)\n    end\n\n    it \"should create a new internal client with token\" do\n      expect(VagrantCloud::Client).to receive(:new).with(access_token: new_token, url_base: anything)\n      subject.store_token(new_token)\n    end\n\n    it \"should open the token path and write the new token\" do\n      f = double(\"file\")\n      expect(token_path).to receive(:open).with(\"w\").and_yield(f)\n      expect(f).to receive(:write).with(new_token)\n      subject.store_token(new_token)\n    end\n  end\n\n  describe \"#token\" do\n    let(:env_token) { \"ENV_TOKEN\" }\n    let(:file_token) { \"FILE_TOKEN\" }\n    let(:token_path) { double(\"token-path\", read: file_token) }\n    let(:path_exists) { false }\n\n    before do\n      allow(subject).to receive(:token).and_call_original\n      allow(subject).to receive(:token_path).and_return(token_path)\n      allow(token_path).to receive(:exist?).and_return(path_exists)\n    end\n\n    context \"when VAGRANT_CLOUD_TOKEN env var is set\" do\n      before { stub_env(\"VAGRANT_CLOUD_TOKEN\" => env_token) }\n\n      it \"should return the env token\" do\n        expect(subject.token).to eq(env_token)\n      end\n\n      context \"when token path exists\" do\n        let(:path_exists) { true }\n\n        it \"should return the env token\" do\n          expect(subject.token).to eq(env_token)\n        end\n\n        it \"should print warning of two tokens\" do\n          expect(env.ui).to receive(:warn)\n          subject.token\n        end\n\n        it \"should only print warning of two tokens once\" do\n          expect(env.ui).to receive(:warn).with(/detected/).once\n          3.times { subject.token }\n        end\n      end\n    end\n\n    context \"when token path exists\" do\n      let(:path_exists) { true }\n\n      it \"should return the stored token\" do\n        expect(subject.token).to eq(file_token)\n      end\n\n      context \"when VAGRANT_CLOUD_TOKEN env var is set\" do\n        before { stub_env(\"VAGRANT_CLOUD_TOKEN\" => env_token) }\n\n        it \"should return the env token\" do\n          expect(subject.token).to eq(env_token)\n        end\n      end\n    end\n\n    context \"when ATLAS_TOKEN env var is set\" do\n      before { stub_env(\"ATLAS_TOKEN\" => env_token) }\n\n      it \"should return the env token\" do\n        expect(subject.token).to eq(env_token)\n      end\n\n      context \"when VAGRANT_CLOUD_TOKEN is set\" do\n        let(:vc_token) { \"VC_TOKEN\" }\n\n        before { stub_env(\"VAGRANT_CLOUD_TOKEN\" => vc_token) }\n\n        it \"should return the VAGRANT_CLOUD_TOKEN value\" do\n          expect(subject.token).to eq(vc_token)\n        end\n      end\n\n      context \"when file exists\" do\n        let(:path_exists) { true }\n\n        it \"should return the file token\" do\n          expect(subject.token).to eq(file_token)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/list_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/list\")\n\ndescribe VagrantPlugins::CloudCommand::Command::List do\n  include_context \"unit\"\n\n  let(:argv)     { [] }\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  subject { described_class.new(argv, iso_env) }\n\n  let(:action_runner) { double(\"action_runner\") }\n\n  before do\n    allow(iso_env).to receive(:action_runner).and_return(action_runner)\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/provider/create_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/provider/create\")\n\ndescribe VagrantPlugins::CloudCommand::ProviderCommand::Command::Create do\n  include_context \"unit\"\n\n  let(:access_token) { double(\"token\") }\n  let(:org_name) { \"my-org\" }\n  let(:box_name) { \"my-box\" }\n  let(:box_version) { \"0.1.0\" }\n  let(:provider_name) { \"my-provider\" }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\", versions: [version]) }\n  let(:version) { double(\"version\", version: box_version, providers: [provider]) }\n  let(:provider) { double(\"provider\", name: provider_name) }\n  let(:provider_url) { double(\"provider_url\") }\n\n  describe \"#create_provider\" do\n    let(:options) { {} }\n    let(:env) { double(\"env\", ui: ui) }\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:argv) { [] }\n\n    before do\n      allow(env).to receive(:ui).and_return(ui)\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: access_token).\n        and_return(account)\n      allow(subject).to receive(:with_version).with(account: account, org: org_name, box: box_name, version: box_version).\n        and_yield(version)\n      allow(account).to receive(:organization).with(name: org_name).\n        and_return(organization)\n      allow(version).to receive(:add_provider).and_return(provider)\n      allow(provider).to receive(:save)\n      allow(provider).to receive(:url=)\n      allow(subject).to receive(:format_box_results)\n    end\n\n    subject { described_class.new(argv, env) }\n\n    it \"should add a new provider to the box version\" do\n      expect(version).to receive(:add_provider).with(provider_name, nil)\n      subject.create_provider(org_name, box_name, box_version, provider_name, provider_url, access_token, options)\n    end\n\n    it \"should not set checksum or checksum_type when not provided\" do\n      expect(provider).not_to receive(:checksum=)\n      expect(provider).not_to receive(:checksum_type=)\n      subject.create_provider(org_name, box_name, box_version, provider_name, provider_url, access_token, options)\n    end\n\n    context \"with checksum and checksum type options set\" do\n      let(:checksum) { double(\"checksum\") }\n      let(:checksum_type) { double(\"checksum_type\") }\n      let(:options) { {checksum: checksum, checksum_type: checksum_type} }\n\n      it \"should set the checksum and checksum type\" do\n        expect(provider).to receive(:checksum=).with(checksum)\n        expect(provider).to receive(:checksum_type=).with(checksum_type)\n        subject.create_provider(org_name, box_name, box_version, provider_name, provider_url, access_token, options)\n      end\n    end\n\n    context \"when URL is set\" do\n      it \"should set the URL\" do\n        expect(provider).to receive(:url=).with(provider_url)\n        subject.create_provider(org_name, box_name, box_version, provider_name, provider_url, access_token, options)\n      end\n    end\n\n    context \"when URL is not set\" do\n      let(:provider_url) { nil }\n\n      it \"should not set the URL\" do\n        expect(provider).not_to receive(:url=).with(provider_url)\n        subject.create_provider(org_name, box_name, box_version, provider_name, provider_url, access_token, options)\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    let(:argv) { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n    let(:client) { double(\"client\", token: access_token) }\n\n    before do\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).\n        and_return(client)\n      allow(subject).to receive(:create_provider)\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with box name argument\" do\n      let(:argv) { [\"#{org_name}/#{box_name}\"] }\n\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n\n      context \"with provider argument\" do\n        let(:provider_arg) { \"my-provider\" }\n\n        before { argv << provider_arg }\n\n        it \"shows help\" do\n          expect { subject.execute }.\n            to raise_error(Vagrant::Errors::CLIInvalidUsage)\n        end\n\n        context \"with version argument\" do\n          let(:version_arg) { \"1.0.0\" }\n\n          before { argv << version_arg }\n\n          it \"should create the provider\" do\n            expect(subject).to receive(:create_provider).with(org_name, box_name, version_arg, provider_arg, any_args)\n            subject.execute\n          end\n\n          it \"should not provide URL value\" do\n            expect(subject).to receive(:create_provider).with(org_name, box_name, version_arg, provider_arg, nil, any_args)\n            subject.execute\n          end\n\n          context \"with URL argument\" do\n            let(:url_arg) { \"provider-url\" }\n\n            before { argv << url_arg }\n\n            it \"should provide the URL value\" do\n              expect(subject).to receive(:create_provider).with(org_name, box_name, version_arg, provider_arg, url_arg, any_args)\n              subject.execute\n            end\n          end\n\n          context \"with checksum and checksum type flags\" do\n            let(:checksum_arg) { \"checksum\" }\n            let(:checksum_type_arg) { \"checksum_type\" }\n\n            before { argv.push(\"--checksum\").push(checksum_arg).push(\"--checksum-type\").push(checksum_type_arg) }\n\n            it \"should include the checksum options\" do\n              expect(subject).to receive(:create_provider).\n                with(org_name, box_name, version_arg, provider_arg, any_args, hash_including(checksum: checksum_arg, checksum_type: checksum_type_arg))\n              subject.execute\n            end\n          end\n\n          it \"should include detected host architecture by default\" do\n            host_arch = double(\"host-arch\")\n            expect(Vagrant::Util::Platform).to receive(:architecture).and_return(host_arch)\n\n            expect(subject).to receive(:create_provider) do |*_, opts|\n              expect(opts[:architecture]).to eq(host_arch)\n            end\n\n            subject.execute\n          end\n\n          context \"with architecture flag\" do\n            let(:arch_option) { \"test-arch\" }\n\n            before { argv.push(\"--architecture\").push(arch_option) }\n\n            it \"should include the architecture option\" do\n              expect(subject).to receive(:create_provider) do |*args, opts|\n                expect(opts[:architecture]).to eq(arch_option)\n              end\n\n              subject.execute\n            end\n          end\n\n          context \"with default architecture flag\" do\n            before { argv.push(default_arch_flag) }\n\n            context \"when flag is enabled\" do\n              let(:default_arch_flag) { \"--default-architecture\" }\n\n              it \"should include default architecture with true value\" do\n                expect(subject).to receive(:create_provider) do |*args, opts|\n                  expect(opts[:default_architecture]).to be(true)\n                end\n\n                subject.execute\n              end\n            end\n\n            context \"when flag is disabled\" do\n              let(:default_arch_flag) { \"--no-default-architecture\" }\n\n              it \"should include default architecture with false value\" do\n                expect(subject).to receive(:create_provider) do |*args, opts|\n                  expect(opts[:default_architecture]).to be(false)\n                end\n\n                subject.execute\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/provider/delete_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/provider/delete\")\n\ndescribe VagrantPlugins::CloudCommand::ProviderCommand::Command::Delete do\n  include_context \"unit\"\n\n  let(:access_token) { double(\"token\") }\n  let(:org_name) { \"my-org\" }\n  let(:box_name) { \"my-box\" }\n  let(:box_version) { \"1.0.0\" }\n  let(:box_version_provider) { \"my-provider\" }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\", versions: [version]) }\n  let(:version) { double(\"version\", version: box_version, providers: [provider]) }\n  let(:provider) { double(\"provider\", name: box_version_provider, architecture: architecture) }\n  let(:architecture) { double(\"test-architecture\") }\n\n  describe \"#delete_provider\" do\n    let(:options) { {} }\n    let(:env) { double(\"env\", ui: ui) }\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:argv) { [] }\n\n    before do\n      allow(env).to receive(:ui).and_return(ui)\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: access_token).\n        and_return(account)\n      allow(subject).to receive(:with_provider).\n        with(\n          account: account,\n          org: org_name,\n          box: box_name,\n          version: box_version,\n          provider: box_version_provider,\n          architecture: architecture\n        ).and_yield(provider)\n      allow(provider).to receive(:delete)\n    end\n\n    subject { described_class.new(argv, env) }\n\n    it \"should delete the provider\" do\n      expect(provider).to receive(:delete)\n      subject.delete_provider(org_name, box_name, box_version, box_version_provider, architecture, account, options)\n    end\n\n    it \"should return zero on success\" do\n      r = subject.delete_provider(org_name, box_name, box_version, box_version_provider, architecture, account, options)\n      expect(r).to eq(0)\n    end\n\n    context \"when error is encountered\" do\n      before do\n        expect(provider).to receive(:delete).and_raise(VagrantCloud::Error)\n      end\n\n      it \"should return non-zero\" do\n        r = subject.delete_provider(org_name, box_name, box_version, box_version_provider, architecture, account, options)\n        expect(r).to be_a(Integer)\n        expect(r).not_to eq(0)\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    let(:argv) { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n    let(:client) { double(\"client\", token: access_token) }\n\n    before do\n      allow(VagrantCloud::Account).to receive(:new).and_return(account)\n      allow(account).to receive(:organization).with(name: org_name).\n        and_return(organization)\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).\n        and_return(client)\n      allow(iso_env.ui).to receive(:ask).\n        and_return(\"y\")\n      allow(subject).to receive(:select_provider_architecture).\n        and_return(architecture)\n      allow(subject).to receive(:delete_provider)\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with box name argument\" do\n      let(:argv) { [\"#{org_name}/#{box_name}\"] }\n\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n\n      context \"with provider argument\" do\n        let(:provider_arg) { \"my-provider\" }\n\n        before { argv << provider_arg }\n\n        it \"shows help\" do\n          expect { subject.execute }.\n            to raise_error(Vagrant::Errors::CLIInvalidUsage)\n        end\n\n        context \"with version argument\" do\n          let(:version_arg) { \"1.0.0\" }\n\n          before { argv << version_arg }\n\n          it \"should delete the provider\" do\n            expect(subject).to receive(:delete_provider).\n              with(org_name, box_name, version_arg, provider_arg, architecture, account, anything)\n            subject.execute\n          end\n\n          it \"should prompt for confirmation\" do\n            expect(iso_env.ui).to receive(:ask).and_return(\"y\")\n            subject.execute\n          end\n\n          context \"with architecture argument\" do\n            let(:architecture_argument) { \"test-arch\" }\n\n            before { argv << architecture_argument }\n\n            it \"should delete the provider\" do\n              expect(subject).to receive(:delete_provider).\n                with(org_name, box_name, version_arg, provider_arg, architecture_argument, account, anything)\n              subject.execute\n            end\n\n            it \"should not attempt to select the architecture\" do\n              expect(subject).not_to receive(:select_provider_architecture)\n              subject.execute\n            end\n          end\n\n          context \"with multiple provider architectures\" do\n            let(:box_version) { double(\"box-version\", providers: providers) }\n            let(:providers) {\n              [\n                double(\"dummy-provider\", architecture: \"amd64\"),\n                double(\"dummy-provider\", architecture: \"arm64\")\n              ]\n            }\n\n            before do\n              expect(subject).to receive(:select_provider_architecture).and_call_original\n              expect(subject).to receive(:with_version).and_yield(box_version)\n            end\n\n            it \"should prompt for architecture selection\" do\n              expect(iso_env.ui).to receive(:ask).and_return(\"amd64\")\n              subject.execute\n            end\n          end\n\n          context \"with force flag\" do\n            before { argv << \"--force\" }\n\n            it \"should not prompt for confirmation\" do\n              expect(iso_env.ui).not_to receive(:ask)\n              subject.execute\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/provider/update_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/provider/update\")\n\ndescribe VagrantPlugins::CloudCommand::ProviderCommand::Command::Update do\n  include_context \"unit\"\n\n  let(:access_token) { double(\"token\") }\n  let(:org_name) { \"my-org\" }\n  let(:box_name) { \"my-box\" }\n  let(:box_version) { \"1.0.0\" }\n  let(:box_version_provider) { \"my-provider\" }\n  let(:box_architecture) { \"box-architecture\" }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\", versions: [version]) }\n  let(:version) { double(\"version\", version: box_version, provdiers: [provider]) }\n  let(:provider) { double(\"provider\", name: box_version_provider) }\n  let(:provider_url) { nil }\n\n  describe \"#update_provider\" do\n    let(:argv) { [] }\n    let(:options) { {} }\n    let(:env) { double(\"env\", ui: ui) }\n    let(:ui) { Vagrant::UI::Silent.new }\n\n    before do\n      allow(env).to receive(:ui).and_return(ui)\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: access_token).\n        and_return(account)\n      allow(subject).to receive(:with_provider).\n        with(\n          account: account,\n          org: org_name,\n          box: box_name,\n          version: box_version,\n          provider: box_version_provider,\n          architecture: box_architecture\n        ).\n        and_yield(provider)\n      allow(provider).to receive(:save)\n      allow(subject).to receive(:format_box_results)\n    end\n\n    subject { described_class.new(argv, env) }\n\n    it \"should update the provider\" do\n      expect(provider).to receive(:save)\n      subject.update_provider(\n        org_name,\n        box_name,\n        box_version,\n        box_version_provider,\n        box_architecture,\n        provider_url,\n        access_token,\n        options\n      )\n    end\n\n    it \"should return 0 on success\" do\n      result = subject.update_provider(\n        org_name,\n        box_name,\n        box_version,\n        box_version_provider,\n        box_architecture,\n        provider_url,\n        access_token,\n        options\n      )\n      expect(result).to eq(0)\n    end\n\n    it \"should return non-zero result on error\" do\n      expect(provider).to receive(:save).and_raise(VagrantCloud::Error)\n      result = subject.update_provider(\n        org_name,\n        box_name,\n        box_version,\n        box_version_provider,\n        box_architecture,\n        provider_url,\n        access_token,\n        options\n      )\n      expect(result).not_to eq(0)\n      expect(result).to be_a(Integer)\n    end\n\n    it \"should not update the URL when unset\" do\n      expect(provider).not_to receive(:url=)\n      subject.update_provider(\n        org_name,\n        box_name,\n        box_version,\n        box_version_provider,\n        box_architecture,\n        provider_url,\n        access_token,\n        options\n      )\n    end\n\n    context \"when URL is set\" do\n      let(:provider_url) { double(\"provider-url\") }\n\n      it \"should update the URL\" do\n        expect(provider).to receive(:url=).with(provider_url)\n        subject.update_provider(\n          org_name,\n          box_name,\n          box_version,\n          box_version_provider,\n          box_architecture,\n          provider_url,\n          access_token,\n          options\n        )\n      end\n    end\n\n    context \"with options set\" do\n      let(:checksum) { double(\"checksum\") }\n      let(:checksum_type) { double(\"checksum_type\") }\n      let(:options) { {} }\n\n      after do\n        subject.update_provider(\n          org_name,\n          box_name,\n          box_version,\n          box_version_provider,\n          box_architecture,\n          provider_url,\n          access_token,\n          options\n        )\n      end\n\n      it \"should not modify option controlled values when unset\" do\n        expect(provider).not_to receive(:checksum=)\n        expect(provider).not_to receive(:checksum_type=)\n        expect(provider).not_to receive(:architecture=)\n        expect(provider).not_to receive(:default_architecture=)\n      end\n\n      context \"with checksum options set\" do\n        let(:options) { {checksum: checksum, checksum_type: checksum_type} }\n\n        it \"should set checksum options before saving\" do\n          expect(provider).to receive(:checksum=).with(checksum)\n          expect(provider).to receive(:checksum_type=).with(checksum_type)\n        end\n      end\n\n      context \"with architecture option set\" do\n        let(:architecture) { double(\"architecture\") }\n        let(:options) { {architecture: architecture} }\n\n        it \"should set architecture before saving\" do\n          expect(provider).to receive(:architecture=).with(architecture)\n        end\n      end\n\n      context \"with default architecture option set\" do\n        context \"with true value\" do\n          let(:options) { {default_architecture: true} }\n\n          it \"should set default architecture to true\" do\n            expect(provider).to receive(:default_architecture=).with(true)\n          end\n        end\n\n        context \"with false value\" do\n          let(:options) { {default_architecture: false} }\n\n          it \"should set default architecture to false\" do\n            expect(provider).to receive(:default_architecture=).with(false)\n          end\n        end\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    let(:argv) { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n    let(:client) { double(\"client\", token: access_token) }\n\n    before do\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).\n        and_return(client)\n      allow(subject).to receive(:update_provider)\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with box name argument\" do\n      let(:argv) { [\"#{org_name}/#{box_name}\"] }\n\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n\n      context \"with provider argument\" do\n        let(:provider_arg) { \"my-provider\" }\n\n        before { argv << provider_arg }\n\n        it \"shows help\" do\n          expect { subject.execute }.\n            to raise_error(Vagrant::Errors::CLIInvalidUsage)\n        end\n\n        context \"with version argument\" do\n          let(:version_arg) { \"1.0.0\" }\n\n          before { argv << version_arg }\n\n          it \"shows help\" do\n            expect { subject.execute }.\n              to raise_error(Vagrant::Errors::CLIInvalidUsage)\n          end\n\n          context \"with architecture argument\" do\n            let(:architecture_arg) { \"box-architecture\" }\n\n            before { argv << architecture_arg }\n\n            it \"should create the provider\" do\n              expect(subject).\n                to receive(:update_provider).\n                     with(\n                       org_name,\n                       box_name,\n                       version_arg,\n                       provider_arg,\n                       architecture_arg,\n                       any_args\n                     )\n              subject.execute\n            end\n\n            it \"should not provide URL value\" do\n              expect(subject).\n                to receive(:update_provider).\n                     with(\n                       org_name,\n                       box_name,\n                       version_arg,\n                       provider_arg,\n                       architecture_arg,\n                       nil,\n                       any_args\n                     )\n              subject.execute\n            end\n\n            context \"with URL argument\" do\n              let(:url_arg) { \"provider-url\" }\n\n              before { argv << url_arg }\n\n              it \"should provide the URL value\" do\n                expect(subject).\n                  to receive(:update_provider).\n                       with(\n                         org_name,\n                         box_name,\n                         version_arg,\n                         provider_arg,\n                         architecture_arg,\n                         url_arg,\n                         any_args\n                       )\n                subject.execute\n              end\n            end\n\n            context \"with checksum and checksum type flags\" do\n              let(:checksum_arg) { \"checksum\" }\n              let(:checksum_type_arg) { \"checksum_type\" }\n\n              before { argv.push(\"--checksum\").push(checksum_arg).push(\"--checksum-type\").push(checksum_type_arg) }\n\n              it \"should include the checksum options\" do\n                expect(subject).\n                  to receive(:update_provider).\n                       with(\n                         org_name,\n                         box_name,\n                         version_arg,\n                         provider_arg,\n                         architecture_arg,\n                         any_args,\n                         hash_including(\n                           checksum: checksum_arg,\n                           checksum_type: checksum_type_arg\n                         )\n                       )\n                subject.execute\n              end\n            end\n\n            context \"with architecture flag\" do\n              let(:architecture_flag) { \"test-arch\" }\n\n              before { argv.push(\"--architecture\").push(architecture_flag) }\n\n              it \"should include the architecture flag\" do\n                expect(subject).to receive(:update_provider) do |*_, opts|\n                  expect(opts[:architecture]).to eq(architecture_flag)\n                end\n                subject.execute\n              end\n            end\n\n            context \"with default architecture flag\" do\n              context \"enabled\" do\n                before { argv.push(\"--default-architecture\") }\n\n                it \"should include default architecture set to true\" do\n                  expect(subject).to receive(:update_provider) do |*_, opts|\n                    expect(opts[:default_architecture]).to be(true)\n                  end\n                  subject.execute\n                end\n              end\n\n              context \"disabled\" do\n                before { argv.push(\"--no-default-architecture\") }\n\n                it \"should include default architecture set to false\" do\n                  expect(subject).to receive(:update_provider) do |*_, opts|\n                    expect(opts[:default_architecture]).to be(false)\n                  end\n                  subject.execute\n                end\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/provider/upload_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/provider/upload\")\n\ndescribe VagrantPlugins::CloudCommand::ProviderCommand::Command::Upload do\n  include_context \"unit\"\n\n  let(:access_token) { double(\"token\") }\n  let(:org_name) { \"my-org\" }\n  let(:box_name) { \"my-box\" }\n  let(:box_version) { \"1.0.0\" }\n  let(:box_version_provider) { \"my-provider\" }\n  let(:box_version_provider_arch) { \"amd64\" }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\", versions: [version]) }\n  let(:version) { double(\"version\", version: box_version, providers: [provider]) }\n  let(:provider) { double(\"provider\", name: box_version_provider, architecture: box_version_provider_arch) }\n  let(:provider_file) { double(\"provider-file\") }\n  let(:provider_file_size) { 1 }\n\n  describe \"#upload_provider\" do\n    let(:argv) { [] }\n    let(:options) { {} }\n    let(:env) { double(\"env\", ui: ui) }\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:upload_url) { double(\"upload-url\") }\n    let(:uploader) { double(\"uploader\") }\n\n    before do\n      allow(I18n).to receive(:t)\n      allow(env).to receive(:ui).and_return(ui)\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: access_token).\n        and_return(account)\n      allow(subject).to receive(:with_version).\n        with(account: account, org: org_name, box: box_name, version: box_version).\n        and_yield(version)\n      allow(provider).to receive(:upload).and_yield(upload_url)\n      allow(uploader).to receive(:upload!)\n      allow(Vagrant::UI::Prefixed).to receive(:new).with(ui, \"cloud\").and_return(ui)\n      allow(Vagrant::Util::Uploader).to receive(:new).and_return(uploader)\n      allow(File).to receive(:stat).with(provider_file).\n        and_return(double(\"provider-stat\", size: provider_file_size))\n    end\n\n    subject { described_class.new(argv, env) }\n\n    it \"should upload the provider file\" do\n      expect(provider).to receive(:upload)\n      subject.upload_provider(org_name, box_name, box_version, box_version_provider, box_version_provider_arch, provider_file, access_token, options)\n    end\n\n    it \"should return zero on success\" do\n      r = subject.upload_provider(org_name, box_name, box_version, box_version_provider, box_version_provider_arch, provider_file, access_token, options)\n      expect(r).to eq(0)\n    end\n\n    it \"should return non-zero on API error\" do\n      expect(provider).to receive(:upload).and_raise(VagrantCloud::Error)\n      r = subject.upload_provider(org_name, box_name, box_version, box_version_provider, box_version_provider_arch, provider_file, access_token, options)\n      expect(r).not_to eq(0)\n      expect(r).to be_a(Integer)\n    end\n\n    it \"should return non-zero on upload error\" do\n      expect(provider).to receive(:upload).and_raise(Vagrant::Errors::UploaderError)\n      r = subject.upload_provider(org_name, box_name, box_version, box_version_provider, box_version_provider_arch, provider_file, access_token, options)\n      expect(r).not_to eq(0)\n      expect(r).to be_a(Integer)\n    end\n\n    it \"should should upload via uploader\" do\n      expect(uploader).to receive(:upload!)\n      subject.upload_provider(org_name, box_name, box_version, box_version_provider, box_version_provider_arch, provider_file, access_token, options)\n    end\n\n    it \"should not use direct upload by default\" do\n      expect(provider).to receive(:upload) do |**args|\n        expect(args[:direct]).to be_falsey\n      end\n      subject.upload_provider(org_name, box_name, box_version, box_version_provider, box_version_provider_arch, provider_file, access_token, options)\n    end\n\n    context \"with direct option\" do\n      let(:options) { {direct: true} }\n\n      it \"should use direct upload\" do\n        expect(provider).to receive(:upload) do |**args|\n          expect(args[:direct]).to be_truthy\n        end\n        subject.upload_provider(org_name, box_name, box_version, box_version_provider, box_version_provider_arch, provider_file, access_token, options)\n      end\n\n      context \"when file size is 5GB\" do\n        let(:provider_file_size) { 5368709120 }\n\n        it \"should use direct upload\" do\n          expect(provider).to receive(:upload) do |**args|\n            expect(args[:direct]).to be_truthy\n          end\n          subject.upload_provider(org_name, box_name, box_version, box_version_provider, box_version_provider_arch, provider_file, access_token, options)\n        end\n      end\n\n      context \"when file size is greater than 5GB\" do\n        let(:provider_file_size) { 5368709121 }\n\n        it \"should disable direct upload\" do\n          expect(provider).to receive(:upload) do |**args|\n            expect(args[:direct]).to be_falsey\n          end\n          subject.upload_provider(org_name, box_name, box_version, box_version_provider, box_version_provider_arch, provider_file, access_token, options)\n        end\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    let(:argv) { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n    let(:client) { double(\"client\", token: access_token) }\n\n    before do\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).\n        and_return(client)\n      allow(subject).to receive(:upload_provider)\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with box name argument\" do\n      let(:argv) { [\"#{org_name}/#{box_name}\"] }\n\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n\n      context \"with provider argument\" do\n        let(:provider_arg) { \"my-provider\" }\n\n        before { argv << provider_arg }\n\n        it \"shows help\" do\n          expect { subject.execute }.\n            to raise_error(Vagrant::Errors::CLIInvalidUsage)\n        end\n\n        context \"with version argument\" do\n          let(:version_arg) { \"1.0.0\" }\n\n          before { argv << version_arg }\n\n          it \"shows help\" do\n            expect { subject.execute }.\n              to raise_error(Vagrant::Errors::CLIInvalidUsage)\n          end\n\n          context \"with architecture argument\" do\n            let(:arch_arg) { \"amd64\" }\n\n            before { argv << arch_arg }\n\n            it \"shows help\" do\n              expect { subject.execute }.\n                to raise_error(Vagrant::Errors::CLIInvalidUsage)\n            end\n\n            context \"with file argument\" do\n              let(:file_arg) { \"/dev/null/file\" }\n\n              before { argv << file_arg }\n\n              it \"should upload the provider file\" do\n                expect(subject).to receive(:upload_provider).\n                                     with(org_name, box_name, version_arg, provider_arg, arch_arg, file_arg, any_args)\n                subject.execute\n              end\n\n              it \"should do direct upload by default\" do\n                expect(subject).to receive(:upload_provider).\n                                     with(any_args, hash_including(direct: true))\n                subject.execute\n              end\n\n              context \"with --no-direct flag\" do\n                before { argv << \"--no-direct\" }\n\n                it \"should not perform direct upload\" do\n                  expect(subject).to receive(:upload_provider).\n                                       with(any_args, hash_including(direct: false))\n                  subject.execute\n                end\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/publish_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/publish\")\n\ndescribe VagrantPlugins::CloudCommand::Command::Publish do\n  include_context \"unit\"\n\n  let(:argv) { [] }\n  let(:iso_env) { double(\"iso_env\") }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\") }\n  let(:box_size) { 1 }\n  let(:version) { double(\"version\") }\n  let(:provider) { double(\"provider\") }\n  let(:uploader) { double(\"uploader\") }\n  let(:ui) { Vagrant::UI::Silent.new }\n  let(:upload_url) { double(\"upload_url\") }\n  let(:access_token) { double(\"access_token\") }\n  let(:default_architecture) { double(\"default-architecture\") }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(Vagrant::Util::Platform).to receive(:architecture).\n      and_return(default_architecture)\n    allow(iso_env).to receive(:ui).and_return(ui)\n    allow(File).to receive(:stat).with(box).\n      and_return(double(\"box_stat\", size: box_size))\n    allow(VagrantCloud::Account).to receive(:new).\n      with(custom_server: anything, access_token: anything).\n      and_return(account)\n    allow(provider).to receive(:architecture=).with(default_architecture)\n  end\n\n  describe \"#upload_box_file\" do\n    before do\n      allow(provider).to receive(:upload).and_yield(upload_url)\n      allow(uploader).to receive(:upload!)\n      allow(File).to receive(:absolute_path).and_return(box)\n      allow(Vagrant::Util::Uploader).to receive(:new).and_return(uploader)\n    end\n\n    it \"should get absolute path for box file\" do\n      expect(File).to receive(:absolute_path).and_return(box)\n      subject.upload_box_file(provider, box)\n    end\n\n    it \"should upload through provider\" do\n      expect(provider).to receive(:upload).and_return(upload_url)\n      subject.upload_box_file(provider, box)\n    end\n\n    it \"should create uploader with given url\" do\n      expect(Vagrant::Util::Uploader).to receive(:new).\n        with(upload_url, any_args).and_return(uploader)\n      subject.upload_box_file(provider, box)\n    end\n\n    it \"should upload with PUT method by default\" do\n      expect(Vagrant::Util::Uploader).to receive(:new).\n        with(upload_url, anything, hash_including(method: :put)).and_return(uploader)\n      subject.upload_box_file(provider, box)\n    end\n\n    context \"with direct upload option enabled\" do\n      it \"should upload with PUT method when direct upload option set\" do\n        expect(Vagrant::Util::Uploader).to receive(:new).\n          with(upload_url, anything, hash_including(method: :put)).and_return(uploader)\n        subject.upload_box_file(provider, box, direct_upload: true)\n      end\n\n      context \"with box size of 5GB\" do\n        let(:box_size) { 5368709120 }\n\n        it \"should upload using direct to storage option\" do\n          expect(provider).to receive(:upload).with(direct: true)\n          subject.upload_box_file(provider, box, direct_upload: true)\n        end\n      end\n\n      context \"with box size greater than 5GB\" do\n        let(:box_size) { 5368709121 }\n\n        it \"should disable direct to storage upload\" do\n          expect(provider).to receive(:upload).with(direct: false)\n          subject.upload_box_file(provider, box, direct_upload: true)\n        end\n      end\n    end\n  end\n\n  describe \"#release_version\" do\n    it \"should release the version\" do\n      expect(version).to receive(:release)\n      subject.release_version(version)\n    end\n  end\n\n  describe \"#set_box_info\" do\n    context \"with no options set\" do\n      let(:options) { {} }\n\n      it \"should not modify the box\" do\n        subject.set_box_info(box, options)\n      end\n    end\n\n    context \"with options set\" do\n      let(:priv) { double(\"private\") }\n      let(:short_description) { double(\"short_description\") }\n      let(:description) { double(\"description\") }\n\n      let(:options) {\n        {private: priv, description: description, short_description: short_description}\n      }\n\n      it \"should set info on box\" do\n        expect(box).to receive(:private=).with(priv)\n        expect(box).to receive(:short_description=).with(short_description)\n        expect(box).to receive(:description=).with(description)\n        subject.set_box_info(box, options)\n      end\n    end\n  end\n\n  describe \"#set_version_info\" do\n    context \"with no options set\" do\n      let(:options) { {} }\n\n      it \"should not modify the verison\" do\n        subject.set_version_info(version, options)\n      end\n    end\n\n    context \"with options set\" do\n      let(:options) { {version_description: version_description} }\n      let(:version_description) { double(\"version_description\") }\n\n      it \"should set info on version\" do\n        expect(version).to receive(:description=).with(version_description)\n        subject.set_version_info(version, options)\n      end\n    end\n  end\n\n  describe \"#set_provider_info\" do\n    context \"with no options set\" do\n      let(:options) { {} }\n\n      it \"should not modify the provider\" do\n        expect(provider).not_to receive(:url=)\n        expect(provider).not_to receive(:checksum=)\n        expect(provider).not_to receive(:checksum_type=)\n        expect(provider).not_to receive(:architecture=)\n        expect(provider).not_to receive(:default_architecture=)\n\n        subject.set_provider_info(provider, options)\n      end\n    end\n\n    context \"with options\" do\n      let(:options) { {} }\n      let(:url) { double(\"url\") }\n      let(:checksum) { double(\"checksum\") }\n      let(:checksum_type) { double(\"checksum_type\") }\n      let(:architecture) { double(\"architecture\") }\n\n      after { subject.set_provider_info(provider, options) }\n\n      context \"with url set\" do\n        before { options[:url] = url }\n\n        it \"should set url on provider\" do\n          expect(provider).to receive(:url=).with(url)\n        end\n      end\n\n      context \"with checksum set\" do\n        before do\n          options[:checksum] = checksum\n          options[:checksum_type] = checksum_type\n        end\n\n        it \"should set checksum on provider\" do\n          expect(provider).to receive(:checksum=).with(checksum)\n          expect(provider).to receive(:checksum_type=).with(checksum_type)\n        end\n      end\n\n      context \"with architecture set\" do\n        before { options[:architecture] = architecture }\n\n        it \"should set architecture on provider\" do\n          expect(provider).to receive(:architecture=).with(architecture)\n        end\n      end\n\n      context \"with default architecture set\" do\n        context \"with true value\" do\n          before { options[:default_architecture] = true }\n\n          it \"should set default architecture to true\" do\n            expect(provider).to receive(:default_architecture=).with(true)\n          end\n        end\n\n        context \"with false value\" do\n          before { options[:default_architecture] = false }\n\n          it \"should set default architecture to false\" do\n            expect(provider).to receive(:default_architecture=).with(false)\n          end\n        end\n      end\n    end\n  end\n\n  describe \"load_box_version\" do\n    let(:box_version) { \"1.0.0\" }\n\n    context \"when version exists\" do\n      before do\n        allow(box).to receive(:versions).and_return([version])\n        allow(version).to receive(:version).and_return(box_version)\n      end\n\n      it \"should return the existing version\" do\n        expect(subject.load_box_version(box, box_version)).to eq(version)\n      end\n    end\n\n    context \"when version does not exist\" do\n      let(:new_version) { double(\"new_version\") }\n\n      before do\n        allow(box).to receive(:versions).and_return([version])\n        allow(version).to receive(:version)\n      end\n\n      it \"should add a new version\" do\n        expect(box).to receive(:add_version).with(box_version).\n          and_return(new_version)\n        expect(subject.load_box_version(box, box_version)).to eq(new_version)\n      end\n    end\n  end\n\n  describe \"#load_box\" do\n    let(:org_name) { \"org-name\" }\n    let(:box_name) { \"my-box\" }\n\n    before do\n      allow(account).to receive(:organization).with(name: org_name).\n        and_return(organization)\n    end\n\n    context \"when box exists\" do\n      before do\n        allow(box).to receive(:name).and_return(box_name)\n        allow(organization).to receive(:boxes).and_return([box])\n      end\n\n      it \"should return the existing box\" do\n        expect(subject.load_box(org_name, box_name, access_token)).to eq(box)\n      end\n    end\n\n    context \"when box does not exist\" do\n      let(:new_box) { double(\"new_box\") }\n\n      before do\n        allow(organization).to receive(:boxes).and_return([])\n      end\n\n      it \"should add a new box to organization\" do\n        expect(organization).to receive(:add_box).with(box_name).\n          and_return(new_box)\n        expect(subject.load_box(org_name, box_name, access_token)).to eq(new_box)\n      end\n    end\n  end\n\n  context \"#execute\" do\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n    let(:client) { double(\"client\", token: \"1234token1234\") }\n    let(:action_runner) { double(\"action_runner\") }\n    let(:box_path) { \"path/to/the/virtualbox.box\" }\n    let(:full_box_path) { \"/full/#{box_path}\" }\n    let(:box) { full_box_path }\n\n    before do\n      allow(iso_env).to receive(:action_runner).\n        and_return(action_runner)\n      allow(subject).to receive(:client_login).\n        and_return(client)\n      allow(subject).to receive(:format_box_results)\n\n      allow(iso_env.ui).to receive(:ask).and_return(\"y\")\n      allow(File).to receive(:absolute_path).with(box_path)\n        .and_return(\"/full/#{box_path}\")\n      allow(File).to receive(:file?).with(box_path)\n        .and_return(true)\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"missing required arguments\" do\n      let(:argv) { [\"vagrant/box\", \"1.0.0\", \"virtualbox\"] }\n\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"missing box file\" do\n      let(:argv) { [\"vagrant/box\", \"1.0.0\", \"virtualbox\", \"/notreal/file.box\"] }\n\n      it \"raises an exception\" do\n        allow(File).to receive(:file?).and_return(false)\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::BoxFileNotExist)\n      end\n    end\n\n    context \"with arguments\" do\n      let(:org_name) { \"vagrant\" }\n      let(:box_name) { \"box\" }\n      let(:box_version) { \"1.0.0\" }\n      let(:box_version_provider) { \"virtualbox\" }\n\n      let(:argv) { [\n        \"#{org_name}/#{box_name}\", box_version, box_version_provider, box_path\n      ] }\n\n      before do\n        allow(account).to receive(:organization).with(name: org_name).\n          and_return(organization)\n        allow(subject).to receive(:load_box).and_return(box)\n        allow(subject).to receive(:load_box_version).and_return(version)\n        allow(subject).to receive(:load_version_provider).and_return(provider)\n        allow(provider).to receive(:upload)\n\n        allow(box).to receive(:save)\n      end\n\n      it \"should prompt user for confirmation\" do\n        expect(iso_env.ui).to receive(:ask).and_return(\"y\")\n        expect(subject.execute).to eq(0)\n      end\n\n      context \"when --force option is provided\" do\n        before { argv << \"--force\" }\n\n        it \"should not prompt user for confirmation\" do\n          expect(iso_env.ui).not_to receive(:ask)\n          expect(subject.execute).to eq(0)\n        end\n      end\n\n      context \"when --release option is provided\" do\n        before do\n          argv << \"--release\"\n        end\n\n        it \"should release box version when not released\" do\n          expect(version).to receive(:released?).and_return(false)\n          expect(version).to receive(:release)\n          expect(subject.execute).to eq(0)\n        end\n      end\n\n      context \"when Vagrant Cloud error is encountered\" do\n        before { expect(box).to receive(:save).and_raise(VagrantCloud::Error) }\n\n        it \"should return non-zero result\" do\n          result = subject.execute\n          expect(result).not_to eq(0)\n          expect(result).to be_a(Integer)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/search_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/search\")\n\ndescribe VagrantPlugins::CloudCommand::Command::Search do\n  include_context \"unit\"\n\n  let(:token) { double(\"token\") }\n  let(:argv)     { [] }\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  subject { described_class.new(argv, iso_env) }\n\n  describe \"#search\" do\n    let(:query) { double(\"query\") }\n    let(:options) { {} }\n    let(:account) { double(\"account\", searcher: searcher) }\n    let(:searcher) { double(\"searcher\") }\n    let(:results) { double(\"results\", boxes: boxes) }\n    let(:boxes) { [] }\n\n    before do\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: token).and_return(account)\n      allow(searcher).to receive(:search).and_return(results)\n      allow(subject).to receive(:format_search_results)\n    end\n\n    it \"should perform search\" do\n      expect(searcher).to receive(:search).with(hash_including(query: query)).and_return(results)\n      subject.search(query, token, options)\n    end\n\n    it \"should print a warning when no results are found\" do\n      expect(iso_env.ui).to receive(:warn)\n      subject.search(query, token, options)\n    end\n\n    context \"with valid options\" do\n      let(:architecture) { double(\"architecture\") }\n      let(:provider) { double(\"provider\") }\n      let(:sort) { double(\"sort\") }\n      let(:order) { double(\"order\") }\n      let(:limit) { double(\"limit\") }\n      let(:page) { double(\"page\") }\n\n      let(:options) { {\n        architecture: architecture,\n        provider: provider,\n        sort: sort,\n        order: order,\n        limit: limit,\n        page: page\n      } }\n\n      it \"should use options when performing search\" do\n        expect(searcher).to receive(:search) do |**args|\n          options.each_pair do |k, v|\n            expect(args[k]).to eq(v)\n          end\n          results\n        end\n        subject.search(query, token, options)\n      end\n\n      context \"with invalid options\" do\n        before { options[:invalid_option] = \"testing\" }\n\n        it \"should only pass supported options to search\" do\n          expect(searcher).to receive(:search) do |**args|\n            options.each_pair do |k, v|\n              next if k == :invalid_option\n              expect(args[k]).to eq(v)\n            end\n            expect(args.key?(:invalid_option)).to be_falsey\n            results\n          end\n          subject.search(query, token, options)\n        end\n      end\n    end\n\n    context \"with search results\" do\n      let(:results) { double(\"results\", boxes: [double(\"result\")]) }\n\n      it \"should format the results\" do\n        expect(subject).to receive(:format_search_results).with(results.boxes, any_args)\n        subject.search(query, token, options)\n      end\n\n      context \"with format options\" do\n        let(:options) { {short: true, json: false} }\n\n        it \"should pass options to format\" do\n          expect(subject).to receive(:format_search_results).with(results.boxes, true, false, iso_env)\n          subject.search(query, token, options)\n        end\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    let(:argv)     { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n\n    let(:client) { double(\"client\", token: token) }\n    let(:box) { double(\"box\") }\n\n    before do\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).and_return(client)\n      allow(subject).to receive(:search)\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with query argument\" do\n      let(:query_arg) { \"search-query\" }\n\n      before { argv << query_arg }\n\n      it \"should run the search\" do\n        expect(subject).to receive(:search).with(query_arg, any_args)\n        subject.execute\n      end\n\n      it \"should setup client login quietly by default\" do\n        expect(subject).to receive(:client_login).with(iso_env, hash_including(quiet: true))\n        subject.execute\n      end\n\n      context \"with --auth flag\" do\n        before { argv << \"--auth\" }\n\n        it \"should not setup login client quietly\" do\n          expect(subject).to receive(:client_login).with(iso_env, hash_including(quiet: false))\n          subject.execute\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/version/create_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/version/create\")\n\ndescribe VagrantPlugins::CloudCommand::VersionCommand::Command::Create do\n  include_context \"unit\"\n\n  let(:access_token) { double(\"token\") }\n  let(:org_name) { \"my-org\" }\n  let(:box_name) { \"my-box\" }\n  let(:box_version) { double(\"box_version\") }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\", versions: [version]) }\n  let(:version) { double(\"version\", version: box_version) }\n\n  describe \"#create_version\" do\n    let(:options) { {} }\n    let(:env) { double(\"env\", ui: ui) }\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:argv) { [] }\n\n    before do\n      allow(env).to receive(:ui).and_return(ui)\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: access_token).\n        and_return(account)\n      allow(subject).to receive(:with_box).with(account: account, org: org_name, box: box_name).\n        and_yield(box)\n      allow(account).to receive(:organization).with(name: org_name).\n        and_return(organization)\n      allow(box).to receive(:add_version).and_return(version)\n      allow(version).to receive(:save)\n      allow(subject).to receive(:format_box_results)\n    end\n\n    subject { described_class.new(argv, env) }\n\n    it \"should add a new version to the box\" do\n      expect(box).to receive(:add_version).with(box_version)\n      subject.create_version(org_name, box_name, box_version, access_token, options)\n    end\n\n    it \"should save the new version\" do\n      expect(version).to receive(:save)\n      subject.create_version(org_name, box_name, box_version, access_token, options)\n    end\n\n    it \"should return 0 on success\" do\n      result = subject.create_version(org_name, box_name, box_version, access_token, options)\n      expect(result).to eq(0)\n    end\n\n    it \"should return non-zero on error\" do\n      expect(version).to receive(:save).and_raise(VagrantCloud::Error)\n      result = subject.create_version(org_name, box_name, box_version, access_token, options)\n      expect(result).not_to eq(0)\n      expect(result).to be_a(Integer)\n    end\n\n    context \"with description option set\" do\n      let(:description) { double(\"description\") }\n      let(:options) { {description: description} }\n\n      it \"should set description on version\" do\n        expect(version).to receive(:description=).with(description)\n        subject.create_version(org_name, box_name, box_version, access_token, options)\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    let(:argv) { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n    let(:client) { double(\"client\", token: access_token) }\n\n    before do\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).\n        and_return(client)\n      allow(subject).to receive(:create_version)\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with box name argument\" do\n      let(:argv) { [\"#{org_name}/#{box_name}\"] }\n\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n\n      context \"with version argument\" do\n        let(:version_arg) { \"1.0.0\" }\n\n        before { argv << version_arg }\n\n        it \"should create the version\" do\n          expect(subject).to receive(:create_version).with(org_name, box_name, version_arg, any_args)\n          subject.execute\n        end\n\n        context \"with description flag\" do\n          let(:description) { \"my-description\" }\n\n          before { argv.push(\"--description\").push(description) }\n\n          it \"should create version with description option set\" do\n            expect(subject).to receive(:create_version).\n              with(org_name, box_name, version_arg, access_token, hash_including(description: description))\n            subject.execute\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/version/delete_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/version/delete\")\n\ndescribe VagrantPlugins::CloudCommand::VersionCommand::Command::Delete do\n  include_context \"unit\"\n\n  let(:access_token) { double(\"token\") }\n  let(:org_name) { \"my-org\" }\n  let(:box_name) { \"my-box\" }\n  let(:box_version) { double(\"box_version\") }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\", versions: [version]) }\n  let(:version) { double(\"version\", version: box_version) }\n\n  describe \"#delete_version\" do\n    let(:options) { {} }\n    let(:env) { double(\"env\", ui: ui) }\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:argv) { [] }\n\n    before do\n      allow(env).to receive(:ui).and_return(ui)\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: access_token).\n        and_return(account)\n      allow(subject).to receive(:with_version).\n        with(account: account, org: org_name, box: box_name, version: box_version).\n        and_yield(version)\n      allow(version).to receive(:delete)\n    end\n\n    subject { described_class.new(argv, env) }\n\n    it \"should delete the version\" do\n      expect(version).to receive(:delete)\n      subject.delete_version(org_name, box_name, box_version, access_token, options)\n    end\n\n    it \"should return 0 on success\" do\n      result = subject.delete_version(org_name, box_name, box_version, access_token, options)\n      expect(result).to eq(0)\n    end\n\n    it \"should return non-zero on error\" do\n      expect(version).to receive(:delete).and_raise(VagrantCloud::Error)\n      result = subject.delete_version(org_name, box_name, box_version, access_token, options)\n      expect(result).not_to eq(0)\n      expect(result).to be_a(Integer)\n    end\n  end\n\n  describe \"#execute\" do\n    let(:argv) { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n    let(:client) { double(\"client\", token: access_token) }\n\n    before do\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).\n        and_return(client)\n      allow(iso_env.ui).to receive(:ask).\n        and_return(\"y\")\n      allow(subject).to receive(:delete_version)\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with box name argument\" do\n      let(:argv) { [\"#{org_name}/#{box_name}\"] }\n\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n\n      context \"with version argument\" do\n        let(:version_arg) { \"1.0.0\" }\n\n        before { argv << version_arg }\n\n        it \"should delete the version\" do\n          expect(subject).to receive(:delete_version).\n            with(org_name, box_name, version_arg, access_token, anything)\n          subject.execute\n        end\n\n        it \"should prompt for confirmation\" do\n          expect(iso_env.ui).to receive(:ask).and_return(\"y\")\n          subject.execute\n        end\n\n        context \"with force flag\" do\n          before { argv << \"--force\" }\n\n          it \"should not prompt for confirmation\" do\n            expect(iso_env.ui).not_to receive(:ask)\n            subject.execute\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/version/release_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/version/release\")\n\ndescribe VagrantPlugins::CloudCommand::VersionCommand::Command::Release do\n  include_context \"unit\"\n\n  let(:access_token) { double(\"token\") }\n  let(:org_name) { \"my-org\" }\n  let(:box_name) { \"my-box\" }\n  let(:box_version) { double(\"box_version\") }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\", versions: [version]) }\n  let(:version) { double(\"version\", version: box_version) }\n\n  describe \"#release_version\" do\n    let(:options) { {} }\n    let(:env) { double(\"env\", ui: ui) }\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:argv) { [] }\n\n    before do\n      allow(env).to receive(:ui).and_return(ui)\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: access_token).\n        and_return(account)\n      allow(subject).to receive(:with_version).\n        with(account: account, org: org_name, box: box_name, version: box_version).\n        and_yield(version)\n      allow(version).to receive(:release)\n    end\n\n    subject { described_class.new(argv, env) }\n\n    it \"should release the version\" do\n      expect(version).to receive(:release)\n      subject.release_version(org_name, box_name, box_version, access_token, options)\n    end\n\n    it \"should return 0 on success\" do\n      result = subject.release_version(org_name, box_name, box_version, access_token, options)\n      expect(result).to eq(0)\n    end\n\n    it \"should return a non-zero on error\" do\n      expect(version).to receive(:release).and_raise(VagrantCloud::Error)\n      result = subject.release_version(org_name, box_name, box_version, access_token, options)\n      expect(result).not_to eq(0)\n      expect(result).to be_a(Integer)\n    end\n  end\n\n  describe \"#execute\" do\n    let(:argv)     { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n    let(:client) { double(\"client\", token: access_token) }\n\n    before do\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).\n        and_return(client)\n      allow(subject).to receive(:release_version)\n      allow(iso_env.ui).to receive(:ask).and_return(\"y\")\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with box name argument\" do\n      let(:argv) { [\"#{org_name}/#{box_name}\"] }\n\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n\n      context \"with version argument\" do\n        let(:version_arg) { \"1.0.0\" }\n\n        before { argv << version_arg }\n\n        it \"should release the version\" do\n          expect(subject).to receive(:release_version).\n            with(org_name, box_name, version_arg, access_token, anything)\n          subject.execute\n        end\n\n        it \"should prompt for confirmation\" do\n          expect(iso_env.ui).to receive(:ask).and_return(\"y\")\n          subject.execute\n        end\n\n        context \"with force flag\" do\n          before { argv << \"--force\" }\n\n          it \"should not prompt for confirmation\" do\n            expect(iso_env.ui).not_to receive(:ask)\n            subject.execute\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/version/revoke_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/version/revoke\")\n\ndescribe VagrantPlugins::CloudCommand::VersionCommand::Command::Revoke do\n  include_context \"unit\"\n\n  let(:access_token) { double(\"token\") }\n  let(:org_name) { \"my-org\" }\n  let(:box_name) { \"my-box\" }\n  let(:box_version) { double(\"box_version\") }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\", versions: [version]) }\n  let(:version) { double(\"version\", version: box_version) }\n\n  describe \"#revoke_version\" do\n    let(:options) { {} }\n    let(:env) { double(\"env\", ui: ui) }\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:argv) { [] }\n\n    before do\n      allow(env).to receive(:ui).and_return(ui)\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: access_token).\n        and_return(account)\n      allow(subject).to receive(:with_version).\n        with(account: account, org: org_name, box: box_name, version: box_version).\n        and_yield(version)\n      allow(version).to receive(:revoke)\n      allow(subject).to receive(:format_box_results)\n    end\n\n    subject { described_class.new(argv, env) }\n\n    it \"should revoke the version\" do\n      expect(version).to receive(:revoke)\n      subject.revoke_version(org_name, box_name, box_version, access_token, options)\n    end\n\n    it \"should return 0 on success\" do\n      result = subject.revoke_version(org_name, box_name, box_version, access_token, options)\n      expect(result).to eq(0)\n    end\n\n    it \"should return non-zero on error\" do\n      expect(version).to receive(:revoke).and_raise(VagrantCloud::Error)\n      result = subject.revoke_version(org_name, box_name, box_version, access_token, options)\n      expect(result).not_to eq(0)\n      expect(result).to be_a(Integer)\n    end\n  end\n\n  describe \"#execute\" do\n    let(:argv)     { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n    let(:client) { double(\"client\", token: access_token) }\n\n    before do\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).\n        and_return(client)\n      allow(subject).to receive(:revoke_version)\n      allow(iso_env.ui).to receive(:ask).\n        and_return(\"y\")\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with box name argument\" do\n      let(:argv) { [\"#{org_name}/#{box_name}\"] }\n\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n\n      context \"with version argument\" do\n        let(:version_arg) { \"1.0.0\" }\n\n        before { argv << version_arg }\n\n        it \"should revoke the version\" do\n          expect(subject).to receive(:revoke_version).\n            with(org_name, box_name, version_arg, access_token, anything)\n          subject.execute\n        end\n\n        it \"should prompt for confirmation\" do\n          expect(iso_env.ui).to receive(:ask).and_return(\"y\")\n          subject.execute\n        end\n\n        context \"with force flag\" do\n          before { argv << \"--force\" }\n\n          it \"should not prompt for confirmation\" do\n            expect(iso_env.ui).not_to receive(:ask)\n            subject.execute\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/cloud/version/update_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/cloud/version/update\")\n\ndescribe VagrantPlugins::CloudCommand::VersionCommand::Command::Update do\n  include_context \"unit\"\n\n  let(:access_token) { double(\"token\") }\n  let(:org_name) { \"my-org\" }\n  let(:box_name) { \"my-box\" }\n  let(:box_version) { double(\"box_version\") }\n  let(:account) { double(\"account\") }\n  let(:organization) { double(\"organization\") }\n  let(:box) { double(\"box\", versions: [version]) }\n  let(:version) { double(\"version\", version: box_version) }\n\n  describe \"#update_version\" do\n    let(:options) { {} }\n    let(:env) { double(\"env\", ui: ui) }\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:argv) { [] }\n\n    before do\n      allow(env).to receive(:ui).and_return(ui)\n      allow(VagrantCloud::Account).to receive(:new).\n        with(custom_server: anything, access_token: access_token).\n        and_return(account)\n      allow(subject).to receive(:with_version).\n        with(account: account, org: org_name, box: box_name, version: box_version).\n        and_yield(version)\n      allow(version).to receive(:save)\n      allow(subject).to receive(:format_box_results)\n    end\n\n    subject { described_class.new(argv, env) }\n\n    it \"should update the version\" do\n      expect(version).to receive(:save)\n      subject.update_version(org_name, box_name, box_version, access_token, options)\n    end\n\n    it \"should return 0 on success\" do\n      result = subject.update_version(org_name, box_name, box_version, access_token, options)\n      expect(result).to eq(0)\n    end\n\n    it \"should return non-zero result on error\" do\n      expect(version).to receive(:save).and_raise(VagrantCloud::Error)\n      result = subject.update_version(org_name, box_name, box_version, access_token, options)\n      expect(result).not_to eq(0)\n      expect(result).to be_a(Integer)\n    end\n\n    context \"with options set\" do\n      let(:description) { double(\"description\") }\n      let(:options) { {description: description} }\n\n      it \"should set version info before saving\" do\n        expect(version).to receive(:description=).with(description)\n        subject.update_version(org_name, box_name, box_version, access_token, options)\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    let(:argv)     { [] }\n    let(:iso_env) do\n      # We have to create a Vagrantfile so there is a root path\n      env = isolated_environment\n      env.vagrantfile(\"\")\n      env.create_vagrant_env\n    end\n\n    subject { described_class.new(argv, iso_env) }\n\n    let(:action_runner) { double(\"action_runner\") }\n    let(:client) { double(\"client\", token: access_token) }\n\n    before do\n      allow(iso_env).to receive(:action_runner).and_return(action_runner)\n      allow(subject).to receive(:client_login).\n        and_return(client)\n      allow(subject).to receive(:update_version)\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with name argument\" do\n      let(:argv) { [\"#{org_name}/#{box_name}\"] }\n\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n\n      context \"with version argument\" do\n        let(:version_arg) { \"1.0.0\" }\n\n        before { argv << version_arg }\n\n        it \"should update the version\" do\n          expect(subject).to receive(:update_version).\n            with(org_name, box_name, version_arg, access_token, anything)\n          subject.execute\n        end\n\n        context \"with description flag\" do\n          let(:description) { \"my-description\" }\n\n          before { argv.push(\"--description\").push(description) }\n\n          it \"should update version with description\" do\n            expect(subject).to receive(:update_version).\n              with(org_name, box_name, version_arg, access_token, hash_including(description: description))\n            subject.execute\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/destroy/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/destroy/command\")\n\ndescribe VagrantPlugins::CommandDestroy::Command do\n  include_context \"unit\"\n\n  let(:entry_klass) { Vagrant::MachineIndex::Entry }\n  let(:argv)     { [] }\n  let(:vagrantfile_content) { \"\" }\n  let(:iso_env) do\n    env = isolated_environment\n    env.vagrantfile(vagrantfile_content)\n    env.create_vagrant_env\n  end\n\n  subject { described_class.new(argv, iso_env) }\n\n  let(:action_runner) { double(\"action_runner\") }\n\n  def new_entry(name)\n    entry_klass.new.tap do |e|\n      e.name = name\n      e.vagrantfile_path = \"/bar\"\n    end\n  end\n\n  before do\n    allow(iso_env).to receive(:action_runner).and_return(action_runner)\n  end\n\n  context \"with no argument\" do\n    before { @state_var = :running }\n    let(:vagrantfile_content){ \"Vagrant.configure(2){|config| config.vm.box = 'dummy'}\" }\n    let(:state) { double(\"state\", id: :running) }\n    let(:machine) do\n      iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|\n        allow(m).to receive(:state).and_return(state)\n        allow(m).to receive(:name).and_return(\"default\")\n      end\n    end\n\n    before do\n      allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n    end\n\n    it \"should destroy the default box\" do\n      allow_any_instance_of(Vagrant::BatchAction).to receive(:action) .with(machine, :destroy, force_confirm_destroy: false, force_halt: true)\n\n      expect(machine.state).to receive(:id).and_return(:running)\n      expect(machine.state).to receive(:id).and_return(:dead)\n      expect(subject.execute).to eq(0)\n    end\n\n    it \"exits 0 if vms are not created\" do\n      allow_any_instance_of(Vagrant::BatchAction).to receive(:action) .with(machine, :destroy, anything)\n      allow(machine.state).to receive(:id).and_return(:not_created)\n\n      expect(machine.state).to receive(:id).and_return(:not_created)\n      expect(subject.execute).to eq(0)\n    end\n\n    it \"exits 1 if a vms destroy was declined\" do\n      allow_any_instance_of(Vagrant::BatchAction).to receive(:action) .with(machine, :destroy, anything)\n\n      expect(machine.state).to receive(:id).and_return(:running)\n      expect(machine.state).to receive(:id).and_return(:running)\n      expect(subject.execute).to eq(1)\n    end\n\n    context \"with multiple machines\" do\n      let(:vagrantfile_content) do \n        <<-VF\n        Vagrant.configure(\"2\") do |config|\n          config.vm.box = \"base\"\n          config.vm.define :machine1\n          config.vm.define :machine2\n        end\n        VF\n      end\n\n      let(:state2) { \"\" }\n\n      let(:machine2) do\n        iso_env.machine(iso_env.machine_names[1], :dummy).tap do |m|\n          allow(m).to receive(:state).and_return(state2)\n          allow(m).to receive(:name).and_return(\"not_default\")\n        end\n      end\n\n      before do\n        allow(subject).to receive(:with_target_vms).and_yield(machine).and_yield(machine2)\n        allow_any_instance_of(Vagrant::BatchAction).to receive(:action) .with(machine2, :destroy, anything)\n        allow_any_instance_of(Vagrant::BatchAction).to receive(:action) .with(machine, :destroy, anything)\n      end\n      \n      context \"second machine is not created\" do\n        let(:state2) { double(\"state\", id: :not_created) }\n        let(:state) { double(\"state\", id: :not_created) }\n\n\n        it \"exits 0 if vms are successfully destroyed\" do\n          expect(machine.state).to receive(:id).and_return(:running)\n          expect(machine.state).to receive(:id).and_return(:dead)\n          expect(machine2.state).to receive(:id).and_return(:not_created)\n          expect(subject.execute).to eq(0)\n        end\n\n        it \"exits 0 if vms are not created\" do\n          expect(machine.state).to receive(:id).and_return(:not_created)\n          expect(machine2.state).to receive(:id).and_return(:not_created)\n          expect(subject.execute).to eq(0)\n        end\n      end\n      \n      context \"second machine is running\" do\n        let(:state2) { double(\"state\", id: :running) }\n        \n        it \"exits 0 if vms are not successfully destroyed\" do\n          expect(machine.state).to receive(:id).and_return(:running)\n          expect(machine.state).to receive(:id).and_return(:dead)\n          expect(machine2.state).to receive(:id).and_return(:running)\n          expect(machine2.state).to receive(:id).and_return(:dead)\n          expect(subject.execute).to eq(0)\n        end\n\n        it \"exits 1 if vms are not successfully destroyed\" do\n          expect(machine.state).to receive(:id).and_return(:running)\n          expect(machine2.state).to receive(:id).and_return(:running)\n          expect(subject.execute).to eq(1)\n        end\n\n        it \"exits 2 if some vms are not successfully destroyed\" do\n          expect(machine.state).to receive(:id).and_return(:running)\n          expect(machine.state).to receive(:id).and_return(:dead)\n          expect(machine2.state).to receive(:id).and_return(:running)\n          expect(subject.execute).to eq(2)\n        end\n      end\n    end\n\n    context \"with VAGRANT_DEFAULT_PROVIDER set\" do\n      before do\n        if ENV[\"VAGRANT_DEFAULT_PROVIDER\"]\n          @original_default = ENV[\"VAGRANT_DEFAULT_PROVIDER\"]\n        end\n        ENV[\"VAGRANT_DEFAULT_PROVIDER\"] = \"unknown\"\n      end\n      after do\n        if @original_default\n          ENV[\"VAGRANT_DEFAULT_PROVIDER\"] = @original_default\n        else\n          ENV.delete(\"VAGRANT_DEFAULT_PROVIDER\")\n        end\n      end\n\n      it \"should attempt to use dummy provider\" do\n        expect{ subject.execute }.to raise_error(Vagrant::Errors::UnimplementedProviderAction)\n      end\n    end\n  end\n\n  context \"with --parallel set\" do\n    let(:argv){ [\"--parallel\", \"--force\"] }\n\n    it \"passes in true to batch\" do\n      batch = double(\"environment_batch\")\n      expect(iso_env).to receive(:batch).with(true).and_yield(batch)\n      expect(batch).to receive(:action).with(anything, :destroy, anything) do |machine,action,args|\n        expect(machine).to be_kind_of(Vagrant::Machine)\n        expect(action).to eq(:destroy)\n      end\n      subject.execute\n    end\n  end\n\n  context \"with --graceful set\" do\n    let(:argv){ [\"--graceful\", \"--force\"] }\n\n    it \"passes in true to batch\" do\n      batch = double(\"environment_batch\")\n      expect(iso_env).to receive(:batch).and_yield(batch)\n      expect(batch).to receive(:action).with(anything, :destroy, force_confirm_destroy: true, force_halt: false)\n      subject.execute\n    end\n  end\n\n  context \"with a global machine\" do\n    let(:argv){ [\"1234\"] }\n\n    it \"destroys a vm with an id\" do\n\n      global_env = isolated_environment\n      global_env.vagrantfile(\"Vagrant.configure(2){|config| config.vm.box = 'dummy'}\")\n      global_venv = global_env.create_vagrant_env\n      global_machine = global_venv.machine(global_venv.machine_names[0], :dummy)\n      global_machine.id = \"1234\"\n      global = new_entry(global_machine.name)\n      global.provider = \"dummy\"\n      global.vagrantfile_path = global_env.workdir\n      locked = iso_env.machine_index.set(global)\n      iso_env.machine_index.release(locked)\n\n      allow(subject).to receive(:with_target_vms) { |&block| block.call global_machine }\n\n\n      batch = double(\"environment_batch\")\n      expect(iso_env).to receive(:batch).and_yield(batch)\n      expect(batch).to receive(:action).with(global_machine, :destroy, anything) do |machine,action,args|\n        expect(machine).to be_kind_of(Vagrant::Machine)\n        expect(action).to eq(:destroy)\n      end\n      subject.execute\n    end\n  end\n\n  context \"with an argument\" do\n    let(:vagrantfile_content) do\n        <<-VF\n        Vagrant.configure(\"2\") do |config|\n          config.vm.define \"app\"\n          config.vm.define \"db\"\n        end\n        VF\n    end\n    let(:argv){ [\"app\"] }\n    let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n    it \"destroys a vm\" do\n      batch = double(\"environment_batch\")\n      expect(iso_env).to receive(:batch).and_yield(batch)\n      expect(batch).to receive(:action).with(machine, :destroy, anything) do |machine,action,args|\n        expect(machine).to be_kind_of(Vagrant::Machine)\n        expect(action).to eq(:destroy)\n      end\n      subject.execute\n    end\n\n    context \"with machine that does not exist\" do\n      let(:argv){ [\"notweb\"] }\n      it \"raises an exception\" do\n        expect { subject.execute }.to raise_error(Vagrant::Errors::MachineNotFound)\n      end\n    end\n\n    context \"with an invalid argument\" do\n      let(:argv){ [\"\"] }\n      it \"raises an exception\" do\n        expect { subject.execute }.to raise_error(Vagrant::Errors::MachineNotFound)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/global-status/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/global-status/command\")\n\ndescribe VagrantPlugins::CommandGlobalStatus::Command do\n  include_context \"unit\"\n\n  let(:entry_klass) { Vagrant::MachineIndex::Entry }\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env(env_opts)\n  end\n\n  let(:env_opts) { {} }\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:argv)     { [] }\n\n\n  def new_entry(name)\n    entry_klass.new.tap do |e|\n      e.name = name\n      e.vagrantfile_path = \"/bar\"\n    end\n  end\n\n  subject { described_class.new(argv, iso_env) }\n\n  describe \"execute with no args\" do\n    it \"succeeds\" do\n      # Let's put some things in the index\n      iso_env.machine_index.set(new_entry(\"foo\"))\n      iso_env.machine_index.set(new_entry(\"bar\"))\n\n      expect(subject.execute).to eq(0)\n    end\n  end\n\n  describe \"with --machine-readable\" do\n    let(:env_opts) { {ui_class: Vagrant::UI::MachineReadable} }\n\n    before do\n      iso_env.machine_index.set(new_entry(\"foo\"))\n      iso_env.machine_index.set(new_entry(\"bar\"))\n      allow($stdout).to receive(:puts)\n    end\n\n    after { subject.execute }\n\n    it \"should include the machine id\" do\n      expect($stdout).to receive(:puts).with(/,machine-id,/).twice\n    end\n\n    it \"should include the machine state\" do\n      expect($stdout).to receive(:puts).with(/,state,/).twice\n    end\n\n    it \"should include the machine count\" do\n      expect($stdout).to receive(:puts).with(/machine-count,2/)\n    end\n\n    it \"should include the machine home path\" do\n      expect($stdout).to receive(:puts).with(/,machine-home,/).twice\n    end\n\n    it \"should include the provider name\" do\n      expect($stdout).to receive(:puts).with(/,provider-name,/).twice\n    end\n  end\n\n  describe \"execute with --prune\" do\n    let(:argv) { [\"--prune\"] }\n\n    it \"removes invalid entries\" do\n      # Invalid entry because vagrantfile path is gone\n      entryA = new_entry(\"A\")\n      entryA.vagrantfile_path = \"/i/dont/exist\"\n      locked = iso_env.machine_index.set(entryA)\n      iso_env.machine_index.release(locked)\n\n      # Invalid entry because that specific machine doesn't exist anymore.\n      entryB_env = isolated_environment\n      entryB_env.vagrantfile(\"\")\n      entryB = new_entry(\"B\")\n      entryB.vagrantfile_path = entryB_env.workdir\n      locked = iso_env.machine_index.set(entryB)\n      iso_env.machine_index.release(locked)\n\n      # Valid entry because the machine does exist\n      entryC_env = isolated_environment\n      entryC_env.vagrantfile(\"\")\n      entryC_venv = entryC_env.create_vagrant_env\n      entryC_machine = entryC_venv.machine(entryC_venv.machine_names[0], :dummy)\n      entryC_machine.id = \"foo\"\n      entryC = new_entry(entryC_machine.name)\n      entryC.provider = \"dummy\"\n      entryC.vagrantfile_path = entryC_env.workdir\n      locked = iso_env.machine_index.set(entryC)\n      iso_env.machine_index.release(locked)\n\n      expect(subject.execute).to eq(0)\n\n      # Reload the data and see that we got things correct\n      entries = []\n      iso_env.machine_index.each(true) { |e| entries << e }\n\n      expect(entries.length).to eq(1)\n      expect(entries[0].name).to eq(entryC_machine.name.to_s)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/init/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\nrequire_relative \"../../../../../plugins/commands/init/command\"\n\ndescribe VagrantPlugins::CommandInit::Command do\n  include_context \"unit\"\n  include_context \"command plugin helpers\"\n\n  let(:iso_env) do\n    isolated_environment\n  end\n\n  let(:env) do\n    iso_env.create_vagrant_env\n  end\n\n  let(:vagrantfile_path) { File.join(env.cwd, \"Vagrantfile\") }\n\n  before do\n    allow(Vagrant.plugin(\"2\").manager).to receive(:commands).and_return({})\n  end\n\n  after do\n    iso_env.close\n  end\n\n  describe \"#execute\" do\n    it \"creates a Vagrantfile with no args\" do\n      described_class.new([], env).execute\n      contents = File.read(vagrantfile_path)\n      expect(contents).to match(/config.vm.box = \"base\"/)\n    end\n\n    it \"creates a minimal Vagrantfile\" do\n      described_class.new([\"-m\"], env).execute\n      contents = File.read(vagrantfile_path)\n      expect(contents).to_not match(/provision/)\n    end\n\n    it \"creates a custom Vagrantfile using a relative template path\" do\n      described_class.new([\"--template\", \"test/unit/templates/commands/init/Vagrantfile\"], env).execute\n      contents = File.read(vagrantfile_path)\n      expect(contents).to match(/config.vm.hostname = \"vagrant.dev\"/)\n    end\n\n    it \"creates a custom Vagrantfile using an absolute template path\" do\n      described_class.new([\"--template\", ::Vagrant.source_root.join(\"test/unit/templates/commands/init/Vagrantfile\").to_s], env).execute\n      contents = File.read(vagrantfile_path)\n      expect(contents).to match(/config.vm.hostname = \"vagrant.dev\"/)\n    end\n\n    it \"creates a custom Vagrantfile using a provided template with the extension included\" do\n      described_class.new([\"--template\", ::Vagrant.source_root.join(\"test/unit/templates/commands/init/Vagrantfile.erb\").to_s], env).execute\n      contents = File.read(vagrantfile_path)\n      expect(contents).to match(/config.vm.hostname = \"vagrant.dev\"/)\n    end\n\n    it \"creates a custom Vagrant file using a template provided from the environment\" do\n      with_temp_env(\"VAGRANT_DEFAULT_TEMPLATE\" => \"test/unit/templates/commands/init/Vagrantfile\") do\n        described_class.new([], env).execute\n      end\n      contents = File.read(vagrantfile_path)\n      expect(contents).to match(/config.vm.hostname = \"vagrant.dev\"/)\n    end\n\n    it \"ignores the environmentally-set default template when a template is explicitly set\" do\n      with_temp_env(\"VAGRANT_DEFAULT_TEMPLATE\" => \"/this_file_does_not_exist\") do\n        described_class.new([\"--template\", \"test/unit/templates/commands/init/Vagrantfile\"], env).execute\n      end\n      contents = File.read(vagrantfile_path)\n      expect(contents).to match(/config.vm.hostname = \"vagrant.dev\"/)\n    end\n\n    it \"ignores the -m option when using a provided template\" do\n      described_class.new([\"-m\", \"--template\", ::Vagrant.source_root.join(\"test/unit/templates/commands/init/Vagrantfile\").to_s], env).execute\n      contents = File.read(vagrantfile_path)\n      expect(contents).to match(/config.vm.hostname = \"vagrant.dev\"/)\n    end\n\n    it \"raises an appropriate exception when the template file can't be found\" do\n      expect {\n      described_class.new([\"--template\", \"./a/b/c/template\"], env).execute\n      }.to raise_error(Vagrant::Errors::VagrantfileTemplateNotFoundError)\n    end\n\n    it \"does not overwrite an existing Vagrantfile\" do\n      # Create an existing Vagrantfile\n      File.open(File.join(env.cwd, \"Vagrantfile\"), \"w+\") { |f| f.write(\"\") }\n\n      expect {\n        described_class.new([], env).execute\n      }.to raise_error(Vagrant::Errors::VagrantfileExistsError)\n    end\n\n    it \"overwrites an existing Vagrantfile with force\" do\n      # Create an existing Vagrantfile\n      File.open(File.join(env.cwd, \"Vagrantfile\"), \"w+\") { |f| f.write(\"\") }\n\n      expect {\n        described_class.new([\"-f\"], env).execute\n      }.to_not raise_error\n\n      contents = File.read(vagrantfile_path)\n      expect(contents).to match(/config.vm.box = \"base\"/)\n    end\n\n    it \"creates a Vagrantfile with a box\" do\n      described_class.new([\"hashicorp/precise64\"], env).execute\n      contents = File.read(vagrantfile_path)\n      expect(contents).to match(/config.vm.box = \"hashicorp\\/precise64\"/)\n    end\n\n    it \"creates a Vagrantfile with a box and box_url\" do\n      described_class.new([\"hashicorp/precise64\", \"http://example.com\"], env).execute\n      contents = File.read(vagrantfile_path)\n      expect(contents).to match(/config.vm.box = \"hashicorp\\/precise64\"/)\n      expect(contents).to match(/config.vm.box_url = \"http:\\/\\/example.com\"/)\n    end\n\n    it \"creates a Vagrantfile with a box and box version\" do\n      described_class.new([\"--box-version\", \"1.2.3\", \"hashicorp/precise64\"], env).execute\n      contents = File.read(vagrantfile_path)\n      expect(contents).to match(/config.vm.box = \"hashicorp\\/precise64\"/)\n      expect(contents).to match(/config.vm.box_version = \"1.2.3\"/)\n    end\n\n    it \"creates a minimal Vagrantfile with a box and box version\" do\n      described_class.new([\"--minimal\", \"--box-version\", \"1.2.3\", \"hashicorp/precise64\"], env).execute\n      contents = File.read(vagrantfile_path)\n      expect(contents).to match(/config.vm.box = \"hashicorp\\/precise64\"/)\n      expect(contents).to match(/config.vm.box_version = \"1.2.3\"/)\n    end\n\n    it \"creates a Vagrantfile at a custom path\" do\n      described_class.new([\"--output\", \"vf.rb\"], env).execute\n      expect(File.exist?(File.join(env.cwd, \"vf.rb\"))).to be(true)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/list-commands/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/list-commands/command\")\n\ndescribe VagrantPlugins::CommandListCommands::Command do\n  include_context \"unit\"\n  include_context \"command plugin helpers\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:argv)     { [] }\n  let(:commands) { {} }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(Vagrant.plugin(\"2\").manager).to receive(:commands).and_return(commands)\n  end\n\n  describe \"execute\" do\n    it \"includes all subcommands\" do\n      commands[:foo] = [command_lambda(\"foo\", 0), { primary: true }]\n      commands[:bar] = [command_lambda(\"bar\", 0), { primary: true }]\n      commands[:baz] = [command_lambda(\"baz\", 0), { primary: false }]\n\n      expect(iso_env.ui).to receive(:info).with(any_args) { |message, opts|\n        expect(message).to include(\"foo\")\n        expect(message).to include(\"bar\")\n        expect(message).to include(\"baz\")\n      }\n\n      subject.execute\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/package/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\nrequire_relative \"../../../../../plugins/commands/package/command\"\n\nRSpec::Matchers.define :a_machine_named do |name|\n  match{ |actual| actual.name.to_s == name.to_s }\nend\n\nRSpec::Matchers.define :an_existing_directory do\n  match{ |actual| File.directory?(actual) }\nend\n\ndescribe VagrantPlugins::CommandPackage::Command do\n  include_context \"unit\"\n\n  let(:argv)     { [] }\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:package_command) { described_class.new(argv, iso_env) }\n\n  let(:action_runner) { double(\"action_runner\") }\n\n  before do\n    allow(iso_env).to receive(:action_runner).and_return(action_runner)\n  end\n\n  describe \"#execute\" do\n\n    context \"with no arguments\" do\n\n      it \"packages default machine\" do\n        expect(package_command).to receive(:package_vm).with(a_machine_named('default'), {})\n        package_command.execute\n      end\n    end\n\n    context \"with single argument\" do\n      context \"set to default\" do\n\n        let(:argv){ ['default'] }\n\n        it \"packages default machine\" do\n          expect(package_command).to receive(:package_vm).with(a_machine_named('default'), {})\n          package_command.execute\n        end\n      end\n\n      context \"set to undefined vm\" do\n\n        let(:argv){ ['undefined'] }\n\n        it \"raises machine not found error\" do\n          expect{ package_command.execute }.to raise_error(Vagrant::Errors::MachineNotFound)\n        end\n      end\n\n      context \"with --output option\" do\n\n        let(:argv){ ['--output', 'package-output-folder/default'] }\n\n        it \"packages default machine inside specified folder\" do\n          expect(package_command).to receive(:package_vm).with(\n            a_machine_named('default'), { output: \"package-output-folder/default\" }\n          )\n          package_command.execute\n        end\n      end\n    end\n\n    context \"with multiple arguments\" do\n\n      let(:argv){ ['default', 'undefined'] }\n\n      it \"ignores the extra arguments\" do\n        expect(package_command).to receive(:package_vm).with(a_machine_named('default'), {})\n        package_command.execute\n      end\n    end\n\n    context \"with --base option\" do\n      context \"and no option value\" do\n\n        let(:argv){ ['--base'] }\n\n        it \"shows help\" do\n          expect{ package_command.execute }.to raise_error(Vagrant::Errors::CLIInvalidOptions)\n        end\n      end\n\n      context \"and option value\" do\n\n        let(:argv){ ['--base', 'machine-id'] }\n\n        it \"packages vm defined within virtualbox\" do\n          expect(package_command).to receive(:package_base).with({ base: 'machine-id' })\n          package_command.execute\n        end\n\n        it \"provides a machine data directory\" do\n          expect(Vagrant::Machine).to receive(:new).with(\n            'machine-id', :virtualbox, anything, nil, anything, anything, an_existing_directory,\n            anything, anything, anything, anything).and_return(double(\"vm\", name: \"machine-id\"))\n          allow(package_command).to receive(:package_vm)\n          package_command.execute\n        end\n      end\n    end\n  end\n\n  describe \"#package_vm\" do\n    context \"calling the package action\" do\n      let(:options) { {output: \"test.box\"} }\n      let(:expected_options) { {\"package.output\"=>\"test.box\"} }\n      let(:machine) { double(\"machine\") }\n      let(:tmp_dir) { \"/home/user/.vagrant.d/tmp/vagrant-package\" }\n      let(:env) { {\"export.temp_dir\"=>tmp_dir} }\n\n      it \"ensures that the package tmp dir is cleaned up\" do\n        allow(FileUtils).to receive(:rm_rf).and_return(true)\n        allow(machine).to receive(:action).with(:package, expected_options).\n          and_return(env)\n\n        expect(FileUtils).to receive(:rm_rf).with(tmp_dir)\n        package_command.send(:package_vm, machine, options)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\ndescribe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do\n  let(:app) { lambda { |env| } }\n  let(:home_path){ '/fake/file/path/.vagrant.d' }\n  let(:gems_path){ \"#{home_path}/gems\" }\n  let(:force){ true }\n  let(:env_local){ false }\n  let(:env_local_only){ nil }\n  let(:global_only){ nil }\n  let(:env) {{\n    ui: Vagrant::UI::Silent.new,\n    home_path: home_path,\n    gems_path: gems_path,\n    force: force,\n    env_local: env_local,\n    env_local_only: env_local_only,\n    global_only: global_only\n  }}\n\n  let(:user_file) { double(\"user_file\", path: user_file_pathname) }\n  let(:user_file_pathname) { double(\"user_file_pathname\", exist?: true, delete: true) }\n  let(:local_file) { nil }\n  let(:bundler) { double(\"bundler\", plugin_gem_path: plugin_gem_path,\n    env_plugin_gem_path: env_plugin_gem_path) }\n  let(:plugin_gem_path) { double(\"plugin_gem_path\", exist?: true, rmtree: true) }\n  let(:env_plugin_gem_path) { nil }\n\n  let(:manager) { double(\"manager\", user_file: user_file, local_file: local_file) }\n\n  let(:expect_to_receive) do\n    lambda do\n      allow(File).to receive(:exist?).with(File.join(home_path, 'plugins.json')).and_return(true)\n      allow(File).to receive(:directory?).with(gems_path).and_return(true)\n      expect(app).to receive(:call).with(env).once\n    end\n  end\n\n  subject { described_class.new(app, env) }\n\n  before do\n    allow(Vagrant::Plugin::Manager).to receive(:instance).and_return(manager)\n    allow(Vagrant::Bundler).to receive(:instance).and_return(bundler)\n  end\n\n  describe \"#call\" do\n    before do\n      instance_exec(&expect_to_receive)\n    end\n\n    it \"should delete all plugins\" do\n      expect(user_file_pathname).to receive(:delete)\n      expect(plugin_gem_path).to receive(:rmtree)\n      subject.call(env)\n    end\n\n    describe \"when force is false\" do\n      let(:force){ false }\n\n      it \"should prompt user before deleting all plugins\" do\n        expect(env[:ui]).to receive(:ask).and_return(\"Y\\n\")\n        subject.call(env)\n      end\n\n      describe \"when user declines prompt\" do\n        let(:expect_to_receive) do\n          lambda do\n            expect(app).not_to receive(:call)\n          end\n        end\n\n        it \"should not delete all plugins\" do\n          expect(env[:ui]).to receive(:ask).and_return(\"N\\n\")\n          subject.call(env)\n        end\n      end\n    end\n\n    context \"when local option is set\" do\n      let(:env_local) { true }\n\n      it \"should delete plugins\" do\n        expect(user_file_pathname).to receive(:delete)\n        expect(plugin_gem_path).to receive(:rmtree)\n        subject.call(env)\n      end\n    end\n\n    context \"when local plugins exist\" do\n      let(:local_file) { double(\"local_file\", path: local_file_pathname) }\n      let(:local_file_pathname) { double(\"local_file_pathname\", exist?: true, delete: true) }\n      let(:env_plugin_gem_path) { double(\"env_plugin_gem_path\", exist?: true, rmtree: true) }\n\n      it \"should delete user and local plugins\" do\n        expect(user_file_pathname).to receive(:delete)\n        expect(local_file_pathname).to receive(:delete)\n        expect(plugin_gem_path).to receive(:rmtree)\n        expect(env_plugin_gem_path).to receive(:rmtree)\n        subject.call(env)\n      end\n\n      context \"when local option is set\" do\n        let(:env_local) { true }\n\n        it \"should delete local plugins\" do\n          expect(local_file_pathname).to receive(:delete)\n          expect(env_plugin_gem_path).to receive(:rmtree)\n          subject.call(env)\n        end\n\n        it \"should delete user plugins\" do\n          expect(user_file_pathname).to receive(:delete)\n          expect(plugin_gem_path).to receive(:rmtree)\n          subject.call(env)\n        end\n\n        context \"when local only option is set\" do\n          let(:env_local_only) { true }\n\n          it \"should delete local plugins\" do\n            expect(local_file_pathname).to receive(:delete)\n            expect(env_plugin_gem_path).to receive(:rmtree)\n            subject.call(env)\n          end\n\n          it \"should not delete user plugins\" do\n            expect(user_file_pathname).not_to receive(:delete)\n            expect(plugin_gem_path).not_to receive(:rmtree)\n            subject.call(env)\n          end\n        end\n\n        context \"when global only option is set\" do\n          let(:global_only) { true }\n\n          it \"should not delete local plugins\" do\n            expect(local_file_pathname).not_to receive(:delete)\n            expect(env_plugin_gem_path).not_to receive(:rmtree)\n            subject.call(env)\n          end\n\n          it \"should delete user plugins\" do\n            expect(user_file_pathname).to receive(:delete)\n            expect(plugin_gem_path).to receive(:rmtree)\n            subject.call(env)\n          end\n        end\n\n        context \"when global and local only options are set\" do\n          let(:env_local_only) { true }\n          let(:global_only) { true }\n\n          it \"should delete local plugins\" do\n            expect(local_file_pathname).to receive(:delete)\n            expect(env_plugin_gem_path).to receive(:rmtree)\n            subject.call(env)\n          end\n\n          it \"should delete user plugins\" do\n            expect(user_file_pathname).to receive(:delete)\n            expect(plugin_gem_path).to receive(:rmtree)\n            subject.call(env)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/plugin/action/install_gem_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\ndescribe VagrantPlugins::CommandPlugin::Action::InstallGem do\n  let(:app) { lambda { |env| } }\n  let(:env) {{\n    ui: Vagrant::UI::Silent.new\n  }}\n\n  let(:manager) { double(\"manager\") }\n\n  subject { described_class.new(app, env) }\n\n  before do\n    allow(Vagrant::Plugin::Manager).to receive(:instance).and_return(manager)\n  end\n\n  describe \"#call\" do\n    it \"should install the plugin\" do\n      spec = Gem::Specification.new\n      expect(manager).to receive(:install_plugin).with(\n        \"foo\", version: nil, require: nil, sources: nil, verbose: false, env_local: nil).once.and_return(spec)\n\n      expect(app).to receive(:call).with(env).once\n\n      env[:plugin_name] = \"foo\"\n      subject.call(env)\n    end\n\n    it \"should specify the version if given\" do\n      spec = Gem::Specification.new\n      expect(manager).to receive(:install_plugin).with(\n        \"foo\", version: \"bar\", require: nil, sources: nil, verbose: false, env_local: nil).once.and_return(spec)\n\n      expect(app).to receive(:call).with(env).once\n\n      env[:plugin_name] = \"foo\"\n      env[:plugin_version] = \"bar\"\n      subject.call(env)\n    end\n\n    it \"should specify the entrypoint if given\" do\n      spec = Gem::Specification.new\n      expect(manager).to receive(:install_plugin).with(\n        \"foo\", version: \"bar\", require: \"baz\", sources: nil, verbose: false, env_local: nil).once.and_return(spec)\n\n      expect(app).to receive(:call).with(env).once\n\n      env[:plugin_entry_point] = \"baz\"\n      env[:plugin_name] = \"foo\"\n      env[:plugin_version] = \"bar\"\n      subject.call(env)\n    end\n\n    it \"should specify the sources if given\" do\n      spec = Gem::Specification.new\n      expect(manager).to receive(:install_plugin).with(\n        \"foo\", version: nil, require: nil, sources: [\"foo\"], verbose: false, env_local: nil).once.and_return(spec)\n\n      expect(app).to receive(:call).with(env).once\n\n      env[:plugin_name] = \"foo\"\n      env[:plugin_sources] = [\"foo\"]\n      subject.call(env)\n    end\n  end\n\n  describe \"#recover\" do\n    it \"should do nothing by default\" do\n      subject.recover(env)\n    end\n\n    context \"with a successful plugin install\" do\n      let(:action_runner) { double(\"action_runner\") }\n\n      before do\n        spec = Gem::Specification.new\n        spec.name = \"foo\"\n        allow(manager).to receive(:install_plugin).and_return(spec)\n\n        env[:plugin_name] = \"foo\"\n        subject.call(env)\n\n        env[:action_runner] = action_runner\n      end\n\n      it \"should uninstall the plugin\" do\n        expect(action_runner).to receive(:run).with(any_args) { |action, newenv|\n          expect(newenv[:plugin_name]).to eql(\"foo\")\n        }\n\n        subject.recover(env)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/plugin/action/plugin_exists_check_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\ndescribe VagrantPlugins::CommandPlugin::Action::PluginExistsCheck do\n  let(:app) { lambda {} }\n  let(:env) { {} }\n\n  let(:manager) { double(\"manager\") }\n\n  subject { described_class.new(app, env) }\n\n  before do\n    allow(Vagrant::Plugin::Manager).to receive(:instance).and_return(manager)\n  end\n\n  it \"should raise an exception if the plugin doesn't exist\" do\n    allow(manager).to receive(:installed_plugins).and_return({ \"foo\" => {} })\n    expect(app).not_to receive(:call)\n\n    env[:plugin_name] = \"bar\"\n    expect { subject.call(env) }.\n      to raise_error(Vagrant::Errors::PluginNotInstalled)\n  end\n\n  it \"should call the app if the plugin is installed\" do\n    allow(manager).to receive(:installed_plugins).and_return({ \"bar\" => {} })\n    expect(app).to receive(:call).once.with(env)\n\n    env[:plugin_name] = \"bar\"\n    subject.call(env)\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/plugin/action/uninstall_plugin_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\ndescribe VagrantPlugins::CommandPlugin::Action::UninstallPlugin do\n  let(:app) { lambda { |env| } }\n  let(:env) {{\n    ui: Vagrant::UI::Silent.new,\n  }}\n\n  let(:manager) { double(\"manager\") }\n\n  subject { described_class.new(app, env) }\n\n  before do\n    allow(Vagrant::Plugin::Manager).to receive(:instance).and_return(manager)\n  end\n\n  it \"uninstalls the specified plugin\" do\n    expect(manager).to receive(:uninstall_plugin).with(\"bar\", any_args).ordered\n    expect(app).to receive(:call).ordered\n\n    env[:plugin_name] = \"bar\"\n    subject.call(env)\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/plugin/action/update_gems_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\ndescribe VagrantPlugins::CommandPlugin::Action::UpdateGems do\n  let(:app) { lambda { |env| } }\n  let(:env) {{\n    ui: Vagrant::UI::Silent.new\n  }}\n\n  let(:manager) { double(\"manager\") }\n\n  subject { described_class.new(app, env) }\n\n  before do\n    allow(Vagrant::Plugin::Manager).to receive(:instance).and_return(manager)\n    allow(manager).to receive(:installed_specs).and_return([])\n  end\n\n  describe \"#call\" do\n    it \"should update all plugins if none are specified\" do\n      expect(manager).to receive(:update_plugins).with([]).once.and_return([])\n      expect(manager).to receive(:installed_plugins).twice.and_return({})\n      expect(app).to receive(:call).with(env).once\n      subject.call(env)\n    end\n\n    it \"should update specified plugins\" do\n      expect(manager).to receive(:update_plugins).with([\"foo\"]).once.and_return([])\n      expect(manager).to receive(:installed_plugins).twice.and_return({})\n      expect(app).to receive(:call).with(env).once\n\n      env[:plugin_name] = [\"foo\"]\n      subject.call(env)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/port/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/port/command\")\n\ndescribe VagrantPlugins::CommandPort::Command do\n  include_context \"unit\"\n  include_context \"command plugin helpers\"\n\n  let(:iso_env) { isolated_environment }\n  let(:env) do\n    iso_env.vagrantfile(<<-VF)\n      Vagrant.configure(\"2\") do |config|\n        config.vm.box = \"hashicorp/precise64\"\n        config.vm.synced_folder \".\", \"/vagrant\", disabled: true\n      end\n    VF\n    iso_env.create_vagrant_env\n  end\n\n  let(:state) { double(:state, id: :running) }\n\n  let(:machine) { env.machine(env.machine_names[0], :dummy) }\n\n  before(:all) do\n    I18n.load_path << Vagrant.source_root.join(\"plugins/commands/port/locales/en.yml\")\n    I18n.reload!\n  end\n\n  subject { described_class.new([], env) }\n\n  before do\n    allow(machine).to receive(:state).and_return(state)\n    allow(subject).to receive(:with_target_vms) { |&block| block.call(machine) }\n  end\n\n  describe \"#execute\" do\n    it \"validates the configuration\" do\n      iso_env.vagrantfile <<-EOH\n        Vagrant.configure(\"2\") do |config|\n          config.vm.box = \"hashicorp/precise64\"\n          config.vm.synced_folder \".\", \"/vagrant\", disabled: true\n          config.push.define \"noop\" do |push|\n            push.bad = \"ham\"\n          end\n        end\n      EOH\n\n      subject = described_class.new([], iso_env.create_vagrant_env)\n\n      expect { subject.execute }.to raise_error(Vagrant::Errors::ConfigInvalid) { |err|\n        expect(err.message).to include(\"The following settings shouldn't exist: bad\")\n      }\n    end\n\n    it \"ensures the vm is running\" do\n      allow(state).to receive(:id).and_return(:stopped)\n      expect(env.ui).to receive(:error).with(/does not support listing forwarded ports/).\n        and_call_original\n\n      expect(subject.execute).to eq(1)\n    end\n\n    it \"shows a friendly error when the capability is not supported\" do\n      allow(machine.provider).to receive(:capability?).and_return(false)\n      expect(env.ui).to receive(:error).with(/does not support listing forwarded ports/).\n        and_call_original\n\n      expect(subject.execute).to eq(1)\n    end\n\n    it \"returns a friendly message when there are no forwarded ports\" do\n      allow(machine.provider).to receive(:capability?).and_return(true)\n      allow(machine.provider).to receive(:capability).with(:forwarded_ports)\n        .and_return([])\n\n      expect(env.ui).to receive(:info).with(/there are no forwarded ports/).\n        and_call_original\n\n      expect(subject.execute).to eq(0)\n    end\n\n    it \"returns the list of ports\" do\n      allow(machine.provider).to receive(:capability?).and_return(true)\n      allow(machine.provider).to receive(:capability).with(:forwarded_ports)\n        .and_return([[2222,22], [1111,11]])\n\n      output = \"\"\n      allow(env.ui).to receive(:info) do |data|\n        output << data\n      end\n\n      expect(subject.execute).to eq(0)\n\n      expect(output).to include(\"forwarded ports for the machine\")\n      expect(output).to include(\"22 (guest) => 2222 (host)\")\n      expect(output).to include(\"11 (guest) => 1111 (host)\")\n    end\n\n    it \"prints the matching host port when --guest is given\" do\n      argv = [\"--guest\", \"22\"]\n      subject = described_class.new(argv, env)\n\n      allow(machine.provider).to receive(:capability?).and_return(true)\n      allow(machine.provider).to receive(:capability).with(:forwarded_ports)\n        .and_return([[2222,22]])\n\n      output = \"\"\n      allow(env.ui).to receive(:info) do |data|\n        output << data\n      end\n\n      expect(subject.execute).to eq(0)\n\n      expect(output).to eq(\"2222\")\n    end\n\n    it \"returns an error with no port is mapped to the --guest option\" do\n      argv = [\"--guest\", \"80\"]\n      subject = described_class.new(argv, env)\n\n      allow(machine.provider).to receive(:capability?).and_return(true)\n      allow(machine.provider).to receive(:capability).with(:forwarded_ports)\n        .and_return([[2222,22]])\n\n      expect(env.ui).to receive(:error).with(/not currently mapping port 80/).\n        and_call_original\n\n      expect(subject.execute).to_not eq(0)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/powershell/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/powershell/command\")\n\ndescribe VagrantPlugins::CommandPS::Command do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:config)  {\n    double(\"config\",\n      vm: double(\"vm\", communicator: communicator_name),\n      winrm: double(\"winrm\", username: winrm_username, password: winrm_password)\n    )\n  }\n  let(:communicator_name) { :winrm }\n  let(:winrm_info) { {host: winrm_host, port: winrm_port} }\n  let(:winrm_username) { double(\"winrm_username\") }\n  let(:winrm_password) { double(\"winrm_password\") }\n  let(:winrm_host) { double(\"winrm_host\") }\n  let(:winrm_port) { double(\"winrm_port\") }\n\n  let(:remoting_ready_result) { {} }\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:argv) { [] }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n    allow(iso_env).to receive(:host).and_return(host)\n    allow(host).to receive(:capability?).with(:ps_client).and_return(true)\n\n    allow(machine.communicate).to receive(:ready?).and_return(true)\n    allow(machine).to receive(:config).and_return(config)\n\n    allow(VagrantPlugins::CommunicatorWinRM::Helper).to receive(:winrm_info).and_return(winrm_info)\n    allow(subject).to receive(:ready_ps_remoting_for).and_return(remoting_ready_result)\n    allow(host).to receive(:capability).with(:ps_client, any_args)\n\n    # Ignore loading up translations\n    allow_any_instance_of(Vagrant::Errors::VagrantError).to receive(:translate_error)\n  end\n\n  describe \"#execute\" do\n    context \"when communicator is not ready\" do\n      before { expect(machine.communicate).to receive(:ready?).and_return(false) }\n\n      it \"should raise error that machine is not created\" do\n        expect { subject.execute }.to raise_error(Vagrant::Errors::VMNotCreatedError)\n      end\n    end\n\n    context \"when communicator is not winrm\" do\n      let(:communicator_name) { :ssh }\n\n      context \"when command is provided\" do\n        let(:argv) { [\"-c\", \"command\"] }\n\n        it \"should raise an error that winrm is not ready\" do\n          expect { subject.execute }.to raise_error(VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady)\n        end\n      end\n\n      context \"when no command is provided\" do\n        it \"should create a powershell session\" do\n          expect(host).to receive(:capability).with(:ps_client, any_args)\n          subject.execute\n        end\n      end\n    end\n\n    context \"when host does not support ps_client\" do\n      before { allow(host).to receive(:capability?).with(:ps_client).and_return(false) }\n\n      context \"when no command is provided\" do\n        it \"should raise an error for unsupported host\" do\n          expect { subject.execute }.to raise_error(VagrantPlugins::CommandPS::Errors::HostUnsupported)\n        end\n      end\n\n      context \"when command is provided\" do\n        let(:argv) { [\"-c\", \"command\"] }\n\n        it \"should execute command when command is provided\" do\n          expect(machine.communicate).to receive(:execute).with(\"command\", any_args).and_return(0)\n          subject.execute\n        end\n      end\n    end\n\n    context \"with command provided\" do\n      let(:argv) { [\"-c\", \"command\"] }\n\n      it \"executes the command on the guest\" do\n        expect(machine.communicate).to receive(:execute).with(\"command\", any_args).and_return(0)\n        subject.execute\n      end\n\n      context \"with elevated flag\" do\n        let(:argv) { [\"-e\", \"-c\", \"command\"] }\n\n        it \"should execute the command with elevated option\" do\n          expect(machine.communicate).to receive(:execute).\n            with(\"command\", hash_including(elevated: true)).and_return(0)\n          subject.execute\n        end\n      end\n    end\n\n    context \"with elevated flag and no command\" do\n      let(:argv) { [\"-e\"] }\n\n      it \"should raise error that command must be provided\" do\n        expect { subject.execute }.to raise_error(VagrantPlugins::CommandPS::Errors::ElevatedNoCommand)\n      end\n    end\n\n    it \"should start a new session\" do\n      expect(host).to receive(:capability).with(:ps_client, any_args)\n      subject.execute\n    end\n\n    context \"when setup returns PreviousTrustedHosts\" do\n      let(:remoting_ready_result) { {\"PreviousTrustedHosts\" => true} }\n\n      it \"should reset the powershell remoting\" do\n        expect(subject).to receive(:reset_ps_remoting_for)\n        subject.execute\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/provider/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/provider/command\")\n\ndescribe VagrantPlugins::CommandProvider::Command do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:argv)     { [] }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n  end\n\n  describe \"execute\" do\n    context \"no arguments\" do\n      it \"exits with the provider name\" do\n        expect(subject.execute).to eq(0)\n      end\n    end\n\n    context \"--usable\" do\n      let(:argv) { [\"--usable\"] }\n\n      it \"exits 0 if it is usable\" do\n        expect(subject.execute).to eq(0)\n      end\n\n      it \"exits 1 if it is not usable\" do\n        expect(machine.provider.class).to receive(:usable?).and_return(false)\n        expect(subject.execute).to eq(1)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/push/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/push/command\")\n\ndescribe VagrantPlugins::CommandPush::Command do\n  include_context \"unit\"\n  include_context \"command plugin helpers\"\n\n  let(:iso_env) { isolated_environment }\n  let(:env) do\n    iso_env.vagrantfile(<<-VF)\n      Vagrant.configure(\"2\") do |config|\n        config.vm.box = \"hashicorp/precise64\"\n        config.vm.synced_folder \".\", \"/vagrant\", disabled: true\n      end\n    VF\n    iso_env.create_vagrant_env\n  end\n\n  let(:argv)   { [] }\n  let(:pushes) { {} }\n\n  subject { described_class.new(argv, env) }\n\n  before do\n    allow(Vagrant.plugin(\"2\").manager).to receive(:pushes).and_return(pushes)\n  end\n\n  describe \"#execute\" do\n    before do\n      allow(subject).to receive(:validate_pushes!)\n        .and_return(:noop)\n      allow(env).to receive(:pushes)\n      allow(env).to receive(:push)\n    end\n\n    it \"validates the pushes\" do\n      expect(subject).to receive(:validate_pushes!).once\n      subject.execute\n    end\n\n    it \"delegates to Environment#push\" do\n      expect(env).to receive(:push).once\n      subject.execute\n    end\n\n    it \"validates the configuration\" do\n      iso_env.vagrantfile <<-EOH\n        Vagrant.configure(\"2\") do |config|\n          config.vm.box = \"hashicorp/precise64\"\n          config.vm.synced_folder \".\", \"/vagrant\", disabled: true\n\n          config.push.define \"noop\" do |push|\n            push.bad = \"ham\"\n          end\n        end\n      EOH\n\n      subject = described_class.new(argv, iso_env.create_vagrant_env)\n      allow(subject).to receive(:validate_pushes!)\n        .and_return(:noop)\n\n      expect { subject.execute }.to raise_error(Vagrant::Errors::ConfigInvalid) { |err|\n        expect(err.message).to include(\"The following settings shouldn't exist: bad\")\n      }\n    end\n  end\n\n  describe \"#validate_pushes!\" do\n    context \"when there are no pushes defined\" do\n      let(:pushes) { [] }\n\n      context \"when a strategy is given\" do\n        it \"raises an exception\" do\n          expect { subject.validate_pushes!(pushes, :noop) }\n            .to raise_error(Vagrant::Errors::PushesNotDefined)\n        end\n      end\n\n      context \"when no strategy is given\" do\n        it \"raises an exception\" do\n          expect { subject.validate_pushes!(pushes) }\n            .to raise_error(Vagrant::Errors::PushesNotDefined)\n        end\n      end\n    end\n\n    context \"when there is one push defined\" do\n      let(:noop) { double(\"noop\") }\n      let(:pushes) { [:noop] }\n\n      context \"when a strategy is given\" do\n        context \"when that strategy is not defined\" do\n          it \"raises an exception\" do\n            expect { subject.validate_pushes!(pushes, :bacon) }\n              .to raise_error(Vagrant::Errors::PushStrategyNotDefined)\n          end\n        end\n\n        context \"when that strategy is defined\" do\n          it \"returns that push\" do\n            expect(subject.validate_pushes!(pushes, :noop)).to eq(:noop)\n          end\n        end\n      end\n\n      context \"when no strategy is given\" do\n        it \"returns the strategy\" do\n          expect(subject.validate_pushes!(pushes)).to eq(:noop)\n        end\n      end\n    end\n\n    context \"when there are multiple pushes defined\" do\n      let(:noop) { double(\"noop\") }\n      let(:ftp)  { double(\"ftp\") }\n      let(:pushes) { [:noop, :ftp] }\n\n      context \"when a strategy is given\" do\n        context \"when that strategy is not defined\" do\n          it \"raises an exception\" do\n            expect { subject.validate_pushes!(pushes, :bacon) }\n              .to raise_error(Vagrant::Errors::PushStrategyNotDefined)\n          end\n        end\n\n        context \"when that strategy is defined\" do\n          it \"returns the strategy\" do\n            expect(subject.validate_pushes!(pushes, :noop)).to eq(:noop)\n            expect(subject.validate_pushes!(pushes, :ftp)).to eq(:ftp)\n          end\n        end\n      end\n\n      context \"when no strategy is given\" do\n        it \"raises an exception\" do\n          expect { subject.validate_pushes!(pushes) }\n            .to raise_error(Vagrant::Errors::PushStrategyNotProvided)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/reload/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/reload/command\")\n\ndescribe VagrantPlugins::CommandReload::Command do\n  include_context \"unit\"\n\n  let(:entry_klass) { Vagrant::MachineIndex::Entry }\n  let(:argv)     { [] }\n  let(:vagrantfile_content){ \"\" }\n  let(:iso_env) do\n    env = isolated_environment\n    env.vagrantfile(vagrantfile_content)\n    env.create_vagrant_env\n  end\n\n  subject { described_class.new(argv, iso_env) }\n\n  let(:action_runner) { double(\"action_runner\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:machine2) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  def new_entry(name)\n    entry_klass.new.tap do |e|\n      e.name = name\n      e.vagrantfile_path = \"/bar\"\n    end\n  end\n\n  before do\n    allow(iso_env).to receive(:action_runner).and_return(action_runner)\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n  end\n\n  context \"with no argument\" do\n    let(:vagrantfile_content) do\n        <<-VF\n        Vagrant.configure(\"2\") do |config|\n          config.vm.define \"app\"\n          config.vm.define \"db\"\n        end\n        VF\n    end\n\n    it \"should reload all vms\" do\n      allow(subject).to receive(:with_target_vms) { |&block|\n        block.call machine\n        block.call machine2\n      }\n      expect(machine).to receive(:action) do |name, opts|\n        expect(name).to eq(:reload)\n      end\n      expect(machine2).to receive(:action) do |name, opts|\n        expect(name).to eq(:reload)\n      end\n\n      expect(subject.execute).to eq(0)\n    end\n  end\n\n  context \"with an argument\" do\n    let(:vagrantfile_content) do\n        <<-VF\n        Vagrant.configure(\"2\") do |config|\n          config.vm.define \"app\"\n          config.vm.define \"db\"\n        end\n        VF\n    end\n    let(:argv) { [\"app\"] }\n\n    it \"should reload a vm\" do\n      expect(machine).to receive(:action) do |name, opts|\n        expect(name).to eq(:reload)\n      end\n\n      expect(subject.execute).to eq(0)\n    end\n  end\n\n  context \"with the force flag\" do\n    let(:vagrantfile_content) do\n        <<-VF\n        Vagrant.configure(\"2\") do |config|\n          config.vm.define \"app\"\n          config.vm.define \"db\"\n        end\n        VF\n    end\n    let(:argv) { [\"--force\"] }\n    it \"should reload a vm\" do\n      expect(machine).to receive(:action) do |name, opts|\n        expect(opts).to include(force_halt: true)\n        expect(name).to eq(:reload)\n      end\n\n      expect(subject.execute).to eq(0)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/snapshot/command/delete_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/snapshot/command/delete\")\n\ndescribe VagrantPlugins::CommandSnapshot::Command::Delete do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:argv) { [] }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(machine.provider).to receive(:capability).with(:snapshot_list).\n      and_return([])\n\n    allow(machine.provider).to receive(:capability?).with(:snapshot_list).\n      and_return(true)\n\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n  end\n\n  describe \"execute\" do\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with an unsupported provider\" do\n      let(:argv)     { [\"test\"] }\n\n      before do\n        allow(machine.provider).to receive(:capability?).with(:snapshot_list).\n          and_return(false)\n      end\n\n      it \"raises an exception\" do\n        machine.id = \"foo\"\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::SnapshotNotSupported)\n      end\n    end\n\n    context \"with a snapshot name given\" do\n      let(:argv)     { [\"test\"] }\n      it \"calls snapshot_delete with a snapshot name\" do\n        machine.id = \"foo\"\n\n        allow(machine.provider).to receive(:capability).with(:snapshot_list).\n          and_return([\"test\"])\n\n        expect(machine).to receive(:action) do |name, opts|\n          expect(name).to eq(:snapshot_delete)\n          expect(opts[:snapshot_name]).to eq(\"test\")\n        end\n\n        expect(subject.execute).to eq(0)\n      end\n\n      it \"doesn't delete a snapshot on a non-existent machine\" do\n        machine.id = nil\n\n        expect(subject).to receive(:with_target_vms){}\n\n        expect(machine).to_not receive(:action)\n        expect(subject.execute).to eq(0)\n      end\n    end\n\n    context \"with a snapshot name that doesn't exist\" do\n      let(:argv)     { [\"nopetest\"] }\n\n      it \"fails to take a snapshot and prints a warning to the user\" do\n        machine.id = \"foo\"\n\n        allow(machine.provider).to receive(:capability).with(:snapshot_list).\n          and_return([\"test\"])\n\n        expect(machine).to_not receive(:action)\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::SnapshotNotFound)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/snapshot/command/list_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/snapshot/command/list\")\n\ndescribe VagrantPlugins::CommandSnapshot::Command::List do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:argv) { [] }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(machine.provider).to receive(:capability?).with(:snapshot_list).\n      and_return(true)\n\n    allow(machine.provider).to receive(:capability).with(:snapshot_list).\n      and_return([])\n\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n  end\n\n  describe \"execute\" do\n    context \"with an unsupported provider\" do\n      let(:argv)     { [\"foo\"] }\n\n      before do\n        allow(machine.provider).to receive(:capability?).with(:snapshot_list).\n          and_return(false)\n      end\n\n      it \"raises an exception\" do\n        machine.id = \"foo\"\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::SnapshotNotSupported)\n      end\n    end\n\n    context \"with a vm given\" do\n      let(:argv)     { [\"foo\"] }\n\n      it \"prints a message if the vm does not exist\" do\n        machine.id = nil\n\n        expect(iso_env.ui).to receive(:info).with(\"==> default: VM not created. Moving on...\", anything).\n          and_call_original\n        expect(machine).to_not receive(:action)\n        expect(subject.execute).to eq(0)\n      end\n\n      it \"prints a message if no snapshots have been taken\" do\n        machine.id = \"foo\"\n\n        expect(iso_env.ui).to receive(:output).and_call_original\n          .with(/No snapshots have been taken yet!/, anything)\n        expect(subject.execute).to eq(0)\n      end\n\n      it \"prints a list of snapshots\" do\n        machine.id = \"foo\"\n\n        allow(machine.provider).to receive(:capability).with(:snapshot_list).\n          and_return([\"foo\", \"bar\", \"baz\"])\n\n        expect(iso_env.ui).to receive(:output).with(/default/, anything).and_call_original\n        expect(iso_env.ui).to receive(:detail).with(/foo/, anything).and_call_original\n        expect(iso_env.ui).to receive(:detail).with(/bar/, anything).and_call_original\n        expect(iso_env.ui).to receive(:detail).with(/baz/, anything)\n        expect(subject.execute).to eq(0)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/snapshot/command/pop_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/snapshot/command/pop\")\n\ndescribe VagrantPlugins::CommandSnapshot::Command::Pop do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:argv)     { [] }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n  end\n\n  describe \"execute\" do\n    before do\n      machine.id = \"mach\"\n      allow(machine.provider).to receive(:capability).\n        with(:snapshot_list).and_return([\"push_2_0\"])\n    end\n\n    it \"calls snapshot_restore with the last pushed snapshot\" do\n      machine.id = \"foo\"\n\n      allow(machine.provider).to receive(:capability).\n        with(:snapshot_list).and_return([\"push_2_0\", \"push_1_0\"])\n\n      expect(machine).to receive(:action) do |name, opts|\n        expect(name).to eq(:snapshot_restore)\n        expect(opts[:snapshot_name]).to eq(\"push_2_0\")\n      end\n\n      expect(subject.execute).to eq(0)\n    end\n\n    it \"isn't an error if no matching snapshot\" do\n      machine.id = \"foo\"\n\n      allow(machine.provider).to receive(:capability).\n        with(:snapshot_list).and_return([\"foo\"])\n\n      expect(machine).to_not receive(:action)\n      expect(subject.execute).to eq(0)\n    end\n\n    it \"should disable ignoring sentinel file for provisioning\" do\n      expect(machine).to receive(:action) do |name, opts|\n        expect(name).to eq(:snapshot_restore)\n        expect(opts[:provision_ignore_sentinel]).to eq(false)\n      end\n      subject.execute\n    end\n\n    it \"should start the snapshot\" do\n      expect(machine).to receive(:action) do |name, opts|\n        expect(name).to eq(:snapshot_restore)\n        expect(opts[:snapshot_start]).to eq(true)\n      end\n      subject.execute\n    end\n\n    context \"when --no-start flag is provided\" do\n      let(:argv) { [\"--no-start\"] }\n\n      it \"should not start the snapshot\" do\n        expect(machine).to receive(:action) do |name, opts|\n          expect(name).to eq(:snapshot_restore)\n          expect(opts[:snapshot_start]).to eq(false)\n        end\n        subject.execute\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/snapshot/command/push_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/snapshot/command/push\")\n\ndescribe VagrantPlugins::CommandSnapshot::Command::Push do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:argv)     { [] }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n  end\n\n  describe \"execute\" do\n    it \"calls snapshot_save with a random snapshot name\" do\n      machine.id = \"foo\"\n\n      expect(machine).to receive(:action) do |name, opts|\n        expect(name).to eq(:snapshot_save)\n        expect(opts[:snapshot_name]).to match(/^push_/)\n      end\n\n      expect(subject.execute).to eq(0)\n    end\n\n    it \"doesn't snapshot a non-existent machine\" do\n      machine.id = nil\n\n      expect(machine).to_not receive(:action)\n      expect(subject.execute).to eq(0)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/snapshot/command/restore_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/snapshot/command/restore\")\n\ndescribe VagrantPlugins::CommandSnapshot::Command::Restore do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:snapshot_name) { \"snapshot_name\" }\n  let(:guest)   { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:argv) { [] }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(machine.provider).to receive(:capability).with(:snapshot_list).\n      and_return([])\n\n    allow(machine.provider).to receive(:capability?).with(:snapshot_list).\n      and_return(true)\n\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n  end\n\n  describe \"execute\" do\n    before do\n      allow(machine.provider).to receive(:capability).\n        with(:snapshot_list).and_return([snapshot_name])\n    end\n\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with an unsupported provider\" do\n      let(:argv)     { [\"test\"] }\n\n      before do\n        allow(machine.provider).to receive(:capability?).with(:snapshot_list).\n          and_return(false)\n      end\n\n      it \"raises an exception\" do\n        machine.id = \"foo\"\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::SnapshotNotSupported)\n      end\n    end\n\n    context \"with a snapshot name given\" do\n      let(:argv)     { [\"test\"] }\n      it \"calls snapshot_delete with a snapshot name\" do\n        machine.id = \"foo\"\n\n        allow(machine.provider).to receive(:capability).with(:snapshot_list).\n          and_return([\"test\"])\n\n        expect(machine).to receive(:action) do |name, opts|\n          expect(name).to eq(:snapshot_restore)\n          expect(opts[:snapshot_name]).to eq(\"test\")\n        end\n\n        expect(subject.execute).to eq(0)\n      end\n\n      it \"doesn't delete a snapshot on a non-existent machine\" do\n        machine.id = nil\n\n        expect(subject).to receive(:with_target_vms){}\n\n        expect(machine).to_not receive(:action)\n        expect(subject.execute).to eq(0)\n      end\n    end\n\n    context \"with a snapshot name that doesn't exist\" do\n      let(:argv)     { [\"nopetest\"] }\n\n      it \"fails to take a snapshot and prints a warning to the user\" do\n        machine.id = \"foo\"\n\n        allow(machine.provider).to receive(:capability).with(:snapshot_list).\n          and_return([\"test\"])\n\n        expect(machine).to_not receive(:action)\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::SnapshotNotFound)\n      end\n    end\n\n    it \"should disable ignoring sentinel file for provisioning\" do\n      argv << snapshot_name\n      expect(machine).to receive(:action) do |_, opts|\n        expect(opts[:provision_ignore_sentinel]).to eq(false)\n      end\n      subject.execute\n    end\n\n    it \"should start the snapshot\" do\n      argv << snapshot_name\n      expect(machine).to receive(:action) do |_, opts|\n        expect(opts[:snapshot_start]).to eq(true)\n      end\n      subject.execute\n    end\n\n    context \"when --no-start flag is provided\" do\n      let(:argv) { [snapshot_name, \"--no-start\"] }\n\n      it \"should not start the snapshot\" do\n        expect(machine).to receive(:action) do |_, opts|\n          expect(opts[:snapshot_start]).to eq(false)\n        end\n        subject.execute\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/snapshot/command/root_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/snapshot/command/root\")\n\ndescribe VagrantPlugins::CommandSnapshot::Command::Root do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:argv) { [] }\n\n  subject { described_class.new(argv, iso_env) }\n\n  describe \"execute\" do\n    context \"--help\" do\n      let(:argv)     { [\"--help\"] }\n      it \"shows help\" do\n        expect(iso_env.ui).\n          to receive(:info).with(/Usage: vagrant snapshot <subcommand>/, anything)\n        expect(subject.execute).to eq(0)\n      end\n    end\n\n    context \"with no subcommand\" do\n      let(:argv)     { [] }\n      it \"shows help and fails\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with invalid subcommand\" do\n      let(:argv)     { [\"invalid\"] }\n      it \"shows help and fails\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/snapshot/command/save_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/snapshot/command/save\")\n\ndescribe VagrantPlugins::CommandSnapshot::Command::Save do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:argv) { [] }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(machine.provider).to receive(:capability).with(:snapshot_list).\n      and_return([])\n\n    allow(machine.provider).to receive(:capability?).with(:snapshot_list).\n      and_return(true)\n\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n  end\n\n  describe \"execute\" do\n    context \"with no arguments\" do\n      it \"shows help\" do\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::CLIInvalidUsage)\n      end\n    end\n\n    context \"with an unsupported provider\" do\n      let(:argv)     { [\"test\"] }\n\n      before do\n        allow(machine.provider).to receive(:capability?).with(:snapshot_list).\n          and_return(false)\n      end\n\n      it \"raises an exception\" do\n        machine.id = \"foo\"\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::SnapshotNotSupported)\n      end\n    end\n\n    context \"with a snapshot name given\" do\n      let(:argv)     { [\"test\"] }\n      it \"calls snapshot_save with a snapshot name\" do\n        machine.id = \"foo\"\n\n        expect(machine).to receive(:action) do |name, opts|\n          expect(name).to eq(:snapshot_save)\n          expect(opts[:snapshot_name]).to eq(\"test\")\n        end\n\n        expect(subject.execute).to eq(0)\n      end\n\n      it \"doesn't snapshot a non-existent machine\" do\n        machine.id = nil\n\n        expect(subject).to receive(:with_target_vms){}\n\n        expect(machine).to_not receive(:action)\n        expect(subject.execute).to eq(0)\n      end\n    end\n\n    context \"with a snapshot guest and name given\" do\n      let(:argv)     { [\"foo\", \"backup\"] }\n      it \"calls snapshot_save with a snapshot name\" do\n        machine.id = \"foo\"\n\n        expect(machine).to receive(:action) do |name, opts|\n          expect(name).to eq(:snapshot_save)\n          expect(opts[:snapshot_name]).to eq(\"backup\")\n        end\n\n        expect(subject.execute).to eq(0)\n      end\n\n      it \"doesn't snapshot a non-existent machine\" do\n        machine.id = nil\n\n        expect(machine).to_not receive(:action)\n        expect(subject.execute).to eq(0)\n      end\n    end\n\n    context \"with a duplicate snapshot name given and no force flag\" do\n      let(:argv)     { [\"test\"] }\n\n      it \"fails to take a snapshot and prints a warning to the user\" do\n        machine.id = \"fool\"\n\n        allow(machine.provider).to receive(:capability).with(:snapshot_list).\n          and_return([\"test\"])\n\n        expect(machine).to_not receive(:action)\n        expect { subject.execute }.\n          to raise_error(Vagrant::Errors::SnapshotConflictFailed)\n      end\n    end\n\n    context \"with a duplicate snapshot name given and a force flag\" do\n      let(:argv)     { [\"test\", \"--force\"] }\n\n      it \"deletes the existing snapshot and takes a new one\" do\n        machine.id = \"foo\"\n\n        allow(machine.provider).to receive(:capability).with(:snapshot_list).\n          and_return([\"test\"])\n\n        expect(machine).to receive(:action).with(:snapshot_delete, snapshot_name: \"test\")\n        expect(machine).to receive(:action).with(:snapshot_save, snapshot_name: \"test\")\n\n        expect(subject.execute).to eq(0)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/ssh_config/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/ssh_config/command\")\n\ndescribe VagrantPlugins::CommandSSHConfig::Command do\n  include_context \"unit\"\n  include_context \"virtualbox\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:argv)     { [] }\n  let(:ssh_info) {{\n    host:             \"testhost.vagrant.dev\",\n    port:             1234,\n    username:         \"testuser\",\n    keys_only:        true,\n    verify_host_key:         false,\n    private_key_path: [\"/home/vagrant/.private/keys.key\"],\n    forward_agent:    false,\n    forward_x11:      false\n  }}\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(machine).to receive(:ssh_info).and_return(ssh_info)\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n  end\n\n  describe \"execute\" do\n    it \"prints out the ssh config for the given machine\" do\n      output = \"\"\n      allow(subject).to receive(:safe_puts) do |data|\n        output += data if data\n      end\n\n      subject.execute\n\n      expect(output).to eq(<<-SSHCONFIG)\nHost #{machine.name}\n  HostName testhost.vagrant.dev\n  User testuser\n  Port 1234\n  UserKnownHostsFile /dev/null\n  StrictHostKeyChecking no\n  PasswordAuthentication no\n  IdentityFile /home/vagrant/.private/keys.key\n  IdentitiesOnly yes\n  LogLevel FATAL\n  PubkeyAcceptedKeyTypes +ssh-rsa\n  HostKeyAlgorithms +ssh-rsa\n      SSHCONFIG\n    end\n\n    it \"turns on agent forwarding when it is configured\" do\n      allow(machine).to receive(:ssh_info) { ssh_info.merge(forward_agent: true) }\n\n      output = \"\"\n      allow(subject).to receive(:safe_puts) do |data|\n        output += data if data\n      end\n\n      subject.execute\n\n      expect(output).to include(\"ForwardAgent yes\")\n    end\n\n    it \"turns on x11 forwarding when it is configured\" do\n      allow(machine).to receive(:ssh_info) { ssh_info.merge(forward_x11: true) }\n\n      output = \"\"\n      allow(subject).to receive(:safe_puts) do |data|\n        output += data if data\n      end\n\n      subject.execute\n\n      expect(output).to include(\"ForwardX11 yes\")\n    end\n\n    it \"handles multiple private key paths\" do\n      allow(machine).to receive(:ssh_info) { ssh_info.merge(private_key_path: [\"foo\", \"bar\"]) }\n\n      output = \"\"\n      allow(subject).to receive(:safe_puts) do |data|\n        output += data if data\n      end\n\n      subject.execute\n\n      expect(output).to include(\"IdentityFile foo\")\n      expect(output).to include(\"IdentityFile bar\")\n    end\n\n    it \"puts quotes around an identityfile path if it has a space\" do\n      allow(machine).to receive(:ssh_info) { ssh_info.merge(private_key_path: [\"with a space\"]) }\n      output = \"\"\n      allow(subject).to receive(:safe_puts) do |data|\n        output += data if data\n      end\n\n      subject.execute\n\n      expect(output).to include('IdentityFile \"with a space\"')\n    end\n\n    it \"omits IdentitiesOnly when keys_only is false\" do\n      allow(machine).to receive(:ssh_info) { ssh_info.merge(keys_only: false) }\n\n      output = \"\"\n      allow(subject).to receive(:safe_puts) do |data|\n        output += data if data\n      end\n\n      subject.execute\n\n      expect(output).not_to include('IdentitiesOnly')\n    end\n\n    it \"omits StrictHostKeyChecking and UserKnownHostsFile when verify_host_key is true\" do\n      allow(machine).to receive(:ssh_info) { ssh_info.merge(verify_host_key: true) }\n\n      output = \"\"\n      allow(subject).to receive(:safe_puts) do |data|\n        output += data if data\n      end\n\n      subject.execute\n\n      expect(output).not_to include('StrictHostKeyChecking ')\n      expect(output).not_to include('UserKnownHostsFile ')\n    end\n\n    it \"formats windows paths if windows\" do\n      allow(machine).to receive(:ssh_info) { ssh_info.merge(private_key_path: [\"C:\\\\path\\\\to\\\\vagrant\\\\home.key\"]) }\n      allow(Vagrant::Util::Platform).to receive(:format_windows_path).and_return(\"/home/vagrant/home.key\")\n      allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n\n      output = \"\"\n      allow(subject).to receive(:safe_puts) do |data|\n        output += data if data\n      end\n\n      subject.execute\n      expect(output).to include('IdentityFile /home/vagrant/home.key')\n    end\n\n    it \"handles verify_host_key :never value\" do\n      allow(machine).to receive(:ssh_info) { ssh_info.merge(verify_host_key: :never) }\n\n      output = \"\"\n      allow(subject).to receive(:safe_puts) do |data|\n        output += data if data\n      end\n\n      subject.execute\n\n      expect(output).to include('StrictHostKeyChecking ')\n      expect(output).to include('UserKnownHostsFile ')\n    end\n\n    it \"includes custom ssh_config path when provided\" do\n      allow(machine).to receive(:ssh_info) { ssh_info.merge(config: \"/custom/ssh/config\") }\n\n      output = \"\"\n      allow(subject).to receive(:safe_puts) do |data|\n        output += data if data\n      end\n\n      subject.execute\n\n      expect(output).to include(\"Include /custom/ssh/config\")\n    end\n\n    it \"includes enabling ssh-rsa key types and host algorithms\" do\n      output = \"\"\n      allow(subject).to receive(:safe_puts) do |data|\n        output += data if data\n      end\n\n      subject.execute\n\n      expect(output).to include(\"PubkeyAcceptedKeyTypes +ssh-rsa\")\n      expect(output).to include(\"HostKeyAlgorithms +ssh-rsa\")\n    end\n\n    it \"does not enable ssh-rsa key types and host algorithms when disabled\" do\n      allow(machine).to receive(:ssh_info) { ssh_info.merge(disable_deprecated_algorithms: true) }\n      output = \"\"\n      allow(subject).to receive(:safe_puts) do |data|\n        output += data if data\n      end\n\n      subject.execute\n\n      expect(output).not_to include(\"PubkeyAcceptedKeyTypes +ssh-rsa\")\n      expect(output).not_to include(\"HostKeyAlgorithms +ssh-rsa\")\n    end\n\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/suspend/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/suspend/command\")\n\ndescribe VagrantPlugins::CommandSuspend::Command do\n  include_context \"unit\"\n\n  let(:entry_klass) { Vagrant::MachineIndex::Entry }\n  let(:argv)     { [] }\n  let(:vagrantfile_content){ \"\" }\n  let(:iso_env) do\n    env = isolated_environment\n    env.vagrantfile(vagrantfile_content)\n    env.create_vagrant_env\n  end\n\n  subject { described_class.new(argv, iso_env) }\n\n  let(:action_runner) { double(\"action_runner\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:machine2) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  def new_entry(name)\n    entry_klass.new.tap do |e|\n      e.name = name\n      e.vagrantfile_path = \"/bar\"\n    end\n  end\n\n  before do\n    allow(iso_env).to receive(:action_runner).and_return(action_runner)\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n  end\n\n  context \"with no argument\" do\n    let(:vagrantfile_content) do\n        <<-VF\n        Vagrant.configure(\"2\") do |config|\n          config.vm.define \"app\"\n          config.vm.define \"db\"\n        end\n        VF\n    end\n\n    it \"should suspend all vms\" do\n      allow(subject).to receive(:with_target_vms) { |&block|\n        block.call machine\n        block.call machine2\n      }\n      expect(machine).to receive(:action) do |name, opts|\n        expect(name).to eq(:suspend)\n      end\n      expect(machine2).to receive(:action) do |name, opts|\n        expect(name).to eq(:suspend)\n      end\n\n      expect(subject.execute).to eq(0)\n    end\n  end\n\n  context \"with an argument\" do\n    let(:vagrantfile_content) do\n        <<-VF\n        Vagrant.configure(\"2\") do |config|\n          config.vm.define \"app\"\n          config.vm.define \"db\"\n        end\n        VF\n    end\n    let(:argv) { [\"app\"] }\n\n    it \"should suspend a vm\" do\n      expect(machine).to receive(:action) do |name, opts|\n        expect(name).to eq(:suspend)\n      end\n\n      expect(subject.execute).to eq(0)\n    end\n  end\n\n  context \"with the global all flag\" do\n    let(:argv){ [\"--all-global\"] }\n\n    it \"should suspend all vms globally\" do\n      global_env = isolated_environment\n      global_env.vagrantfile(\"Vagrant.configure(2){|config| config.vm.box = 'dummy'}\")\n      global_venv = global_env.create_vagrant_env\n      global_machine = global_venv.machine(global_venv.machine_names[0], :dummy)\n      global_machine.id = \"1234\"\n      global = new_entry(global_machine.name)\n      global.provider = \"dummy\"\n      global.vagrantfile_path = global_env.workdir\n      locked = iso_env.machine_index.set(global)\n      iso_env.machine_index.release(locked)\n\n      allow(subject).to receive(:with_target_vms) { |&block| block.call global_machine }\n      expect(global_machine).to receive(:action) do |name, opts|\n        expect(name).to eq(:suspend)\n      end\n\n      expect(subject.execute).to eq(0)\n    end\n\n    context \"with an argument is used\" do\n      let(:argv){ [\"machine\", \"--all-global\"] }\n\n      it \"errors out\" do\n        expect{subject.execute}.to raise_error(Vagrant::Errors::CommandSuspendAllArgs)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/up/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/up/command\")\n\ndescribe VagrantPlugins::CommandUp::Command do\n  include_context \"unit\"\n\n  let(:entry_klass) { Vagrant::MachineIndex::Entry }\n  let(:argv)     { [] }\n  let(:vagrantfile_content){ \"\" }\n  let(:iso_env) do\n    env = isolated_environment\n    env.vagrantfile(vagrantfile_content)\n    env.create_vagrant_env\n  end\n\n  subject { described_class.new(argv, iso_env) }\n\n  let(:action_runner) { double(\"action_runner\") }\n\n  def new_entry(name)\n    entry_klass.new.tap do |e|\n      e.name = name\n      e.vagrantfile_path = \"/bar\"\n    end\n  end\n\n  before do\n    allow(iso_env).to receive(:action_runner).and_return(action_runner)\n  end\n\n  context \"with no argument\" do\n    let(:vagrantfile_content){ \"Vagrant.configure(2){|config| config.vm.box = 'dummy'}\" }\n\n    it \"should bring up the default box\" do\n      batch = double(\"environment_batch\")\n      expect(iso_env).to receive(:batch).and_yield(batch)\n      expect(batch).to receive(:action).with(anything, :up, anything)\n      subject.execute\n    end\n\n    context \"with VAGRANT_DEFAULT_PROVIDER set\" do\n      before do\n        if ENV[\"VAGRANT_DEFAULT_PROVIDER\"]\n          @original_default = ENV[\"VAGRANT_DEFAULT_PROVIDER\"]\n        end\n        ENV[\"VAGRANT_DEFAULT_PROVIDER\"] = \"unknown\"\n      end\n      after do\n        if @original_default\n          ENV[\"VAGRANT_DEFAULT_PROVIDER\"] = @original_default\n        else\n          ENV.delete(\"VAGRANT_DEFAULT_PROVIDER\")\n        end\n      end\n\n      it \"should attempt to use dummy provider\" do\n        expect{ subject.execute }.to raise_error(Vagrant::Errors::ProviderNotFound)\n      end\n\n      context \"with --provider set\" do\n        let(:argv){ [\"--provider\", \"dummy\"] }\n\n        it \"should only use provider explicitly set\" do\n          batch = double(\"environment_batch\")\n          expect(iso_env).to receive(:batch).and_yield(batch)\n          expect(batch).to receive(:action).with(anything, :up, anything)\n          subject.execute\n        end\n      end\n    end\n  end\n\n  context \"with a global machine\" do\n    let(:argv){ [\"1234\"] }\n\n    it \"brings up a vm with an id\" do\n\n      global_env = isolated_environment\n      global_env.vagrantfile(\"Vagrant.configure(2){|config| config.vm.box = 'dummy'}\")\n      global_venv = global_env.create_vagrant_env\n      global_machine = global_venv.machine(global_venv.machine_names[0], :dummy)\n      global_machine.id = \"1234\"\n      global = new_entry(global_machine.name)\n      global.provider = \"dummy\"\n      global.vagrantfile_path = global_env.workdir\n      locked = iso_env.machine_index.set(global)\n      iso_env.machine_index.release(locked)\n\n      allow(subject).to receive(:with_target_vms) { |&block| block.call global_machine }\n\n\n      batch = double(\"environment_batch\")\n      expect(iso_env).to receive(:batch).and_yield(batch)\n      expect(batch).to receive(:action).with(global_machine, :up, anything) do |machine,action,args|\n        expect(machine).to be_kind_of(Vagrant::Machine)\n        expect(action).to eq(:up)\n      end\n      subject.execute\n    end\n  end\n\n  context \"with an argument\" do\n    let(:vagrantfile_content) do\n        <<-VF\n        Vagrant.configure(\"2\") do |config|\n          config.vm.define \"app\"\n          config.vm.define \"db\"\n        end\n        VF\n    end\n    let(:argv){ [\"app\"] }\n    let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n    it \"brings up a vm\" do\n      batch = double(\"environment_batch\")\n      expect(iso_env).to receive(:batch).and_yield(batch)\n      expect(batch).to receive(:action).with(machine, :up, anything) do |machine,action,args|\n        expect(machine).to be_kind_of(Vagrant::Machine)\n        expect(action).to eq(:up)\n      end\n      subject.execute\n    end\n\n    context \"with an invalid argument\" do\n      let(:argv){ [\"notweb\"] }\n      it \"brings up a vm\" do\n        expect { subject.execute }.to raise_error(Vagrant::Errors::MachineNotFound)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/up/middleware/store_box_metadata_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/up/middleware/store_box_metadata\")\n\ndescribe VagrantPlugins::CommandUp::StoreBoxMetadata do\n  include_context \"unit\"\n\n  let(:app) { double(\"app\") }\n  let(:machine) { double(\"machine\", box: box) }\n  let(:box) {\n    double(\"box\",\n      name: box_name,\n      version: box_version,\n      provider: box_provider,\n      directory: box_directory\n    )\n  }\n  let(:box_name) { \"BOX_NAME\" }\n  let(:box_version) { \"1.0.0\" }\n  let(:box_provider) { \"dummy\" }\n  let(:box_directory) { File.join(vagrant_user_data_path, box_directory_relative) }\n  let(:box_directory_relative) { File.join(\"boxes\", \"BOX_NAME\") }\n  let(:vagrant_user_data_path) { \"/vagrant/user/data\" }\n  let(:meta_path) { \"META_PATH\" }\n  let(:env) { {machine: machine} }\n\n  let(:subject) { described_class.new(app, env) }\n\n  describe \"#call\" do\n    context \"with no box file\" do\n      let(:machine) { double(\"machine\", name: \"guest\", provider_name: \"provider\") }\n      let(:env) { {machine: machine} }\n\n      before do\n        allow(machine).to receive(:box).and_return(nil)\n        expect(app).to receive(:call).with(env)\n      end\n\n\n      it \"does not write the metadata file\" do\n        expect(File).to_not receive(:open)\n        subject.call(env)\n      end\n    end\n\n    let(:meta_file) { double(\"meta_file\") }\n\n    before do\n      allow(Vagrant).to receive(:user_data_path).and_return(vagrant_user_data_path)\n      allow(machine).to receive(:data_dir).and_return(meta_path)\n      allow(meta_path).to receive(:join).with(\"box_meta\").and_return(meta_path)\n      allow(File).to receive(:open)\n      expect(app).to receive(:call).with(env)\n    end\n\n    after { subject.call(env) }\n\n    it \"should open a metadata file\" do\n      expect(File).to receive(:open).with(meta_path, anything)\n    end\n\n    context \"contents of metadata file\" do\n\n      before { expect(File).to receive(:open).with(meta_path, anything).and_yield(meta_file) }\n\n      it \"should be JSON data\" do\n        expect(meta_file).to receive(:write) do |data|\n          val = JSON.parse(data)\n          expect(val).to be_a(Hash)\n        end\n      end\n\n      it \"should include box name\" do\n        expect(meta_file).to receive(:write) do |data|\n          val = JSON.parse(data)\n          expect(val[\"name\"]).to eq(box_name)\n        end\n      end\n\n      it \"should include box version\" do\n        expect(meta_file).to receive(:write) do |data|\n          val = JSON.parse(data)\n          expect(val[\"version\"]).to eq(box_version)\n        end\n      end\n\n      it \"should include box provider\" do\n        expect(meta_file).to receive(:write) do |data|\n          val = JSON.parse(data)\n          expect(val[\"provider\"]).to eq(box_provider)\n        end\n      end\n\n      it \"should include relative box directory\" do\n        expect(meta_file).to receive(:write) do |data|\n          val = JSON.parse(data)\n          expect(val[\"directory\"]).to eq(box_directory_relative)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/upload/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/commands/upload/command\")\n\ndescribe VagrantPlugins::CommandUpload::Command do\n  include_context \"unit\"\n  include_context \"virtualbox\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\", capability_host_chain: guest_chain) }\n  let(:host)    { double(\"host\", capability_host_chain: host_chain) }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { double(\"communicator\") }\n  let(:host_chain){ [[]] }\n  let(:guest_chain){ [[]] }\n\n  let(:argv)     { [] }\n  let(:config) {\n    double(\"config\")\n  }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(machine).to receive(:config).and_return(config)\n    allow(subject).to receive(:with_target_vms)\n  end\n\n  it \"should raise invalid usage error by default\" do\n    expect { subject.execute }.to raise_error(Vagrant::Errors::CLIInvalidUsage)\n  end\n\n  context \"when three arguments are provided\" do\n    let(:argv) { [\"source\", \"destination\", \"guest\"] }\n\n    before { allow(File).to receive(:file?).and_return(true) }\n\n    it \"should use third argument as name of guest\" do\n      expect(subject).to receive(:with_target_vms).with(\"guest\", any_args)\n      subject.execute\n    end\n\n    it \"should use first argument as source and second as destination\" do\n      allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n      expect(communicator).to receive(:upload).with(\"source\", \"destination\")\n      subject.execute\n    end\n  end\n\n  context \"when two arguments are provided\" do\n    let(:argv) { [\"source\", \"ambiguous\"] }\n    let(:active_machines) { [] }\n\n    before do\n      allow(File).to receive(:file?).and_return(true)\n      allow(iso_env).to receive(:active_machines).and_return(active_machines)\n    end\n\n    it \"should use the the second argument as destination when not a machine name\" do\n      allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n      expect(communicator).to receive(:upload).with(\"source\", \"ambiguous\")\n      subject.execute\n    end\n\n    context \"when active machine matches second argument\" do\n      let(:active_machines) { [[\"ambiguous\"]] }\n\n      it \"should use second argument as guest name and generate destination\" do\n        allow(subject).to receive(:with_target_vms).with(\"ambiguous\", any_args) { |&block| block.call machine }\n        expect(communicator).to receive(:upload).with(\"source\", \"source\")\n        subject.execute\n      end\n    end\n  end\n\n  context \"when single argument is provided\" do\n    let(:argv) { [\"item\"] }\n\n    before do\n      allow(File).to receive(:file?)\n      allow(File).to receive(:directory?)\n    end\n\n    it \"should check if source is a file\" do\n      expect(File).to receive(:file?).with(\"item\").and_return(true)\n      subject.execute\n    end\n\n    it \"should check if source is a directory\" do\n      expect(File).to receive(:directory?).with(\"item\").and_return(true)\n      subject.execute\n    end\n\n    it \"should raise error if source is not a directory or file\" do\n      expect { subject.execute }.to raise_error(Vagrant::Errors::UploadSourceMissing)\n    end\n\n    context \"when source path ends with double quote\" do\n      let(:argv) { [\".\\\\item\\\"\"] }\n\n      it \"should remove trailing quote\" do\n        expect(File).to receive(:file?).with(\".\\\\item\").and_return(true)\n        subject.execute\n      end\n    end\n\n    context \"when source path ends with single quote\" do\n      let(:argv) { ['.\\item\\''] }\n\n      it \"should remove trailing quote\" do\n        expect(File).to receive(:file?).with(\".\\\\item\").and_return(true)\n        subject.execute\n      end\n    end\n\n    context \"when source is a directory\" do\n      before do\n        allow(File).to receive(:file?).with(\"item\").and_return(false)\n        allow(File).to receive(:directory?).with(\"item\").and_return(true)\n        allow(communicator).to receive(:upload)\n        allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n      end\n\n      it \"should upload the directory\" do\n        expect(communicator).to receive(:upload).with(/item/, anything)\n        subject.execute\n      end\n\n      it \"should append separator and dot to source path for upload\" do\n        expect(communicator).to receive(:upload).with(\"item/.\", anything)\n        subject.execute\n      end\n    end\n\n    context \"when source is a file\" do\n      before do\n        allow(File).to receive(:file?).with(\"item\").and_return(true)\n        allow(communicator).to receive(:upload)\n        allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n        allow(machine).to receive(:guest).and_return(guest)\n        allow(machine).to receive(:env).and_return(double(\"env\", host: host))\n        allow(guest).to receive(:capability?).and_return(true)\n        allow(guest).to receive(:capability)\n      end\n\n      it \"should upload the file\" do\n        expect(communicator).to receive(:upload).with(\"item\", anything)\n        subject.execute\n      end\n\n      it \"should name destination after the source\" do\n        expect(communicator).to receive(:upload).with(\"item\", \"item\")\n        subject.execute\n      end\n\n      context \"when temporary option is set\" do\n        before { argv << \"-t\" }\n\n        it \"should get temporary path for destination from guest\" do\n          expect(guest).to receive(:capability).with(:create_tmp_path, any_args).and_return(\"TMP_PATH\")\n          expect(communicator).to receive(:upload).with(\"item\", \"TMP_PATH\")\n          subject.execute\n        end\n      end\n\n      context \"when compress option is set\" do\n        before do\n          argv << \"-c\"\n          allow(guest).to receive(:capability).with(:create_tmp_path, any_args).and_return(\"TMP\")\n          allow(subject).to receive(:compress_source_zip).and_return(\"COMPRESS_SOURCE\")\n          allow(subject).to receive(:compress_source_tgz).and_return(\"COMPRESS_SOURCE\")\n          allow(FileUtils).to receive(:rm).with(\"COMPRESS_SOURCE\")\n        end\n\n        it \"should check for guest decompression\" do\n          expect(guest).to receive(:capability?).with(:decompress_tgz).and_return(true)\n          subject.execute\n        end\n\n        it \"should compress the source\" do\n          expect(subject).to receive(:compress_source_tgz).with(\"item\").and_return(\"COMPRESS_SOURCE\")\n          subject.execute\n        end\n\n        it \"should cleanup compressed source\" do\n          expect(FileUtils).to receive(:rm).with(\"COMPRESS_SOURCE\")\n          subject.execute\n        end\n\n        it \"should upload the compressed source\" do\n          expect(communicator).to receive(:upload).with(\"COMPRESS_SOURCE\", anything)\n          subject.execute\n        end\n\n        it \"should upload compressed source to temporary location\" do\n          expect(communicator).to receive(:upload).with(\"COMPRESS_SOURCE\", \"TMP\")\n          subject.execute\n        end\n\n        it \"should have guest decompress file\" do\n          expect(guest).to receive(:capability).with(:decompress_tgz, \"TMP\", any_args)\n          subject.execute\n        end\n\n        it \"should provide destination for guest decompression of file\" do\n          expect(guest).to receive(:capability).with(:decompress_tgz, \"TMP\", \"item\", any_args)\n          subject.execute\n        end\n\n        it \"should provide the destination type for guest decompression\" do\n          expect(guest).to receive(:capability).with(:decompress_tgz, \"TMP\", \"item\", hash_including(type: :file))\n          subject.execute\n        end\n\n        context \"with compression type set to zip\" do\n          before { argv << \"-C\" << \"zip\" }\n\n          it \"should test guest for decompression capability\" do\n            expect(guest).to receive(:capability?).with(:decompress_zip).and_return(true)\n            subject.execute\n          end\n\n          it \"should compress source using zip\" do\n            expect(subject).to receive(:compress_source_zip)\n            subject.execute\n          end\n\n          it \"should have guest decompress file using zip\" do\n            expect(guest).to receive(:capability).with(:decompress_zip, any_args)\n            subject.execute\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/validate/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\nrequire_relative \"../../../../../plugins/commands/validate/command\"\n\ndescribe VagrantPlugins::CommandValidate::Command do\n  include_context \"unit\"\n  include_context \"command plugin helpers\"\n\n  let(:vagrantfile_content){ \"\" }\n  let(:iso_env) do\n    env = isolated_environment\n    env.vagrantfile(vagrantfile_content)\n    env.create_vagrant_env\n  end\n\n  let(:action_runner) { double(\"action_runner\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:argv)   { [] }\n\n  before(:all) do\n    I18n.load_path << Vagrant.source_root.join(\"plugins/commands/port/locales/en.yml\")\n    I18n.reload!\n  end\n\n  subject { described_class.new(argv, iso_env) }\n\n  describe \"#execute\" do\n    context \"validating configs\" do\n      let(:vagrantfile_content) do\n          <<-VF\n          Vagrant.configure(\"2\") do |config|\n            config.vm.box = \"hashicorp/precise64\"\n            config.vm.synced_folder \".\", \"/vagrant\", disabled: true\n          end\n          VF\n      end\n      it \"validates correct Vagrantfile\" do\n        expect(machine).to receive(:action_raw) do |name, action, env|\n          expect(name).to eq(:config_validate)\n          expect(action).to eq(Vagrant::Action::Builtin::ConfigValidate)\n          expect(env).to eq({})\n        end\n        expect(iso_env.ui).to receive(:info).with(any_args) { |message, _|\n          expect(message).to include(\"Vagrantfile validated successfully.\")\n        }\n\n        expect(subject.execute).to eq(0)\n      end\n    end\n\n    context \"invalid configs\" do\n      let(:vagrantfile_content) do\n        <<-VF\n        Vagrant.configure(\"2\") do |config|\n          config.vm.bix = \"hashicorp/precise64\"\n          config.vm.synced_folder \".\", \"/vagrant\", disabled: true\n        end\n        VF\n      end\n      it \"validates the configuration\" do\n        expect { subject.execute }.to raise_error(Vagrant::Errors::ConfigInvalid) { |err|\n          expect(err.message).to include(\"The following settings shouldn't exist: bix\")\n        }\n      end\n    end\n\n    context \"valid configs for multiple vms\" do\n      let(:vagrantfile_content) do\n        <<-VF\n        Vagrant.configure(\"2\") do |config|\n          config.vm.box = \"hashicorp/precise64\"\n          config.vm.synced_folder \".\", \"/vagrant\", disabled: true\n\n          config.vm.define \"test\" do |vm|\n            vm.vm.provider :virtualbox\n          end\n\n          config.vm.define \"machine\" do |vm|\n            vm.vm.provider :virtualbox\n          end\n        end\n        VF\n      end\n      it \"validates correct Vagrantfile of all vms\" do\n        expect(machine).to receive(:action_raw) do |name, action, env|\n          expect(name).to eq(:config_validate)\n          expect(action).to eq(Vagrant::Action::Builtin::ConfigValidate)\n          expect(env).to eq({})\n        end\n        expect(iso_env.ui).to receive(:info).with(any_args) { |message, _|\n          expect(message).to include(\"Vagrantfile validated successfully.\")\n        }\n\n        expect(subject.execute).to eq(0)\n      end\n    end\n\n    context \"an invalid config for some vms\" do\n      let(:vagrantfile_content) do\n        <<-VF\n        Vagrant.configure(\"2\") do |config|\n          config.vm.box = \"hashicorp/precise64\"\n          config.vm.synced_folder \".\", \"/vagrant\", disabled: true\n\n          config.vm.define \"test\" do |vm|\n            vm.vm.provider :virtualbox\n          end\n\n          config.vm.define \"machine\" do |vm|\n            vm.vm.not_provider :virtualbox\n          end\n        end\n        VF\n      end\n      it \"validates the configuration of all vms\" do\n        expect(machine).to receive(:action_raw) do |name, action, env|\n          expect(name).to eq(:config_validate)\n          expect(action).to eq(Vagrant::Action::Builtin::ConfigValidate)\n          expect(env).to eq({})\n        end\n\n        expect { subject.execute }.to raise_error(Vagrant::Errors::ConfigInvalid) { |err|\n          expect(err.message).to include(\"The following settings shouldn't exist: not_provider\")\n        }\n      end\n    end\n\n    context \"with the ignore provider flag\" do\n      let(:argv) { [\"--ignore-provider\"]}\n      let(:vagrantfile_content) do\n        <<-VF\n        Vagrant.configure(\"2\") do |config|\n          config.vm.box = \"hashicorp/precise64\"\n          config.vm.synced_folder \".\", \"/vagrant\", disabled: true\n\n          config.vm.define \"test\" do |vm|\n            vm.vm.hostname = \"test\"\n            vm.vm.provider :virtualbox do |v|\n              v.not_a_real_option = true\n            end\n          end\n        end\n        VF\n      end\n      it \"ignores provider specific configurations with the flag\" do\n        allow(subject).to receive(:mockup_providers!).and_return(\"\")\n        allow(FileUtils).to receive(:remove_entry).and_return(true)\n        expect(iso_env.ui).to receive(:info).with(any_args) { |message, _|\n          expect(message).to include(\"Vagrantfile validated successfully.\")\n        }\n\n        expect(machine).to receive(:action_raw) do |name, action, env|\n          expect(name).to eq(:config_validate)\n          expect(action).to eq(Vagrant::Action::Builtin::ConfigValidate)\n          expect(env).to eq({:ignore_provider=>true})\n        end\n\n        expect(subject.execute).to eq(0)\n      end\n    end\n\n    context \"no vagrantfile\" do\n      let(:vagrantfile_content){ \"\" }\n      let(:env) { isolated_environment.create_vagrant_env }\n      subject { described_class.new(argv, env) }\n      it \"throws an exception if there's no Vagrantfile\" do\n        expect { subject.execute }.to raise_error(Vagrant::Errors::NoEnvironmentError)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/winrm/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/winrm/command\")\n\ndescribe VagrantPlugins::CommandWinRM::Command do\n  include_context \"unit\"\n  include_context \"virtualbox\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { double(\"communicator\") }\n\n  let(:argv)     { [] }\n  let(:config) {\n    double(\"config\",\n      vm: double(\"vm-config\", communicator: communicator_name))\n  }\n  let(:communicator_name) { :winrm }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(machine).to receive(:config).and_return(config)\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n    allow(communicator).to receive(:execute)\n  end\n\n  it \"should exit successfully when no command is provided\" do\n    expect(subject.execute).to eq(0)\n  end\n\n  context \"with command provided\" do\n    let(:argv){ [\"-c\", \"dir\"] }\n\n    it \"should execute the command via the communicator\" do\n      expect(communicator).to receive(:execute).with(\"dir\", any_args)\n      subject.execute\n    end\n\n    it \"should execute with default shell\" do\n      expect(communicator).to receive(:execute).with(anything, hash_including(shell: :powershell))\n      subject.execute\n    end\n\n    it \"should execute without elevated privileges\" do\n      expect(communicator).to receive(:execute).with(anything, hash_including(elevated: false))\n      subject.execute\n    end\n\n    context \"with elevated option set\" do\n      let(:argv) { [\"-c\", \"dir\", \"-e\"] }\n\n      it \"should execute with elevated privileges\" do\n        expect(communicator).to receive(:execute).with(anything, hash_including(elevated: true))\n        subject.execute\n      end\n    end\n\n    context \"with shell option set\" do\n      let(:argv) { [\"-c\", \"dir\", \"-s\", \"cmd\"] }\n\n      it \"should execute with custom shell\" do\n        expect(communicator).to receive(:execute).with(anything, hash_including(shell: :cmd))\n        subject.execute\n      end\n    end\n  end\n\n  context \"with multiple command provided\" do\n    let(:argv) { [\"-c\", \"dir\", \"-c\", \"dir2\"] }\n\n    it \"should execute multiple commands via the communicator\" do\n      expect(communicator).to receive(:execute).with(\"dir\", any_args)\n      expect(communicator).to receive(:execute).with(\"dir2\", any_args)\n      subject.execute\n    end\n  end\n\n  context \"with invalid communicator configured\" do\n    let(:communicator_name) { :ssh }\n\n    it \"should raise an error\" do\n      expect { subject.execute }.to raise_error(Vagrant::Errors::WinRMInvalidCommunicator)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/commands/winrm_config/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/commands/winrm_config/command\")\nrequire Vagrant.source_root.join(\"plugins/communicators/winrm/helper\")\n\ndescribe VagrantPlugins::CommandWinRMConfig::Command do\n  include_context \"unit\"\n  include_context \"virtualbox\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:argv)     { [] }\n  let(:winrm_info) {{\n    host: \"testhost.vagrant.dev\",\n    port: 1234\n  }}\n  let(:config) {\n    double(\"config\",\n      winrm: double(\"winrm-config\", username: \"vagrant\", password: \"vagrant\"),\n      rdp: rdp_config,\n      vm: double(\"vm-config\", communicator: :winrm)\n    )\n  }\n\n  let(:rdp_config) { double(\"rdp-config\", port: 9876) }\n\n  subject { described_class.new(argv, iso_env) }\n\n  before do\n    allow(machine).to receive(:config).and_return(config)\n    allow(VagrantPlugins::CommunicatorWinRM::Helper).to receive(:winrm_info).and_return(winrm_info)\n    allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n  end\n\n  describe \"execute\" do\n    it \"prints out the winrm config for the given machine\" do\n      output = \"\"\n      allow(subject).to receive(:safe_puts) do |data|\n        output += data if data\n      end\n\n      subject.execute\n\n      expect(output).to eq(<<-WINRMCONFIG)\nHost #{machine.name}\n  HostName testhost.vagrant.dev\n  User vagrant\n  Password vagrant\n  Port 1234\n  RDPHostName testhost.vagrant.dev\n  RDPPort 9876\n  RDPUser vagrant\n  RDPPassword vagrant\n      WINRMCONFIG\n    end\n\n    context \"with host option set\" do\n      let(:argv) { [\"--host\", \"my-host\"]}\n\n      it \"should use custom host name in config output\" do\n        output = \"\"\n        allow(subject).to receive(:safe_puts) do |data|\n          output += data if data\n        end\n\n        subject.execute\n\n        expect(output).to eq(<<-WINRMCONFIG)\nHost my-host\n  HostName testhost.vagrant.dev\n  User vagrant\n  Password vagrant\n  Port 1234\n  RDPHostName testhost.vagrant.dev\n  RDPPort 9876\n  RDPUser vagrant\n  RDPPassword vagrant\n      WINRMCONFIG\n      end\n    end\n\n    context \"when no RDP port is configured\" do\n      let(:rdp_config) {  double(\"rdp-config\", port: nil) }\n\n      it \"should not include any RDP configuration information\" do\n        output = \"\"\n        allow(subject).to receive(:safe_puts) do |data|\n          output += data if data\n        end\n\n        subject.execute\n        expect(output).not_to include(\"RDP\")\n      end\n    end\n\n    context \"when provider has rdp_info capability\" do\n      let(:rdp_info) {\n        {host: \"provider-host\", port: 9999, username: \"pvagrant\", password: \"pvagrant\"}\n      }\n\n      before do\n        allow(machine.provider).to receive(:capability?).with(:rdp_info).and_return(true)\n        allow(machine.provider).to receive(:capability).with(:rdp_info).and_return(rdp_info)\n      end\n\n      it \"should use provider RDP information\" do\n        output = \"\"\n        allow(subject).to receive(:safe_puts) do |data|\n          output += data if data\n        end\n\n        subject.execute\n        expect(output).to include(\"RDPPort 9999\")\n        expect(output).to include(\"RDPHostName provider-host\")\n        expect(output).to include(\"RDPUser pvagrant\")\n        expect(output).to include(\"RDPPassword pvagrant\")\n      end\n\n      context \"when provider rdp_info does not include host\" do\n        before { rdp_info[:host] = nil }\n\n        it \"should use winrm host\" do\n          output = \"\"\n          allow(subject).to receive(:safe_puts) do |data|\n            output += data if data\n          end\n\n          subject.execute\n          expect(output).to include(\"RDPHostName testhost.vagrant.dev\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/communicators/none/communicator_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/communicators/none/communicator\")\n\ndescribe VagrantPlugins::CommunicatorNone::Communicator do\n  include_context \"unit\"\n\n  let(:machine) { double(:machine) }\n\n  subject { described_class.new(machine) }\n\n  context \"#ready?\" do\n    it \"should be true\" do\n      expect(subject.ready?).to be\n    end\n  end\n\n  context \"#execute\" do\n    it \"should be successful regardless of command\" do\n      expect(subject.execute(\"/bin/false\")).to eq(0)\n    end\n  end\n\n  context \"#sudo\" do\n    it \"should be successful regardless of command\" do\n      expect(subject.execute(\"/bin/false\")).to eq(0)\n    end\n  end\n\n  context \"test\" do\n    it \"should be true\" do\n      expect(subject.test(\"/bin/false\")).to be\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/communicators/ssh/communicator_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/communicators/ssh/communicator\")\n\ndescribe VagrantPlugins::CommunicatorSSH::Communicator do\n  include_context \"unit\"\n\n  let(:export_command_template){ 'export %ENV_KEY%=\"%ENV_VALUE%\"' }\n\n  # SSH configuration information mock\n  let(:ssh) do\n    double(\"ssh\",\n      key_type: :auto,\n      timeout: 1,\n      host: nil,\n      port: 5986,\n      guest_port: 5986,\n      pty: false,\n      keep_alive: false,\n      insert_key: insert_ssh_key,\n      export_command_template: export_command_template,\n      shell: 'bash -l'\n    )\n  end\n  # Do not insert public key by default\n  let(:insert_ssh_key){ false }\n  # Configuration mock\n  let(:config) { double(\"config\", ssh: ssh) }\n  # Provider mock\n  let(:provider) { double(\"provider\") }\n  let(:ui) { Vagrant::UI::Silent.new }\n  # Machine mock built with previously defined\n  let(:machine) do\n    double(\"machine\",\n      config: config,\n      provider: provider,\n      ui: ui,\n      env: env\n    )\n  end\n  let(:env){ double(\"env\", host: host) }\n  let(:host){ double(\"host\") }\n  # SSH information of the machine\n  let(:machine_ssh_info){ {host: '10.1.2.3', port: 22} }\n  # Subject instance to test\n  let(:communicator){ @communicator ||= described_class.new(machine) }\n  # Underlying net-ssh connection mock\n  let(:connection) { double(\"connection\") }\n  # Base net-ssh connection channel mock\n  let(:channel) { double(\"channel\") }\n  # net-ssh connection channel mock for running commands\n  let(:command_channel) { double(\"command_channel\") }\n  # Default exit data for commands run\n  let(:exit_data) { double(\"exit_data\", read_long: 0) }\n  # Core shell command used when starting command connection\n  let(:core_shell_cmd) { \"bash -l\" }\n  # Marker used for flagging start of output\n  let(:command_garbage_marker) { communicator.class.const_get(:CMD_GARBAGE_MARKER) }\n  # Start marker output when PTY is enabled\n  let(:pty_delim_start) { communicator.class.const_get(:PTY_DELIM_START) }\n  # End marker output when PTY is enabled\n  let(:pty_delim_end) { communicator.class.const_get(:PTY_DELIM_END) }\n  # Command output returned on stdout\n  let(:command_stdout_data) { '' }\n  # Command output returned on stderr\n  let(:command_stderr_data) { '' }\n  # Mock for net-ssh scp\n  let(:scp) { double(\"scp\") }\n  # Value returned from remote ssh supported key check\n  let(:sudo_supported_key_list) { \"pubkeyacceptedalgorithms ssh-rsa\" }\n\n  # Setup for commands using the net-ssh connection. This can be reused where needed\n  # by providing to `before`\n  connection_setup = proc do\n    allow(connection).to receive(:closed?).and_return false\n    allow(connection).to receive(:open_channel).\n      and_yield(channel).and_return(channel)\n    allow(connection).to receive(:close)\n    allow(channel).to receive(:wait).and_return true\n    allow(channel).to receive(:close)\n    allow(command_channel).to receive(:send_data)\n    allow(command_channel).to receive(:eof!)\n    allow(command_channel).to receive(:on_data).\n      and_yield(nil, command_stdout_data)\n    allow(command_channel).to receive(:on_extended_data).\n      and_yield(nil, nil, command_stderr_data)\n    allow(machine).to receive(:ssh_info).and_return(machine_ssh_info)\n    allow(channel).to receive(:exec).with(core_shell_cmd).\n      and_yield(command_channel, '').and_return channel\n    allow(command_channel).to receive(:on_request).with('exit-status').\n      and_yield(nil, exit_data)\n    # Return mocked net-ssh connection during setup\n    allow(communicator).to receive(:retryable).and_return(connection)\n    # Stub in a response for supported key types check\n    allow(communicator).to receive(:sudo).with(\"sshd -T | grep key\", any_args).\n      and_yield(:stdout, sudo_supported_key_list).and_return(0)\n  end\n\n  before do\n    allow(host).to receive(:capability?).and_return(false)\n  end\n\n  describe \"#wait_for_ready\" do\n    before(&connection_setup)\n    context \"with no static config (default scenario)\" do\n      context \"when ssh_info requires a multiple tries before it is ready\" do\n        before do\n          expect(machine).to receive(:ssh_info).\n            and_return(nil).ordered\n          expect(machine).to receive(:ssh_info).\n            and_return(host: '10.1.2.3', port: 22).ordered\n        end\n\n        it \"retries ssh_info until ready\" do\n          # retries are every 0.5 so buffer the timeout just a hair over\n          expect(communicator.wait_for_ready(0.6)).to eq(true)\n        end\n      end\n\n      context \"when printing message to the user\" do\n        before do\n          allow(machine).to receive(:ssh_info).\n            and_return(host: '10.1.2.3', port: 22)\n          allow(communicator).to receive(:connect)\n          allow(communicator).to receive(:ready?).and_return(true)\n          allow(ui).to receive(:detail).and_call_original\n        end\n\n        it \"should print message\" do\n          expect(communicator).to receive(:connect).and_raise(Vagrant::Errors::SSHConnectionTimeout)\n          expect(ui).to receive(:detail).with(/timeout/).and_call_original\n          communicator.wait_for_ready(0.5)\n        end\n\n        it \"should not print the same message twice\" do\n          expect(communicator).to receive(:connect).and_raise(Vagrant::Errors::SSHConnectionTimeout)\n          expect(communicator).to receive(:connect).and_raise(Vagrant::Errors::SSHConnectionTimeout)\n          expect(ui).to receive(:detail).with(/timeout/).and_call_original\n          expect(ui).not_to receive(:detail).with(/timeout/)\n          communicator.wait_for_ready(0.5)\n        end\n\n        it \"should print different messages\" do\n          expect(communicator).to receive(:connect).and_raise(Vagrant::Errors::SSHConnectionTimeout)\n          expect(communicator).to receive(:connect).and_raise(Vagrant::Errors::SSHDisconnected)\n          expect(ui).to receive(:detail).with(/timeout/).and_call_original\n          expect(ui).to receive(:detail).with(/disconnect/).and_call_original\n          communicator.wait_for_ready(0.5)\n        end\n\n        it \"should not print different messages twice\" do\n          expect(communicator).to receive(:connect).and_raise(Vagrant::Errors::SSHConnectionTimeout)\n          expect(communicator).to receive(:connect).and_raise(Vagrant::Errors::SSHDisconnected)\n          expect(communicator).to receive(:connect).and_raise(Vagrant::Errors::SSHConnectionTimeout)\n          expect(communicator).to receive(:connect).and_raise(Vagrant::Errors::SSHDisconnected)\n          expect(ui).to receive(:detail).with(/timeout/).and_call_original\n          expect(ui).to receive(:detail).with(/disconnect/).and_call_original\n          expect(ui).not_to receive(:detail).with(/timeout/)\n          expect(ui).not_to receive(:detail).with(/disconnect/)\n          communicator.wait_for_ready(0.5)\n        end\n      end\n    end\n  end\n\n  describe \"#reset!\" do\n    let(:connection) { double(\"connection\") }\n\n    before do\n      allow(communicator).to receive(:wait_for_ready)\n      allow(connection).to receive(:close)\n      communicator.send(:instance_variable_set, :@connection, connection)\n    end\n\n    it \"should close existing connection\" do\n      expect(connection).to receive(:close)\n      communicator.reset!\n    end\n\n    it \"should call wait_for_ready to re-enable the connection\" do\n      expect(communicator).to receive(:wait_for_ready)\n      communicator.reset!\n    end\n  end\n\n  describe \"#ready?\" do\n    before(&connection_setup)\n    it \"returns true if shell test is successful\" do\n      expect(communicator.ready?).to be(true)\n    end\n\n    context \"with an invalid shell test\" do\n      before do\n        expect(exit_data).to receive(:read_long).and_return 1\n      end\n\n      it \"returns raises SSHInvalidShell error\" do\n        expect{ communicator.ready? }.to raise_error Vagrant::Errors::SSHInvalidShell\n      end\n    end\n\n    context \"with insert key enabled\" do\n      before do\n        allow(machine).to receive(:guest).and_return(guest)\n        allow(guest).to receive(:capability?).with(:insert_public_key).\n          and_return(has_insert_cap)\n        allow(guest).to receive(:capability?).with(:remove_public_key).\n          and_return(has_remove_cap)\n        allow(communicator).to receive(:insecure_key?).with(\"KEY_PATH\").and_return(true)\n      end\n\n      let(:insert_ssh_key){ true }\n      let(:has_insert_cap){ false }\n      let(:has_remove_cap){ false }\n      let(:machine_ssh_info){\n        {host: '10.1.2.3', port: 22, private_key_path: [\"KEY_PATH\"]}\n      }\n      let(:guest){ double(\"guest\") }\n\n      context \"without guest insert_ssh_key or remove_ssh_key capabilities\" do\n        it \"should raise an error\" do\n          expect{ communicator.ready? }.to raise_error(Vagrant::Errors::SSHInsertKeyUnsupported)\n        end\n      end\n\n      context \"without guest insert_ssh_key capability\" do\n        let(:has_remove_cap){ true }\n\n        it \"should raise an error\" do\n          expect{ communicator.ready? }.to raise_error(Vagrant::Errors::SSHInsertKeyUnsupported)\n        end\n      end\n\n      context \"without guest remove_ssh_key capability\" do\n        let(:has_insert_cap){ true }\n\n        it \"should raise an error\" do\n          expect{ communicator.ready? }.to raise_error(Vagrant::Errors::SSHInsertKeyUnsupported)\n        end\n      end\n\n      context \"with guest insert_ssh_key capability and remove_ssh_key capability\" do\n        let(:has_insert_cap){ true }\n        let(:has_remove_cap){ true }\n        let(:new_public_key){ :new_public_key }\n        let(:new_private_key){ :new_private_key }\n        let(:openssh){ :openssh }\n        let(:private_key_file){ double(\"private_key_file\") }\n        let(:path_joiner){ double(\"path_joiner\") }\n        let(:algorithms) { double(:algorithms) }\n        let(:transport) { double(:transport, algorithms: algorithms) }\n\n        before do\n          allow(Vagrant::Util::Keypair).to receive(:create).\n            and_return([new_public_key, new_private_key, openssh])\n          allow(private_key_file).to receive(:open).and_yield(private_key_file)\n          allow(private_key_file).to receive(:write)\n          allow(private_key_file).to receive(:chmod)\n          allow(guest).to receive(:capability)\n          allow(File).to receive(:chmod)\n          allow(machine).to receive(:data_dir).and_return(path_joiner)\n          allow(path_joiner).to receive(:join).and_return(private_key_file)\n          allow(guest).to receive(:capability).with(:insert_public_key)\n          allow(guest).to receive(:capability).with(:remove_public_key)\n          allow(connection).to receive(:transport).and_return(transport)\n          allow(communicator).to receive(:supported_key_types).and_raise(described_class.const_get(:ServerDataError))\n        end\n\n        it \"should create a new key pair\" do\n          expect(Vagrant::Util::Keypair).to receive(:create).\n            and_return([new_public_key, new_private_key, openssh])\n          communicator.ready?\n        end\n\n        it \"should call the insert_public_key guest capability\" do\n          expect(guest).to receive(:capability).with(:insert_public_key, openssh)\n          communicator.ready?\n        end\n\n        it \"should write the new private key\" do\n          expect(private_key_file).to receive(:write).with(new_private_key)\n          communicator.ready?\n        end\n\n        it \"should call the set_ssh_key_permissions host capability\" do\n          expect(host).to receive(:capability?).with(:set_ssh_key_permissions).and_return(true)\n          expect(host).to receive(:capability).with(:set_ssh_key_permissions, private_key_file)\n          communicator.ready?\n        end\n\n        it \"should remove the default public key\" do\n          expect(guest).to receive(:capability).with(:remove_public_key, any_args)\n          communicator.ready?\n        end\n\n        context \"with server algorithm support data\" do\n          before do\n            allow(communicator).to receive(:supported_key_types).and_return(valid_key_types)\n          end\n\n          context \"when rsa is the only match\" do\n            let(:valid_key_types) { [\"ssh-ecdsa\", \"ssh-rsa\"] }\n\n            it \"should use rsa type\" do\n              expect(Vagrant::Util::Keypair).to receive(:create).\n                with(type: :rsa).and_call_original\n              communicator.ready?\n            end\n          end\n\n          context \"when ed25519 and rsa are both available\" do\n            let(:valid_key_types) { [\"ssh-ed25519\", \"ssh-rsa\"] }\n\n            it \"should use ed25519 type\" do\n              expect(Vagrant::Util::Keypair).to receive(:create).\n                with(type: :ed25519).and_call_original\n              communicator.ready?\n            end\n          end\n\n          context \"when ed25519 is the only match\" do\n            let(:valid_key_types) { [\"ssh-ecdsa\", \"ssh-ed25519\"] }\n\n            it \"should use ed25519 type\" do\n              expect(Vagrant::Util::Keypair).to receive(:create).\n                with(type: :ed25519).and_call_original\n              communicator.ready?\n            end\n          end\n\n          context \"with key_type set as :auto in configuration\" do\n            let(:valid_key_types) { [\"ssh-ed25519\", \"ssh-rsa\"] }\n            before { allow(ssh).to receive(:key_type).and_return(:auto) }\n\n            it \"should use the preferred ed25519 key type\" do\n              expect(Vagrant::Util::Keypair).to receive(:create).\n                with(type: :ed25519).and_call_original\n              communicator.ready?\n            end\n\n            context \"when no supported key type is detected\" do\n              let(:valid_key_types) { [\"fake-type\", \"other-fake-type\"] }\n\n              it \"should raise an error\" do\n                expect { communicator.ready? }.to raise_error(Vagrant::Errors::SSHKeyTypeNotSupportedByServer)\n              end\n            end\n          end\n\n          context \"with key_type set as :ecdsa521 in configuration\" do\n            let(:valid_key_types) { [\"ssh-ed25519\", \"ssh-rsa\", \"ecdsa-sha2-nistp521\", \"ecdsa-sha2-nistp256\"] }\n            before { allow(ssh).to receive(:key_type).and_return(:ecdsa521) }\n\n            it \"should use the requested key type\" do\n              expect(Vagrant::Util::Keypair).to receive(:create).\n                with(type: :ecdsa521).and_call_original\n              communicator.ready?\n            end\n\n            context \"when requested key type is not supported\" do\n              let(:valid_key_types) { [\"ssh-ed25519\", \"ssh-rsa\", \"ecdsa-sha2-nistp256\"] }\n\n              it \"should raise an error\" do\n                expect { communicator.ready? }.to raise_error(Vagrant::Errors::SSHKeyTypeNotSupportedByServer)\n              end\n            end\n          end\n        end\n\n        context \"when an error is encountered getting server data\" do\n          before do\n            expect(communicator).to receive(:supported_key_types).and_raise(StandardError)\n          end\n\n          it \"should default to rsa key\" do\n            expect(Vagrant::Util::Keypair).to receive(:create).\n              with(type: :rsa).and_call_original\n            communicator.ready?\n          end\n        end\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    before(&connection_setup)\n    it \"runs valid command and returns successful status code\" do\n      expect(command_channel).to receive(:send_data).with(/ls \\/\\n/)\n      expect(communicator.execute(\"ls /\")).to eq(0)\n    end\n\n    it \"prepends UUID output to command for garbage removal\" do\n      expect(command_channel).to receive(:send_data).\n        with(\"printf '#{command_garbage_marker}'\\n(>&2 printf '#{command_garbage_marker}')\\nls /\\n\")\n      expect(communicator.execute(\"ls /\")).to eq(0)\n    end\n\n    context \"with command returning an error\" do\n      let(:exit_data) { double(\"exit_data\", read_long: 1) }\n\n      it \"raises error when exit-code is non-zero\" do\n        expect(command_channel).to receive(:send_data).with(/ls \\/\\n/)\n        expect{ communicator.execute(\"ls /\") }.to raise_error(Vagrant::Errors::VagrantError)\n      end\n\n      it \"returns exit-code when exit-code is non-zero and error check is disabled\" do\n        expect(command_channel).to receive(:send_data).with(/ls \\/\\n/)\n        expect(communicator.execute(\"ls /\", error_check: false)).to eq(1)\n      end\n    end\n\n    context \"with no exit code received\" do\n      let(:exit_data) { double(\"exit_data\", read_long: nil) }\n\n      it \"raises error when exit code is nil\" do\n        expect(command_channel).to receive(:send_data).with(/make\\n/)\n        expect{ communicator.execute(\"make\") }.to raise_error(Vagrant::Errors::SSHNoExitStatus)\n      end\n    end\n\n    context \"with garbage content prepended to command output\" do\n      let(:command_stdout_data) do\n        \"Line of garbage\\nMore garbage\\n#{command_garbage_marker}bin\\ntmp\\n\"\n      end\n      let(:command_stderr_data) { \"some data\" }\n\n      it \"removes any garbage output prepended to command output\" do\n        stdout = ''\n        expect(\n          communicator.execute(\"ls /\") do |type, data|\n            if type == :stdout\n              stdout << data\n            end\n          end\n        ).to eq(0)\n        expect(stdout).to eq(\"bin\\ntmp\\n\")\n      end\n\n      it \"should not receive any stderr data\" do\n        stderr = ''\n        communicator.execute(\"ls /\") do |type, data|\n          if type == :stderr\n            stderr << data\n          end\n        end\n        expect(stderr).to be_empty\n      end\n    end\n\n    context \"with no command output\" do\n      let(:command_stdout_data) do\n        \"#{command_garbage_marker}\"\n      end\n      let(:command_stderr_data) { \"some data\" }\n\n      it \"does not send empty stdout data string\" do\n        empty = true\n        expect(\n          communicator.execute(\"ls /\") do |type, data|\n            if type == :stdout && data.empty?\n              empty = false\n            end\n          end\n        ).to eq(0)\n        expect(empty).to be(true)\n      end\n\n      it \"should not receive any stderr data\" do\n        stderr = ''\n        communicator.execute(\"ls /\") do |type, data|\n          if type == :stderr\n            stderr << data\n          end\n        end\n        expect(stderr).to be_empty\n      end\n    end\n\n    context \"with garbage content prepended to command stderr output\" do\n      let(:command_stderr_data) do\n        \"Line of garbage\\nMore garbage\\n#{command_garbage_marker}bin\\ntmp\\n\"\n      end\n      let(:command_stdout_data) { \"some data\" }\n\n      it \"removes any garbage output prepended to command stderr output\" do\n        stderr = ''\n        expect(\n          communicator.execute(\"ls /\") do |type, data|\n            if type == :stderr\n              stderr << data\n            end\n          end\n        ).to eq(0)\n        expect(stderr).to eq(\"bin\\ntmp\\n\")\n      end\n\n      it \"should not receive any stdout data\" do\n        stdout = ''\n        communicator.execute(\"ls /\") do |type, data|\n          if type == :stdout\n            stdout << data\n          end\n        end\n        expect(stdout).to be_empty\n      end\n    end\n\n    context \"with no command output on stderr\" do\n      let(:command_stderr_data) do\n        \"#{command_garbage_marker}\"\n      end\n      let(:command_std_data) { \"some data\" }\n\n      it \"does not send empty stderr data string\" do\n        empty = true\n        expect(\n          communicator.execute(\"ls /\") do |type, data|\n            if type == :stderr && data.empty?\n              empty = false\n            end\n          end\n        ).to eq(0)\n        expect(empty).to be(true)\n      end\n\n      it \"should not receive any stdout data\" do\n        stdout = ''\n        communicator.execute(\"ls /\") do |type, data|\n          if type == :stdout\n            stdout << data\n          end\n        end\n        expect(stdout).to be_empty\n      end\n    end\n\n    context \"with pty enabled\" do\n      before do\n        expect(ssh).to receive(:pty).and_return true\n        expect(channel).to receive(:request_pty).and_yield(command_channel, true)\n        expect(command_channel).to receive(:send_data).\n          with(/#{Regexp.escape(pty_delim_start)}/)\n      end\n\n      let(:command_stdout_data) do\n        \"#{pty_delim_start}bin\\ntmp\\n#{pty_delim_end}\"\n      end\n\n      it \"requests pty for connection\" do\n        expect(communicator.execute(\"ls\")).to eq(0)\n      end\n\n      context \"with sudo enabled\" do\n        let(:core_shell_cmd){ 'sudo bash -l' }\n\n        before do\n          expect(ssh).to receive(:sudo_command).and_return 'sudo %c'\n        end\n\n        it \"wraps command in elevated shell when sudo is true\" do\n          expect(communicator.execute(\"ls\", sudo: true)).to eq(0)\n        end\n      end\n    end\n\n    context \"with sudo enabled\" do\n      let(:core_shell_cmd){ 'sudo bash -l' }\n\n      before do\n        expect(ssh).to receive(:sudo_command).and_return 'sudo %c'\n      end\n\n      it \"wraps command in elevated shell when sudo is true\" do\n        expect(communicator.execute(\"ls\", sudo: true)).to eq(0)\n      end\n    end\n  end\n\n  describe \"#test\" do\n    before(&connection_setup)\n    context \"with exit code as zero\" do\n      it \"returns true\" do\n        expect(communicator.test(\"ls\")).to be(true)\n      end\n    end\n\n    context \"with exit code as non-zero\" do\n      before do\n        expect(exit_data).to receive(:read_long).and_return 1\n      end\n\n      it \"returns false\" do\n        expect(communicator.test(\"/bin/false\")).to be(false)\n      end\n    end\n  end\n\n  describe \"#upload\" do\n    before do\n      expect(communicator).to receive(:scp_connect).and_yield(scp)\n      allow(communicator).to receive(:create_remote_directory)\n    end\n\n    context \"directory uploads\" do\n      let(:test_dir) { @dir }\n      let(:test_file) { File.join(test_dir, \"test-file\") }\n      let(:dir_name) { File.basename(test_dir) }\n      let(:file_name) { File.basename(test_file) }\n\n      before do\n        @dir = Dir.mktmpdir(\"vagrant-test\")\n        FileUtils.touch(test_file)\n      end\n\n      after { FileUtils.rm_rf(test_dir) }\n\n      it \"uploads directory when directory path provided\" do\n        expect(scp).to receive(:upload!).with(instance_of(File),\n          File.join(\"\", \"destination\", dir_name, file_name))\n        communicator.upload(test_dir, \"/destination\")\n      end\n\n      it \"uploads contents of directory when dot suffix provided on directory\" do\n        expect(scp).to receive(:upload!).with(instance_of(File),\n          File.join(\"\", \"destination\", file_name))\n        communicator.upload(File.join(test_dir, \".\"), \"/destination\")\n      end\n\n      it \"creates directories before upload\" do\n        expect(communicator).to receive(:create_remote_directory).with(\n          /#{Regexp.escape(File.join(\"\", \"destination\", dir_name))}/)\n        allow(scp).to receive(:upload!)\n        communicator.upload(test_dir, \"/destination\")\n      end\n    end\n\n    it \"uploads a file if local path is a file\" do\n      file = Tempfile.new('vagrant-test')\n      begin\n        expect(scp).to receive(:upload!).with(instance_of(File), '/destination/file')\n        communicator.upload(file.path, '/destination/file')\n      ensure\n        file.delete\n      end\n    end\n\n    it \"uploads file to directory if destination ends with file separator\" do\n      file = Tempfile.new('vagrant-test')\n      begin\n        expect(scp).to receive(:upload!).with(instance_of(File), \"/destination/dir/#{File.basename(file.path)}\")\n        expect(communicator).to receive(:create_remote_directory).with(\"/destination/dir\")\n        communicator.upload(file.path, \"/destination/dir/\")\n      ensure\n        file.delete\n      end\n    end\n\n    it \"creates remote directory path to destination on upload\" do\n      file = Tempfile.new('vagrant-test')\n      begin\n        expect(scp).to receive(:upload!).with(instance_of(File), \"/destination/dir/file.txt\")\n        expect(communicator).to receive(:create_remote_directory).with(\"/destination/dir\")\n        communicator.upload(file.path, \"/destination/dir/file.txt\")\n      ensure\n        file.delete\n      end\n    end\n\n    it \"creates remote directory given an empty directory\" do\n      file = Dir.mktmpdir\n      begin\n        expect(communicator).to receive(:create_remote_directory).with(\"/destination/dir/#{ File.basename(file)}/\")\n        communicator.upload(file, \"/destination/dir\")\n      ensure\n        FileUtils.rm_rf(file)\n      end\n    end\n\n    it \"raises custom error on permission errors\" do\n      file = Tempfile.new('vagrant-test')\n      begin\n        expect(scp).to receive(:upload!).with(instance_of(File), '/destination/file').\n          and_raise(\"Permission denied\")\n        expect{ communicator.upload(file.path, '/destination/file') }.to(\n          raise_error(Vagrant::Errors::SCPPermissionDenied)\n        )\n      ensure\n        file.delete\n      end\n    end\n\n    it \"does not raise custom error on non-permission errors\" do\n      file = Tempfile.new('vagrant-test')\n      begin\n        expect(scp).to receive(:upload!).with(instance_of(File), '/destination/file').\n          and_raise(\"Some other error\")\n        expect{ communicator.upload(file.path, '/destination/file') }.to raise_error(RuntimeError)\n      ensure\n        file.delete\n      end\n    end\n  end\n\n  describe \"#download\" do\n    before do\n      expect(communicator).to receive(:scp_connect).and_yield(scp)\n    end\n\n    it \"calls scp to download file\" do\n      expect(scp).to receive(:download!).with('/path/from', '/path/to')\n      communicator.download('/path/from', '/path/to')\n    end\n  end\n\n  describe \"#connect\" do\n\n    it \"cannot be called directly\" do\n      expect{ communicator.connect }.to raise_error(NoMethodError)\n    end\n\n    context \"with default configuration\" do\n\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: nil,\n          port: nil,\n          private_key_path: nil,\n          username: nil,\n          password: nil,\n          keys_only: true,\n          verify_host_key: false\n        )\n      end\n\n      it \"has keys_only enabled\" do\n        expect(Net::SSH).to receive(:start).with(\n          nil, nil, hash_including(\n            keys_only: true\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n\n      it \"has verify_host_key disabled\" do\n        expect(Net::SSH).to receive(:start).with(\n          nil, nil, hash_including(\n            verify_host_key: false\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n\n      it \"does not include any private key paths\" do\n        expect(Net::SSH).to receive(:start).with(\n          nil, nil, hash_excluding(\n            keys: anything\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n\n      it \"includes `none` and `hostbased` auth methods\" do\n        expect(Net::SSH).to receive(:start).with(\n          nil, nil, hash_including(\n            auth_methods: [\"none\", \"hostbased\"]\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n\n      context \"keep ssh connection alive\" do\n        let(:ssh) do\n          double(\"ssh\",\n            timeout: 1,\n            host: nil,\n            port: 5986,\n            guest_port: 5986,\n            pty: false,\n            keep_alive: true,\n            insert_key: insert_ssh_key,\n            export_command_template: export_command_template,\n            shell: 'bash -l'\n          )\n        end\n\n        it \"sets keepalive settings\" do\n          expect(Net::SSH).to receive(:start).with(\n            nil, nil, hash_including(\n              keepalive: true,\n              keepalive_interval: 5\n            )\n          ).and_return(true)\n          communicator.send(:connect)\n        end\n      end\n    end\n\n    context \"with keys_only disabled and verify_host_key enabled\" do\n\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: nil,\n          port: nil,\n          private_key_path: nil,\n          username: nil,\n          password: nil,\n          keys_only: false,\n          verify_host_key: true\n        )\n      end\n\n      it \"has keys_only enabled\" do\n        expect(Net::SSH).to receive(:start).with(\n          nil, nil, hash_including(\n            keys_only: false\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n\n      it \"has verify_host_key disabled\" do\n        expect(Net::SSH).to receive(:start).with(\n          nil, nil, hash_including(\n            verify_host_key: true\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n    end\n\n    context \"with host and port configured\" do\n\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: '127.0.0.1',\n          port: 2222,\n          private_key_path: nil,\n          username: nil,\n          password: nil,\n          keys_only: true,\n          verify_host_key: false\n        )\n      end\n\n      it \"specifies configured host\" do\n        expect(Net::SSH).to receive(:start).with(\"127.0.0.1\", anything, anything)\n        communicator.send(:connect)\n      end\n\n      it \"has port defined\" do\n        expect(Net::SSH).to receive(:start).with(\"127.0.0.1\", anything, hash_including(port: 2222))\n        communicator.send(:connect)\n      end\n    end\n\n    context \"with private_key_path configured\" do\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: '127.0.0.1',\n          port: 2222,\n          private_key_path: ['/priv/key/path'],\n          username: nil,\n          password: nil,\n          keys_only: true,\n          verify_host_key: false\n        )\n      end\n\n      it \"includes private key paths\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            keys: [\"/priv/key/path\"]\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n\n      it \"includes `publickey` auth method\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            auth_methods: [\"none\", \"hostbased\", \"publickey\"]\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n    end\n\n    context \"with username and password configured\" do\n\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: '127.0.0.1',\n          port: 2222,\n          private_key_path: nil,\n          username: 'vagrant',\n          password: 'vagrant',\n          keys_only: true,\n          verify_host_key: false\n        )\n      end\n\n      it \"has username defined\" do\n        expect(Net::SSH).to receive(:start).with(anything, 'vagrant', anything).and_return(true)\n        communicator.send(:connect)\n      end\n\n      it \"has password defined\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            password: 'vagrant'\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n\n      it \"includes `password` auth method\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            auth_methods: [\"none\", \"hostbased\", \"password\"]\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n    end\n\n    context \"with password and private_key_path configured\" do\n\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: '127.0.0.1',\n          port: 2222,\n          private_key_path: ['/priv/key/path'],\n          username: 'vagrant',\n          password: 'vagrant',\n          keys_only: true,\n          verify_host_key: false\n        )\n      end\n\n      it \"has password defined\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            password: 'vagrant'\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n\n      it \"includes private key paths\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            keys: [\"/priv/key/path\"]\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n\n      it \"includes `publickey` and `password` auth methods\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            auth_methods: [\"none\", \"hostbased\", \"publickey\", \"password\"]\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n    end\n\n    context \"with config configured\" do\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: '127.0.0.1',\n          port: 2222,\n          config: './ssh_config',\n          keys_only: true,\n          verify_host_key: false\n        )\n      end\n\n      it \"has config defined\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            config: './ssh_config'\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n    end\n\n    context \"with remote_user configured\" do\n      let(:remote_user) { double(\"remote_user\") }\n\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: '127.0.0.1',\n          port: 2222,\n          remote_user: remote_user\n        )\n      end\n\n      it \"has remote_user defined\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            remote_user: remote_user\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n    end\n\n    context \"with connect_timeout configured\" do\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: '127.0.0.1',\n          port: 2222,\n          connect_timeout: 30\n        )\n      end\n\n      it \"has connect_timeout defined\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            timeout: 30\n          )\n        ).and_return(true)\n        communicator.send(:connect)\n      end\n    end\n\n    context \"with connect_retries configured\" do\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: '127.0.0.1',\n          port: 2222,\n          connect_retries: 3\n        )\n      end\n\n      it \"should retry the connect three times\" do\n        expect(Net::SSH).to receive(:start).and_raise(Errno::EACCES).twice\n        expect(Net::SSH).to receive(:start)\n\n        communicator.send(:connect)\n      end\n\n      it \"should override from passed options\" do\n        expect(Net::SSH).to receive(:start).and_raise(Errno::EACCES).thrice\n        expect(Net::SSH).to receive(:start)\n\n        communicator.send(:connect, retries: 4)\n      end\n    end\n\n    context \"with connect_retry_delay configured\" do\n      let(:retries) { 3 }\n      let(:sleep_delay) { 5 }\n\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: '127.0.0.1',\n          port: 2222,\n          connect_retries: retries,\n          connect_retry_delay: sleep_delay\n        )\n      end\n\n      it \"should sleep twice while retrying\" do\n        expect(Net::SSH).to receive(:start).and_raise(Errno::EACCES).twice\n        expect(Net::SSH).to receive(:start)\n\n        expect(communicator).to receive(:sleep).with(sleep_delay).twice\n\n        communicator.send(:connect)\n      end\n\n      it \"should overrride from passed options\" do\n        expect(Net::SSH).to receive(:start).and_raise(Errno::EACCES).twice\n        expect(Net::SSH).to receive(:start)\n\n        expect(communicator).to receive(:sleep).with(20).twice\n\n        communicator.send(:connect, retry_delay: 20)\n      end\n\n      context \"when no retries are configured\" do\n        let(:retries) { 0 }\n\n        it \"should not sleep\" do\n          expect(Net::SSH).to receive(:start).and_raise(Errno::EACCES)\n          expect(communicator).not_to receive(:sleep)\n\n          expect { communicator.send(:connect) }.to raise_error(Vagrant::Errors::SSHConnectEACCES)\n        end\n      end\n\n    end\n  end\n\n  describe \"#insecure_key?\" do\n    let(:key_data) { \"\" }\n    let(:key_file) {\n      if !@key_file\n        f = Tempfile.new\n        f.write(key_data)\n        f.close\n        @key_file = f.path\n      end\n      @key_file\n    }\n\n    after { File.delete(key_file) }\n\n    context \"when using rsa private key\" do\n      let(:key_data) { File.read(Vagrant.source_root.join(\"keys\", \"vagrant.key.rsa\")) }\n\n      it \"should match as insecure key\" do\n        expect(communicator.send(:insecure_key?, key_file)).to be_truthy\n      end\n    end\n\n    context \"when using ed25519 private key\" do\n      let(:key_data) { File.read(Vagrant.source_root.join(\"keys\", \"vagrant.key.ed25519\")) }\n\n      it \"should match as insecure key\" do\n        expect(communicator.send(:insecure_key?, key_file)).to be_truthy\n      end\n    end\n\n    context \"when using unknown private key\" do\n      let(:key_data) { \"invalid data\" }\n\n      it \"should not match as insecure key\" do\n        expect(communicator.send(:insecure_key?, key_file)).to be_falsey\n      end\n    end\n  end\n\n  describe \"#generate_environment_export\" do\n    it \"should generate bourne shell compatible export\" do\n      expect(communicator.send(:generate_environment_export, \"TEST\", \"value\")).to eq(\"export TEST=\\\"value\\\"\\n\")\n    end\n\n    context \"with custom template defined\" do\n      let(:export_command_template){ \"setenv %ENV_KEY% %ENV_VALUE%\" }\n\n      it \"should generate custom export based on template\" do\n        expect(communicator.send(:generate_environment_export, \"TEST\", \"value\")).to eq(\"setenv TEST value\\n\")\n      end\n    end\n  end\n\n  describe \"#supported_key_types\" do\n    let(:sudo_result) { 0 }\n    let(:sudo_data) { \"\" }\n    let(:server_data_error) { VagrantPlugins::CommunicatorSSH::Communicator::ServerDataError }\n    let(:transport) { double(\"transport\", algorithms: algorithms) }\n    let(:algorithms) { double(\"algorithms\") }\n\n    before do\n      allow(communicator).to receive(:ready?).and_return(true)\n      expect(communicator).to receive(:sudo).\n        with(\"sshd -T | grep key\", any_args).\n        and_yield(:stdout, sudo_data).\n        and_return(sudo_result)\n      # The @connection value is checked to determine if supported key types\n      # can be checked. To facilitate this, set it to a non-nil value\n      communicator.instance_variable_set(:@connection, connection)\n      allow(connection).to receive(:transport).and_return(transport)\n    end\n\n    it \"should raise an error when no data is returned\" do\n      expect { communicator.send(:supported_key_types) }.to raise_error(server_data_error)\n    end\n\n    context \"when sudo command is unsuccessful\" do\n      let(:sudo_result) { 1 }\n\n      it \"should inspect the net-ssh connection\" do\n        expect(algorithms).to receive(:instance_variable_get).\n          with(:@server_data).and_return({})\n        communicator.send(:supported_key_types)\n      end\n    end\n\n    context \"when data includes pubkeyacceptedalgorithms\" do\n      let(:sudo_data) do\n        \"pubkeyauthentication yes\ngssapikeyexchange no\ngssapistorecredentialsonrekey no\ntrustedusercakeys none\nrevokedkeys none\nauthorizedkeyscommand none\nauthorizedkeyscommanduser none\nhostkeyagent none\nhostbasedacceptedkeytypes ecdsa-sha2-nistp521,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa\nhostkeyalgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa\npubkeyacceptedalgorithms rsa-sha2-512,rsa-sha2-256,ssh-rsa\nauthorizedkeysfile .ssh/authorized_keys\nhostkey /etc/ssh/ssh_host_rsa_key\nrekeylimit 0 0\"\n      end\n\n      it \"should return expected values\" do\n        expect(communicator.send(:supported_key_types)).to eq([\"rsa-sha2-512\", \"rsa-sha2-256\", \"ssh-rsa\"])\n      end\n    end\n\n    context \"when data includes pubkeyacceptedkeytypes\" do\n      let(:sudo_data) do\n        \"pubkeyauthentication yes\ngssapikeyexchange no\ngssapistorecredentialsonrekey no\ntrustedusercakeys none\nrevokedkeys none\nauthorizedkeyscommand none\nauthorizedkeyscommanduser none\nhostkeyagent none\nhostbasedacceptedkeytypes ecdsa-sha2-nistp521,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa\nhostkeyalgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa\npubkeyacceptedkeytypes rsa-sha2-512,rsa-sha2-256,ssh-rsa\nauthorizedkeysfile .ssh/authorized_keys\nhostkey /etc/ssh/ssh_host_rsa_key\nrekeylimit 0 0\"\n      end\n\n      it \"should return expected values\" do\n        expect(communicator.send(:supported_key_types)).\n          to eq([\"rsa-sha2-512\", \"rsa-sha2-256\", \"ssh-rsa\"])\n      end\n    end\n\n    context \"when data does not include pubkeyacceptedalgorithms or pubkeyacceptedkeytypes\" do\n      let(:sudo_data) do\n        \"pubkeyauthentication yes\ngssapikeyexchange no\ngssapistorecredentialsonrekey no\ntrustedusercakeys none\nrevokedkeys none\nauthorizedkeyscommand none\nauthorizedkeyscommanduser none\nhostkeyagent none\nhostbasedacceptedkeytypes ecdsa-sha2-nistp521,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa\nhostkeyalgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa\nauthorizedkeysfile .ssh/authorized_keys\nhostkey /etc/ssh/ssh_host_rsa_key\nrekeylimit 0 0\"\n      end\n\n      it \"should use hostkeyalgorithms\" do\n        expect(communicator.send(:supported_key_types)).\n          to eq([\"ssh-ed25519\", \"rsa-sha2-512\", \"rsa-sha2-256\", \"ssh-rsa\"])\n      end\n    end\n\n    context \"when data does not include defined config options\" do\n      let(:sudo_data) do\n        \"pubkeyauthentication yes\ngssapikeyexchange no\ngssapistorecredentialsonrekey no\ntrustedusercakeys none\nrevokedkeys none\nauthorizedkeyscommand none\nauthorizedkeyscommanduser none\nhostkeyagent none\nauthorizedkeysfile .ssh/authorized_keys\nhostkey /etc/ssh/ssh_host_rsa_key\nrekeylimit 0 0\"\n      end\n\n      it \"should raise error\" do\n        expect { communicator.send(:supported_key_types) }.\n          to raise_error(server_data_error)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/communicators/winrm/command_filter_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/communicators/winrm/command_filter\")\n\ndescribe VagrantPlugins::CommunicatorWinRM::CommandFilter, unit: true do\n\n  describe '.command_filters' do\n    it 'initializes all command filters in command filters directory' do\n      expect(subject.command_filters()).not_to be_empty\n    end\n  end\n\n  describe '.filter' do\n    it 'filters out uname commands' do\n      expect(subject.filter('uname -s stuff')).to eq('')\n    end\n\n    it 'filters out grep commands' do\n      expect(subject.filter(\"grep 'Fedora release [12][67890]' /etc/redhat-release\")).to eq(\"\")\n    end\n\n    it 'filters out which commands' do\n      expect(subject.filter('which ruby')).to include(\n        '[Array](Get-Command \"ruby\" -errorAction SilentlyContinue)')\n    end\n\n    it 'filters out test -d commands' do\n      expect(subject.filter('test -d /tmp/dir')).to include(\n        \"$p = \\\"/tmp/dir\\\"\")\n      expect(subject.filter('test -d /tmp/dir')).to include(\n        \"if ((Test-Path $p) -and (get-item $p).PSIsContainer) {\")\n    end\n\n    it 'filters out test -f commands' do\n      expect(subject.filter('test -f /tmp/file.txt')).to include(\n        \"$p = \\\"/tmp/file.txt\\\"\")\n      expect(subject.filter('test -f /tmp/file.txt')).to include(\n        \"if ((Test-Path $p) -and (!(get-item $p).PSIsContainer)) {\")\n    end\n\n    it 'filters out test -x commands' do\n      expect(subject.filter('test -x /tmp/file.txt')).to include(\n        \"$p = \\\"/tmp/file.txt\\\"\")\n      expect(subject.filter('test -x /tmp/file.txt')).to include(\n        \"if ((Test-Path $p) -and (!(get-item $p).PSIsContainer)) {\")\n    end\n\n    it 'filters out other test commands' do\n      expect(subject.filter('test -L /tmp/file.txt')).to include(\n        \"$p = \\\"/tmp/file.txt\\\"\")\n      expect(subject.filter('test -L /tmp/file.txt')).to include(\n        \"if (Test-Path $p) {\")\n    end\n\n    it 'filters out rm recurse commands' do\n      expect(subject.filter('rm -Rf /some/dir')).to eq(\n        \"if (Test-Path \\\"/some/dir\\\") {Remove-Item \\\"/some/dir\\\" -force -recurse}\")\n      expect(subject.filter('rm -fr /some/dir')).to eq(\n        \"if (Test-Path \\\"/some/dir\\\") {Remove-Item \\\"/some/dir\\\" -force -recurse}\")\n      expect(subject.filter('rm -r /some/dir')).to eq(\n        \"if (Test-Path \\\"/some/dir\\\") {Remove-Item \\\"/some/dir\\\" -force -recurse}\")\n      expect(subject.filter('rm -r \"/some/dir\"')).to eq(\n        \"if (Test-Path \\\"/some/dir\\\") {Remove-Item \\\"/some/dir\\\" -force -recurse}\")\n    end\n\n    it 'filters out rm commands' do\n      expect(subject.filter('rm /some/dir')).to eq(\n        \"if (Test-Path \\\"/some/dir\\\") {Remove-Item \\\"/some/dir\\\" -force}\")\n      expect(subject.filter('rm -f /some/dir')).to eq(\n        \"if (Test-Path \\\"/some/dir\\\") {Remove-Item \\\"/some/dir\\\" -force}\")\n      expect(subject.filter('rm -f \"/some/dir\"')).to eq(\n        \"if (Test-Path \\\"/some/dir\\\") {Remove-Item \\\"/some/dir\\\" -force}\")\n    end\n\n    it 'filters out mkdir commands' do\n      expect(subject.filter('mkdir /some/dir')).to eq(\n        \"mkdir \\\"/some/dir\\\" -force\")\n      expect(subject.filter('mkdir -p /some/dir')).to eq(\n        \"mkdir \\\"/some/dir\\\" -force\")\n      expect(subject.filter('mkdir \"/some/dir\"')).to eq(\n        \"mkdir \\\"/some/dir\\\" -force\")\n    end\n\n    it 'filters out chown commands' do\n      expect(subject.filter(\"chown -R root '/tmp/dir'\")).to eq('')\n    end\n\n    it 'filters out chmod commands' do\n      expect(subject.filter(\"chmod 0600 ~/.ssh/authorized_keys\")).to eq('')\n    end\n\n    it 'filters out certain cat commands' do\n      expect(subject.filter(\"cat /etc/release | grep -i OmniOS\")).to eq('')\n    end\n\n    it 'should not filter out other cat commands' do\n      expect(subject.filter(\"cat /tmp/somefile\")).to eq('cat /tmp/somefile')\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/communicators/winrm/communicator_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/communicators/winrm/communicator\")\n\ndescribe VagrantPlugins::CommunicatorWinRM::Communicator do\n  include_context \"unit\"\n\n  let(:winrm) { double(\"winrm\", timeout: 1, host: nil, port: 5986, guest_port: 5986) }\n  let(:config) { double(\"config\", winrm: winrm) }\n  let(:provider) { double(\"provider\") }\n  let(:ui) { Vagrant::UI::Silent.new }\n  let(:machine) { double(\"machine\", config: config, provider: provider, ui: ui) }\n  let(:shell) { double(\"shell\") }\n  let(:good_output) { WinRM::Output.new.tap { |out| out.exitcode = 0 } }\n  let(:bad_output) { WinRM::Output.new.tap { |out| out.exitcode = 1 } }\n\n  subject do\n    described_class.new(machine).tap do |comm|\n      allow(comm).to receive(:create_shell).and_return(shell)\n    end\n  end\n\n  before do\n    allow(shell).to receive(:username).and_return('vagrant')\n    allow(shell).to receive(:password).and_return('password')\n    allow(shell).to receive(:execution_time_limit).and_return('PT2H')\n  end\n\n  describe \".wait_for_ready\" do\n    context \"with no winrm_info capability and no static config (default scenario)\" do\n      before do\n        # No default providers support this capability\n        allow(provider).to receive(:capability?).with(:winrm_info).and_return(false)\n\n        # Get us through the detail prints\n        allow(shell).to receive(:host)\n        allow(shell).to receive(:port)\n        allow(shell).to receive(:username)\n        allow(shell).to receive(:config) { double(\"config\", transport: nil)}\n      end\n\n      context \"when ssh_info requires a multiple tries before it is ready\" do\n        before do\n          allow(machine).to receive(:ssh_info).and_return(nil, {\n            host: '10.1.2.3',\n            port: '22',\n          })\n          # Makes ready? return true\n          allow(shell).to receive(:cmd).with(\"hostname\").and_return({ exitcode: 0 })\n        end\n\n        it \"retries ssh_info until ready\" do\n          expect(subject.wait_for_ready(2)).to eq(true)\n        end\n      end\n    end\n  end\n\n  describe \".reset!\" do\n    it \"should create a new shell\" do\n      expect(subject).to receive(:shell).with(true)\n      subject.reset!\n    end\n  end\n\n  describe \".ready?\" do\n    it \"returns true if hostname command executes without error\" do\n      expect(shell).to receive(:cmd).with(\"hostname\").and_return({ exitcode: 0 })\n      expect(subject.ready?).to be(true)\n    end\n\n    it \"returns false if hostname command fails with a transient error\" do\n      expect(shell).to receive(:cmd).with(\"hostname\").and_raise(VagrantPlugins::CommunicatorWinRM::Errors::TransientError)\n      expect(subject.ready?).to be(false)\n    end\n\n    it \"returns false if hostname command fails with a WinRMNotReady error\" do\n      expect(shell).to receive(:cmd).with(\"hostname\").and_raise(VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady)\n      expect(subject.ready?).to be(false)\n    end\n\n    it \"raises an error if hostname command fails with an unknown error\" do\n      expect(shell).to receive(:cmd).with(\"hostname\").and_raise(Vagrant::Errors::VagrantError)\n      expect { subject.ready? }.to raise_error(Vagrant::Errors::VagrantError)\n    end\n\n    it \"raises timeout error when hostname command takes longer then winrm timeout\" do\n      expect(shell).to receive(:cmd).with(\"hostname\") do\n        sleep 2 # winrm.timeout = 1\n      end\n      expect { subject.ready? }.to raise_error(Timeout::Error)\n    end\n  end\n\n  describe \".execute\" do\n    it \"defaults to running in powershell\" do\n      expect(shell).to receive(:powershell).with(kind_of(String), kind_of(Hash)).and_return(good_output)\n      expect(subject.execute(\"dir\")).to eq(0)\n    end\n\n    it \"use elevated shell script when elevated is true\" do\n      expect(shell).to receive(:elevated).and_return(good_output)\n      expect(subject.execute(\"dir\", { elevated: true })).to eq(0)\n    end\n\n    it \"can use cmd shell\" do\n      expect(shell).to receive(:cmd).with(kind_of(String), kind_of(Hash)).and_return(good_output)\n      expect(subject.execute(\"dir\", { shell: :cmd })).to eq(0)\n    end\n\n    it \"raises error when error_check is true and exit code is non-zero\" do\n      expect(shell).to receive(:powershell).with(kind_of(String), kind_of(Hash)).and_return(bad_output)\n      expect { subject.execute(\"dir\") }.to raise_error(\n        VagrantPlugins::CommunicatorWinRM::Errors::WinRMBadExitStatus)\n    end\n\n    it \"does not raise error when error_check is false and exit code is non-zero\" do\n      expect(shell).to receive(:powershell).with(kind_of(String), kind_of(Hash)).and_return(bad_output)\n      expect(subject.execute(\"dir\", { error_check: false })).to eq(1)\n    end\n  end\n\n  describe \".test\" do\n    it \"returns true when exit code is zero\" do\n      expect(shell).to receive(:powershell).with(kind_of(String)).and_return(good_output)\n      expect(subject.test(\"test -d c:/windows\")).to be(true)\n    end\n\n    it \"returns false when exit code is non-zero\" do\n      expect(shell).to receive(:powershell).with(kind_of(String)).and_return(bad_output)\n      expect(subject.test(\"test -d /tmp/foobar\")).to be(false)\n    end\n\n    it \"returns false when stderr contains output\" do\n      output = WinRM::Output.new\n      output.exitcode = 1\n      output << { stderr: 'this is an error' }\n      expect(shell).to receive(:powershell).with(kind_of(String)).and_return(output)\n      expect(subject.test(\"[-x stuff] && foo\")).to be(false)\n    end\n\n    it \"returns false when command is testing for linux OS\" do\n      expect(subject.test(\"uname -s | grep Debian\")).to be(false)\n    end\n  end\n\n  describe \".upload\" do\n    it \"calls upload on shell\" do\n      expect(shell).to receive(:upload).with(\"from\", \"to\")\n      subject.upload(\"from\", \"to\")\n    end\n  end\n\n  describe \".download\" do\n    it \"calls download on shell\" do\n      expect(shell).to receive(:download).with(\"from\", \"to\")\n      subject.download(\"from\", \"to\")\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/communicators/winrm/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/communicators/winrm/config\")\n\ndescribe VagrantPlugins::CommunicatorWinRM::Config do\n  let(:machine) { double(\"machine\") }\n\n  subject { described_class.new }\n\n  it \"is valid by default\" do\n    subject.finalize!\n    result = subject.validate(machine)\n    expect(result[\"WinRM\"]).to be_empty\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/communicators/winrm/helper_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/communicators/winrm/helper\")\n\ndescribe VagrantPlugins::CommunicatorWinRM::Helper do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    test_iso_env.vagrantfile(\"\")\n    test_iso_env.create_vagrant_env\n  end\n  let(:test_iso_env) { isolated_environment }\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  subject { described_class }\n\n  describe \".winrm_address\" do\n    before do\n      machine.config.winrm.host = nil\n    end\n\n    it \"returns the configured host if set\" do\n      machine.config.winrm.host = \"foo\"\n      expect(subject.winrm_address(machine)).to eq(\"foo\")\n    end\n\n    it \"returns the SSH info host if available\" do\n      allow(machine).to receive(:ssh_info).and_return({ host: \"bar\" })\n      expect(subject.winrm_address(machine)).to eq(\"bar\")\n    end\n\n    it \"raise an exception if it can't detect a host\" do\n      allow(machine).to receive(:ssh_info).and_return(nil)\n      expect { subject.winrm_address(machine) }.\n        to raise_error(VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady)\n    end\n\n    it \"raise an exception if it detects an empty host ip\" do\n      allow(machine).to receive(:ssh_info).and_return({ host: \"\" })\n      expect { subject.winrm_address(machine) }.\n        to raise_error(VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady)\n    end\n\n    it \"raise a WinRMNotReady exception if it detects an unset host ip\" do\n      allow(machine).to receive(:ssh_info).and_return({ host: nil })\n      expect { subject.winrm_address(machine) }.\n        to raise_error(VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady)\n    end\n\n    it \"raise an exception if it detects an APIPA\" do\n      allow(machine).to receive(:ssh_info).and_return({ host: \"169.254.123.123\" })\n      expect { subject.winrm_address(machine) }.\n        to raise_error(VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady)\n    end\n  end\n\n  describe \".winrm_info\" do\n    before do\n      allow(machine.provider).to receive(:capability?)\n        .with(:winrm_info).and_return(false)\n      allow(subject).to receive(:winrm_address).and_return(nil)\n      allow(subject).to receive(:winrm_port).and_return(nil)\n    end\n\n    it \"returns default info if no capability\" do\n      allow(subject).to receive(:winrm_address).and_return(\"bar\")\n      allow(subject).to receive(:winrm_port).and_return(45)\n\n      result = subject.winrm_info(machine)\n      expect(result[:host]).to eq(\"bar\")\n      expect(result[:port]).to eq(45)\n    end\n\n    it \"raises an exception if capability returns nil\" do\n      allow(machine.provider).to receive(:capability?)\n        .with(:winrm_info).and_return(true)\n      allow(machine.provider).to receive(:capability)\n        .with(:winrm_info).and_return(nil)\n\n      expect { subject.winrm_info(machine) }.\n        to raise_error(VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady)\n    end\n\n    it \"returns the proper information if set\" do\n      allow(machine.provider).to receive(:capability?)\n        .with(:winrm_info).and_return(true)\n      allow(machine.provider).to receive(:capability).with(:winrm_info).and_return({\n        host: \"foo\",\n        port: 12,\n      })\n\n      result = subject.winrm_info(machine)\n      expect(result[:host]).to eq(\"foo\")\n      expect(result[:port]).to eq(12)\n    end\n\n    it \"defaults information if capability doesn't set it\" do\n      allow(machine.provider).to receive(:capability?)\n        .with(:winrm_info).and_return(true)\n      allow(machine.provider).to receive(:capability).with(:winrm_info).and_return({})\n\n      allow(subject).to receive(:winrm_address).and_return(\"bar\")\n      allow(subject).to receive(:winrm_port).and_return(45)\n\n      result = subject.winrm_info(machine)\n      expect(result[:host]).to eq(\"bar\")\n      expect(result[:port]).to eq(45)\n    end\n  end\n\n  describe \".winrm_port\" do\n    it \"returns the configured port if no guest port set\" do\n      machine.config.winrm.port = 22\n      machine.config.winrm.guest_port = nil\n\n      expect(subject.winrm_port(machine)).to eq(22)\n    end\n\n    it \"returns the configured guest port if non local\" do\n      machine.config.winrm.port = 22\n      machine.config.winrm.guest_port = 2222\n      machine.config.vm.network \"forwarded_port\", host: 1234, guest: 2222\n\n      expect(subject.winrm_port(machine, false)).to eq(2222)\n    end\n\n    it \"returns a forwarded port that matches the guest port\" do\n      machine.config.winrm.port = 22\n      machine.config.winrm.guest_port = 2222\n      machine.config.vm.network \"forwarded_port\", host: 1234, guest: 2222\n\n      expect(subject.winrm_port(machine)).to eq(1234)\n    end\n\n    it \"uses the provider capability if it exists\" do\n      machine.config.winrm.port = 22\n      machine.config.winrm.guest_port = 2222\n      machine.config.vm.network \"forwarded_port\", host: 1234, guest: 2222\n\n      allow(machine.provider).to receive(:capability?).with(:forwarded_ports).and_return(true)\n      allow(machine.provider).to receive(:capability).with(:forwarded_ports).and_return({\n        1234 => 4567,\n        2456 => 2222,\n      })\n\n      expect(subject.winrm_port(machine)).to eq(2456)\n    end\n\n    it \"does not error when the provider capability returns nil result\" do\n      machine.config.winrm.port = 22\n      machine.config.winrm.guest_port = 2222\n      machine.config.vm.network \"forwarded_port\", host: 1234, guest: 2222\n      allow(machine.provider).to receive(:capability).with(:forwarded_ports).and_return(nil)\n\n      expect(subject.winrm_port(machine)).to eq(1234)\n    end\n  end\n\n  describe \".winrm_info_invalid?\" do\n    it \"returns true if it can't detect a host\" do\n      allow(machine).to receive(:ssh_info).and_return(nil)\n      expect(subject).to be_winrm_info_invalid(machine.ssh_info)\n    end\n\n    it \"returns true if it detects an empty host ip\" do\n      allow(machine).to receive(:ssh_info).and_return({ host: \"\" })\n      expect(subject).to be_winrm_info_invalid(machine.ssh_info)\n    end\n\n    it \"returns true if it detects an unset host ip\" do\n      allow(machine).to receive(:ssh_info).and_return({ host: nil })\n      expect(subject).to be_winrm_info_invalid(machine.ssh_info)\n    end\n\n    it \"returns true if it detects an APIPA\" do\n      allow(machine).to receive(:ssh_info).and_return({ host: \"169.254.123.123\" })\n      expect(subject).to be_winrm_info_invalid(machine.ssh_info)\n    end\n\n    it \"returns false if the IP is valid\" do\n      allow(machine).to receive(:ssh_info).and_return({ host: \"192.168.123.123\" })\n      expect(subject).not_to be_winrm_info_invalid(machine.ssh_info)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/communicators/winrm/plugin_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/communicators/winrm/plugin\")\n\ndescribe VagrantPlugins::CommunicatorWinRM::Plugin do\n  describe \"#init!\" do\n    let(:manager_instance) { double(\"manager_instance\", installed_plugins: installed_plugins) }\n    let(:installed_plugins) { {} }\n\n    before do\n      allow(I18n).to receive(:load_path).and_return(\"\")\n      allow(I18n).to receive(:reload!)\n      allow(described_class).to receive(:require)\n      allow(Vagrant::Plugin::Manager).to receive(:instance).and_return(manager_instance)\n    end\n\n    after do\n      described_class.init!\n      described_class.reset!\n    end\n\n    it \"should not output any warning\" do\n      expect($stderr).not_to receive(:puts).with(/WARNING/)\n    end\n\n    context \"when vagrant-winrm plugin is installed\" do\n      let(:installed_plugins) { {\"vagrant-winrm\" => \"PLUGIN_INFO\"} }\n\n      it \"should output a warning\" do\n        expect($stderr).to receive(:puts).with(/WARNING/)\n      end\n\n      context \"with VAGRANT_IGNORE_WINRM_PLUGIN set\" do\n        before { allow(ENV).to receive(:[]).with(\"VAGRANT_IGNORE_WINRM_PLUGIN\").and_return(\"1\") }\n\n        it \"should not output any warning\" do\n          expect($stderr).not_to receive(:puts).with(/WARNING/)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/communicators/winrm/shell_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/communicators/winrm/shell\")\nrequire Vagrant.source_root.join(\"plugins/communicators/winrm/config\")\n\ndescribe VagrantPlugins::CommunicatorWinRM::WinRMShell do\n  include_context \"unit\"\n\n  let(:connection) { double(\"winrm_connection\") }\n  let(:shell) { double(\"winrm_shell\") }\n  let(:port) { config.transport == :ssl ? 5986 : 5985 }\n  let(:config)  {\n    VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|\n      c.username = 'username'\n      c.password = 'password'\n      c.max_tries = 3\n      c.retry_delay = 0\n      c.basic_auth_only = false\n      c.retry_delay = 1\n      c.max_tries = 2\n      c.finalize!\n    end\n  }\n  let(:output) { WinRM::Output.new.tap { |out| out.exitcode = 0 } }\n\n  before { allow(connection).to receive(:shell).and_yield(shell) }\n\n  subject do\n    described_class.new('localhost', port, config).tap do |comm|\n      allow(comm).to receive(:new_connection).and_return(connection)\n    end\n  end\n\n  describe \"#upload\" do\n    let(:fm) { double(\"file_manager\") }\n\n    before do\n      allow(WinRM::FS::FileManager).to receive(:new).with(connection)\n        .and_return(fm)\n    end\n\n    it \"should call file_manager.upload for each passed in path\" do\n      from = [\"/path\", \"/path/folder\", \"/path/folder/file.py\"]\n      to = \"/destination\"\n      size = 80\n\n      allow(fm).to receive(:upload).and_return(size)\n\n      expect(fm).to receive(:upload).exactly(from.size).times\n      expect(subject.upload(from, to)).to eq(size*from.size)\n    end\n\n    it \"should call file_manager.upload once for a single path\" do\n      from = \"/path/folder/file.py\"\n      to = \"/destination\"\n      size = 80\n\n      allow(fm).to receive(:upload).and_return(size)\n\n      expect(fm).to receive(:upload).exactly(1).times\n      expect(subject.upload(from, to)).to eq(size)\n    end\n\n    context \"when source is a directory\" do\n      let(:source) { \"path/sourcedir\" }\n\n      before do\n        allow(File).to receive(:directory?).with(/#{Regexp.escape(source)}/).and_return(true)\n      end\n\n      it \"should add source directory name to destination\" do\n        expect(fm).to receive(:upload) do |from, to|\n          expect(to).to include(\"sourcedir\")\n        end\n        subject.upload(source, \"/dest\")\n      end\n\n      it \"should not add source directory name to destination when source ends with '.'\" do\n        source << \"/.\"\n        expect(fm).to receive(:upload) do |from, to|\n          expect(to).to eq(\"/dest\")\n        end\n        subject.upload(source, \"/dest\")\n      end\n    end\n  end\n\n  describe \".powershell\" do\n    it \"should call winrm powershell\" do\n      expect(shell).to receive(:run).with(\"dir\").and_return(output)\n      expect(subject.powershell(\"dir\").exitcode).to eq(0)\n    end\n\n    it \"should raise an execution error when an exception occurs\" do\n      expect(shell).to receive(:run).with(\"dir\").and_raise(\n        StandardError.new(\"Oh no! a 500 SOAP error!\"))\n      expect { subject.powershell(\"dir\") }.to raise_error(\n        VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError)\n    end\n  end\n\n  describe \".elevated\" do\n    let(:eusername) { double(\"elevatedusername\") }\n    let(:username) { double(\"username\") }\n    let(:failed_output) { WinRM::Output.new.tap { |out|\n        out.exitcode = described_class.const_get(:INVALID_USERID_EXITCODE)\n        out << {stderr: \"(10,8):UserId:\"}\n        out << {stderr: \"At line:72 char:1\"}\n      } }\n\n    before do\n      allow(subject).to receive(:elevated_username).and_return(eusername)\n      allow(shell).to receive(:username).and_return(username)\n      allow(shell).to receive(:username=)\n    end\n\n    it \"should call winrm elevated\" do\n      expect(shell).to receive(:run).with(\"dir\").and_return(output)\n      expect(shell).to receive(:interactive_logon=).with(false)\n      expect(subject.elevated(\"dir\").exitcode).to eq(0)\n    end\n\n    it \"should set interactive_logon when interactive is true\" do\n      expect(shell).to receive(:run).with(\"dir\").and_return(output)\n      expect(shell).to receive(:interactive_logon=).with(true)\n      expect(subject.elevated(\"dir\", { interactive: true }).exitcode).to eq(0)\n    end\n\n    it \"should raise an execution error when an exception occurs\" do\n      expect(shell).to receive(:run).with(\"dir\").and_raise(\n        StandardError.new(\"Oh no! a 500 SOAP error!\"))\n      expect { subject.powershell(\"dir\") }.to raise_error(\n        VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError)\n    end\n\n    it \"should use elevated username and retry on username failure\" do\n      expect(subject).to receive(:elevated_username).and_return(eusername)\n      expect(shell).to receive(:run).with(\"dir\").and_return(failed_output)\n      expect(shell).to receive(:run).with(\"dir\").and_return(output)\n      expect(shell).to receive(:interactive_logon=).with(false)\n      expect(subject.elevated(\"dir\").exitcode).to eq(0)\n    end\n\n    it \"should not retry on username failure if elevated username is the same\" do\n      expect(subject).to receive(:elevated_username).and_return(username)\n      expect(shell).to receive(:run).with(\"dir\").and_return(failed_output)\n      expect(shell).to receive(:interactive_logon=).with(false)\n      expect(subject.elevated(\"dir\").exitcode).to eq(failed_output.exitcode)\n    end\n  end\n\n  describe \".cmd\" do\n    it \"should call winrm cmd\" do\n      expect(connection).to receive(:shell).with(:cmd, { })\n      expect(shell).to receive(:run).with(\"dir\").and_return(output)\n      expect(subject.cmd(\"dir\").exitcode).to eq(0)\n    end\n\n    context \"when codepage is given\" do\n      let(:config)  {\n        VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|\n          c.codepage = 800\n          c.finalize!\n        end\n      }\n\n      it \"creates shell with the given codepage when set\" do\n        expect(connection).to receive(:shell).with(:cmd, { codepage: 800 })\n        expect(shell).to receive(:run).with(\"dir\").and_return(output)\n        expect(subject.cmd(\"dir\").exitcode).to eq(0)\n      end\n    end\n\n    it \"should catch timeout errors\" do\n      expect(connection).to receive(:shell).with(:cmd, { })\n      expect(shell).to receive(:run).with(\"hostname\").and_raise(IO::TimeoutError)\n      expect { subject.cmd(\"hostname\") }.to raise_error(VagrantPlugins::CommunicatorWinRM::Errors::ConnectionTimeout)\n    end\n  end\n\n  describe \".wql\" do\n    it \"should call winrm wql\" do\n      expect(connection).to receive(:run_wql).with(\"select * from Win32_OperatingSystem\")\n      subject.wql(\"select * from Win32_OperatingSystem\")\n    end\n\n    it \"should retry when a WinRMAuthorizationError is received\" do\n      expect(connection).to receive(:run_wql).with(\"select * from Win32_OperatingSystem\").exactly(2).times.and_raise(\n        # Note: The initialize for WinRMAuthorizationError may require a status_code as\n        # the second argument in a future WinRM release. Currently it doesn't track the\n        # status code.\n        WinRM::WinRMAuthorizationError.new(\"Oh no!! Unauthorized\")\n      )\n      expect { subject.wql(\"select * from Win32_OperatingSystem\") }.to raise_error(\n        VagrantPlugins::CommunicatorWinRM::Errors::AuthenticationFailed)\n    end\n  end\n\n  describe \".endpoint\" do\n    context 'when transport is :ssl' do\n      let(:config)  {\n        VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|\n          c.transport = :ssl\n          c.finalize!\n        end\n      }\n      it \"should create winrm endpoint address using https\" do\n        expect(subject.send(:endpoint)).to eq(\"https://localhost:5986/wsman\")\n      end\n    end\n\n    context \"when transport is :negotiate\" do\n      it \"should create winrm endpoint address using http\" do\n        expect(subject.send(:endpoint)).to eq(\"http://localhost:5985/wsman\")\n      end\n    end\n\n    context \"when transport is :plaintext\" do\n      let(:config)  {\n        VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|\n          c.transport = :plaintext\n          c.finalize!\n        end\n      }\n      it \"should create winrm endpoint address using http\" do\n        expect(subject.send(:endpoint)).to eq(\"http://localhost:5985/wsman\")\n      end\n    end\n  end\n\n  describe \".endpoint_options\" do\n    it \"should create endpoint options\" do\n      expect(subject.send(:endpoint_options)).to eq(\n        { endpoint: \"http://localhost:5985/wsman\", operation_timeout: 1800,\n          user: \"username\", password: \"password\", host: \"localhost\", port: 5985,\n          basic_auth_only: false, no_ssl_peer_verification: false,\n          retry_delay: 1, retry_limit: 2, transport: :negotiate })\n    end\n  end\n\n  describe \"#elevated_username\" do\n    let(:username) { \"username\" }\n\n    before do\n      allow(subject).to receive(:username).and_return(username)\n      allow(subject).to receive(:powershell)\n    end\n\n    it \"should return username\" do\n      expect(subject.send(:elevated_username)).to eq(username)\n    end\n\n    it \"should attempt to get computer name\" do\n      expect(subject).to receive(:powershell).with(/computername/)\n      subject.send(:elevated_username)\n    end\n\n    it \"should prepend computer name when available\" do\n      expect(subject).to receive(:powershell).with(/computername/).and_yield(:stdout, \"COMPUTERNAME\")\n      expect(subject.send(:elevated_username)).to eq(\"COMPUTERNAME\\\\#{username}\")\n    end\n\n    it \"should compute elevated username every time\" do\n      expect(subject).to receive(:powershell).twice.with(/computername/).and_yield(:stdout, \"COMPUTERNAME\")\n      expect(subject.send(:elevated_username)).to eq(\"COMPUTERNAME\\\\#{username}\")\n      expect(subject.send(:elevated_username)).to eq(\"COMPUTERNAME\\\\#{username}\")\n    end\n\n    context \"when username includes computer/domain name\" do\n      let(:username) { \"machine\\\\username\" }\n\n      it \"should not attempt to get computer name\" do\n        expect(subject).not_to receive(:powershell)\n        expect(subject.send(:elevated_username)).to eq(username)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/communicators/winssh/communicator_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/communicators/winssh/communicator\")\nrequire Vagrant.source_root.join(\"plugins/communicators/winssh/config\")\n\ndescribe VagrantPlugins::CommunicatorWinSSH::Communicator do\n  include_context \"unit\"\n\n  let(:export_command_template){ 'export %ENV_KEY%=\"%ENV_VALUE%\"' }\n\n  let(:ssh) do\n    double(\"ssh\",\n      timeout: 1,\n      host: nil,\n      port: 5986,\n      guest_port: 5986,\n      keep_alive: false\n    )\n  end\n\n  let(:shell) { \"cmd\" }\n\n  # SSH configuration information mock\n  let(:winssh) do\n    double(\"winssh\",\n      insert_key: false,\n      export_command_template: export_command_template,\n      shell: shell,\n      upload_directory: \"C:\\\\Windows\\\\Temp\"\n    )\n  end\n  # Configuration mock\n  let(:config) { double(\"config\", winssh: winssh, ssh: ssh) }\n  # Provider mock\n  let(:provider) { double(\"provider\") }\n  let(:ui) { Vagrant::UI::Silent.new }\n  # SSH info mock\n  let(:ssh_info) { double(\"ssh_info\") }\n  # Machine mock built with previously defined\n  let(:machine) do\n    double(\"machine\",\n      config: config,\n      provider: provider,\n      ui: ui,\n      ssh_info: ssh_info\n    )\n  end\n  # Subject instance to test\n  let(:communicator){ @communicator ||= described_class.new(machine) }\n  # Underlying net-ssh connection mock\n  let(:connection) { double(\"connection\", open_channel: nil) }\n  # Base net-ssh connection channel mock\n  let(:channel) { double(\"channel\") }\n  # net-ssh connection channel mock for running commands\n  let(:command_channel) { double(\"command_channel\") }\n  # Default exit data for commands run\n  let(:exit_data) { double(\"exit_data\", read_long: 0) }\n  # Marker used for flagging start of output\n  let(:command_garbage_marker) { communicator.class.const_get(:CMD_GARBAGE_MARKER) }\n  # Start marker output when PTY is enabled\n  let(:pty_delim_start) { communicator.class.const_get(:PTY_DELIM_START) }\n  # End marker output when PTY is enabled\n  let(:pty_delim_end) { communicator.class.const_get(:PTY_DELIM_END) }\n  # Command output returned on stdout\n  let(:command_stdout_data) { '' }\n  # Command output returned on stderr\n  let(:command_stderr_data) { '' }\n  # Mock for net-ssh sftp\n  let(:sftp) { double(\"sftp\") }\n  # Prevent connection patching by default in tests\n  let(:winssh_patch) { true }\n\n  # Setup for commands using the net-ssh connection. This can be reused where needed\n  # by providing to `before`\n  connection_setup = proc do\n    connection.instance_variable_set(:@winssh_patched, winssh_patch)\n    allow(connection).to receive(:logger)\n    allow(connection).to receive(:closed?).and_return(false)\n    allow(connection).to receive(:open_channel).\n      and_yield(channel).and_return(channel)\n     allow(channel).to receive(:wait).and_return(true)\n    allow(channel).to receive(:close)\n    allow(command_channel).to receive(:send_data)\n    allow(command_channel).to receive(:eof!)\n    allow(command_channel).to receive(:on_data).\n      and_yield(nil, command_stdout_data)\n    allow(command_channel).to receive(:on_extended_data).\n      and_yield(nil, nil, command_stderr_data)\n    allow(machine).to receive(:ssh_info).and_return(host: '10.1.2.3', port: 22)\n    allow(channel).to receive(:[]=).with(any_args).and_return(true)\n    allow(channel).to receive(:on_close)\n    allow(channel).to receive(:on_data)\n    allow(channel).to receive(:on_extended_data)\n    allow(channel).to receive(:on_request)\n    allow(channel).to receive(:on_process)\n    allow(channel).to receive(:exec).with(anything).\n      and_yield(command_channel, '').and_return(channel)\n    allow(command_channel).to receive(:on_request).with('exit-status').\n      and_yield(nil, exit_data)\n    # Return mocked net-ssh connection during setup\n    allow(communicator).to receive(:retryable).and_return(connection)\n    allow(sftp).to receive(:upload!)\n    allow(communicator).to receive(:sftp_connect).and_return(true)\n    allow(communicator).to receive(:execute).and_call_original\n    allow(communicator).to receive(:execute).\n      with(described_class.const_get(:READY_COMMAND), error_check: false).\n      and_return(0)\n  end\n\n  describe \"#wait_for_ready\" do\n    before(&connection_setup)\n    context \"with no static config (default scenario)\" do\n      context \"when ssh_info requires a multiple tries before it is ready\" do\n        before do\n          expect(machine).to receive(:ssh_info).\n            and_return(nil).ordered\n          expect(machine).to receive(:ssh_info).\n            and_return(host: '10.1.2.3', port: 22).ordered\n        end\n\n        it \"retries ssh_info until ready\" do\n          # retries are every 0.5 so buffer the timeout just a hair over\n          expect(communicator.wait_for_ready(0.6)).to eq(true)\n        end\n      end\n    end\n  end\n\n  describe \"#ready?\" do\n    before(&connection_setup)\n    it \"returns true if shell test is successful\" do\n      expect(communicator.ready?).to be_truthy\n    end\n\n    context \"with an invalid shell test\" do\n      before do\n        allow(communicator).to receive(:execute).\n          with(described_class.const_get(:READY_COMMAND), error_check: false).\n          and_return(1)\n      end\n\n      it \"returns raises SSHInvalidShell error\" do\n        expect{ communicator.ready? }.to raise_error(Vagrant::Errors::SSHInvalidShell)\n      end\n    end\n  end\n\n  describe \"#execute\" do\n    before(&connection_setup)\n\n    it \"runs valid command and returns successful status code\" do\n      expect(communicator.execute(\"command-to-run\", error_check: false)).to eq(0)\n    end\n\n    it \"prepends UUID output to command for garbage removal\" do\n      expect(channel).to receive(:exec).\n        with(/Write-Output #{command_garbage_marker};\\[Console\\]::Error.WriteLine\\('#{command_garbage_marker}'\\).*/)\n      expect(communicator.execute(\"command-to-run\")).to eq(0)\n    end\n\n    context \"with command returning an error\" do\n      let(:exit_data) { double(\"exit_data\", read_long: 1) }\n\n      it \"raises error when exit-code is non-zero\" do\n        expect{ communicator.execute(\"command-to-run\") }.to raise_error(Vagrant::Errors::VagrantError)\n      end\n\n      it \"returns exit-code when exit-code is non-zero and error check is disabled\" do\n        expect(communicator.execute(\"command-to-run\", error_check: false)).to eq(1)\n      end\n    end\n\n    context \"with garbage content prepended to command output\" do\n      let(:command_stdout_data) do\n        \"Line of garbage\\nMore garbage\\n#{command_garbage_marker}Dir1\\nDir2\\n\"\n      end\n\n      it \"removes any garbage output prepended to command output\" do\n        stdout = ''\n        expect(\n          communicator.execute(\"command-to-run\") do |type, data|\n            if type == :stdout\n              stdout << data\n            end\n          end\n        ).to eq(0)\n        expect(stdout).to eq(\"Dir1\\nDir2\\n\")\n      end\n    end\n\n    context \"with garbage content prepended to command stderr output\" do\n      let(:command_stderr_data) do\n        \"Line of garbage\\nMore garbage\\n#{command_garbage_marker}Dir1\\nDir2\\n\"\n      end\n\n      it \"removes any garbage output prepended to command stderr output\" do\n        stderr = ''\n        expect(\n          communicator.execute(\"command-to-run\") do |type, data|\n            if type == :stderr\n              stderr << data\n            end\n          end\n        ).to eq(0)\n        expect(stderr).to eq(\"Dir1\\nDir2\\n\")\n      end\n    end\n  end\n\n  describe \"#test\" do\n    before(&connection_setup)\n    context \"with exit code as zero\" do\n      it \"returns true\" do\n        expect(communicator.test(\"dir\")).to be_truthy\n      end\n    end\n\n    context \"with exit code as non-zero\" do\n      before do\n        expect(exit_data).to receive(:read_long).and_return(1)\n      end\n\n      it \"returns false\" do\n        expect(communicator.test(\"false.exe\")).to be_falsey\n      end\n    end\n  end\n\n  describe \"#upload\" do\n    before do\n      allow(sftp).to receive(:upload)\n      expect(communicator).to receive(:sftp_connect).and_yield(sftp)\n    end\n\n    it \"uploads a directory if local path is a directory\" do\n      Dir.mktmpdir('vagrant-test') do |dir|\n        FileUtils.touch(File.join(dir, \"test-file\"))\n        expect(sftp).to receive(:mkdir).with(/destination/).exactly(2).times\n        expect(sftp).to receive(:upload!).with(an_instance_of(File), /test-file/)\n        communicator.upload(dir, 'C:\\destination')\n      end\n    end\n\n    it \"uploads a file if local path is a file\" do\n      file = Tempfile.new('vagrant-test')\n      begin\n        expect(sftp).to receive(:mkdir).with(/destination/)\n        expect(sftp).to receive(:upload!).with(instance_of(File), 'C:/destination/file')\n        expect(Vagrant::Util::Platform).to receive(:unix_windows_path).with('C:\\destination\\file').\n          and_call_original\n        communicator.upload(file.path, 'C:\\destination\\file')\n      ensure\n        file.delete\n      end\n    end\n\n    it \"does not raise custom error on non-permission errors\" do\n      file = Tempfile.new('vagrant-test')\n      begin\n        expect(sftp).to receive(:mkdir).with(/destination/)\n        expect(sftp).to receive(:upload!).with(instance_of(File), 'C:/destination/file').\n          and_raise(\"Some other error\")\n        expect{ communicator.upload(file.path, 'C:\\destination\\file') }.to raise_error(RuntimeError)\n      ensure\n        file.delete\n      end\n    end\n  end\n\n  describe \"#download\" do\n    before do\n      expect(communicator).to receive(:sftp_connect).and_yield(sftp)\n    end\n\n    it \"calls sftp to download file\" do\n      expect(sftp).to receive(:download!).with('/path/from', 'C:\\path\\to')\n      communicator.download('/path/from', 'C:\\path\\to')\n    end\n  end\n\n  describe \"#connect\" do\n\n    it \"cannot be called directly\" do\n      expect{ communicator.connect }.to raise_error(NoMethodError)\n    end\n\n    context \"with default configuration\" do\n\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: nil,\n          port: nil,\n          private_key_path: nil,\n          username: nil,\n          password: nil,\n          keys_only: true,\n          verify_host_key: false\n        )\n      end\n\n      it \"has keys_only enabled\" do\n        expect(Net::SSH).to receive(:start).with(\n          nil, nil, hash_including(\n            keys_only: true\n          )\n        ).and_return(connection)\n        communicator.send(:connect)\n      end\n\n      it \"has verify_host_key disabled\" do\n        expect(Net::SSH).to receive(:start).with(\n          nil, nil, hash_including(\n            verify_host_key: false\n          )\n        ).and_return(connection)\n        communicator.send(:connect)\n      end\n\n      it \"does not include any private key paths\" do\n        expect(Net::SSH).to receive(:start).with(\n          nil, nil, hash_excluding(\n            keys: anything\n          )\n        ).and_return(connection)\n        communicator.send(:connect)\n      end\n\n      it \"includes `none` and `hostbased` auth methods\" do\n        expect(Net::SSH).to receive(:start).with(\n          nil, nil, hash_including(\n            auth_methods: [\"none\", \"hostbased\"]\n          )\n        ).and_return(connection)\n        communicator.send(:connect)\n      end\n    end\n\n    context \"with keys_only disabled and verify_host_key enabled\" do\n\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: nil,\n          port: nil,\n          private_key_path: nil,\n          username: nil,\n          password: nil,\n          keys_only: false,\n          verify_host_key: true\n        )\n      end\n\n      it \"has keys_only enabled\" do\n        expect(Net::SSH).to receive(:start).with(\n          nil, nil, hash_including(\n            keys_only: false\n          )\n        ).and_return(connection)\n        communicator.send(:connect)\n      end\n\n      it \"has verify_host_key disabled\" do\n        expect(Net::SSH).to receive(:start).with(\n          nil, nil, hash_including(\n            verify_host_key: true\n          )\n        ).and_return(connection)\n        communicator.send(:connect)\n      end\n    end\n\n    context \"with host and port configured\" do\n\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: '127.0.0.1',\n          port: 2222,\n          private_key_path: nil,\n          username: nil,\n          password: nil,\n          keys_only: true,\n          verify_host_key: false\n        )\n      end\n\n      it \"specifies configured host\" do\n        expect(Net::SSH).to receive(:start).with(\"127.0.0.1\", anything, anything).\n          and_return(connection)\n        communicator.send(:connect)\n      end\n\n      it \"has port defined\" do\n        expect(Net::SSH).to receive(:start).with(\"127.0.0.1\", anything, hash_including(port: 2222)).\n          and_return(connection)\n        communicator.send(:connect)\n      end\n    end\n\n    context \"with private_key_path configured\" do\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: '127.0.0.1',\n          port: 2222,\n          private_key_path: ['/priv/key/path'],\n          username: nil,\n          password: nil,\n          keys_only: true,\n          verify_host_key: false\n        )\n      end\n\n      it \"includes private key paths\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            keys: [\"/priv/key/path\"]\n          )\n        ).and_return(connection)\n        communicator.send(:connect)\n      end\n\n      it \"includes `publickey` auth method\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            auth_methods: [\"none\", \"hostbased\", \"publickey\"]\n          )\n        ).and_return(connection)\n        communicator.send(:connect)\n      end\n    end\n\n    context \"with username and password configured\" do\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: '127.0.0.1',\n          port: 2222,\n          private_key_path: nil,\n          username: 'vagrant',\n          password: 'vagrant',\n          keys_only: true,\n          verify_host_key: false\n        )\n      end\n\n      it \"has username defined\" do\n        expect(Net::SSH).to receive(:start).with(anything, 'vagrant', anything).\n          and_return(connection)\n        communicator.send(:connect)\n      end\n\n      it \"has password defined\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            password: 'vagrant'\n          )\n        ).and_return(connection)\n        communicator.send(:connect)\n      end\n\n      it \"includes `password` auth method\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            auth_methods: [\"none\", \"hostbased\", \"password\"]\n          )\n        ).and_return(connection)\n        communicator.send(:connect)\n      end\n    end\n\n    context \"with password and private_key_path configured\" do\n\n      before do\n        expect(machine).to receive(:ssh_info).and_return(\n          host: '127.0.0.1',\n          port: 2222,\n          private_key_path: ['/priv/key/path'],\n          username: 'vagrant',\n          password: 'vagrant',\n          keys_only: true,\n          verify_host_key: false\n        )\n      end\n\n      it \"has password defined\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            password: 'vagrant'\n          )\n        ).and_return(connection)\n        communicator.send(:connect)\n      end\n\n      it \"includes private key paths\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            keys: [\"/priv/key/path\"]\n          )\n        ).and_return(connection)\n        communicator.send(:connect)\n      end\n\n      it \"includes `publickey` and `password` auth methods\" do\n        expect(Net::SSH).to receive(:start).with(\n          anything, anything, hash_including(\n            auth_methods: [\"none\", \"hostbased\", \"publickey\", \"password\"]\n          )\n        ).and_return(connection)\n        communicator.send(:connect)\n      end\n    end\n\n    context \"when not patched for winssh\" do\n      let(:winssh_patch) { false }\n\n      before(&connection_setup)\n\n      it \"should patch the connection instance on first request\" do\n        expect(connection).to receive(:define_singleton_method)\n        communicator.send(:connect)\n      end\n\n      it \"should force powershell on exec\" do\n        expect(channel).to receive(:exec).with(/powershell/).and_return(channel)\n        communicator.execute(\"test\", error_check: false)\n      end\n    end\n  end\n\n  describe \"#generate_environment_export\" do\n    let(:winssh) do\n      @c ||= VagrantPlugins::CommunicatorWinSSH::Config.new\n      @c.finalize!\n      @c\n    end\n\n    it \"should generate bourne shell compatible export\" do\n      expect(communicator.send(:generate_environment_export, \"TEST\", \"value\")).to eq(\"$env:TEST=\\\"value\\\"\\n\")\n    end\n\n    context \"with custom template defined\" do\n      let(:winssh) do\n        @c ||= VagrantPlugins::CommunicatorWinSSH::Config.new\n        @c.export_command_template = \"setenv %ENV_KEY% %ENV_VALUE%\"\n        @c.finalize!\n        @c\n      end\n\n      it \"should generate custom export based on template\" do\n        expect(communicator.send(:generate_environment_export, \"TEST\", \"value\")).to eq(\"setenv TEST value\\n\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/alma/cap/flavor_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestAlma::Cap::Flavor\" do\n  let(:caps) do\n    VagrantPlugins::GuestAlma::Plugin\n      .components\n      .guest_capabilities[:alma]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".flavor\" do\n    let(:cap) { caps.get(:flavor) }\n\n    {\n      \"\" => :alma,\n      \"8.2\" => :alma_8,\n      \"9\" => :alma_9,\n      \"invalid\" => :alma\n    }.each do |str, expected|\n      it \"returns #{expected} for #{str}\" do\n        comm.stub_command(\"source /etc/os-release && printf $VERSION_ID\", stdout: str)\n        expect(cap.flavor(machine)).to be(expected)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/alpine/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe 'VagrantPlugins::GuestAlpine::Cap::ChangeHostname' do\n  let(:described_class) do\n    VagrantPlugins::GuestAlpine::Plugin.components.guest_capabilities[:alpine].get(:change_host_name)\n  end\n  let(:machine) { double('machine') }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:old_hostname) { 'oldhostname.olddomain.tld' }\n  let(:networks) {[\n    [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n  ]}\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    communicator.stub_command('hostname -f', stdout: old_hostname)\n    allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n  end\n\n  after do\n    communicator.verify_expectations!\n  end\n\n  describe '.change_host_name' do\n    it 'updates /etc/hostname on the machine' do\n      communicator.expect_command(\"echo 'newhostname' > /etc/hostname\")\n      described_class.change_host_name(machine, 'newhostname.newdomain.tld')\n    end\n\n    it 'only tries to update /etc/hosts when the provided hostname is not different' do\n      described_class.change_host_name(machine, 'oldhostname.olddomain.tld')\n      expect(communicator.received_commands[0]).to eq('hostname -f')\n      expect(communicator.received_commands.length).to eq(2)\n    end\n\n    it 'refreshes the hostname service with the hostname command' do\n      communicator.expect_command('hostname -F /etc/hostname')\n      described_class.change_host_name(machine, 'newhostname.newdomain.tld')\n    end\n\n    it 'renews dhcp on the system with the new hostname' do\n      communicator.expect_command('ifdown -a; ifup -a; ifup eth0')\n      described_class.change_host_name(machine, 'newhostname.newdomain.tld')\n    end\n\n    describe 'flipping out the old hostname in /etc/hosts' do\n      context \"minimal network config\" do\n        it \"sets the hostname\" do\n          described_class.change_host_name(machine, 'newhostname.newdomain.tld')\n          add_to_loopback_cmd = communicator.received_commands.find { |c| c =~ /127.0.\\$\\{i\\}.1/ }\n          expect(add_to_loopback_cmd).to_not eq(nil)\n        end\n    end\n\n      context \"multiple networks configured with hostname\" do\n        let(:networks) {[\n            [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}],\n            [:public_network, {:ip=>\"192.168.0.1\", :hostname=>true, :protocol=>\"tcp\", :id=>\"93a4ad88-0774-4127-a161-ceb715ff372f\"}],\n            [:public_network, {:ip=>\"192.168.0.2\", :protocol=>\"tcp\", :id=>\"5aebe848-7d85-4425-8911-c2003d924120\"}]\n        ]}\n      \n        it \"sets the hostname\" do\n            described_class.change_host_name(machine, 'newhostname.newdomain.tld')\n            add_to_loopback_cmd = communicator.received_commands.find { |c| c =~ /sed -i '\\/newhostname.newdomain.tld\\/d'/ }\n            expect(add_to_loopback_cmd).to_not eq(nil)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/alpine/cap/configure_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe 'VagrantPlugins::GuestAlpine::Cap::ConfigureNetworks' do\n  let(:described_class) do\n    VagrantPlugins::GuestAlpine::Plugin.components.guest_capabilities[:alpine].get(:configure_networks)\n  end\n  let(:machine) { double('machine') }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n  end\n\n  after do\n    communicator.verify_expectations!\n  end\n\n  it 'should configure networks' do\n    networks = [\n      { type: :static, ip: '192.168.10.10', netmask: '255.255.255.0', interface: 0, name: 'eth0' },\n      { type: :dhcp, interface: 1, name: 'eth1' }\n    ]\n\n    expect(communicator).to receive(:sudo).with(\"sed -e '/^#VAGRANT-BEGIN/,$ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces.pre\")\n    expect(communicator).to receive(:sudo).with(\"sed -ne '/^#VAGRANT-END/,$ p' /etc/network/interfaces | tail -n +2 > /tmp/vagrant-network-interfaces.post\")\n    expect(communicator).to receive(:sudo).with(/\\/sbin\\/ifdown eth0/)\n    expect(communicator).to receive(:sudo).with('/sbin/ip addr flush dev eth0 2> /dev/null')\n    expect(communicator).to receive(:sudo).with(/\\/sbin\\/ifdown eth1/)\n    expect(communicator).to receive(:sudo).with('/sbin/ip addr flush dev eth1 2> /dev/null')\n    expect(communicator).to receive(:sudo).with('cat /tmp/vagrant-network-interfaces.pre /tmp/vagrant-network-entry /tmp/vagrant-network-interfaces.post > /etc/network/interfaces')\n    expect(communicator).to receive(:sudo).with('rm -f /tmp/vagrant-network-interfaces.pre /tmp/vagrant-network-entry /tmp/vagrant-network-interfaces.post')\n    expect(communicator).to receive(:sudo).with('/sbin/ifup eth0')\n    expect(communicator).to receive(:sudo).with('/sbin/ifup eth1')\n\n    allow_message_expectations_on_nil\n\n    described_class.configure_networks(machine, networks)\n  end\n\n  context \"dhcp assigned default route\" do\n    let(:networks) {\n      [{type: :dhcp, use_dhcp_assigned_default_route: is_enabled}]\n    }\n    let(:is_enabled) { false }\n    let(:tempfile) { double(:tempfile, binmode: true, close: true, path: \"/dev/null\") }\n\n    before do\n      allow(Tempfile).to receive(:new).and_return(tempfile)\n    end\n\n    context \"when not enabled\" do\n      it \"should not configure default route\" do\n        expect(tempfile).not_to receive(:write).with(/post-up route del default dev eth0/)\n\n        described_class.configure_networks(machine, networks)\n      end\n    end\n\n    context \"when enabled\" do\n      let(:is_enabled) { true }\n\n      it \"should configure default route\" do\n        expect(tempfile).to receive(:write).with(/post-up route del default dev eth0/)\n\n        described_class.configure_networks(machine, networks)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/alpine/cap/halt_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe 'VagrantPlugins::GuestAlpine::Cap::Halt' do\n    let(:described_class) do\n        VagrantPlugins::GuestAlpine::Plugin.components.guest_capabilities[:alpine].get(:halt)\n    end\n    let(:machine) { double('machine') }\n    let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n    before do\n        allow(machine).to receive(:communicate).and_return(communicator)\n    end\n\n    after do\n        communicator.verify_expectations!\n    end\n\n    it 'should halt guest' do\n        expect(communicator).to receive(:sudo).with('poweroff')\n        allow_message_expectations_on_nil\n        described_class.halt(machine)\n    end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/alpine/cap/nfs_client_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe 'VagrantPlugins::GuestAlpine::Cap::NFSClient' do\n    let(:described_class) do\n        VagrantPlugins::GuestAlpine::Plugin.components.guest_capabilities[:alpine].get(:nfs_client_install)\n    end\n\n    let(:machine) { double('machine') }\n    let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n    before do\n        allow(machine).to receive(:communicate).and_return(communicator)\n    end\n\n    after do\n        communicator.verify_expectations!\n    end\n\n    it 'should install nfs client' do\n        described_class.nfs_client_install(machine)\n\n        expect(communicator.received_commands[0]).to match(/apk update/)\n        expect(communicator.received_commands[1]).to match(/apk add --upgrade nfs-utils/)\n        expect(communicator.received_commands[2]).to match(/rc-update add rpc.statd/)\n        expect(communicator.received_commands[3]).to match(/rc-service rpc.statd start/)\n    end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/alpine/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe 'VagrantPlugins::GuestAlpine::Cap::RSync' do\n    let(:machine) { double('machine') }\n    let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n    before do\n        allow(machine).to receive(:communicate).and_return(communicator)\n    end\n\n    after do\n        communicator.verify_expectations!\n    end\n\n    let(:described_class) do\n        VagrantPlugins::GuestAlpine::Plugin.components.guest_capabilities[:alpine].get(:rsync_install)\n    end\n\n    it 'should install rsync with --update-cache flag' do\n        # communicator.should_receive(:sudo).with('apk add rsync')\n        expect(communicator).to receive(:sudo).with('apk add --update-cache rsync')\n        allow_message_expectations_on_nil\n        described_class.rsync_install(machine)\n    end\n\n    let(:described_class) do\n        VagrantPlugins::GuestAlpine::Plugin.components.guest_capabilities[:alpine].get(:rsync_installed)\n    end\n\n    it 'should verify rsync installed' do\n        # communicator.should_receive(:test).with('test -f /usr/bin/rsync')\n        expect(communicator).to receive(:test).with('test -f /usr/bin/rsync')\n        allow_message_expectations_on_nil\n        described_class.rsync_installed(machine)\n    end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/alpine/plugin_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\n\ndescribe VagrantPlugins::GuestAlpine::Plugin do\n  let(:manager) { double(\"manager\") }\n\n  before do\n    allow(Vagrant::Plugin::Manager).to receive(:instance).and_return(manager)\n  end\n\n  context \"when vagrant-alpine plugin is not installed\" do\n    before do\n      allow(manager).to receive(:installed_plugins).and_return({})\n    end\n\n    it \"should not display a warning\" do\n      expect($stderr).to_not receive(:puts)\n      VagrantPlugins::GuestAlpine::Plugin.check_community_plugin\n    end\n  end\n\n  context \"when vagrant-alpine plugin is installed\" do\n    before do\n      allow(manager).to receive(:installed_plugins).and_return({ \"vagrant-alpine\" => {} })\n    end\n\n    it \"should display a warning\" do\n      expect($stderr).to receive(:puts).with(/vagrant plugin uninstall vagrant-alpine/)\n      VagrantPlugins::GuestAlpine::Plugin.check_community_plugin\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/alt/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestALT::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestALT::Plugin\n      .components\n      .guest_capabilities[:alt]\n      .get(:change_host_name)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:name) { \"banana-rama.example.com\" }\n  let(:basename) { \"banana-rama\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    context \"minimal network config\" do \n      let(:networks) { [\n        [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n      ] }\n\n      before do \n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n      end\n\n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n\n        described_class.change_host_name(machine, name)\n        expect(comm.received_commands[2]).to match(/hostnamectl set-hostname --static '#{name}'/)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n\n        described_class.change_host_name(machine, name)\n        expect(comm).to_not receive(:sudo).with(/hostnamectl set-hostname --static '#{name}'/)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/alt/cap/configure_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestALT::Cap::ConfigureNetworks\" do\n  let(:caps) do\n    VagrantPlugins::GuestALT::Plugin\n      .components\n      .guest_capabilities[:alt]\n  end\n\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:config) { double(\"config\", vm: vm) }\n  let(:guest) { double(\"guest\") }\n  let(:machine) { double(\"machine\", guest: guest, config: config) }\n  let(:networks){ [[:public_network, network_1], [:private_network, network_2]] }\n  let(:vm){ double(\"vm\", networks: networks) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".configure_networks\" do\n    let(:cap) { caps.get(:configure_networks) }\n\n    before do\n      allow(guest).to receive(:capability)\n        .with(:flavor)\n        .and_return(:alt)\n\n      allow(guest).to receive(:capability)\n        .with(:network_scripts_dir)\n        .and_return(\"/etc/net\")\n\n      allow(guest).to receive(:capability)\n        .with(:network_interfaces)\n        .and_return([\"eth1\", \"eth2\"])\n    end\n\n    let(:network_1) do\n      {\n        interface: 0,\n        type: \"dhcp\",\n      }\n    end\n\n    let(:network_2) do\n      {\n        interface: 1,\n        type: \"static\",\n        ip: \"33.33.33.10\",\n        netmask: \"255.255.0.0\",\n        gateway: \"33.33.0.1\",\n      }\n    end\n\n    context \"with NetworkManager installed\" do\n      let(:net1_nm_controlled) { true }\n      let(:net2_nm_controlled) { true }\n\n      let(:networks){ [\n        [:public_network, network_1.merge(nm_controlled: net1_nm_controlled)],\n        [:private_network, network_2.merge(nm_controlled: net2_nm_controlled)]\n      ] }\n\n      before do\n        allow(cap).to receive(:nmcli?).and_return true\n      end\n\n      context \"with devices managed by NetworkManager\" do\n        before do\n          allow(cap).to receive(:nm_controlled?).and_return true\n        end\n\n        context \"with nm_controlled option omitted\" do\n          let(:networks){ [\n            [:public_network, network_1],\n            [:private_network, network_2]\n          ] }\n\n          it \"downs networks via nmcli, creates ifaces and restart NetworksManager\" do\n            cap.configure_networks(machine, [network_1, network_2])\n            expect(comm.received_commands[0]).to match(/nmcli.*disconnect/)\n            expect(comm.received_commands[0]).to match(/mkdir.*\\/etc\\/net\\/ifaces/)\n            expect(comm.received_commands[0]).to match(/NetworkManager/)\n            expect(comm.received_commands[0]).to_not match(/ifdown|ifup/)\n          end\n        end\n\n        context \"with nm_controlled option set to true\" do\n          it \"downs networks via nmcli, creates ifaces and restart NetworksManager\" do\n            cap.configure_networks(machine, [network_1, network_2])\n            expect(comm.received_commands[0]).to match(/nmcli.*disconnect/)\n            expect(comm.received_commands[0]).to match(/mkdir.*\\/etc\\/net\\/ifaces/)\n            expect(comm.received_commands[0]).to match(/NetworkManager/)\n            expect(comm.received_commands[0]).to_not match(/(ifdown|ifup)/)\n          end\n        end\n\n        context \"with nm_controlled option set to false\" do\n          let(:net1_nm_controlled) { false }\n          let(:net2_nm_controlled) { false }\n\n          it \"downs networks manually, creates ifaces, starts networks manually and restart NetworksManager\" do\n            cap.configure_networks(machine, [network_1, network_2])\n            expect(comm.received_commands[0]).to match(/ifdown/)\n            expect(comm.received_commands[0]).to match(/mkdir.*\\/etc\\/net\\/ifaces/)\n            expect(comm.received_commands[0]).to match(/ifup/)\n            expect(comm.received_commands[0]).to match(/NetworkManager/)\n            expect(comm.received_commands[0]).to_not match(/nmcli/)\n          end\n        end\n\n        context \"with nm_controlled option set to false on first device\" do\n          let(:net1_nm_controlled) { false }\n          let(:net2_nm_controlled) { true }\n\n          it \"downs networks, creates ifaces and starts the networks with one managed manually and one NetworkManager controlled\" do\n            cap.configure_networks(machine, [network_1, network_2])\n            expect(comm.received_commands[0]).to match(/ifdown/)\n            expect(comm.received_commands[0]).to match(/nmcli.*disconnect/)\n            expect(comm.received_commands[0]).to match(/mkdir.*\\/etc\\/net\\/ifaces/)\n            expect(comm.received_commands[0]).to match(/ifup/)\n            expect(comm.received_commands[0]).to match(/NetworkManager/)\n          end\n        end\n      end\n\n      context \"with devices not managed by NetworkManager\" do\n        before do\n          allow(cap).to receive(:nm_controlled?).and_return false\n        end\n\n        context \"with nm_controlled option omitted\" do\n          let(:networks){ [\n            [:public_network, network_1],\n            [:private_network, network_2]\n          ] }\n\n          it \"creates and starts the networks manually\" do\n            cap.configure_networks(machine, [network_1, network_2])\n            expect(comm.received_commands[0]).to match(/ifdown/)\n            expect(comm.received_commands[0]).to match(/mkdir.*\\/etc\\/net\\/ifaces/)\n            expect(comm.received_commands[0]).to match(/ifup/)\n            expect(comm.received_commands[0]).to match(/NetworkManager/)\n            expect(comm.received_commands[0]).to_not match(/nmcli/)\n          end\n        end\n\n        context \"with nm_controlled option set to true\" do\n          let(:net1_nm_controlled) { true }\n          let(:net2_nm_controlled) { true }\n\n          it \"creates and starts the networks via nmcli\" do\n            cap.configure_networks(machine, [network_1, network_2])\n            expect(comm.received_commands[0]).to match(/ifdown/)\n            expect(comm.received_commands[0]).to match(/mkdir.*\\/etc\\/net\\/ifaces/)\n            expect(comm.received_commands[0]).to match(/NetworkManager/)\n            expect(comm.received_commands[0]).to_not match(/ifup/)\n          end\n        end\n\n        context \"with nm_controlled option set to false\" do\n          let(:net1_nm_controlled) { false }\n          let(:net2_nm_controlled) { false }\n\n          it \"creates and starts the networks via ifup \" do\n            cap.configure_networks(machine, [network_1, network_2])\n            expect(comm.received_commands[0]).to match(/ifdown/)\n            expect(comm.received_commands[0]).to match(/mkdir.*\\/etc\\/net\\/ifaces/)\n            expect(comm.received_commands[0]).to match(/ifup/)\n            expect(comm.received_commands[0]).to match(/NetworkManager/)\n            expect(comm.received_commands[0]).to_not match(/nmcli/)\n          end\n        end\n\n        context \"with nm_controlled option set to false on first device\" do\n          let(:net1_nm_controlled) { false }\n          let(:net2_nm_controlled) { true }\n\n          it \"creates and starts the networks with one managed manually and one NetworkManager controlled\" do\n            cap.configure_networks(machine, [network_1, network_2])\n            expect(comm.received_commands[0]).to match(/ifdown/)\n            expect(comm.received_commands[0]).to match(/mkdir.*\\/etc\\/net\\/ifaces/)\n            expect(comm.received_commands[0]).to match(/ifup/)\n            expect(comm.received_commands[0]).to match(/NetworkManager/)\n            expect(comm.received_commands[0]).to_not match(/nmcli.*disconnect/)\n          end\n        end\n      end\n    end\n\n    context \"without NetworkManager installed\" do\n      before do\n        allow(cap).to receive(:nmcli?).and_return false\n      end\n\n      context \"with nm_controlled option omitted\" do\n\n        it \"creates and starts the networks manually\" do\n          cap.configure_networks(machine, [network_1, network_2])\n          expect(comm.received_commands[0]).to match(/ifdown/)\n          expect(comm.received_commands[0]).to match(/mkdir.*\\/etc\\/net\\/ifaces/)\n          expect(comm.received_commands[0]).to match(/ifup/)\n          expect(comm.received_commands[0]).to_not match(/nmcli/)\n          expect(comm.received_commands[0]).to_not match(/NetworkManager/)\n        end\n      end\n\n      context \"with nm_controlled option omitted\" do\n        let(:networks){ [[{nm_controlled: false}], [{nm_controlled: false}]] }\n\n        it \"creates and starts the networks manually\" do\n          cap.configure_networks(machine, [network_1, network_2])\n          expect(comm.received_commands[0]).to match(/ifdown/)\n          expect(comm.received_commands[0]).to match(/mkdir.*\\/etc\\/net\\/ifaces/)\n          expect(comm.received_commands[0]).to match(/ifup/)\n          expect(comm.received_commands[0]).to_not match(/nmcli/)\n          expect(comm.received_commands[0]).to_not match(/NetworkManager/)\n        end\n      end\n\n      context \"with nm_controlled option set\" do\n        let(:networks){ [\n          [:public_network, network_1.merge(nm_controlled: true)],\n          [:private_network, network_2.merge(nm_controlled: true)]\n        ] }\n\n        it \"raises an error\" do\n          expect{ cap.configure_networks(machine, [network_1, network_2]) }.to raise_error(Vagrant::Errors::NetworkManagerNotInstalled)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/alt/cap/flavor_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestALT::Cap::Flavor\" do\n  let(:caps) do\n    VagrantPlugins::GuestALT::Plugin\n      .components\n      .guest_capabilities[:alt]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".flavor\" do\n    let(:cap) { caps.get(:flavor) }\n\n    context \"without /etc/os-release file\" do\n      {\n        \"ALT 8.1 Server\" => :alt_8,\n        \"ALT Education 8.1\" => :alt_8,\n        \"ALT Workstation 8.1\" => :alt_8,\n        \"ALT Workstation K 8.1  (Centaurea Ruthenica)\" => :alt_8,\n        \"ALT Linux p8 (Hypericum)\" => :alt_8,\n\n        \"ALT Sisyphus (unstable) (sisyphus)\" => :alt,\n        \"ALT Linux Sisyphus (unstable)\" => :alt,\n        \"ALT Linux 6.0.1 Spt  (separator)\" => :alt,\n        \"ALT Linux 7.0.5 School Master\" => :alt,\n        \"ALT starter kit (Hypericum)\" => :alt,\n\n        \"ALT\" => :alt,\n        \"Simply\" => :alt,\n      }.each do |str, expected|\n        it \"returns #{expected} for #{str} in /etc/altlinux-release\" do\n          comm.stub_command(\"test -f /etc/os-release\", exit_code: 1)\n          comm.stub_command(\"cat /etc/altlinux-release\", stdout: str)\n          expect(cap.flavor(machine)).to be(expected)\n        end\n      end\n    end\n\n    context \"with /etc/os-release file\" do\n      {\n        [ \"NAME=\\\"Sisyphus\\\"\", \"VERSION_ID=20161130\" ] => :alt,\n\n        [ \"NAME=\\\"ALT Education\\\"\", \"VERSION_ID=8.1\" ] => :alt_8,\n        [ \"NAME=\\\"ALT Server\\\"\", \"VERSION_ID=8.1\" ] => :alt_8,\n        [ \"NAME=\\\"ALT SPServer\\\"\", \"VERSION_ID=8.0\" ] => :alt_8,\n        [ \"NAME=\\\"starter kit\\\"\", \"VERSION_ID=p8\" ] => :alt_8,\n        [ \"NAME=\\\"ALT Linux\\\"\", \"VERSION_ID=8.0.0\" ] => :alt_8,\n        [ \"NAME=\\\"Simply Linux\\\"\", \"VERSION_ID=7.95.0\" ] => :alt_8,\n\n        [ \"NAME=\\\"ALT Linux\\\"\", \"VERSION_ID=7.0.5\" ] => :alt_7,\n        [ \"NAME=\\\"School Junior\\\"\", \"VERSION_ID=7.0.5\" ] => :alt_7,\n      }.each do |strs, expected|\n        it \"returns #{expected} for #{strs[0]} and #{strs[1]} in /etc/os-release\" do\n          comm.stub_command(\"test -f /etc/os-release\", exit_code: 0)\n          comm.stub_command(\"grep NAME /etc/os-release\", stdout: strs[0])\n          comm.stub_command(\"grep VERSION_ID /etc/os-release\", stdout: strs[1])\n          expect(cap.flavor(machine)).to be(expected)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/alt/cap/network_scripts_dir_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestALT::Cap::NetworkScriptsDir\" do\n  let(:caps) do\n    VagrantPlugins::GuestALT::Plugin\n      .components\n      .guest_capabilities[:alt]\n  end\n\n  let(:machine) { double(\"machine\") }\n\n  describe \".network_scripts_dir\" do\n    let(:cap) { caps.get(:network_scripts_dir) }\n\n    let(:name) { \"banana-rama.example.com\" }\n\n    it \"is /etc/net\" do\n      expect(cap.network_scripts_dir(machine)).to eq(\"/etc/net\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/alt/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestALT::Cap:RSync\" do\n  let(:caps) do\n    VagrantPlugins::GuestALT::Plugin\n      .components\n      .guest_capabilities[:alt]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".rsync_install\" do\n    let(:cap) { caps.get(:rsync_install) }\n\n    it \"installs rsync\" do\n      cap.rsync_install(machine)\n      expect(comm.received_commands[0]).to match(/install rsync/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/amazon/cap/configure_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestAmazon::Cap::ConfigureNetworks\" do\n  let(:caps) do\n    VagrantPlugins::GuestAmazon::Plugin\n      .components\n      .guest_capabilities[:amazon]\n  end\n\n  let(:machine) { double(\"machine\", communicate: communicator) }\n  let(:communicator) { double(\"communicator\") }\n  let(:networks) { double(\"networks\") }\n\n  describe \".configure_networks\" do\n    let(:cap) { caps.get(:configure_networks) }\n    before do\n      allow(cap).to receive(:systemd_networkd?).\n        with(communicator).and_return(is_networkd)\n    end\n\n    context \"when guest is using networkd\" do\n      let(:is_networkd) { true  }\n\n      it \"should call the debian capability\" do\n        expect(VagrantPlugins::GuestDebian::Cap::ConfigureNetworks).\n          to receive(:configure_networks).with(machine, networks)\n\n        cap.configure_networks(machine, networks)\n      end\n    end\n\n    context \"when guest is not using networkd\" do\n      let(:is_networkd) { false }\n\n      it \"should call the redhat capability\" do\n        expect(VagrantPlugins::GuestRedHat::Cap::ConfigureNetworks).\n          to receive(:configure_networks).with(machine, networks)\n\n        cap.configure_networks(machine, networks)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/amazon/cap/flavor_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestAmazon::Cap::Flavor\" do\n  let(:caps) do\n    VagrantPlugins::GuestAmazon::Plugin\n      .components\n      .guest_capabilities[:amazon]\n  end\n\n  let(:machine) { double(\"machine\") }\n\n  describe \".flavor\" do\n    let(:cap) { caps.get(:flavor) }\n\n    it \"returns rhel\" do\n      expect(cap.flavor(machine)).to be(:rhel)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/arch/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestArch::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestArch::Plugin\n      .components\n      .guest_capabilities[:arch]\n      .get(:change_host_name)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:name) { \"banana-rama.example.com\" }\n  let(:basename) { \"banana-rama\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    context \"minimal network config\" do \n      let(:networks) { [\n        [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n      ] }\n\n      before do \n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n      end\n\n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n\n        described_class.change_host_name(machine, name)\n        expect(comm.received_commands[2]).to match(/hostnamectl set-hostname '#{basename}'/)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n\n        described_class.change_host_name(machine, name)\n        expect(comm).to_not receive(:sudo).with(/hostnamectl set-hostname/)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/arch/cap/configure_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestArch::Cap::ConfigureNetworks\" do\n  let(:caps) do\n    VagrantPlugins::GuestArch::Plugin\n      .components\n      .guest_capabilities[:arch]\n  end\n\n  let(:guest) { double(\"guest\") }\n  let(:machine) { double(\"machine\", guest: guest) }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".configure_networks\" do\n    let(:cap) { caps.get(:configure_networks) }\n\n    before do\n      allow(guest).to receive(:capability).with(:network_interfaces)\n        .and_return([\"eth1\", \"eth2\"])\n      allow(cap).to receive(:systemd_networkd?).and_return(true)\n      allow(cap).to receive(:systemd_network_manager?).and_return(false)\n    end\n\n    let(:network_1) do\n      {\n        interface: 0,\n        type: \"dhcp\",\n      }\n    end\n\n    let(:network_2) do\n      {\n        interface: 1,\n        type: \"static\",\n        ip: \"33.33.33.10\",\n        netmask: \"255.255.0.0\",\n        gateway: \"33.33.0.1\",\n      }\n    end\n\n    it \"creates and starts the networks\" do\n      cap.configure_networks(machine, [network_1, network_2])\n      expect(comm.received_commands[0]).to match(/chmod 0644 '(.+)'/)\n      expect(comm.received_commands[0]).to match(/mv (.+) '\\/etc\\/systemd\\/network\\/eth1.network'/)\n      expect(comm.received_commands[0]).to match(/networkctl reload/)\n\n      expect(comm.received_commands[0]).to match(/chmod 0644 '(.+)'/)\n      expect(comm.received_commands[0]).to match(/mv (.+) '\\/etc\\/systemd\\/network\\/eth2.network'/)\n      expect(comm.received_commands[0]).to match(/networkctl reload/)\n    end\n\n    it \"should not extraneous && joiners\" do\n      cap.configure_networks(machine, [network_1, network_2])\n      expect(comm.received_commands[0]).not_to match(/^\\s*&&\\s*$/)\n    end\n\n    context \"network is not contolled by systemd\" do\n      before do\n        allow(cap).to receive(:systemd_networkd?).and_return(false)\n      end\n\n      it \"creates and starts the networks\" do\n        cap.configure_networks(machine, [network_1, network_2])\n        expect(comm.received_commands[0]).to match(/mv (.+) '\\/etc\\/netctl\\/eth1'/)\n        expect(comm.received_commands[0]).to match(/ip link set 'eth1' down/)\n        expect(comm.received_commands[0]).to match(/netctl restart 'eth1'/)\n        expect(comm.received_commands[0]).to match(/netctl enable 'eth1'/)\n        expect(comm.received_commands[0]).to match(/mv (.+) '\\/etc\\/netctl\\/eth2'/)\n        expect(comm.received_commands[0]).to match(/ip link set 'eth2' down/)\n        expect(comm.received_commands[0]).to match(/netctl restart 'eth2'/)\n        expect(comm.received_commands[0]).to match(/netctl enable 'eth2'/)\n      end\n    end\n\n    context \"network is controlled by NetworkManager via systemd\" do\n      before do\n        expect(cap).to receive(:systemd_network_manager?).and_return(true)\n      end\n\n      it \"should configure for network manager\" do\n        expect(cap).to receive(:configure_network_manager).with(machine, [network_1])\n\n        cap.configure_networks(machine, [network_1])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/arch/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestArch::Cap:RSync\" do\n  let(:described_class) do\n    VagrantPlugins::GuestArch::Plugin\n      .components\n      .guest_capabilities[:arch]\n      .get(:rsync_install)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".rsync_install\" do\n    it \"installs rsync=\" do\n      described_class.rsync_install(machine)\n\n      expect(comm.received_commands[0]).to match(/pacman -Sy --noconfirm/)\n      expect(comm.received_commands[0]).to match(/pacman -S --noconfirm rsync/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/arch/cap/smb_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestArch::Cap::SMB\" do\n  let(:described_class) do\n    VagrantPlugins::GuestArch::Plugin\n      .components\n      .guest_capabilities[:arch]\n      .get(:smb_install)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".smb_install\" do\n    it \"installs smb when /usr/bin/mount.cifs does not exist\" do\n      comm.stub_command(\"test -f /usr/bin/mount.cifs\", exit_code: 1)\n      described_class.smb_install(machine)\n\n      expect(comm.received_commands[1]).to match(/pacman -Sy --noconfirm/)\n      expect(comm.received_commands[1]).to match(/pacman -S --noconfirm smbclient cifs-utils/)\n    end\n\n    it \"does not install smb when /usr/bin/mount.cifs exists\" do\n      comm.stub_command(\"test -f /usr/bin/mount.cifs\", exit_code: 0)\n      described_class.smb_install(machine)\n\n      expect(comm.received_commands.join(\"\")).to_not match(/-S/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/atomic/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestAtomic::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestAtomic::Plugin\n      .components\n      .guest_capabilities[:atomic]\n      .get(:change_host_name)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:name) { \"banana-rama.example.com\" }\n  let(:basename) { \"banana-rama\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    context \"minimal network config\" do \n      let(:networks) { [\n        [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n      ] }\n\n      before do \n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n      end\n\n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n\n        described_class.change_host_name(machine, name)\n        expect(comm.received_commands[2]).to match(/hostnamectl set-hostname '#{basename}'/)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n\n        described_class.change_host_name(machine, name)\n        expect(comm).to_not receive(:sudo).with(/hostnamectl set-hostname/)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/atomic/cap/docker_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestAtomic::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestAtomic::Plugin\n      .components\n      .guest_capabilities[:atomic]\n      .get(:docker_daemon_running)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".docker_daemon_running\" do\n    it \"checks /run/docker/sock\" do\n      described_class.docker_daemon_running(machine)\n      expect(comm.received_commands[0]).to eq(\"test -S /run/docker.sock\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/bsd/cap/file_system_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestBSD::Cap::FileSystem\" do\n  let(:caps) do\n    VagrantPlugins::GuestBSD::Plugin\n      .components\n      .guest_capabilities[:bsd]\n  end\n\n  let(:machine) { double(\"machine\", communicate: comm) }\n  let(:comm) { double(\"comm\") }\n\n  before { allow(comm).to receive(:execute) }\n\n  describe \".create_tmp_path\" do\n    let(:cap) { caps.get(:create_tmp_path) }\n    let(:opts) { {} }\n\n    it \"should generate path on guest\" do\n      expect(comm).to receive(:execute).with(/mktemp/)\n      cap.create_tmp_path(machine, opts)\n    end\n\n    it \"should capture path generated on guest\" do\n      expect(comm).to receive(:execute).with(/mktemp/).and_yield(:stdout, \"TMP_PATH\")\n      expect(cap.create_tmp_path(machine, opts)).to eq(\"TMP_PATH\")\n    end\n\n    it \"should strip newlines on path\" do\n      expect(comm).to receive(:execute).with(/mktemp/).and_yield(:stdout, \"TMP_PATH\\n\")\n      expect(cap.create_tmp_path(machine, opts)).to eq(\"TMP_PATH\")\n    end\n\n    context \"when type is a directory\" do\n      before { opts[:type] = :directory }\n\n      it \"should create guest path as a directory\" do\n        expect(comm).to receive(:execute).with(/-d/)\n        cap.create_tmp_path(machine, opts)\n      end\n    end\n  end\n\n  describe \".decompress_tgz\" do\n    let(:cap) { caps.get(:decompress_tgz) }\n    let(:comp) { \"compressed_file\" }\n    let(:dest) { \"path/to/destination\" }\n    let(:opts) { {} }\n\n    before { allow(cap).to receive(:create_tmp_path).and_return(\"TMP_DIR\") }\n    after{ cap.decompress_tgz(machine, comp, dest, opts) }\n\n    it \"should create temporary directory for extraction\" do\n      expect(cap).to receive(:create_tmp_path)\n    end\n\n    it \"should extract file with tar\" do\n      expect(comm).to receive(:execute).with(/tar/)\n    end\n\n    it \"should extract file to temporary directory\" do\n      expect(comm).to receive(:execute).with(/TMP_DIR/)\n    end\n\n    it \"should remove compressed file from guest\" do\n      expect(comm).to receive(:execute).with(/rm .*#{comp}/)\n    end\n\n    it \"should remove extraction directory from guest\" do\n      expect(comm).to receive(:execute).with(/rm .*TMP_DIR/)\n    end\n\n    it \"should create parent directories for destination\" do\n      expect(comm).to receive(:execute).with(/mkdir -p .*to'/)\n    end\n\n    context \"when type is directory\" do\n      before { opts[:type] = :directory }\n\n      it \"should create destination directory\" do\n        expect(comm).to receive(:execute).with(/mkdir -p .*destination'/)\n      end\n    end\n  end\n\n  describe \".decompress_zip\" do\n    let(:cap) { caps.get(:decompress_zip) }\n    let(:comp) { \"compressed_file\" }\n    let(:dest) { \"path/to/destination\" }\n    let(:opts) { {} }\n\n    before { allow(cap).to receive(:create_tmp_path).and_return(\"TMP_DIR\") }\n    after{ cap.decompress_zip(machine, comp, dest, opts) }\n\n    it \"should create temporary directory for extraction\" do\n      expect(cap).to receive(:create_tmp_path)\n    end\n\n    it \"should extract file with zip\" do\n      expect(comm).to receive(:execute).with(/zip/)\n    end\n\n    it \"should extract file to temporary directory\" do\n      expect(comm).to receive(:execute).with(/TMP_DIR/)\n    end\n\n    it \"should remove compressed file from guest\" do\n      expect(comm).to receive(:execute).with(/rm .*#{comp}/)\n    end\n\n    it \"should remove extraction directory from guest\" do\n      expect(comm).to receive(:execute).with(/rm .*TMP_DIR/)\n    end\n\n    it \"should create parent directories for destination\" do\n      expect(comm).to receive(:execute).with(/mkdir -p .*to'/)\n    end\n\n    context \"when type is directory\" do\n      before { opts[:type] = :directory }\n\n      it \"should create destination directory\" do\n        expect(comm).to receive(:execute).with(/mkdir -p .*destination'/)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/bsd/cap/halt_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestBSD::Cap::Halt\" do\n  let(:caps) do\n    VagrantPlugins::GuestBSD::Plugin\n      .components\n      .guest_capabilities[:bsd]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:shutdown_command) { \"/sbin/shutdown -p now\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".halt\" do\n    let(:cap) { caps.get(:halt) }\n\n    it \"runs the shutdown command\" do\n      comm.expect_command(shutdown_command)\n      cap.halt(machine)\n    end\n\n    it \"ignores an IOError\" do\n      comm.stub_command(shutdown_command, raise: IOError)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n\n    it \"ignores a Vagrant::Errors::SSHDisconnected\" do\n      comm.stub_command(shutdown_command, raise: Vagrant::Errors::SSHDisconnected)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/bsd/cap/insert_public_key_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestBSD::Cap::InsertPublicKey\" do\n  let(:caps) do\n    VagrantPlugins::GuestBSD::Plugin\n      .components\n      .guest_capabilities[:bsd]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".insert_public_key\" do\n    let(:cap) { caps.get(:insert_public_key) }\n\n    it \"inserts the public key\" do\n      cap.insert_public_key(machine, \"ssh-rsa ...\")\n      expect(comm.received_commands[0]).to match(/mkdir -p ~\\/.ssh/)\n      expect(comm.received_commands[0]).to match(/chmod 0700 ~\\/.ssh/)\n      expect(comm.received_commands[0]).to match(/cat '\\/tmp\\/vagrant-(.+)' >> ~\\/.ssh\\/authorized_keys/)\n      expect(comm.received_commands[0]).to match(/chmod 0600 ~\\/.ssh\\/authorized_keys/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/bsd/cap/mount_virtual_box_shared_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestBSD::Cap::MountVirtualBoxSharedFolder\" do\n  let(:caps) do\n    VagrantPlugins::GuestBSD::Plugin\n      .components\n      .guest_capabilities[:bsd]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:mount_owner){ \"vagrant\" }\n  let(:mount_group){ \"vagrant\" }\n  let(:mount_uid){ \"1000\" }\n  let(:mount_gid){ \"1000\" }\n  let(:mount_name){ \"vagrant\" }\n  let(:mount_guest_path){ \"/vagrant\" }\n  let(:folder_options) do\n    {\n      owner: mount_owner,\n      group: mount_group,\n      hostpath: \"/host/directory/path\"\n    }\n  end\n  let(:cap){ caps.get(:mount_virtualbox_shared_folder) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".mount_virtualbox_shared_folder\" do\n    it \"raises an error as unsupported\" do\n      expect {cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options) }.\n        to raise_error(Vagrant::Errors::VirtualBoxMountNotSupportedBSD)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/bsd/cap/nfs_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestBSD::Cap::NFS\" do\n  let(:caps) do\n    VagrantPlugins::GuestBSD::Plugin\n      .components\n      .guest_capabilities[:bsd]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".mount_nfs_folder\" do\n    let(:cap) { caps.get(:mount_nfs_folder) }\n    let(:ip) { \"1.2.3.4\" }\n\n    it \"mounts the folder\" do\n      folders = {\n        \"/vagrant-nfs\" => {\n          guestpath: \"/guest\",\n          hostpath: \"/host\",\n        }\n      }\n      cap.mount_nfs_folder(machine, ip, folders)\n\n      expect(comm.received_commands[0]).to match(/mkdir -p \\/guest/)\n      expect(comm.received_commands[1]).to match(/mount -t nfs/)\n      expect(comm.received_commands[1]).to match(/1.2.3.4:\\/host \\/guest/)\n    end\n\n    it \"mounts with options\" do\n      folders = {\n        \"/vagrant-nfs\" => {\n          guestpath: \"/guest\",\n          hostpath: \"/host\",\n          nfs_version: 2,\n          nfs_udp: true,\n          mount_options: [\"banana\"]\n        }\n      }\n      cap.mount_nfs_folder(machine, ip, folders)\n\n      expect(comm.received_commands[1]).to match(/mount -t nfs -o 'nfsv2,mntudp,banana'/)\n    end\n\n    it \"escapes host and guest paths\" do\n      folders = {\n        \"/vagrant-nfs\" => {\n          guestpath: \"/guest with spaces\",\n          hostpath: \"/host's\",\n        }\n      }\n      cap.mount_nfs_folder(machine, ip, folders)\n\n      expect(comm.received_commands[1]).to match(/host\\\\\\'s/)\n      expect(comm.received_commands[1]).to match(/guest\\\\\\ with\\\\\\ spaces/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/bsd/cap/remove_public_key_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestBSD::Cap::RemovePublicKey\" do\n  let(:caps) do\n    VagrantPlugins::GuestBSD::Plugin\n      .components\n      .guest_capabilities[:bsd]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".remove_public_key\" do\n    let(:cap) { caps.get(:remove_public_key) }\n\n    it \"removes the public key\" do\n      cap.remove_public_key(machine, \"ssh-rsa ...\")\n      expect(comm.received_commands[0]).to match(/grep -v -x -f '\\/tmp\\/vagrant-(.+)' ~\\/\\.ssh\\/authorized_keys > ~\\/.ssh\\/authorized_keys\\.tmp/)\n      expect(comm.received_commands[0]).to match(/mv ~\\/.ssh\\/authorized_keys\\.tmp ~\\/.ssh\\/authorized_keys/)\n      expect(comm.received_commands[0]).to match(/chmod 0600 ~\\/.ssh\\/authorized_keys/)\n      expect(comm.received_commands[0]).to match(/rm -f '\\/tmp\\/vagrant-(.+)'/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/centos/cap/flavor_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestCentos::Cap::Flavor\" do\n  let(:caps) do\n    VagrantPlugins::GuestCentos::Plugin\n      .components\n      .guest_capabilities[:centos]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".flavor\" do\n    let(:cap) { caps.get(:flavor) }\n\n    # /etc/os-release was added in EL7+\n    context \"without /etc/os-release file\" do\n      {\n        \"\" => :centos\n      }.each do |str, expected|\n        it \"returns #{expected} for #{str}\" do\n          comm.stub_command(\"test -f /etc/os-release\", exit_code: 1)\n          expect(cap.flavor(machine)).to be(expected)\n        end\n      end\n    end\n    context \"with /etc/os-release file\" do\n      {\n        \"7\" => :centos_7,\n        \"8\" => :centos_8,\n        \"9.0\" => :centos_9,\n        \"9.1\" => :centos_9,\n        \"\" => :centos,\n        \"banana\" => :centos,\n      }.each do |str, expected|\n        it \"returns #{expected} for #{str}\" do\n          comm.stub_command(\"test -f /etc/os-release\", exit_code: 0)\n          comm.stub_command(\"source /etc/os-release && printf $VERSION_ID\", stdout: str)\n          expect(cap.flavor(machine)).to be(expected)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/coreos/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestCoreOS::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestCoreOS::Plugin\n      .components\n      .guest_capabilities[:coreos]\n      .get(:change_host_name)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    let(:name) { \"banana-rama.example.com\" }\n    let(:has_cloudinit) { false }\n\n    before do\n      allow(described_class).to receive(:systemd_unit_file?).\n        with(anything, /cloudinit/).and_return(has_cloudinit)\n    end\n\n    context \"with systemd cloud-init\" do\n      let(:has_cloudinit) { true }\n\n      it \"should upload cloudinit configuration file\" do\n        expect(comm).to receive(:upload)\n        described_class.change_host_name(machine, name)\n      end\n\n      it \"should set hostname in configuration file\" do\n        expect(comm).to receive(:upload) do |src, dst|\n          contents = File.read(src)\n          expect(contents).to include(name)\n        end\n        described_class.change_host_name(machine, name)\n      end\n\n      it \"should start the cloudinit service\" do\n        expect(comm).to receive(:sudo).with(/systemctl start system-cloudinit/)\n        described_class.change_host_name(machine, name)\n      end\n    end\n\n    context \"without systemd cloud-init\" do\n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n        comm.expect_command(\"hostname 'banana-rama'\")\n        described_class.change_host_name(machine, name)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n        described_class.change_host_name(machine, name)\n        expect(comm.received_commands.size).to eq(1)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/coreos/cap/configure_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestCoreOS::Cap::ConfigureNetworks\" do\n  let(:described_class) do\n    VagrantPlugins::GuestCoreOS::Plugin\n      .components\n      .guest_capabilities[:coreos]\n      .get(:configure_networks)\n  end\n\n  let(:machine) { double(\"machine\", config: config, guest: guest) }\n  let(:guest) { double(\"guest\") }\n  let(:config) { double(\"config\", vm: vm) }\n  let(:vm) { double(\"vm\") }\n  let(:comm) { double(\"comm\") }\n  let(:env) do\n    double(\"env\", machine: machine, active_machines: [machine])\n  end\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n    allow(machine).to receive(:env).and_return(env)\n  end\n\n  describe \".configure_networks\" do\n    context \"with network manager\" do\n      let(:network_1) do\n        {\n          interface: 1,\n          type: \"static\",\n          ip: \"10.0.0.3\",\n          netmask: \"255.255.255.0\",\n          mac_address: \"00:00:00:00:00:00\",\n          gateway: \"10.0.0.2\",\n        }\n      end\n      let(:network_2) do\n        {\n          interface: 2,\n          type: \"static\",\n          ip: \"192.168.3.3\",\n          netmask: \"255.255.0.0\",\n        }\n      end\n      let(:nm_list) do\n        [\n          \"Wired connection 1:UUID_for_eth1:ethernet:eth1\\n\",\n          \"Wired connection 2:UUID_for_eth2:ethernet:eth2\\n\"\n        ]\n      end\n      let(:interfaces) { [\"eth0\", \"eth1\", \"eth2\"] }\n      let(:networks) do\n        [\n          network_1,\n          network_2,\n        ]\n      end\n      let(:tempfile) do\n        double(\"tempfile\",\n          close: nil,\n          delete: nil,\n          path: temp_path,\n        ).tap do |f|\n          allow(f).to receive(:puts)\n        end\n      end\n      let(:temp_path) { \"/dev/null\" }\n\n      before do\n        allow(guest).to receive(:capability).\n          with(:network_interfaces).\n          and_return(interfaces)\n        allow(comm).to receive(:upload)\n        allow(comm).to receive(:sudo)\n        allow(comm).to receive(:execute)\n        allow(Tempfile).to receive(:new).and_return(tempfile)\n\n        expect(comm).to receive(:execute).\n          with(\"nmcli -t c show\") { |&block|\n            nm_list.each { |line|\n              block.call(:stdout, line)\n            }\n          }\n\n        allow(comm).to receive(:test).\n          with(\"command -v cloud-init\").\n          and_return(false)\n      end\n\n      it \"should test for cloud-init\" do\n        expect(comm).to receive(:test).\n          with(\"command -v cloud-init\").\n          and_return(false)\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should remove any previous vagrant configuration\" do\n        expect(comm).to receive(:sudo).\n          with(/rm .*vagrant-.*conf/, error_check: false)\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should get MAC address from guest if not provided\" do\n        expect(comm).to receive(:execute).\n          with(/cat .*eth2\\/address/)\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should not get MAC address from guest when provided\" do\n        expect(comm).not_to receive(:execute).\n          with(/cat .*eth1\\/address/)\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should provide a default gateway when one is not provided\" do\n        expect(tempfile).to receive(:puts).\n          with(\"gateway=192.168.0.1\")\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should use gateway value when provided\" do\n        expect(tempfile).to receive(:puts).\n          with(\"gateway=10.0.0.2\")\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should disconnect device in network manager\" do\n        expect(comm).to receive(:sudo).\n          with(\"nmcli d disconnect 'eth1'\", error_check: false)\n        expect(comm).to receive(:sudo).\n          with(\"nmcli d disconnect 'eth2'\", error_check: false)\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should delete connection from network manager\" do\n        expect(comm).to receive(:sudo).\n          with(\"nmcli c delete 'UUID_for_eth1'\", error_check: false)\n        expect(comm).to receive(:sudo).\n          with(\"nmcli c delete 'UUID_for_eth2'\", error_check: false)\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should upload configuration files\" do\n        expect(comm).to receive(:upload).twice\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should change file ownership to root\" do\n        expect(comm).to receive(:sudo).\n          with(/chown root:root .*/)\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should modify file permissions to remove read access\" do\n        expect(comm).to receive(:sudo).\n          with(/chmod 0600 .*/)\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should delete local temporary files\" do\n        expect(tempfile).to receive(:delete)\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should load the configuration files into network manager\" do\n        expect(comm).to receive(:sudo).\n          with(/nmcli c load .*conf/).twice\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should connect the devices in network manager\" do\n        expect(comm).to receive(:sudo).\n          with(\"nmcli d connect 'eth1'\")\n        expect(comm).to receive(:sudo).\n          with(\"nmcli d connect 'eth2'\")\n        described_class.configure_networks(machine, networks)\n      end\n    end\n\n    context \"with cloud-init\" do\n      let(:interfaces) { [\"eth0\", \"eth1\", \"lo\"] }\n\n      let(:network_1) do\n        {\n          interface: 0,\n          type: \"dhcp\",\n        }\n      end\n      let(:netconfig_1) do\n        [:public_interface, {}]\n      end\n      let(:network_2) do\n        {\n          interface: 1,\n          type: \"static\",\n          ip: \"33.33.33.10\",\n          netmask: \"255.255.0.0\",\n          gateway: \"33.33.0.1\",\n        }\n      end\n      let(:netconfig_2) do\n        [:public_network, {ip: \"33.33.33.10\", netmask: 16}]\n      end\n      let(:network_3) do\n        {\n          interface: 2,\n          type: \"static\",\n          ip: \"192.168.120.22\",\n          netmask: \"255.255.255.0\",\n          gateway: \"192.168.120.1\"\n        }\n      end\n      let(:netconfig_3) do\n        [:private_network, {ip: \"192.168.120.22\", netmask: 24}]\n      end\n      let(:networks) { [network_1, network_2, network_3] }\n      let(:network_configs) { [netconfig_1, netconfig_2, netconfig_3] }\n      let(:vm) { double(\"vm\") }\n      let(:default_env_ip) { described_class.const_get(:DEFAULT_ENVIRONMENT_IP) }\n\n      before do\n        allow(guest).to receive(:capability).with(:network_interfaces).\n          and_return(interfaces)\n        allow(vm).to receive(:networks).and_return(network_configs)\n        allow(comm).to receive(:upload)\n        allow(comm).to receive(:sudo)\n\n        allow(comm).to receive(:test).\n          with(\"command -v cloud-init\").\n          and_return(true)\n      end\n\n      it \"should upload network configuration file\" do\n        expect(comm).to receive(:upload)\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should configure public ipv4 address\" do\n        expect(comm).to receive(:upload) do |src, dst|\n          content = File.read(src)\n          expect(content).to include(\"COREOS_PUBLIC_IPV4=#{netconfig_2.last[:ip]}\")\n        end\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should configure the private ipv4 address\" do\n        expect(comm).to receive(:upload) do |src, dst|\n          content = File.read(src)\n          expect(content).to include(\"COREOS_PRIVATE_IPV4=#{netconfig_3.last[:ip]}\")\n        end\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should configure network interfaces\" do\n        expect(comm).to receive(:upload) do |src, dst|\n          content = File.read(src)\n          interfaces.each { |i| expect(content).to include(\"Name=#{i}\") }\n        end\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should configure DHCP interface\" do\n        expect(comm).to receive(:upload) do |src, dst|\n          content = File.read(src)\n          expect(content).to include(\"DHCP=yes\")\n        end\n        described_class.configure_networks(machine, networks)\n      end\n\n      it \"should configure static IP addresses\" do\n        expect(comm).to receive(:upload) do |src, dst|\n          content = File.read(src)\n          network_configs.map(&:last).find_all { |c| c[:ip] }.each { |c|\n            expect(content).to include(\"Address=#{c[:ip]}\")\n          }\n        end\n        described_class.configure_networks(machine, networks)\n      end\n\n      context \"when no public network is defined\" do\n        let(:networks) { [network_1, network_3] }\n        let(:network_configs) { [netconfig_1, netconfig_3] }\n\n\n        it \"should set public IP to the default environment IP\" do\n          expect(comm).to receive(:upload) do |src, dst|\n            content = File.read(src)\n            expect(content).to include(\"COREOS_PUBLIC_IPV4=#{default_env_ip}\")\n          end\n          described_class.configure_networks(machine, networks)\n        end\n\n        it \"should set the private IP to the private network\" do\n          expect(comm).to receive(:upload) do |src, dst|\n            content = File.read(src)\n            expect(content).to include(\"COREOS_PRIVATE_IPV4=#{netconfig_3.last[:ip]}\")\n          end\n          described_class.configure_networks(machine, networks)\n        end\n      end\n\n      context \"when no private network is defined\" do\n        let(:networks) { [network_1, network_2] }\n        let(:network_configs) { [netconfig_1, netconfig_2] }\n\n\n        it \"should set public IP to the public network\" do\n          expect(comm).to receive(:upload) do |src, dst|\n            content = File.read(src)\n            expect(content).to include(\"COREOS_PUBLIC_IPV4=#{netconfig_2.last[:ip]}\")\n          end\n          described_class.configure_networks(machine, networks)\n        end\n\n        it \"should set the private IP to the public IP\" do\n          expect(comm).to receive(:upload) do |src, dst|\n            content = File.read(src)\n            expect(content).to include(\"COREOS_PRIVATE_IPV4=#{netconfig_2.last[:ip]}\")\n          end\n          described_class.configure_networks(machine, networks)\n        end\n      end\n\n      context \"when no public or private network is defined\" do\n        let(:networks) { [network_1] }\n        let(:network_configs) { [netconfig_1] }\n\n\n        it \"should set public IP to the default environment IP\" do\n          expect(comm).to receive(:upload) do |src, dst|\n            content = File.read(src)\n            expect(content).to include(\"COREOS_PUBLIC_IPV4=#{default_env_ip}\")\n          end\n          described_class.configure_networks(machine, networks)\n        end\n\n        it \"should set the private IP to the default environment IP\" do\n          expect(comm).to receive(:upload) do |src, dst|\n            content = File.read(src)\n            expect(content).to include(\"COREOS_PRIVATE_IPV4=#{default_env_ip}\")\n          end\n          described_class.configure_networks(machine, networks)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/coreos/cap/docker_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestCoreOS::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestCoreOS::Plugin\n      .components\n      .guest_capabilities[:coreos]\n      .get(:docker_daemon_running)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".docker_daemon_running\" do\n    it \"checks /run/docker/sock\" do\n      described_class.docker_daemon_running(machine)\n      expect(comm.received_commands[0]).to eq(\"test -S /run/docker.sock\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/darwin/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestDarwin::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestDarwin::Plugin\n      .components\n      .guest_capabilities[:darwin]\n      .get(:change_host_name)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:name) { \"banana-rama.example.com\" }\n  let(:basename) { \"banana-rama\" }\n  let(:networks) {}\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n    allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    context \"minimal network config\" do\n      let(:networks) { [\n        [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n      ] }\n\n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n        described_class.change_host_name(machine, name)\n        expect(comm.received_commands[1]).to match(/scutil --set ComputerName '#{name}'/)\n        expect(comm.received_commands[1]).to match(/scutil --set HostName '#{name}'/)\n        expect(comm.received_commands[1]).to match(/scutil --set LocalHostName '#{basename}'/)\n        expect(comm.received_commands[1]).to match(/hostname 'banana-rama.example.com'/)\n        expect(described_class).to_not receive(:add_hostname_to_loopback_interface)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n        described_class.change_host_name(machine, name)\n        expect(comm).to_not receive(:sudo).with(/scutil --set ComputerName '#{name}/)\n        expect(described_class).to_not receive(:add_hostname_to_loopback_interface)\n      end\n    end\n\n    context \"multiple networks configured with hostname\" do \n      it \"adds a new entry only for the hostname\" do \n        networks = [\n          [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}],\n          [:public_network, {:ip=>\"192.168.0.1\", :hostname=>true, :protocol=>\"tcp\", :id=>\"93a4ad88-0774-4127-a161-ceb715ff372f\"}],\n          [:public_network, {:ip=>\"192.168.0.2\", :protocol=>\"tcp\", :id=>\"5aebe848-7d85-4425-8911-c2003d924120\"}]\n        ]\n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n        expect(described_class).to receive(:replace_host)\n        expect(described_class).to_not receive(:add_hostname_to_loopback_interface)\n        described_class.change_host_name(machine, name)\n      end\n\n      it \"appends an entry to the loopback interface\" do \n        networks = [\n          [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}],\n          [:public_network, {:ip=>\"192.168.0.1\", :protocol=>\"tcp\", :id=>\"93a4ad88-0774-4127-a161-ceb715ff372f\"}],\n          [:public_network, {:ip=>\"192.168.0.2\", :protocol=>\"tcp\", :id=>\"5aebe848-7d85-4425-8911-c2003d924120\"}]\n        ]\n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n        expect(described_class).to_not receive(:replace_host)\n        expect(described_class).to receive(:add_hostname_to_loopback_interface).once\n        described_class.change_host_name(machine, name)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/darwin/cap/choose_addressable_ip_addr_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestDarwin::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestDarwin::Plugin\n      .components\n      .guest_capabilities[:darwin]\n      .get(:choose_addressable_ip_addr)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".choose_addressable_ip_addr\" do\n    let(:possible) { [\"1.2.3.4\", \"5.6.7.8\"] }\n\n    it \"retrieves the value\" do\n      comm.stub_command(\"ping -c1 -t1 5.6.7.8\", exit_code: 0)\n      result = described_class.choose_addressable_ip_addr(machine, possible)\n      expect(result).to eq(\"5.6.7.8\")\n    end\n\n    it \"returns nil if no ips are found\" do\n      result = described_class.choose_addressable_ip_addr(machine, [])\n      expect(result).to be(nil)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/darwin/cap/darwin_version_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestDarwin::Cap::DarwinVersion\" do\n  let(:caps) do\n    VagrantPlugins::GuestDarwin::Plugin\n      .components\n      .guest_capabilities[:darwin]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".darwin_version\" do\n    let(:cap) { caps.get(:darwin_version) }\n\n    {\n      \"kern.osrelease: 19.6.0\" => \"19.6.0\",\n      \"kern.osrelease: 20.1.10\" => \"20.1.10\",\n    }.each do |str, expected|\n      it \"returns #{expected} for #{str}\" do\n        comm.stub_command(\"sysctl kern.osrelease\", stdout: str)\n        expect(cap.darwin_version(machine)).to eq(expected)\n      end\n    end\n  end\n\n  describe \".darwin_major_version\" do\n    let(:cap) { caps.get(:darwin_major_version) }\n\n    {\n      \"kern.osrelease: 19.6.0\" => 19,\n      \"kern.osrelease: 20.1.10\" => 20,\n      \"\" => nil\n    }.each do |str, expected|\n      it \"returns #{expected} for #{str}\" do\n        comm.stub_command(\"sysctl kern.osrelease\", stdout: str)\n        expect(cap.darwin_major_version(machine)).to eq(expected)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/darwin/cap/halt_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestDarwin::Cap::Halt\" do\n  let(:caps) do\n    VagrantPlugins::GuestDarwin::Plugin\n      .components\n      .guest_capabilities[:darwin]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".halt\" do\n    let(:cap) { caps.get(:halt) }\n\n    it \"runs the shutdown command\" do\n      comm.expect_command(\"/sbin/shutdown -h now\")\n      cap.halt(machine)\n    end\n\n    it \"ignores an IOError\" do\n      comm.stub_command(\"/sbin/shutdown -h now\", raise: IOError)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n\n    it \"ignores a Vagrant::Errors::SSHDisconnected\" do\n      comm.stub_command(\"/sbin/shutdown -h now\", raise: Vagrant::Errors::SSHDisconnected)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/darwin/cap/mount_vmware_shared_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestDarwin::Cap::MountVmwareSharedFolder\" do\n  let(:described_class) do\n    VagrantPlugins::GuestDarwin::Plugin\n      .components\n      .guest_capabilities[:darwin]\n      .get(:mount_vmware_shared_folder)\n  end\n\n  let(:machine) { double(\"machine\", communicate: communicator, id: \"MACHINE_ID\", guest: guest) }\n  let(:guest) {double(\"guest\")}\n  let(:communicator) { double(\"communicator\") }\n\n  before do\n    allow(communicator).to receive(:test)\n    allow(communicator).to receive(:sudo)\n    allow(VagrantPlugins::GuestDarwin::Plugin).to receive(:action_hook)\n  end\n\n  describe \".mount_vmware_shared_folder\" do\n    let(:name) { \"-vagrant\" }\n    let(:guestpath) { \"/vagrant\" }\n    let(:options) { {} }\n\n    before do\n      allow(described_class).to receive(:system_firmlink?)\n      described_class.reset!\n    end\n\n    after { \n      described_class.mount_vmware_shared_folder(machine, name, guestpath, options) \n    }\n\n    context \"with APFS root container\" do\n      before do\n        expect(communicator).to receive(:test).with(\"test -d /System/Volumes/Data\").and_return(true)\n      end\n\n      it \"should check for existing entry\" do\n        expect(communicator).to receive(:test).with(/synthetic\\.conf/)\n      end\n\n      context \"with guest path within existing directory\" do\n        let(:guestpath) { \"/Users/vagrant/workspace\" }\n\n        it \"should test if guest path is a symlink\" do\n          expect(communicator).to receive(:test).with(/test -L/)\n        end\n\n        it \"should remove guest path if it is a symlink\" do\n          expect(communicator).to receive(:test).with(/test -L/).and_return(true)\n          expect(communicator).to receive(:sudo).with(/rm -f/)\n        end\n\n        it \"should not test if guest path is a directory if guest path is symlink\" do\n          expect(communicator).to receive(:test).with(/test -L/).and_return(true)\n          expect(communicator).not_to receive(:test).with(/test -d/)\n        end\n\n        it \"should test if guest path is directory if not a symlink\" do\n          expect(communicator).to receive(:test).with(/test -d/)\n        end\n\n        it \"should remove guest path if it is a directory\" do\n          expect(communicator).to receive(:test).with(/test -d/).and_return(true)\n          expect(communicator).to receive(:sudo).with(/rm -Rf/)\n        end\n\n        it \"should create the symlink to the vmware folder\" do\n          expect(communicator).to receive(:sudo).with(/ln -s/)\n        end\n\n        it \"should create the symlink within the writable APFS container\" do\n          expect(communicator).to receive(:sudo).with(%r{ln -s .+/System/Volumes/Data.+})\n        end\n\n        {\n          19 => \"-B\",\n          20 => \"-t\",\n          21 => \"-t\",\n          nil => \"-B\"\n        }.each do |version, expected_flag|\n          it \"should re-bootstrap root dir for darwin version #{version}\" do\n            expect(communicator).to receive(:sudo).with(/apfs.util #{expected_flag}/, any_args)\n            expect(guest).to receive(:capability).with(\"darwin_major_version\").and_return(version)\n\n            described_class.mount_vmware_shared_folder(machine, name, guestpath, options) \n            described_class.apfs_firmlinks_delayed[machine.id].call\n          end\n        end\n\n        context \"when firmlink is provided by the system\" do\n          before { expect(described_class).to receive(:system_firmlink?).and_return(true) }\n\n          it \"should not register an action hook\" do\n            expect(VagrantPlugins::GuestDarwin::Plugin).not_to receive(:action_hook).with(:apfs_firmlinks, :after_synced_folders)\n          end\n        end\n      end\n    end\n\n    context \"with non-APFS root container\" do\n      before do\n        expect(communicator).to receive(:test).with(\"test -d /System/Volumes/Data\").and_return(false)\n      end\n\n      it \"should test if guest path is a symlink\" do\n        expect(communicator).to receive(:test).with(/test -L/)\n      end\n\n      it \"should remove guest path if it is a symlink\" do\n        expect(communicator).to receive(:test).with(/test -L/).and_return(true)\n        expect(communicator).to receive(:sudo).with(/rm -f/)\n      end\n\n      it \"should not test if guest path is a directory if guest path is symlink\" do\n        expect(communicator).to receive(:test).with(/test -L/).and_return(true)\n        expect(communicator).not_to receive(:test).with(/test -d/)\n      end\n\n      it \"should test if guest path is directory if not a symlink\" do\n        expect(communicator).to receive(:test).with(/test -d/)\n      end\n\n      it \"should remove guest path if it is a directory\" do\n        expect(communicator).to receive(:test).with(/test -d/).and_return(true)\n        expect(communicator).to receive(:sudo).with(/rm -Rf/)\n      end\n\n      it \"should create the symlink to the vmware folder\" do\n        expect(communicator).to receive(:sudo).with(/ln -s/)\n      end\n\n      it \"should not register an action hook\" do\n        expect(VagrantPlugins::GuestDarwin::Plugin).not_to receive(:action_hook).with(:apfs_firmlinks, :after_synced_folders)\n      end\n    end\n  end\n\n  describe \".system_firmlink?\" do\n    before { described_class.reset! }\n\n    context \"when file does not exist\" do\n      before { allow(File).to receive(:exist?).with(\"/usr/share/firmlinks\").and_return(false) }\n\n      it \"should always return false\" do\n        expect(described_class.system_firmlink?(\"test\")).to be_falsey\n      end\n    end\n\n    context \"when file does exist\" do\n      let(:content) {\n        [\"/Users\\tUsers\",\n          \"/usr/local\\tusr/local\"]\n      }\n\n      before do\n        expect(File).to receive(:exist?).with(\"/usr/share/firmlinks\").and_return(true)\n        expect(File).to receive(:readlines).with(\"/usr/share/firmlinks\").and_return(content)\n      end\n\n      it \"should return true when firmlink exists\" do\n        expect(described_class.system_firmlink?(\"/Users\")).to be_truthy\n      end\n\n      it \"should return true when firmlink is not prefixed with /\" do\n        expect(described_class.system_firmlink?(\"Users\")).to be_truthy\n      end\n\n      it \"should return false when firmlink does not exist\" do\n        expect(described_class.system_firmlink?(\"/testing\")).to be_falsey\n      end\n    end\n  end\n\n  describe \".write_apfs_firmlinks\" do\n    let(:env) { nil }\n    let(:action) { double(\"action\", call: nil) }\n    let(:machine) { double(\"machine\", id: machine_id) }\n    let(:machine_id) { double(\"machine_id\") }\n    let(:delayed) { {} }\n\n    context \"when env is nil\" do\n      it \"should be a no-op\" do\n        expect(described_class).not_to receive(:apfs_firmlinks_delayed)\n        described_class.write_apfs_firmlinks(env)\n      end\n    end\n\n    context \"when env is empty hash\" do\n      let(:env) { {} }\n\n      it \"should be a no-op\" do\n        expect(described_class).not_to receive(:apfs_firmlinks_delayed)\n        described_class.write_apfs_firmlinks(env)\n      end\n    end\n\n    context \"when machine is defined within env\" do\n      let(:env) { {machine: machine} }\n\n      it \"should request the stored delayed actions\" do\n        expect(described_class).to receive(:apfs_firmlinks_delayed).and_return(delayed)\n        described_class.write_apfs_firmlinks(env)\n      end\n    end\n\n    context \"when delayed action is stored for machine\" do\n      let(:env) { {machine: machine} }\n\n      before { described_class.apfs_firmlinks_delayed[machine_id] = action }\n      after { described_class.apfs_firmlinks_delayed.clear }\n\n      it \"should call the delayed action\" do\n        expect(action).to receive(:call)\n        described_class.write_apfs_firmlinks(env)\n      end\n\n      it \"should remove the action after calling\" do\n        expect(action).to receive(:call)\n        described_class.write_apfs_firmlinks(env)\n        expect(delayed).to be_empty\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/darwin/cap/shell_expand_guest_path_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestDarwin::Cap::ShellExpandGuestPath\" do\n  let(:caps) do\n    VagrantPlugins::GuestDarwin::Plugin\n      .components\n      .guest_capabilities[:darwin]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  describe \"#shell_expand_guest_path\" do\n    let(:cap) { caps.get(:shell_expand_guest_path) }\n\n    it \"expands the path\" do\n      path = \"/home/vagrant/folder\"\n      allow(machine.communicate).to receive(:execute).\n        with(any_args).and_yield(:stdout, \"/home/vagrant/folder\")\n\n      cap.shell_expand_guest_path(machine, path)\n    end\n\n    it \"raises an exception if no path was detected\" do\n      path = \"/home/vagrant/folder\"\n      expect { cap.shell_expand_guest_path(machine, path) }.\n        to raise_error(Vagrant::Errors::ShellExpandFailed)\n    end\n\n    it \"returns a path with a space in it\" do\n      path = \"/home/vagrant folder/folder\"\n      path_with_spaces = \"/home/vagrant\\\\ folder/folder\"\n      allow(machine.communicate).to receive(:execute).\n        with(any_args).and_yield(:stdout, path_with_spaces)\n\n      expect(machine.communicate).to receive(:execute).with(\"printf #{path_with_spaces}\")\n      cap.shell_expand_guest_path(machine, path)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/debian/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestDebian::Cap::ChangeHostName\" do\n  let(:caps) do\n    VagrantPlugins::GuestDebian::Plugin\n      .components\n      .guest_capabilities[:debian]\n  end\n\n  let(:machine) { double(\"machine\", name: \"guestname\") }\n  let(:logger) { double(\"logger\", debug: true) }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    let(:cap) { caps.get(:change_host_name) }\n    let(:name) { 'banana-rama.example.com' }\n    let(:systemd) { true }\n    let(:hostnamectl) { true }\n    let(:networkd) { true }\n    let(:network_manager) { false }\n    let(:networks) { [\n      [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n    ] }\n\n    before do\n      allow(cap).to receive(:systemd?).and_return(systemd)\n      allow(cap).to receive(:hostnamectl?).and_return(hostnamectl)\n      allow(cap).to receive(:systemd_networkd?).and_return(networkd)\n      allow(cap).to receive(:systemd_controlled?).with(anything, /NetworkManager/).and_return(network_manager)\n      allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n      allow(cap).to receive(:add_hostname_to_loopback_interface)\n      allow(cap).to receive(:replace_host)\n    end\n\n    context \"minimal network config\" do\n      it \"sets the hostname if not set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n        cap.change_host_name(machine, name)\n        expect(comm.received_commands[1]).to match(/echo 'banana-rama' > \\/etc\\/hostname/)\n      end\n\n      it \"sets the hostname if not set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n        cap.change_host_name(machine, name)\n        expect(comm.received_commands[1]).to_not match(/echo 'banana-rama' > \\/etc\\/hostname/)\n      end\n    end\n\n    context \"multiple networks configured with hostname\" do \n      it \"adds a new entry only for the hostname\" do \n        networks = [\n          [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}],\n          [:public_network, {:ip=>\"192.168.0.1\", :hostname=>true, :protocol=>\"tcp\", :id=>\"93a4ad88-0774-4127-a161-ceb715ff372f\"}],\n          [:public_network, {:ip=>\"192.168.0.2\", :protocol=>\"tcp\", :id=>\"5aebe848-7d85-4425-8911-c2003d924120\"}]\n        ]\n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n        expect(cap).to receive(:replace_host)\n        expect(cap).to_not receive(:add_hostname_to_loopback_interface)\n        cap.change_host_name(machine, name)\n      end\n\n      it \"appends an entry to the loopback interface\" do \n        networks = [\n          [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}],\n          [:public_network, {:ip=>\"192.168.0.1\", :protocol=>\"tcp\", :id=>\"93a4ad88-0774-4127-a161-ceb715ff372f\"}],\n          [:public_network, {:ip=>\"192.168.0.2\", :protocol=>\"tcp\", :id=>\"5aebe848-7d85-4425-8911-c2003d924120\"}]\n        ]\n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n        expect(cap).to_not receive(:replace_host)\n        expect(cap).to receive(:add_hostname_to_loopback_interface).once\n        cap.change_host_name(machine, name)\n      end\n    end\n\n    context \"when hostnamectl is in use\" do\n      let(:hostnamectl) { true }\n\n      it \"sets hostname with hostnamectl\" do\n        cap.change_host_name(machine, name)\n        expect(comm.received_commands[2]).to match(/hostnamectl/)\n      end\n    end\n\n    context \"when hostnamectl is not in use\" do\n      let(:hostnamectl) { false }\n\n      it \"sets hostname with hostname command\" do\n        cap.change_host_name(machine, name)\n        expect(comm.received_commands[2]).to match(/hostname -F/)\n      end\n    end\n\n    context \"restarts the network\" do\n      context \"when networkd is in use\" do\n        let(:networkd) { true }\n\n        it \"restarts networkd with systemctl\" do\n          cap.change_host_name(machine, name)\n          expect(comm.received_commands[3]).to match(/systemctl restart systemd-networkd/)\n        end\n      end\n\n      context \"when NetworkManager is in use\" do\n        let(:networkd) { false }\n        let(:network_manager) { true }\n\n        it \"restarts NetworkManager with systemctl\" do\n          cap.change_host_name(machine, name)\n          expect(comm.received_commands[3]).to match(/systemctl restart NetworkManager/)\n        end\n      end\n\n      context \"when networkd and NetworkManager are not in use\" do\n        let(:networkd) { false }\n        let(:network_manager) { false }\n        let(:systemd) { true }\n\n        it \"restarts the network using systemctl\" do\n          expect(cap).to receive(:restart_each_interface).\n            with(machine, anything)\n          cap.change_host_name(machine, name)\n        end\n\n        it \"restarts networking with networking init script\" do\n          expect(cap).to receive(:restart_each_interface).\n            with(machine, anything)\n          cap.change_host_name(machine, name)\n        end\n      end\n\n      context \"when systemd is not in use\" do\n        let(:systemd) { false }\n\n        it \"restarts the network using service\" do\n          expect(cap).to receive(:restart_each_interface).\n            with(machine, anything)\n          cap.change_host_name(machine, name)\n        end\n\n        it \"restarts the network using ifdown/ifup\" do\n          expect(cap).to receive(:restart_each_interface).\n            with(machine, anything)\n          cap.change_host_name(machine, name)\n        end\n      end\n    end\n\n    it \"does not set the hostname if unset\" do\n      comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n      expect(cap).to_not receive(:add_hostname_to_loopback_interface)\n      expect(cap).to_not receive(:replace_host)\n      cap.change_host_name(machine, name)\n    end\n  end\n\n  describe \".restart_each_interface\" do\n    let(:cap) { caps.get(:change_host_name) }\n    let(:systemd) { true }\n    let(:interfaces) { [\"eth0\", \"eth1\", \"eth2\"] }\n\n    before do\n      allow(cap).to receive(:systemd?).and_return(systemd)\n      allow(VagrantPlugins::GuestLinux::Cap::NetworkInterfaces).to receive(:network_interfaces).\n        and_return(interfaces)\n    end\n\n    context \"with nettools\" do\n      let(:systemd) { false }\n\n      it \"restarts every interface\" do\n        cap.send(:restart_each_interface, machine, logger)\n        expect(comm.received_commands[0]).to match(/ifdown eth0;ifup eth0/)\n        expect(comm.received_commands[1]).to match(/ifdown eth1;ifup eth1/)\n        expect(comm.received_commands[2]).to match(/ifdown eth2;ifup eth2/)\n      end\n    end\n\n    context \"with systemctl\" do\n      it \"restarts every interface\" do\n        cap.send(:restart_each_interface, machine, logger)\n        expect(comm.received_commands[0]).to match(/systemctl stop ifup@eth0.service;systemctl start ifup@eth0.service/)\n        expect(comm.received_commands[1]).to match(/systemctl stop ifup@eth1.service;systemctl start ifup@eth1.service/)\n        expect(comm.received_commands[2]).to match(/systemctl stop ifup@eth2.service;systemctl start ifup@eth2.service/)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/debian/cap/configure_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestDebian::Cap::ConfigureNetworks\" do\n  let(:caps) do\n    VagrantPlugins::GuestDebian::Plugin\n      .components\n      .guest_capabilities[:debian]\n  end\n\n  let(:guest) { double(\"guest\") }\n  let(:machine) { double(\"machine\", guest: guest) }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \"#build_interface_entries\" do\n    let(:network_0) do\n      {\n        interface: 0,\n        type: \"dhcp\",\n      }\n    end\n\n    let(:network_1) do\n      {\n        interface: 1,\n        type: \"static\",\n        ip: \"33.33.33.10\",\n        netmask: \"255.255.0.0\",\n        gateway: \"33.33.0.1\",\n      }\n    end\n  end\n\n  describe \".configure_networks\" do\n    let(:cap) { caps.get(:configure_networks) }\n\n    before do\n      allow(guest).to receive(:capability).with(:network_interfaces)\n        .and_return([\"eth1\", \"eth2\"])\n    end\n\n    let(:network_0) do\n      {\n        interface: 0,\n        type: \"dhcp\",\n      }\n    end\n\n    let(:network_1) do\n      {\n        interface: 1,\n        type: \"static\",\n        ip: \"33.33.33.10\",\n        netmask: \"255.255.0.0\",\n        gateway: \"33.33.0.1\",\n      }\n    end\n\n    before do\n      allow(comm).to receive(:test).with(\"nmcli -t d show eth1\").and_return(false)\n      allow(comm).to receive(:test).with(\"nmcli -t d show eth2\").and_return(false)\n      allow(comm).to receive(:test).with(\"ps -o comm= 1 | grep systemd\", {sudo: true}).and_return(false)\n      allow(comm).to receive(:test).with(\"systemctl -q is-active systemd-networkd.service\", anything).and_return(false)\n      allow(comm).to receive(:test).with(\"command -v netplan\").and_return(false)\n    end\n\n    it \"creates and starts the networks using net-tools\" do\n      cap.configure_networks(machine, [network_0, network_1])\n\n      expect(comm.received_commands[0]).to match(\"/sbin/ifdown 'eth1' || true\")\n      expect(comm.received_commands[0]).to match(\"/sbin/ip addr flush dev 'eth1'\")\n      expect(comm.received_commands[0]).to match(\"/sbin/ifdown 'eth2' || true\")\n      expect(comm.received_commands[0]).to match(\"/sbin/ip addr flush dev 'eth2'\")\n      expect(comm.received_commands[1]).to match(\"/sbin/ifup 'eth1'\")\n      expect(comm.received_commands[1]).to match(\"/sbin/ifup 'eth2'\")\n\n    end\n\n    context \"with systemd\" do\n      before do\n        expect(comm).to receive(:test).with(\"ps -o comm= 1 | grep systemd\", {sudo: true}).and_return(true)\n        allow(comm).to receive(:test).with(\"command -v netplan\").and_return(false)\n      end\n\n      it \"creates and starts the networks using net-tools\" do\n        cap.configure_networks(machine, [network_0, network_1])\n\n        expect(comm.received_commands[0]).to match(\"/sbin/ifdown 'eth1' || true\")\n        expect(comm.received_commands[0]).to match(\"/sbin/ip addr flush dev 'eth1'\")\n        expect(comm.received_commands[0]).to match(\"/sbin/ifdown 'eth2' || true\")\n        expect(comm.received_commands[0]).to match(\"/sbin/ip addr flush dev 'eth2'\")\n        expect(comm.received_commands[1]).to match(\"/sbin/ifup 'eth1'\")\n        expect(comm.received_commands[1]).to match(\"/sbin/ifup 'eth2'\")\n      end\n\n      context \"with systemd-networkd\" do\n        let(:net_conf_dhcp) { \"[Match]\\nName=eth1\\n[Network]\\nDHCP=yes\" }\n        let(:net_conf_static) { \"[Match]\\nName=eth2\\n[Network]\\nDHCP=no\\nAddress=33.33.33.10/16\\nGateway=33.33.0.1\" }\n\n        before do\n          expect(comm).to receive(:test).with(\"systemctl -q is-active systemd-networkd.service\", anything).and_return(true)\n        end\n\n        it \"creates and starts the networks using systemd-networkd\" do\n          cap.configure_networks(machine, [network_0, network_1])\n\n          expect(comm.received_commands[0]).to match(\"mv -f '/tmp/vagrant-network-entry.*' '/etc/systemd/network/.*network'\")\n          expect(comm.received_commands[0]).to match(\"chown\")\n          expect(comm.received_commands[0]).to match(\"chmod\")\n          expect(comm.received_commands[2]).to match(\"systemctl restart\")\n        end\n\n        it \"properly configures DHCP and static IPs if defined\" do\n          expect(cap).to receive(:upload_tmp_file).with(comm, net_conf_dhcp)\n          expect(cap).to receive(:upload_tmp_file).with(comm, net_conf_static)\n\n          cap.configure_networks(machine, [network_0, network_1])\n\n          expect(comm.received_commands[0]).to match(\"mkdir -p /etc/systemd/network\")\n          expect(comm.received_commands[0]).to match(\"mv -f '' '/etc/systemd/network/50-vagrant-eth1.network'\")\n          expect(comm.received_commands[0]).to match(\"chown root:root '/etc/systemd/network/50-vagrant-eth1.network'\")\n          expect(comm.received_commands[0]).to match(\"chmod 0644 '/etc/systemd/network/50-vagrant-eth1.network'\")\n          expect(comm.received_commands[2]).to match(\"systemctl restart\")\n        end\n      end\n\n      context \"with netplan\" do\n        before do\n          expect(comm).to receive(:test).with(\"command -v netplan\").and_return(true)\n        end\n\n        let(:nm_yml) { \"---\\nnetwork:\\n  version: 2\\n  renderer: NetworkManager\\n  ethernets:\\n    eth1:\\n      dhcp4: true\\n    eth2:\\n      addresses:\\n      - 33.33.33.10/16\\n      gateway4: 33.33.0.1\\n\" }\n        let(:networkd_yml) { \"---\\nnetwork:\\n  version: 2\\n  renderer: networkd\\n  ethernets:\\n    eth1:\\n      dhcp4: true\\n    eth2:\\n      addresses:\\n      - 33.33.33.10/16\\n      gateway4: 33.33.0.1\\n\" }\n\n        it \"uses NetworkManager if detected on device\" do\n          allow(cap).to receive(:systemd_networkd?).and_return(false)\n          allow(cap).to receive(:nmcli?).and_return(true)\n          allow(cap).to receive(:nm_controlled?).and_return(true)\n          allow(comm).to receive(:test).with(\"nmcli -t d show eth1\").and_return(true)\n          allow(comm).to receive(:test).with(\"nmcli -t d show eth2\").and_return(true)\n\n          expect(cap).to receive(:upload_tmp_file).with(comm, nm_yml)\n            .and_return(\"/tmp/vagrant-network-entry.1234\")\n\n          cap.configure_networks(machine, [network_0, network_1])\n\n\n          expect(comm.received_commands[0]).to match(\"mv -f '/tmp/vagrant-network-entry.*' '/etc/netplan/.*.yaml'\")\n          expect(comm.received_commands[0]).to match(\"chown\")\n          expect(comm.received_commands[0]).to match(\"chmod\")\n          expect(comm.received_commands[0]).to match(\"netplan apply\")\n        end\n\n        it \"raises and error if NetworkManager is detected on device but nmcli is not installed\" do\n          allow(cap).to receive(:systemd_networkd?).and_return(true)\n          allow(cap).to receive(:nmcli?).and_return(false)\n          allow(cap).to receive(:nm_controlled?).and_return(true)\n          allow(comm).to receive(:test).with(\"nmcli -t d show eth1\").and_return(true)\n          allow(comm).to receive(:test).with(\"nmcli -t d show eth2\").and_return(true)\n\n          expect { cap.configure_networks(machine, [network_0, network_1]) }.to raise_error(Vagrant::Errors::NetworkManagerNotInstalled)\n        end\n\n        it \"creates and starts the networks for systemd with netplan\" do\n          allow(cap).to receive(:systemd_networkd?).and_return(true)\n          expect(cap).to receive(:upload_tmp_file).with(comm, networkd_yml)\n            .and_return(\"/tmp/vagrant-network-entry.1234\")\n\n          cap.configure_networks(machine, [network_0, network_1])\n\n          expect(comm.received_commands[0]).to match(\"mv -f '/tmp/vagrant-network-entry.*' '/etc/netplan/.*.yaml'\")\n          expect(comm.received_commands[0]).to match(\"chown\")\n          expect(comm.received_commands[0]).to match(\"chmod\")\n          expect(comm.received_commands[0]).to match(\"netplan apply\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/debian/cap/nfs_client_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestDebian::Cap::NFSClient\" do\n  let(:described_class) do\n    VagrantPlugins::GuestDebian::Plugin\n      .components\n      .guest_capabilities[:debian]\n      .get(:nfs_client_install)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".nfs_client_install\" do\n    it \"installs nfs client utilities\" do\n      described_class.nfs_client_install(machine)\n\n      expect(comm.received_commands[0]).to match(/apt-get -yqq update/)\n      expect(comm.received_commands[0]).to match(/apt-get -yqq install nfs-common portmap/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/debian/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestDebian::Cap:RSync\" do\n  let(:described_class) do\n    VagrantPlugins::GuestDebian::Plugin\n      .components\n      .guest_capabilities[:debian]\n      .get(:rsync_install)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".rsync_install\" do\n    it \"installs rsync\" do\n      described_class.rsync_install(machine)\n\n      expect(comm.received_commands[0]).to match(/apt-get -yqq update/)\n      expect(comm.received_commands[0]).to match(/apt-get -yqq install rsync/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/debian/cap/smb_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestDebian::Cap::SMB\" do\n  let(:described_class) do\n    VagrantPlugins::GuestDebian::Plugin\n      .components\n      .guest_capabilities[:debian]\n      .get(:smb_install)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".smb_install\" do\n    it \"installs smb when /sbin/mount.cifs does not exist\" do\n      comm.stub_command(\"test -f /sbin/mount.cifs\", exit_code: 1)\n      described_class.smb_install(machine)\n\n      expect(comm.received_commands[1]).to match(/apt-get -yqq update/)\n      expect(comm.received_commands[1]).to match(/apt-get -yqq install cifs-utils/)\n    end\n\n    it \"does not install smb when /sbin/mount.cifs exists\" do\n      comm.stub_command(\"test -f /sbin/mount.cifs\", exit_code: 0)\n      described_class.smb_install(machine)\n\n      expect(comm.received_commands.join(\"\")).to_not match(/update/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/esxi/cap/halt_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestEsxi::Cap::Halt\" do\n  let(:caps) do\n    VagrantPlugins::GuestEsxi::Plugin\n      .components\n      .guest_capabilities[:esxi]\n  end\n\n  let(:shutdown_command){ \"/bin/halt -d 0\" }\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".halt\" do\n    let(:cap) { caps.get(:halt) }\n\n    it \"runs the shutdown command\" do\n      comm.expect_command(shutdown_command)\n      cap.halt(machine)\n    end\n\n    it \"ignores an IOError\" do\n      comm.stub_command(shutdown_command, raise: IOError)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n\n    it \"ignores a Vagrant::Errors::SSHDisconnected\" do\n      comm.stub_command(shutdown_command, raise: Vagrant::Errors::SSHDisconnected)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/esxi/cap/public_key_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestEsxi::Cap::PublicKey\" do\n  let(:caps) do\n    VagrantPlugins::GuestEsxi::Plugin\n      .components\n      .guest_capabilities[:esxi]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".insert_public_key\" do\n    let(:cap) { caps.get(:insert_public_key) }\n\n    it \"inserts the public key\" do\n      cap.insert_public_key(machine, \"ssh-rsa ...\")\n      expect(comm.received_commands[0]).to match(/SSH_DIR=\".*\"/)\n      expect(comm.received_commands[0]).to match(/mkdir -p \"\\${SSH_DIR}\"/)\n      expect(comm.received_commands[0]).to match(/chmod 0700 \"\\${SSH_DIR}\"/)\n      expect(comm.received_commands[0]).to match(/cat '\\/tmp\\/vagrant-(.+)' >> \"\\${SSH_DIR}\\/authorized_keys\"/)\n      expect(comm.received_commands[0]).to match(/chmod 0600 \"\\${SSH_DIR}\\/authorized_keys\"/)\n      expect(comm.received_commands[0]).to match(/rm -f '\\/tmp\\/vagrant-(.+)'/)\n    end\n  end\n\n  describe \".remove_public_key\" do\n    let(:cap) { caps.get(:remove_public_key) }\n\n    it \"removes the public key\" do\n      cap.remove_public_key(machine, \"ssh-rsa ...\")\n      expect(comm.received_commands[0]).to match(/SSH_DIR=\".*\"/)\n      expect(comm.received_commands[0]).to match(/grep -v -x -f '\\/tmp\\/vagrant-(.+)' \"\\${SSH_DIR}\\/authorized_keys\" > \"\\${SSH_DIR}\\/authorized_keys\\.tmp\"/)\n      expect(comm.received_commands[0]).to match(/mv \"\\${SSH_DIR}\\/authorized_keys\\.tmp\" \"\\${SSH_DIR}\\/authorized_keys\"/)\n      expect(comm.received_commands[0]).to match(/chmod 0600 \"\\${SSH_DIR}\\/authorized_keys\"/)\n      expect(comm.received_commands[0]).to match(/rm -f '\\/tmp\\/vagrant-(.+)'/)\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/freebsd/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestFreeBSD::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestFreeBSD::Plugin\n      .components\n      .guest_capabilities[:freebsd]\n      .get(:change_host_name)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:name) { \"banana-rama.example.com\" }\n  let(:basename) { \"banana-rama\" }\n  let(:networks) {}\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n    allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    context \"minimal network config\" do\n      let(:networks) { [\n        [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n      ] }\n\n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n        described_class.change_host_name(machine, name)\n        expect(comm.received_commands[1]).to match(/hostname '#{name}'/)\n        expect(described_class).to_not receive(:add_hostname_to_loopback_interface)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n        described_class.change_host_name(machine, name)\n        expect(comm).to_not receive(:sudo).with(/hostname '#{name}'/)\n        expect(described_class).to_not receive(:add_hostname_to_loopback_interface)\n      end\n    end\n\n    context \"multiple networks configured with hostname\" do \n      it \"adds a new entry only for the hostname\" do \n        networks = [\n          [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}],\n          [:public_network, {:ip=>\"192.168.0.1\", :hostname=>true, :protocol=>\"tcp\", :id=>\"93a4ad88-0774-4127-a161-ceb715ff372f\"}],\n          [:public_network, {:ip=>\"192.168.0.2\", :protocol=>\"tcp\", :id=>\"5aebe848-7d85-4425-8911-c2003d924120\"}]\n        ]\n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n        expect(described_class).to receive(:replace_host)\n        expect(described_class).to_not receive(:add_hostname_to_loopback_interface)\n        described_class.change_host_name(machine, name)\n      end\n\n      it \"appends an entry to the loopback interface\" do \n        networks = [\n          [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}],\n          [:public_network, {:ip=>\"192.168.0.1\", :protocol=>\"tcp\", :id=>\"93a4ad88-0774-4127-a161-ceb715ff372f\"}],\n          [:public_network, {:ip=>\"192.168.0.2\", :protocol=>\"tcp\", :id=>\"5aebe848-7d85-4425-8911-c2003d924120\"}]\n        ]\n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n        expect(described_class).to_not receive(:replace_host)\n        expect(described_class).to receive(:add_hostname_to_loopback_interface).once\n        described_class.change_host_name(machine, name)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/freebsd/cap/configure_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestFreeBSD::Cap::ConfigureNetworks\" do\n  let(:described_class) do\n    VagrantPlugins::GuestFreeBSD::Plugin\n      .components\n      .guest_capabilities[:freebsd]\n      .get(:configure_networks)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n    comm.stub_command(\"ifconfig -l ether\", stdout: \"em1 em2\")\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".configure_networks\" do\n    let(:network_1) do\n      {\n        interface: 0,\n        type: \"dhcp\",\n      }\n    end\n\n    let(:network_2) do\n      {\n        interface: 1,\n        type: \"static\",\n        ip: \"33.33.33.10\",\n        netmask: \"255.255.0.0\",\n        gateway: \"33.33.0.1\",\n      }\n    end\n\n    it \"creates and starts the networks\" do\n      described_class.configure_networks(machine, [network_1, network_2])\n      expect(comm.received_commands[1]).to match(/dhclient 'em1'/)\n      expect(comm.received_commands[1]).to match(/\\/etc\\/rc.d\\/netif restart 'em1'/)\n\n      expect(comm.received_commands[1]).to_not match(/dhclient 'em2'/)\n      expect(comm.received_commands[1]).to match(/\\/etc\\/rc.d\\/netif restart 'em2'/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/freebsd/cap/mount_virtual_box_shared_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestFreeBSD::Cap::MountVirtualBoxSharedFolder\" do\n  let(:caps) do\n    VagrantPlugins::GuestFreeBSD::Plugin\n      .components\n      .guest_capabilities[:freebsd]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:mount_owner){ \"vagrant\" }\n  let(:mount_group){ \"vagrant\" }\n  let(:mount_uid){ \"1000\" }\n  let(:mount_gid){ \"1000\" }\n  let(:mount_name){ \"vagrant\" }\n  let(:mount_guest_path){ \"/vagrant\" }\n  let(:folder_options) do\n    {\n      owner: mount_owner,\n      group: mount_group,\n      hostpath: \"/host/directory/path\"\n    }\n  end\n  let(:cap){ caps.get(:mount_virtualbox_shared_folder) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".mount_virtualbox_shared_folder\" do\n\n    before do\n      allow(comm).to receive(:sudo).with(any_args)\n      allow(comm).to receive(:execute).with(any_args)\n    end\n\n    it \"generates the expected default mount command\" do\n      expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n      expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_yield(:stdout, \"vagrant:x:#{mount_gid}:\")\n      expect(comm).to receive(:sudo).with(\"mount -t vboxvfs -o uid=#{mount_uid},gid=#{mount_gid} #{mount_name} #{mount_guest_path}\", anything)\n      cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n    end\n\n    it \"automatically chown's the mounted directory on guest\" do\n      expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n      expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_yield(:stdout, \"vagrant:x:#{mount_gid}:\")\n      expect(comm).to receive(:sudo).with(\"mount -t vboxvfs -o uid=#{mount_uid},gid=#{mount_gid} #{mount_name} #{mount_guest_path}\", anything)\n      expect(comm).to receive(:sudo).with(\"chown #{mount_uid}:#{mount_gid} #{mount_guest_path}\")\n      cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n    end\n\n    context \"with owner user ID explicitly defined\" do\n\n      before do\n        expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_yield(:stdout, \"vagrant:x:#{mount_gid}:\")\n      end\n\n      context \"with user ID provided as Integer\" do\n        let(:mount_owner){ 2000 }\n\n        it \"generates the expected mount command using mount_owner directly\" do\n          expect(comm).to receive(:sudo).with(\"mount -t vboxvfs -o uid=#{mount_owner},gid=#{mount_gid} #{mount_name} #{mount_guest_path}\", anything)\n          expect(comm).to receive(:sudo).with(\"chown #{mount_owner}:#{mount_gid} #{mount_guest_path}\")\n          cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n        end\n      end\n\n      context \"with user ID provided as String\" do\n        let(:mount_owner){ \"2000\" }\n\n        it \"generates the expected mount command using mount_owner directly\" do\n          expect(comm).to receive(:sudo).with(\"mount -t vboxvfs -o uid=#{mount_owner},gid=#{mount_gid} #{mount_name} #{mount_guest_path}\", anything)\n          expect(comm).to receive(:sudo).with(\"chown #{mount_owner}:#{mount_gid} #{mount_guest_path}\")\n          cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n        end\n      end\n\n    end\n\n    context \"with owner group ID explicitly defined\" do\n\n      before do\n        expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n      end\n\n      context \"with owner group ID provided as Integer\" do\n        let(:mount_group){ 2000 }\n\n        it \"generates the expected mount command using mount_group directly\" do\n          expect(comm).to receive(:sudo).with(\"mount -t vboxvfs -o uid=#{mount_uid},gid=#{mount_group} #{mount_name} #{mount_guest_path}\", anything)\n          expect(comm).to receive(:sudo).with(\"chown #{mount_uid}:#{mount_group} #{mount_guest_path}\")\n          cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n        end\n      end\n\n      context \"with owner group ID provided as String\" do\n        let(:mount_group){ \"2000\" }\n\n        it \"generates the expected mount command using mount_group directly\" do\n          expect(comm).to receive(:sudo).with(\"mount -t vboxvfs -o uid=#{mount_uid},gid=#{mount_group} #{mount_name} #{mount_guest_path}\", anything)\n          expect(comm).to receive(:sudo).with(\"chown #{mount_uid}:#{mount_group} #{mount_guest_path}\")\n          cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n        end\n      end\n\n    end\n\n    context \"with non-existent default owner group\" do\n\n      it \"fetches the effective group ID of the user\" do\n        expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n        expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_raise(Vagrant::Errors::VirtualBoxMountFailed, {command: '', output: ''})\n        expect(comm).to receive(:execute).with(\"id -g #{mount_owner}\", anything).and_yield(:stdout, \"1\").and_return(0)\n        cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n      end\n    end\n\n    context \"with non-existent owner group\" do\n\n      it \"raises an error\" do\n        expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n        expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_raise(Vagrant::Errors::VirtualBoxMountFailed, {command: '', output: ''})\n        expect do\n          cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n        end.to raise_error Vagrant::Errors::VirtualBoxMountFailed\n      end\n    end\n\n    context \"with read-only option defined\" do\n\n      it \"does not chown mounted guest directory\" do\n        expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n        expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_yield(:stdout, \"vagrant:x:#{mount_gid}:\")\n        expect(comm).to receive(:sudo).with(\"mount -t vboxvfs -o ro,uid=#{mount_uid},gid=#{mount_gid} #{mount_name} #{mount_guest_path}\", anything)\n        expect(comm).not_to receive(:sudo).with(\"chown #{mount_uid}:#{mount_gid} #{mount_guest_path}\")\n        cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options.merge(mount_options: [\"ro\"]))\n      end\n    end\n\n    context \"with upstart init\" do\n\n      it \"emits mount event\" do\n        expect(comm).to receive(:sudo).with(/initctl emit/)\n        cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n      end\n    end\n\n    context \"with custom mount options\" do\n\n      let(:ui){ Vagrant::UI::Silent.new }\n      before do\n        allow(machine).to receive(:ui).and_return(ui)\n      end\n\n      context \"with uid defined\" do\n        let(:options_uid){ '1234' }\n\n        it \"should only include uid defined within mount options\" do\n          expect(comm).not_to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n          expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_yield(:stdout, \"vagrant:x:#{mount_gid}:\")\n          expect(comm).to receive(:sudo).with(\"mount -t vboxvfs -o uid=#{options_uid},gid=#{mount_gid} #{mount_name} #{mount_guest_path}\", anything)\n          cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options.merge(mount_options: [\"uid=#{options_uid}\"]))\n        end\n      end\n\n      context \"with gid defined\" do\n        let(:options_gid){ '1234' }\n\n        it \"should only include gid defined within mount options\" do\n          expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n          expect(comm).not_to receive(:execute).with(\"getent group #{mount_group}\", anything).and_yield(:stdout, \"vagrant:x:#{mount_gid}:\")\n          expect(comm).to receive(:sudo).with(\"mount -t vboxvfs -o uid=#{mount_uid},gid=#{options_gid} #{mount_name} #{mount_guest_path}\", anything)\n          cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options.merge(mount_options: [\"gid=#{options_gid}\"]))\n        end\n      end\n\n      context \"with uid and gid defined\" do\n        let(:options_gid){ '1234' }\n        let(:options_uid){ '1234' }\n\n        it \"should only include uid and gid defined within mount options\" do\n          expect(comm).not_to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n          expect(comm).not_to receive(:execute).with(\"getent group #{mount_group}\", anything).and_yield(:stdout, \"vagrant:x:#{options_gid}:\")\n          expect(comm).to receive(:sudo).with(\"mount -t vboxvfs -o uid=#{options_uid},gid=#{options_gid} #{mount_name} #{mount_guest_path}\", anything)\n          cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options.merge(mount_options: [\"gid=#{options_gid}\", \"uid=#{options_uid}\"]))\n        end\n      end\n    end\n\n    context \"with guest builtin vboxvfs module\" do\n      let(:vbox_stderr){ <<-EOF\nmount.vboxvfs cannot be used with mainline vboxvfs; instead use:\n\n    mount -cit vboxvfs NAME MOUNTPOINT\nEOF\n      }\n      it \"should perform guest mount using builtin module\" do\n        expect(comm).to receive(:sudo).with(/mount -t vboxvfs/, any_args).and_yield(:stderr, vbox_stderr).and_return(1)\n        expect(comm).to receive(:sudo).with(/mount -cit/, any_args)\n        cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n      end\n    end\n  end\n\n  describe \".unmount_virtualbox_shared_folder\" do\n\n    after { cap.unmount_virtualbox_shared_folder(machine, mount_guest_path, folder_options) }\n\n    it \"unmounts shared directory and deletes directory on guest\" do\n      expect(comm).to receive(:sudo).with(\"umount #{mount_guest_path}\", anything).and_return(0)\n      expect(comm).to receive(:sudo).with(\"rmdir #{mount_guest_path}\", anything)\n    end\n\n    it \"does not delete guest directory if unmount fails\" do\n      expect(comm).to receive(:sudo).with(\"umount #{mount_guest_path}\", anything).and_return(1)\n      expect(comm).not_to receive(:sudo).with(\"rmdir #{mount_guest_path}\", anything)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/freebsd/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestFreeBSD::Cap::RSync\" do\n  let(:caps) do\n    VagrantPlugins::GuestFreeBSD::Plugin\n      .components\n      .guest_capabilities[:freebsd]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".rsync_install\" do\n    let(:cap) { caps.get(:rsync_install) }\n\n    it \"installs rsync\" do\n      comm.expect_command(\"pkg install -y rsync\")\n      cap.rsync_install(machine)\n    end\n  end\n\n  describe \".rsync_installed\" do\n    let(:cap) { caps.get(:rsync_installed) }\n\n    it \"checks if rsync is installed\" do\n      comm.expect_command(\"which rsync\")\n      cap.rsync_installed(machine)\n    end\n  end\n\n  describe \".rsync_command\" do\n    let(:cap) { caps.get(:rsync_command) }\n\n    it \"defaults to 'sudo rsync'\" do\n      expect(cap.rsync_command(machine)).to eq(\"sudo rsync\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/freebsd/cap/shell_expand_guest_path_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestFreeBSD::Cap::ShellExpandGuestPath\" do\n  let(:caps) do\n    VagrantPlugins::GuestFreeBSD::Plugin\n      .components\n      .guest_capabilities[:freebsd]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  describe \"#shell_expand_guest_path\" do\n    let(:cap) { caps.get(:shell_expand_guest_path) }\n\n    it \"expands the path\" do\n      path = \"/home/vagrant/folder\"\n      allow(machine.communicate).to receive(:execute).\n        with(any_args).and_yield(:stdout, \"/home/vagrant/folder\")\n\n      cap.shell_expand_guest_path(machine, path)\n    end\n\n    it \"raises an exception if no path was detected\" do\n      path = \"/home/vagrant/folder\"\n      expect { cap.shell_expand_guest_path(machine, path) }.\n        to raise_error(Vagrant::Errors::ShellExpandFailed)\n    end\n\n    it \"returns a path with a space in it\" do\n      path = \"/home/vagrant folder/folder\"\n      path_with_spaces = \"/home/vagrant\\\\ folder/folder\"\n      allow(machine.communicate).to receive(:execute).\n        with(any_args).and_yield(:stdout, path_with_spaces)\n\n      expect(machine.communicate).to receive(:execute)\n        .with(\"printf #{path_with_spaces}\", {:shell=>\"sh\"})\n      cap.shell_expand_guest_path(machine, path)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/gentoo/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestGentoo::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestGentoo::Plugin\n      .components\n      .guest_capabilities[:gentoo]\n      .get(:change_host_name)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:name) { \"banana-rama.example.com\" }\n  let(:basename) { \"banana-rama\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    context \"minimal network config\" do \n      let(:networks) { [\n        [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n      ] }\n\n      before do \n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n      end\n\n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n\n        described_class.change_host_name(machine, name)\n        expect(comm.received_commands[2]).to match(/systemctl set-hostname '#{name}'/)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n\n        described_class.change_host_name(machine, name)\n        expect(comm).to_not receive(:sudo).with(/systemctl set-hostname '#{name}'/)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/haiku/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestHaiku::Cap::RSync\" do\n  let(:caps) do\n    VagrantPlugins::GuestHaiku::Plugin\n      .components\n      .guest_capabilities[:haiku]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".rsync_install\" do\n    let(:cap) { caps.get(:rsync_install) }\n\n    it \"installs rsync\" do\n      comm.expect_command(\"pkgman install -y rsync\")\n      cap.rsync_install(machine)\n    end\n  end\n\n  describe \".rsync_installed\" do\n    let(:cap) { caps.get(:rsync_installed) }\n\n    it \"checks if rsync is installed\" do\n      comm.expect_command(\"test -f /bin/rsync\")\n      cap.rsync_installed(machine)\n    end\n  end\n\n  describe \".rsync_command\" do\n    let(:cap) { caps.get(:rsync_command) }\n\n    it \"defaults to 'rsync -zz'\" do\n      expect(cap.rsync_command(machine)).to eq(\"rsync -zz\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestLinux::Plugin\n        .components\n        .guest_capabilities[:linux]\n        .get(:change_host_name)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:name) { \"banana-rama.example.com\" }\n  let(:basename) { \"banana-rama\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    context \"minimal network config\" do \n      let(:networks) { [\n        [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n      ] }\n\n      before do \n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n      end\n\n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n        described_class.change_host_name(machine, name)\n        expect(comm.received_commands[2]).to match(/hostname '#{name}'/)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n\n        described_class.change_host_name(machine, name)\n        expect(comm).to_not receive(:sudo).with(/hostname '#{name}'/)\n      end\n    end\n\n    context \"multiple networks configured with hostname\" do \n      it \"adds a new entry only for the hostname\" do \n        networks = [\n          [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}],\n          [:public_network, {:ip=>\"192.168.0.1\", :hostname=>true, :protocol=>\"tcp\", :id=>\"93a4ad88-0774-4127-a161-ceb715ff372f\"}],\n          [:public_network, {:ip=>\"192.168.0.2\", :protocol=>\"tcp\", :id=>\"5aebe848-7d85-4425-8911-c2003d924120\"}]\n        ]\n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n        expect(described_class).to receive(:replace_host)\n        expect(described_class).to_not receive(:add_hostname_to_loopback_interface)\n        described_class.change_host_name(machine, name)\n      end\n\n      it \"appends an entry to the loopback interface\" do \n        networks = [\n          [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}],\n          [:public_network, {:ip=>\"192.168.0.1\", :protocol=>\"tcp\", :id=>\"93a4ad88-0774-4127-a161-ceb715ff372f\"}],\n          [:public_network, {:ip=>\"192.168.0.2\", :protocol=>\"tcp\", :id=>\"5aebe848-7d85-4425-8911-c2003d924120\"}]\n        ]\n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n        expect(described_class).to_not receive(:replace_host)\n        expect(described_class).to receive(:add_hostname_to_loopback_interface).once\n        described_class.change_host_name(machine, name)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/choose_addressable_ip_addr_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::ChooseAddressableIPAddr\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".choose_addressable_ip_addr\" do\n    let(:cap) { caps.get(:choose_addressable_ip_addr) }\n\n    it \"returns the first matching IP address\" do\n      possible = [\"1.2.3.4\", \"5.6.7.8\"]\n      possible.each do |ip|\n        comm.stub_command(\"ping -c1 -w1 -W1 #{ip}\", exit_code: 0)\n      end\n      result = cap.choose_addressable_ip_addr(machine, possible)\n      expect(result).to eq(\"1.2.3.4\")\n    end\n\n    it \"returns nil when there are no matches\" do\n      result = cap.choose_addressable_ip_addr(machine, [])\n      expect(result).to be(nil)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/file_system_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::FileSystem\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\", communicate: comm) }\n  let(:comm) { double(\"comm\") }\n\n  before { allow(comm).to receive(:execute) }\n\n  describe \".create_tmp_path\" do\n    let(:cap) { caps.get(:create_tmp_path) }\n    let(:opts) { {} }\n\n    it \"should generate path on guest\" do\n      expect(comm).to receive(:execute).with(/mktemp/)\n      cap.create_tmp_path(machine, opts)\n    end\n\n    it \"should capture path generated on guest\" do\n      expect(comm).to receive(:execute).with(/mktemp/).and_yield(:stdout, \"TMP_PATH\")\n      expect(cap.create_tmp_path(machine, opts)).to eq(\"TMP_PATH\")\n    end\n\n    it \"should strip newlines on path\" do\n      expect(comm).to receive(:execute).with(/mktemp/).and_yield(:stdout, \"TMP_PATH\\n\")\n      expect(cap.create_tmp_path(machine, opts)).to eq(\"TMP_PATH\")\n    end\n\n    context \"when type is a directory\" do\n      before { opts[:type] = :directory }\n\n      it \"should create guest path as a directory\" do\n        expect(comm).to receive(:execute).with(/-d/)\n        cap.create_tmp_path(machine, opts)\n      end\n    end\n  end\n\n  describe \".decompress_tgz\" do\n    let(:cap) { caps.get(:decompress_tgz) }\n    let(:comp) { \"compressed_file\" }\n    let(:dest) { \"path/to/destination\" }\n    let(:opts) { {} }\n\n    before { allow(cap).to receive(:create_tmp_path).and_return(\"TMP_DIR\") }\n    after{ cap.decompress_tgz(machine, comp, dest, opts) }\n\n    it \"should create temporary directory for extraction\" do\n      expect(cap).to receive(:create_tmp_path)\n    end\n\n    it \"should extract file with tar\" do\n      expect(comm).to receive(:execute).with(/tar/)\n    end\n\n    it \"should extract file to temporary directory\" do\n      expect(comm).to receive(:execute).with(/TMP_DIR/)\n    end\n\n    it \"should remove compressed file from guest\" do\n      expect(comm).to receive(:execute).with(/rm .*#{comp}/)\n    end\n\n    it \"should remove extraction directory from guest\" do\n      expect(comm).to receive(:execute).with(/rm .*TMP_DIR/)\n    end\n\n    it \"should create parent directories for destination\" do\n      expect(comm).to receive(:execute).with(/mkdir -p .*to'/)\n    end\n\n    context \"when type is directory\" do\n      before { opts[:type] = :directory }\n\n      it \"should create destination directory\" do\n        expect(comm).to receive(:execute).with(/mkdir -p .*destination'/)\n      end\n    end\n  end\n\n  describe \".decompress_zip\" do\n    let(:cap) { caps.get(:decompress_zip) }\n    let(:comp) { \"compressed_file\" }\n    let(:dest) { \"path/to/destination\" }\n    let(:opts) { {} }\n\n    before { allow(cap).to receive(:create_tmp_path).and_return(\"TMP_DIR\") }\n    after{ cap.decompress_zip(machine, comp, dest, opts) }\n\n    it \"should create temporary directory for extraction\" do\n      expect(cap).to receive(:create_tmp_path)\n    end\n\n    it \"should extract file with zip\" do\n      expect(comm).to receive(:execute).with(/zip/)\n    end\n\n    it \"should extract file to temporary directory\" do\n      expect(comm).to receive(:execute).with(/TMP_DIR/)\n    end\n\n    it \"should remove compressed file from guest\" do\n      expect(comm).to receive(:execute).with(/rm .*#{comp}/)\n    end\n\n    it \"should remove extraction directory from guest\" do\n      expect(comm).to receive(:execute).with(/rm .*TMP_DIR/)\n    end\n\n    it \"should create parent directories for destination\" do\n      expect(comm).to receive(:execute).with(/mkdir -p .*to'/)\n    end\n\n    context \"when type is directory\" do\n      before { opts[:type] = :directory }\n\n      it \"should create destination directory\" do\n        expect(comm).to receive(:execute).with(/mkdir -p .*destination'/)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/halt_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::Halt\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  context \"systemd not enabled\" do\n    before do\n      allow(machine).to receive(:communicate).and_return(comm)\n      allow(comm).to receive(:test).and_return(false)\n    end\n\n    after do\n      comm.verify_expectations!\n    end\n\n    describe \".halt\" do\n      let(:cap) { caps.get(:halt) }\n\n      it \"runs the shutdown command\" do\n        comm.expect_command(\"shutdown -h now\")\n        cap.halt(machine)\n      end\n\n      it \"does not raise an IOError\" do\n        comm.stub_command(\"shutdown -h now\", raise: IOError)\n        expect {\n          cap.halt(machine)\n        }.to_not raise_error\n      end\n\n      it \"does not raise a SSHDisconnected\" do\n        comm.stub_command(\"shutdown -h now\", raise: Vagrant::Errors::SSHDisconnected)\n        expect {\n          cap.halt(machine)\n        }.to_not raise_error\n      end\n    end\n\n    context \"systemd enabled\" do\n      before do\n        allow(machine).to receive(:communicate).and_return(comm)\n        allow(comm).to receive(:test).and_return(true)\n      end\n\n      after do\n        comm.verify_expectations!\n      end\n\n      describe \".halt\" do\n        let(:cap) { caps.get(:halt) }\n\n        it \"runs the shutdown command\" do\n          comm.expect_command(\"systemctl poweroff\")\n          cap.halt(machine)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/insert_public_key_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::InsertPublicKey\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".insert_public_key\" do\n    let(:cap) { caps.get(:insert_public_key) }\n\n    it \"inserts the public key\" do\n      cap.insert_public_key(machine, \"ssh-rsa ...\")\n      expect(comm.received_commands[0]).to match(/mkdir -p ~\\/.ssh/)\n      expect(comm.received_commands[0]).to match(/chmod 0700 ~\\/.ssh/)\n      expect(comm.received_commands[0]).to match(/cat '\\/tmp\\/vagrant-(.+)' >> ~\\/.ssh\\/authorized_keys/)\n      expect(comm.received_commands[0]).to match(/chmod 0600 ~\\/.ssh\\/authorized_keys/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/mount_nfs_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::MountNFS\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".mount_nfs_folder\" do\n    let(:cap) { caps.get(:mount_nfs_folder) }\n\n    let(:ip) { \"1.2.3.4\" }\n\n    let(:hostpath) { \"/host\" }\n    let(:guestpath) { \"/guest\" }\n\n    before do\n      allow(machine).to receive(:guest).and_return(\n        double(\"capability\", capability: guestpath)\n      )\n    end\n\n    it \"mounts the folder\" do\n      folders = {\n        \"/vagrant-nfs\" => {\n          type: :nfs,\n          guestpath: \"/guest\",\n          hostpath: \"/host\",\n        }\n      }\n      cap.mount_nfs_folder(machine, ip, folders)\n\n      expect(comm.received_commands[0]).to match(/mkdir -p #{guestpath}/)\n      expect(comm.received_commands[1]).to match(/1.2.3.4:#{hostpath} #{guestpath}/)\n    end\n\n    it \"mounts with options\" do\n      folders = {\n        \"/vagrant-nfs\" => {\n          type: :nfs,\n          guestpath: \"/guest\",\n          hostpath: \"/host\",\n          nfs_version: 2,\n          nfs_udp: true,\n        }\n      }\n      cap.mount_nfs_folder(machine, ip, folders)\n\n      expect(comm.received_commands[1]).to match(/mount -o vers=2,udp/)\n    end\n\n    it \"emits an event\" do\n      folders = {\n        \"/vagrant-nfs\" => {\n          type: :nfs,\n          guestpath: \"/guest\",\n          hostpath: \"/host\",\n        }\n      }\n      cap.mount_nfs_folder(machine, ip, folders)\n\n      expect(comm.received_commands[2]).to include(\n        \"/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guestpath}\")\n    end\n\n    it \"escapes host and guest paths\" do\n      folders = {\n        \"/vagrant-nfs\" => {\n          guestpath: \"/guest with spaces\",\n          hostpath: \"/host's\",\n        }\n      }\n      cap.mount_nfs_folder(machine, ip, folders)\n\n      expect(comm.received_commands[1]).to match(/host\\\\\\'s/)\n      expect(comm.received_commands[1]).to match(/guest\\\\\\ with\\\\\\ spaces/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/mount_shared_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::MountSharedFolder\" do\n  let(:machine) { double(\"machine\") }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:guest) { double(\"guest\") }\n\n  before do\n    allow(machine).to receive(:guest).and_return(guest)\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(guest).to receive(:capability).and_return(nil)\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/mount_smb_shared_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::MountSMBSharedFolder\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\", env: env, config: config) }\n  let(:env) { double(\"env\", host: host, ui: Vagrant::UI::Silent.new, data_dir: double(\"data_dir\")) }\n  let(:host) { double(\"host\") }\n  let(:guest) { double(\"guest\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:config) { double(\"config\", vm: vm) }\n  let(:vm) { double(\"vm\" ) }\n  let(:mount_owner){ \"vagrant\" }\n  let(:mount_group){ \"vagrant\" }\n  let(:mount_uid){ \"1000\" }\n  let(:mount_gid){ \"1000\" }\n  let(:mount_name){ \"vagrant\" }\n  let(:mount_guest_path){ \"/vagrant\" }\n  let(:folder_options) do\n    Vagrant::Plugin::V2::SyncedFolder::Collection[\n      {\n        owner: mount_owner,\n        group: mount_group,\n        smb_host: \"localhost\",\n        smb_username: \"user\",\n        smb_password: \"pass\",\n        plugin: folder_plugin\n      }\n    ]\n  end\n  let(:folder_plugin) { double(\"folder_plugin\") }\n  let(:cap){ caps.get(:mount_smb_shared_folder) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n    allow(host).to receive(:capability?).and_return(false)\n    allow(vm).to receive(:allow_fstab_modification).and_return(true)\n\n    allow(folder_plugin).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options).\n    and_return([\"uid=#{mount_uid},gid=#{mount_gid},sec=ntlmssp,credentials=/etc/smb_creds_id\", mount_uid, mount_gid])\n    allow(folder_plugin).to receive(:capability).with(:mount_type).and_return(\"cifs\")\n    allow(folder_plugin).to receive(:capability).with(:mount_name, mount_name, folder_options).and_return(\"//localhost/#{mount_name}\")\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".mount_smb_shared_folder\" do\n    before do\n      allow(comm).to receive(:sudo).with(any_args).and_return(0)\n      allow(comm).to receive(:execute).with(any_args)\n      allow(machine).to receive(:guest).and_return(guest)\n      allow(guest).to receive(:capability).with(:shell_expand_guest_path, mount_guest_path).and_return(mount_guest_path)\n      allow(ENV).to receive(:[]).and_call_original\n      allow(ENV).to receive(:[]).with(\"VAGRANT_DISABLE_SMBMFSYMLINKS\").and_return(false)\n      allow(ENV).to receive(:[]).with(\"GEM_SKIP\").and_return(false)\n      allow(cap).to receive(:display_mfsymlinks_warning)\n    end\n\n    it \"generates the expected default mount command\" do\n      expect(comm).to receive(:sudo).with(/mount -t cifs/, anything).and_return(0)\n      cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n    end\n\n    it \"creates directory on guest machine\" do\n      expect(comm).to receive(:sudo).with(\"mkdir -p #{mount_guest_path}\")\n      cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n    end\n\n    it \"writes username into guest credentials file\" do\n      expect(comm).to receive(:sudo).with(/smb_creds.*username=#{folder_options[:smb_username]}/m)\n      cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n    end\n\n    it \"writes password into guest credentials file\" do\n      expect(comm).to receive(:sudo).with(/smb_creds.*password=#{folder_options[:smb_password]}/m)\n      cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n    end\n\n    it \"removes the credentials file before completion\" do\n      allow(vm).to receive(:allow_fstab_modification).and_return(false)\n      expect(comm).to receive(:sudo).with(/rm.+smb_creds_.+/)\n      cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n    end\n\n    it \"sends upstart notification after mount\" do\n      expect(comm).to receive(:sudo).with(/emit/)\n      cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n    end\n  end\n\n  describe \".display_mfsymlinks_warning\" do\n    let(:gate_file){ double(\"gate\") }\n\n    before do\n      allow(env.data_dir).to receive(:join).and_return(gate_file)\n      allow(gate_file).to receive(:exist?).and_return(false)\n      allow(gate_file).to receive(:to_path).and_return(\"PATH\")\n      allow(FileUtils).to receive(:touch)\n    end\n\n    it \"should output warning message\" do\n      expect(env.ui).to receive(:warn).with(/VAGRANT_DISABLE_SMBMFSYMLINKS=1/)\n      cap.display_mfsymlinks_warning(env)\n    end\n\n    it \"should not output warning message if gate file exists\" do\n      allow(gate_file).to receive(:exist?).and_return(true)\n\n      expect(env.ui).not_to receive(:warn)\n      cap.display_mfsymlinks_warning(env)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/mount_virtual_box_shared_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::MountVirtualBoxSharedFolder\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:mount_owner){ \"vagrant\" }\n  let(:mount_group){ \"vagrant\" }\n  let(:mount_uid){ \"1000\" }\n  let(:mount_gid){ \"1000\" }\n  let(:mount_name){ \"vagrant\" }\n  let(:mount_guest_path){ \"/vagrant\" }\n  let(:folder_options) do\n    Vagrant::Plugin::V2::SyncedFolder::Collection[\n      {\n        owner: mount_owner,\n        group: mount_group,\n        hostpath: \"/host/directory/path\",\n        plugin: folder_plugin\n      }\n    ]\n  end\n  let(:cap){ caps.get(:mount_virtualbox_shared_folder) }\n  let(:folder_plugin) { double(\"folder_plugin\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".mount_virtualbox_shared_folder\" do\n\n    before do\n      allow(comm).to receive(:sudo).with(any_args)\n      allow(comm).to receive(:execute).with(any_args)\n    end\n\n    it \"generates the expected default mount command\" do\n      expect(folder_plugin).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options).\n        and_return([\"uid=#{mount_uid},gid=#{mount_gid}\", mount_uid, mount_gid])\n      expect(folder_plugin).to receive(:capability).with(:mount_type).and_return(\"vboxsf\")\n      expect(comm).to receive(:sudo).with(\"mount -t vboxsf -o uid=#{mount_uid},gid=#{mount_gid} #{mount_name} #{mount_guest_path}\", anything)\n\n      cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n    end\n\n    it \"automatically chown's the mounted directory on guest\" do\n      expect(folder_plugin).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options).\n        and_return([\"uid=#{mount_uid},gid=#{mount_gid}\", mount_uid, mount_gid])\n      expect(folder_plugin).to receive(:capability).with(:mount_type).and_return(\"vboxsf\")\n      expect(comm).to receive(:sudo).with(\"mount -t vboxsf -o uid=#{mount_uid},gid=#{mount_gid} #{mount_name} #{mount_guest_path}\", anything)\n      expect(comm).to receive(:sudo).with(\"chown #{mount_uid}:#{mount_gid} #{mount_guest_path}\")\n\n      cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n    end\n\n    context \"with upstart init\" do\n\n      it \"emits mount event\" do\n        expect(comm).to receive(:sudo).with(/initctl emit/)\n        expect(folder_plugin).to receive(:capability).with(:mount_type).and_return(\"vboxsf\")\n        expect(folder_plugin).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options).\n          and_return([\"uid=#{mount_uid},gid=#{mount_gid}\", mount_uid, mount_gid])\n\n        cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n      end\n    end\n\n    context \"with guest builtin vboxsf module\" do\n      let(:vbox_stderr){ <<-EOF\nmount.vboxsf cannot be used with mainline vboxsf; instead use:\n\n    mount -cit vboxsf NAME MOUNTPOINT\nEOF\n      }\n      it \"should perform guest mount using builtin module\" do\n        expect(folder_plugin).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options).\n          and_return([\"uid=#{mount_uid},gid=#{mount_gid}\", mount_uid, mount_gid])\n        expect(folder_plugin).to receive(:capability).with(:mount_type).and_return(\"vboxsf\")\n        expect(comm).to receive(:sudo).with(/mount -t vboxsf/, any_args).and_yield(:stderr, vbox_stderr).and_return(1)\n        expect(comm).to receive(:sudo).with(/mount -cit/, any_args)\n\n        cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)\n      end\n    end\n  end\n\n  describe \".unmount_virtualbox_shared_folder\" do\n\n    after { cap.unmount_virtualbox_shared_folder(machine, mount_guest_path, folder_options) }\n\n    it \"unmounts shared directory and deletes directory on guest\" do\n      expect(comm).to receive(:sudo).with(\"umount #{mount_guest_path}\", anything).and_return(0)\n      expect(comm).to receive(:sudo).with(\"rmdir #{mount_guest_path}\", anything)\n    end\n\n    it \"does not delete guest directory if unmount fails\" do\n      expect(comm).to receive(:sudo).with(\"umount #{mount_guest_path}\", anything).and_return(1)\n      expect(comm).not_to receive(:sudo).with(\"rmdir #{mount_guest_path}\", anything)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/network_interfaces_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::NetworkInterfaces\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".network_interfaces\" do\n    let(:cap){ caps.get(:network_interfaces) }\n\n    it \"sorts discovered classic interfaces\" do\n      expect(comm).to receive(:sudo).twice.and_yield(:stdout, \"eth1\\neth2\\neth0\")\n      expect(comm).to receive(:sudo).and_yield(:stdout, \"lo\")\n      result = cap.network_interfaces(machine)\n      expect(result).to eq([\"eth0\", \"eth1\", \"eth2\"])\n    end\n\n    it \"sorts discovered classic interfaces and handles trailing newline\" do\n      expect(comm).to receive(:sudo).twice.and_yield(:stdout, \"eth1\\neth2\\neth0\\n\")\n      expect(comm).to receive(:sudo).and_yield(:stdout, \"lo\")\n      result = cap.network_interfaces(machine)\n      expect(result).to eq([\"eth0\", \"eth1\", \"eth2\"])\n    end\n\n    it \"filters out lo interface\" do\n      expect(comm).to receive(:sudo).twice.and_yield(:stdout, \"lo\\neth0\")\n      expect(comm).to receive(:sudo).and_yield(:stdout, \"lo\")\n      result = cap.network_interfaces(machine)\n      expect(result).to eq([\"eth0\"])\n    end\n\n    it \"sorts discovered predictable network interfaces\" do\n      expect(comm).to receive(:sudo).twice.and_yield(:stdout, \"enp0s8\\nenp0s3\\nenp0s5\")\n      expect(comm).to receive(:sudo).and_yield(:stdout, \"lo\")\n      result = cap.network_interfaces(machine)\n      expect(result).to eq([\"enp0s3\", \"enp0s5\", \"enp0s8\"])\n    end\n\n    it \"sorts discovered classic interfaces naturally\" do\n      expect(comm).to receive(:sudo).twice.and_yield(:stdout, \"eth1\\neth2\\neth12\\neth0\\neth10\")\n      expect(comm).to receive(:sudo).and_yield(:stdout, \"lo\")\n      result = cap.network_interfaces(machine)\n      expect(result).to eq([\"eth0\", \"eth1\", \"eth2\", \"eth10\", \"eth12\"])\n    end\n\n    it \"sorts discovered predictable network interfaces naturally\" do\n      expect(comm).to receive(:sudo).twice.and_yield(:stdout, \"enp0s8\\nenp0s3\\nenp0s5\\nenp0s10\\nenp1s3\")\n      expect(comm).to receive(:sudo).and_yield(:stdout, \"lo\")\n      result = cap.network_interfaces(machine)\n      expect(result).to eq([\"enp0s3\", \"enp0s5\", \"enp0s8\", \"enp0s10\", \"enp1s3\"])\n    end\n\n    it \"sorts ethernet devices discovered with classic naming first in list\" do\n      expect(comm).to receive(:sudo).twice.and_yield(:stdout, \"eth1\\neth2\\ndocker0\\nbridge0\\neth0\")\n      expect(comm).to receive(:sudo).and_yield(:stdout, \"lo\")\n      result = cap.network_interfaces(machine)\n      expect(result).to eq([\"eth0\", \"eth1\", \"eth2\", \"bridge0\", \"docker0\"])\n    end\n\n    it \"sorts ethernet devices discovered with predictable network interfaces naming first in list\" do\n      expect(comm).to receive(:sudo).twice.and_yield(:stdout, \"enp0s8\\ndocker0\\nenp0s3\\nbridge0\\nenp0s5\")\n      expect(comm).to receive(:sudo).and_yield(:stdout, \"lo\")\n      result = cap.network_interfaces(machine)\n      expect(result).to eq([\"enp0s3\", \"enp0s5\", \"enp0s8\", \"bridge0\", \"docker0\"])\n    end\n\n    it \"sorts ethernet devices discovered with predictable network interfaces naming first in list with less\" do\n      expect(comm).to receive(:sudo).twice.and_yield(:stdout, \"enp0s3\\nenp0s8\\ndocker0\")\n      expect(comm).to receive(:sudo).and_yield(:stdout, \"lo\")\n      result = cap.network_interfaces(machine)\n      expect(result).to eq([\"enp0s3\", \"enp0s8\", \"docker0\"])\n    end\n\n    it \"does not include ethernet devices aliases within prefix device listing\" do\n      expect(comm).to receive(:sudo).twice.and_yield(:stdout, \"eth1\\neth2\\ndocker0\\nbridge0\\neth0\\ndocker1\\neth0:0\")\n      expect(comm).to receive(:sudo).and_yield(:stdout, \"lo\")\n      result = cap.network_interfaces(machine)\n      expect(result).to eq([\"eth0\", \"eth1\", \"eth2\", \"bridge0\", \"docker0\", \"docker1\", \"eth0:0\"])\n    end\n\n    it \"does not include ethernet devices aliases within prefix device listing with dot separators\" do\n      expect(comm).to receive(:sudo).twice.and_yield(:stdout, \"eth1\\neth2\\ndocker0\\nbridge0\\neth0\\ndocker1\\neth0.1@eth0\")\n      expect(comm).to receive(:sudo).and_yield(:stdout, \"lo\")\n      result = cap.network_interfaces(machine)\n      expect(result).to eq([\"eth0\", \"eth1\", \"eth2\", \"bridge0\", \"docker0\", \"docker1\", \"eth0.1@eth0\"])\n    end\n\n    it \"properly sorts non-consistent device name formats\" do\n      expect(comm).to receive(:sudo).twice.and_yield(:stdout, \"eth0\\neth1\\ndocker0\\nveth437f7f9\\nveth06b3e44\\nveth8bb7081\")\n      expect(comm).to receive(:sudo).and_yield(:stdout, \"lo\")\n      result = cap.network_interfaces(machine)\n      expect(result).to eq([\"eth0\", \"eth1\", \"docker0\", \"veth8bb7081\", \"veth437f7f9\", \"veth06b3e44\"])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/nfs_client_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::NFSClient\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".nfs_client_installed\" do\n    let(:cap) { caps.get(:nfs_client_installed) }\n\n    it \"installs nfs client utilities\" do\n      comm.expect_command(\"test -x /sbin/mount.nfs\")\n      cap.nfs_client_installed(machine)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/persist_mount_shared_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::PersistMountSharedFolder\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:options_gid){ '1234' }\n  let(:options_uid){ '1234' }\n  let(:cap){ caps.get(:persist_mount_shared_folder) }\n  let(:folder_plugin){ double(\"folder_plugin\") }\n  let(:ssh_info) {{\n    :username => \"vagrant\"\n  }}\n  let (:fstab_folders) {\n    Vagrant::Plugin::V2::SyncedFolder::Collection[\n      {\n        \"test1\" => {guestpath: \"/test1\", hostpath: \"/my/host/path\", disabled: false, plugin: folder_plugin,\n          __vagrantfile: true, owner: \"vagrant\", group: \"vagrant\", mount_options: [\"uid=#{options_uid}\", \"gid=#{options_gid}\"]},\n        \"vagrant\" => {guestpath: \"/vagrant\", hostpath: \"/my/host/vagrant\", disabled: false, __vagrantfile: true,\n          owner: \"vagrant\", group: \"vagrant\", mount_options: [\"uid=#{options_uid}\", \"gid=#{options_gid}}\"], plugin: folder_plugin}\n      }\n    ]\n  }\n  let (:folders) { {\n    :folder_type => fstab_folders\n  } }\n  let(:expected_mount_options) { \"uid=#{options_uid},gid=#{options_gid}\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n    allow(machine).to receive(:ssh_info).and_return(ssh_info)\n    allow(folder_plugin).to receive(:capability?).with(:mount_name).and_return(false)\n    allow(folder_plugin).to receive(:capability?).with(:mount_type).and_return(true)\n    allow(folder_plugin).to receive(:capability).with(:mount_options, any_args).\n      and_return([\"uid=#{options_uid},gid=#{options_gid}\", options_uid, options_gid])\n    allow(folder_plugin).to receive(:capability).with(:mount_type).and_return(\"vboxsf\")\n    allow(cap).to receive(:fstab_exists?).and_return(true)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".persist_mount_shared_folder\" do\n\n    let(:ui){ Vagrant::UI::Silent.new }\n\n    before do\n      allow(comm).to receive(:sudo).with(any_args)\n      allow(machine).to receive(:ui).and_return(ui)\n    end\n\n    it \"inserts folders into /etc/fstab\" do\n      expected_entry_vagrant = \"vagrant /vagrant vboxsf #{expected_mount_options} 0 0\"\n      expected_entry_test = \"test1 /test1 vboxsf #{expected_mount_options} 0 0\"\n      expect(cap).to receive(:remove_vagrant_managed_fstab)\n      expect(comm).to receive(:sudo).with(/#{expected_entry_test}\\n#{expected_entry_vagrant}/)\n\n      cap.persist_mount_shared_folder(machine, folders)\n    end\n\n    it \"does not insert an empty set of folders\" do\n      expect(cap).to receive(:remove_vagrant_managed_fstab)\n      cap.persist_mount_shared_folder(machine, nil)\n    end\n\n    context \"folders do not support mount_type capability\" do\n      before do\n        allow(folder_plugin).to receive(:capability?).with(:mount_type).and_return(false)\n      end\n\n      it \"does not inserts folders into /etc/fstab\" do\n        expect(cap).to receive(:remove_vagrant_managed_fstab)\n        expect(comm).not_to receive(:sudo).with(/echo '' >> \\/etc\\/fstab/)\n        cap.persist_mount_shared_folder(machine, folders)\n      end\n    end\n\n    context \"fstab does not exist\" do\n      before do\n        allow(cap).to receive(:fstab_exists?).and_return(false)\n        # Ensure /etc/fstab is not being modified\n        expect(comm).not_to receive(:sudo).with(/sed -i .? \\/etc\\/fstab/)\n      end\n\n      it \"creates /etc/fstab\" do\n        expect(cap).to receive(:remove_vagrant_managed_fstab)\n        expect(comm).to receive(:sudo).with(/>> \\/etc\\/fstab/)\n        cap.persist_mount_shared_folder(machine, [])\n      end\n\n      it \"does not remove contents of /etc/fstab\" do\n        expect(cap).to receive(:remove_vagrant_managed_fstab)\n        expect(comm).not_to receive(:sudo).with(/echo '' >> \\/etc\\/fstab/)\n        cap.persist_mount_shared_folder(machine, nil)\n      end\n    end\n\n    context \"smb folder\" do\n      let (:fstab_folders) {\n        Vagrant::Plugin::V2::SyncedFolder::Collection[\n          {\n            \"test1\" => {guestpath: \"/test1\", hostpath: \"/my/host/path\", disabled: false, plugin: folder_plugin,\n              __vagrantfile: true, owner: \"vagrant\", group: \"vagrant\", smb_host: \"192.168.42.42\", smb_id: \"vtg-id1\" },\n            \"vagrant\" => {guestpath: \"/vagrant\", hostpath: \"/my/host/vagrant\", disabled: false, plugin: folder_plugin,\n               __vagrantfile: true, owner: \"vagrant\", group: \"vagrant\", smb_host: \"192.168.42.42\", smb_id: \"vtg-id2\"}\n          }\n        ]\n      }\n      let (:folders) { {\n        :smb => fstab_folders\n      } }\n\n      context \"folder with mount_name cap\" do\n        before do\n          allow(folder_plugin).to receive(:capability).with(:mount_type).and_return(\"cifs\")\n          allow(folder_plugin).to receive(:capability?).with(:mount_name).and_return(true)\n          allow(folder_plugin).to receive(:capability).with(:mount_name, instance_of(String), any_args).and_return(\"//192.168.42.42/dummyname\")\n        end\n\n        it \"inserts folders into /etc/fstab\" do\n          expected_entry_vagrant = \"//192.168.42.42/dummyname /vagrant cifs #{expected_mount_options} 0 0\"\n          expected_entry_test = \"//192.168.42.42/dummyname /test1 cifs #{expected_mount_options} 0 0\"\n          expect(cap).to receive(:remove_vagrant_managed_fstab)\n          expect(comm).to receive(:sudo).with(/#{expected_entry_test}\\n#{expected_entry_vagrant}/)\n\n          cap.persist_mount_shared_folder(machine, folders)\n        end\n      end\n    end\n  end\n  describe \".remove_vagrant_managed_fstab\" do\n    let(:fstab_exists) { true }\n\n    before do\n      allow(comm).to receive(:sudo).with(any_args)\n      allow(cap).to receive(:fstab_exists?).and_return(fstab_exists)\n    end\n\n    it \"removes vagrant managed fstab entries\" do\n      expect(cap).to receive(:contains_vagrant_data?).and_return(true)\n      expect(comm).to receive(:sudo).with(\"sed -i '/#VAGRANT-BEGIN/,/#VAGRANT-END/d' /etc/fstab\")\n      cap.remove_vagrant_managed_fstab(machine)\n    end\n\n    context \"fstab does not exist\" do\n      let(:fstab_exists) { false }\n\n      it \"does not try to remove fstab entries\" do\n        expect(cap).not_to receive(:contains_vagrant_data?)\n        expect(comm).not_to receive(:sudo).with(\"sed -i '/#VAGRANT-BEGIN/,/#VAGRANT-END/d' /etc/fstab\")\n        cap.remove_vagrant_managed_fstab(machine)\n      end\n    end\n\n    context \"fstab does not contain vagrant data\" do\n      before do\n        expect(comm).to receive(:test).with(\"grep '#VAGRANT-BEGIN' /etc/fstab\").and_return(false)\n      end\n\n      it \"does not try to remove fstab entries\" do\n        expect(comm).not_to receive(:sudo).with(\"sed -i '/#VAGRANT-BEGIN/,/#VAGRANT-END/d' /etc/fstab\")\n        cap.remove_vagrant_managed_fstab(machine)\n      end\n    end\n\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/port_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::Port\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".port_open_check\" do\n    let(:cap) { caps.get(:port_open_check) }\n\n    it \"checks if the port is open\" do\n      port = 8080\n      comm.expect_command(\"nc -z -w2 127.0.0.1 #{port}\")\n      cap.port_open_check(machine, port)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/reboot_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/guests/linux/cap/reboot\")\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::Reboot\" do\n  let(:described_class) do\n    VagrantPlugins::GuestLinux::Plugin.components.guest_capabilities[:linux].get(:wait_for_reboot)\n  end\n\n  let(:machine) { double(\"machine\", guest: guest) }\n  let(:guest) { double(\"guest\") }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:ui) { Vagrant::UI::Silent.new }\n\n  context \"systemd not enabled\" do\n    before do\n      allow(machine).to receive(:communicate).and_return(communicator)\n      allow(machine).to receive(:guest).and_return(guest)\n      allow(machine.guest).to receive(:ready?).and_return(true)\n      allow(machine).to receive(:ui).and_return(ui)\n      allow(communicator).to receive(:test).and_return(false)\n    end\n\n    after do\n      communicator.verify_expectations!\n    end\n\n    describe \".reboot\" do\n      it \"reboots the vm\" do\n        allow(communicator).to receive(:execute)\n\n        expect(communicator).to receive(:execute).with(/reboot/, nil).and_return(0)\n        expect(described_class).to receive(:wait_for_reboot)\n\n        described_class.reboot(machine)\n      end\n\n      context \"user output\" do\n        before do\n          allow(communicator).to receive(:execute)\n          allow(described_class).to receive(:wait_for_reboot)\n        end\n\n        after { described_class.reboot(machine) }\n\n        it \"sends message to user that guest is rebooting\" do\n          expect(ui).to receive(:info).and_call_original\n        end\n      end\n    end\n\n    context \"systemd enabled\" do\n      before do\n        allow(machine).to receive(:communicate).and_return(communicator)\n        allow(machine).to receive(:guest).and_return(guest)\n        allow(machine.guest).to receive(:ready?).and_return(true)\n        allow(machine).to receive(:ui).and_return(ui)\n        allow(communicator).to receive(:test).and_return(true)\n      end\n\n      after do\n        communicator.verify_expectations!\n      end\n\n      it \"reboots the vm\" do\n        allow(communicator).to receive(:execute)\n\n        expect(communicator).to receive(:execute).with(/systemctl reboot/, nil).and_return(0)\n        expect(described_class).to receive(:wait_for_reboot)\n\n        described_class.reboot(machine)\n      end\n    end\n\n    context \"reboot configuration\" do\n      before do\n        allow(communicator).to receive(:execute)\n        expect(communicator).to receive(:execute).with(/reboot/, nil).and_return(0)\n        allow(described_class).to receive(:sleep).and_return(described_class::WAIT_SLEEP_TIME)\n        allow(described_class).to receive(:wait_for_reboot).and_raise(Vagrant::Errors::MachineGuestNotReady)\n      end\n\n      context \"default retry duration value\" do\n        let(:max_retries) { (described_class::DEFAULT_MAX_REBOOT_RETRY_DURATION / described_class::WAIT_SLEEP_TIME) + 2 }\n\n        it \"should receive expected number of wait_for_reboot calls\" do\n          expect(described_class).to receive(:wait_for_reboot).exactly(max_retries).times\n          expect { described_class.reboot(machine) }.to raise_error(Vagrant::Errors::MachineGuestNotReady)\n        end\n      end\n\n      context \"with custom retry duration value\" do\n        let(:duration) { 10 }\n        let(:max_retries) { (duration / described_class::WAIT_SLEEP_TIME) + 2 }\n\n        before do\n          expect(ENV).to receive(:fetch).with(\"VAGRANT_MAX_REBOOT_RETRY_DURATION\", anything).and_return(duration)\n        end\n\n        it \"should receive expected number of wait_for_reboot calls\" do\n          expect(described_class).to receive(:wait_for_reboot).exactly(max_retries).times\n          expect { described_class.reboot(machine) }.to raise_error(Vagrant::Errors::MachineGuestNotReady)\n        end\n      end\n    end\n  end\n\n  describe \".wait_for_reboot\" do\n    before do\n      allow(machine).to receive(:communicate).and_return(communicator)\n      allow(communicator).to receive(:execute).and_return(0)\n      allow(guest).to receive(:ready?).and_return(false)\n    end\n\n    context \"when guest is ready\" do\n      before { expect(guest).to receive(:ready?).and_return(true) }\n\n      it \"should sleep\" do\n        expect(described_class).to receive(:sleep).with(10)\n        described_class.wait_for_reboot(machine)\n      end\n\n      context \"when check script fails\" do\n        before { expect(communicator).to receive(:execute).with(/grep/, any_args).and_return(1) }\n\n        it \"should not sleep\" do\n          expect(described_class).not_to receive(:sleep)\n          described_class.wait_for_reboot(machine)\n        end\n      end\n\n      context \"when communicator raises an error\" do\n        let(:error) { Class.new(StandardError) }\n\n        before do\n          expect(communicator).to receive(:execute).with(/grep/, any_args).and_raise(error)\n          expect(guest).to receive(:ready?).and_return(true)\n        end\n\n        it \"should sleep once for exception and once for the guest being ready\" do\n          expect(described_class).to receive(:sleep).with(10).twice\n          described_class.wait_for_reboot(machine)\n        end\n\n        context \"when communicator raises error more than once\" do\n          before { expect(communicator).to receive(:execute).with(/grep/, any_args).and_raise(error) }\n\n          it \"should sleep once and raise error\" do\n            expect(described_class).to receive(:sleep).with(10)\n            expect { described_class.wait_for_reboot(machine) }.to raise_error(error)\n          end\n        end\n      end\n    end\n\n    context \"when guest is not ready\" do\n      before { expect(guest).to receive(:ready?).and_return(false) }\n\n      it \"should not sleep\" do\n        expect(described_class).not_to receive(:sleep)\n        described_class.wait_for_reboot(machine)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/remove_public_key_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::RemovePublicKey\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".remove_public_key\" do\n    let(:cap) { caps.get(:remove_public_key) }\n\n    it \"removes the public key\" do\n      cap.remove_public_key(machine, \"ssh-rsa ...\")\n      expect(comm.received_commands[0]).to match(/grep -v -x -f '\\/tmp\\/vagrant-(.+)' ~\\/\\.ssh\\/authorized_keys > ~\\/.ssh\\/authorized_keys\\.tmp/)\n      expect(comm.received_commands[0]).to match(/mv ~\\/.ssh\\/authorized_keys\\.tmp ~\\/.ssh\\/authorized_keys/)\n      expect(comm.received_commands[0]).to match(/chmod 0600 ~\\/.ssh\\/authorized_keys/)\n      expect(comm.received_commands[0]).to match(/rm -f '\\/tmp\\/vagrant-(.+)'/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::Rsync\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:guest_directory){ \"/guest/directory/path\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".rsync_installed\" do\n    let(:cap) { caps.get(:rsync_installed) }\n\n    it \"checks if the command is installed\" do\n      comm.expect_command(\"which rsync\")\n      cap.rsync_installed(machine)\n    end\n  end\n\n  describe \".rsync_command\" do\n    let(:cap) { caps.get(:rsync_command) }\n\n    it \"provides the rsync command to use\" do\n      expect(cap.rsync_command(machine)).to eq(\"sudo rsync\")\n    end\n  end\n\n  describe \".rsync_pre\" do\n    let(:cap) { caps.get(:rsync_pre) }\n\n    it \"creates target directory on guest\" do\n      comm.expect_command(\"mkdir -p #{guest_directory}\")\n      cap.rsync_pre(machine, :guestpath => guest_directory)\n    end\n  end\n\n  describe \".rsync_post\" do\n    let(:cap) { caps.get(:rsync_post) }\n    let(:host_directory){ '.' }\n    let(:owner) { \"vagrant-user\" }\n    let(:group) { \"vagrant-group\" }\n    let(:excludes) { false }\n    let(:options) do\n      {\n        hostpath: host_directory,\n        guestpath: guest_directory,\n        owner: owner,\n        group: group,\n        exclude: excludes\n      }\n    end\n\n    it \"chowns files within the guest directory\" do\n      comm.expect_command(\n        \"find #{guest_directory} '!' -type l -a '(' ! -user #{owner} -or \" \\\n          \"! -group #{group} ')' -exec chown #{owner}:#{group} '{}' +\"\n      )\n      cap.rsync_post(machine, options)\n    end\n\n    context \"with excludes provided\" do\n      let(:excludes){ [\"tmp\", \"state/*\", \"path/with a/space\"] }\n\n      it \"ignores files that are excluded\" do\n        # comm.expect_command(\n        #   \"find #{guest_directory} -path #{Shellwords.escape(File.join(guest_directory, excludes.first))} -prune -o \" \\\n        #     \"-path #{Shellwords.escape(File.join(guest_directory, excludes.last))} -prune -o '!' \" \\\n        #     \"-path -type l -a '(' ! -user \" \\\n        #     \"#{owner} -or ! -group #{group} ')' -exec chown #{owner}:#{group} '{}' +\"\n        # )\n        cap.rsync_post(machine, options)\n        excludes.each do |ex_path|\n          expect(comm.received_commands.first).to include(\"-path #{Shellwords.escape(File.join(guest_directory, ex_path))} -prune\")\n        end\n      end\n\n      it \"properly escapes excluded directories\" do\n        cap.rsync_post(machine, options)\n        exclude_with_space = excludes.detect{|ex| ex.include?(' ')}\n        escaped_exclude_with_space = Shellwords.escape(exclude_with_space)\n        expect(comm.received_commands.first).not_to include(exclude_with_space)\n        expect(comm.received_commands.first).to include(escaped_exclude_with_space)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/linux/cap/shell_expand_guest_path_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestLinux::Cap::ShellExpandGuestPath\" do\n  let(:caps) do\n    VagrantPlugins::GuestLinux::Plugin\n      .components\n      .guest_capabilities[:linux]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  describe \"#shell_expand_guest_path\" do\n    let(:cap) { caps.get(:shell_expand_guest_path) }\n\n    it \"expands the path\" do\n      path = \"/home/vagrant/folder\"\n      allow(machine.communicate).to receive(:execute).\n        with(any_args).and_yield(:stdout, \"/home/vagrant/folder\")\n\n      cap.shell_expand_guest_path(machine, path)\n    end\n\n    it \"expands a path with tilde\" do\n      path = \"~/folder\"\n      allow(machine.communicate).to receive(:execute).\n          with(any_args).and_yield(:stdout, \"/home/vagrant/folder\")\n\n      cap.shell_expand_guest_path(machine, path)\n    end\n\n    it \"raises an exception if no path was detected\" do\n      path = \"/home/vagrant/folder\"\n      expect { cap.shell_expand_guest_path(machine, path) }.\n        to raise_error(Vagrant::Errors::ShellExpandFailed)\n    end\n\n    it \"returns a path with a space in it\" do\n      path = \"/home/vagrant folder/folder\"\n      path_with_spaces = \"/home/vagrant\\\\ folder/folder\"\n      allow(machine.communicate).to receive(:execute).\n        with(any_args).and_yield(:stdout, path_with_spaces)\n\n      expect(machine.communicate).to receive(:execute).with(\"echo; printf #{path_with_spaces}\")\n      cap.shell_expand_guest_path(machine, path)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/netbsd/cap/shell_expand_guest_path_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestNetBSD::Cap::ShellExpandGuestPath\" do\n  let(:caps) do\n    VagrantPlugins::GuestNetBSD::Plugin\n      .components\n      .guest_capabilities[:netbsd]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  describe \"#shell_expand_guest_path\" do\n    let(:cap) { caps.get(:shell_expand_guest_path) }\n\n    it \"expands the path\" do\n      path = \"/home/vagrant/folder\"\n      allow(machine.communicate).to receive(:execute).\n        with(any_args).and_yield(:stdout, \"/home/vagrant/folder\")\n\n      cap.shell_expand_guest_path(machine, path)\n    end\n\n    it \"raises an exception if no path was detected\" do\n      path = \"/home/vagrant/folder\"\n      expect { cap.shell_expand_guest_path(machine, path) }.\n        to raise_error(Vagrant::Errors::ShellExpandFailed)\n    end\n\n    it \"returns a path with a space in it\" do\n      path = \"/home/vagrant folder/folder\"\n      path_with_spaces = \"/home/vagrant\\\\ folder/folder\"\n      allow(machine.communicate).to receive(:execute).\n        with(any_args).and_yield(:stdout, path_with_spaces)\n\n      expect(machine.communicate).to receive(:execute).with(\"printf #{path_with_spaces}\")\n      cap.shell_expand_guest_path(machine, path)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/omnios/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestOmniOS::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestOmniOS::Plugin\n      .components\n      .guest_capabilities[:omnios]\n      .get(:change_host_name)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:name) { \"banana-rama.example.com\" }\n  let(:basename) { \"banana-rama\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    context \"minimal network config\" do \n      let(:networks) { [\n        [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n      ] }\n\n      before do \n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n      end\n\n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n\n        described_class.change_host_name(machine, name)\n        expect(comm.received_commands[2]).to match(/hostname '#{name}'/)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n\n        described_class.change_host_name(machine, name)\n        expect(comm).to_not receive(:sudo).with(/hostname '#{name}'/)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/omnios/cap/mount_nfs_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestOmniOS::Cap:RSync\" do\n  let(:caps) do\n    VagrantPlugins::GuestOmniOS::Plugin\n      .components\n      .guest_capabilities[:omnios]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".mount_nfs_folder\" do\n    let(:cap) { caps.get(:mount_nfs_folder) }\n\n    let(:ip) { \"1.2.3.4\" }\n\n    let(:hostpath) { \"/host\" }\n    let(:guestpath) { \"/guest\" }\n\n    it \"mounts the folder\" do\n      folders = {\n        \"/vagrant-nfs\" => {\n          type: :nfs,\n          guestpath: \"/guest\",\n          hostpath: \"/host\",\n        }\n      }\n      cap.mount_nfs_folder(machine, ip, folders)\n\n      expect(comm.received_commands[0]).to match(/mkdir -p '#{guestpath}'/)\n      expect(comm.received_commands[0]).to match(/'1.2.3.4:#{hostpath}' '#{guestpath}'/)\n    end\n\n    it \"mounts with options\"\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/omnios/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestOmniOS::Cap:RSync\" do\n  let(:caps) do\n    VagrantPlugins::GuestOmniOS::Plugin\n      .components\n      .guest_capabilities[:omnios]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".rsync_install\" do\n    let(:cap) { caps.get(:rsync_install) }\n\n    it \"installs rsync\" do\n      cap.rsync_install(machine)\n      expect(comm.received_commands[0]).to match(/pkg install rsync/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/openbsd/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestOpenBSD::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestOpenBSD::Plugin\n      .components\n      .guest_capabilities[:openbsd]\n      .get(:change_host_name)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:name) { \"banana-rama.example.com\" }\n  let(:basename) { \"banana-rama\" }\n  let(:networks) {}\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n    allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    context \"minimal network config\" do\n      let(:networks) { [\n        [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n      ] }\n\n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n        described_class.change_host_name(machine, name)\n        expect(comm.received_commands[1]).to match(/hostname '#{name}'/)\n        expect(described_class).to_not receive(:add_hostname_to_loopback_interface)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n        described_class.change_host_name(machine, name)\n        expect(comm).to_not receive(:sudo).with(/hostname '#{name}'/)\n        expect(described_class).to_not receive(:add_hostname_to_loopback_interface)\n      end\n    end\n\n    context \"multiple networks configured with hostname\" do \n      it \"adds a new entry only for the hostname\" do \n        networks = [\n          [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}],\n          [:public_network, {:ip=>\"192.168.0.1\", :hostname=>true, :protocol=>\"tcp\", :id=>\"93a4ad88-0774-4127-a161-ceb715ff372f\"}],\n          [:public_network, {:ip=>\"192.168.0.2\", :protocol=>\"tcp\", :id=>\"5aebe848-7d85-4425-8911-c2003d924120\"}]\n        ]\n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n        expect(described_class).to receive(:replace_host)\n        expect(described_class).to_not receive(:add_hostname_to_loopback_interface)\n        described_class.change_host_name(machine, name)\n      end\n\n      it \"appends an entry to the loopback interface\" do \n        networks = [\n          [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}],\n          [:public_network, {:ip=>\"192.168.0.1\", :protocol=>\"tcp\", :id=>\"93a4ad88-0774-4127-a161-ceb715ff372f\"}],\n          [:public_network, {:ip=>\"192.168.0.2\", :protocol=>\"tcp\", :id=>\"5aebe848-7d85-4425-8911-c2003d924120\"}]\n        ]\n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n        expect(described_class).to_not receive(:replace_host)\n        expect(described_class).to receive(:add_hostname_to_loopback_interface).once\n        described_class.change_host_name(machine, name)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/openbsd/cap/halt_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestOpenBSD::Cap::Halt\" do\n  let(:caps) do\n    VagrantPlugins::GuestOpenBSD::Plugin\n      .components\n      .guest_capabilities[:openbsd]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".halt\" do\n    let(:cap) { caps.get(:halt) }\n\n    it \"runs the shutdown command\" do\n      comm.expect_command(\"/sbin/shutdown -p -h now\")\n      cap.halt(machine)\n    end\n\n    it \"ignores an IOError\" do\n      comm.stub_command(\"/sbin/shutdown -p -h now\", raise: IOError)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n\n    it \"ignores a Vagrant::Errors::SSHDisconnected\" do\n      comm.stub_command(\"/sbin/shutdown -p -h now\", raise: Vagrant::Errors::SSHDisconnected)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/openbsd/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestOpenBSD::Cap::RSync\" do\n  let(:caps) do\n    VagrantPlugins::GuestOpenBSD::Plugin\n      .components\n      .guest_capabilities[:openbsd]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".rsync_install\" do\n    let(:cap) { caps.get(:rsync_install) }\n\n    describe \"successful installation\" do\n      it \"installs rsync\" do\n        cap.rsync_install(machine)\n        expect(comm.received_commands[0]).to match(/pkg_add -I rsync/)\n        expect(comm.received_commands[1]).to match(/pkg_info/)\n      end\n    end\n\n    describe \"failure installation\" do\n      before do\n        expect(comm).to receive(:execute).and_raise(Vagrant::Errors::RSyncNotInstalledInGuest, {command: '', output: ''})\n      end\n\n      it \"raises custom exception\" do\n        expect{ cap.rsync_install(machine) }.to raise_error(Vagrant::Errors::RSyncNotInstalledInGuest)\n      end\n    end\n  end\n\n  describe \".rsync_installed\" do\n    let(:cap) { caps.get(:rsync_installed) }\n\n    it \"checks if rsync is installed\" do\n      comm.expect_command(\"which rsync\")\n      cap.rsync_installed(machine)\n    end\n  end\n\n  describe \".rsync_command\" do\n    let(:cap) { caps.get(:rsync_command) }\n\n    it \"defaults to 'sudo rsync'\" do\n      expect(cap.rsync_command(machine)).to eq(\"sudo rsync\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/openbsd/cap/shell_expand_guest_path_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestOpenBSD::Cap::ShellExpandGuestPath\" do\n  let(:caps) do\n    VagrantPlugins::GuestOpenBSD::Plugin\n      .components\n      .guest_capabilities[:openbsd]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  describe \"#shell_expand_guest_path\" do\n    let(:cap) { caps.get(:shell_expand_guest_path) }\n\n    it \"expands the path\" do\n      path = \"/home/vagrant/folder\"\n      allow(machine.communicate).to receive(:execute).\n        with(any_args).and_yield(:stdout, \"/home/vagrant/folder\")\n\n      cap.shell_expand_guest_path(machine, path)\n    end\n\n    it \"raises an exception if no path was detected\" do\n      path = \"/home/vagrant/folder\"\n      expect { cap.shell_expand_guest_path(machine, path) }.\n        to raise_error(Vagrant::Errors::ShellExpandFailed)\n    end\n\n    it \"returns a path with a space in it\" do\n      path = \"/home/vagrant folder/folder\"\n      path_with_spaces = \"/home/vagrant\\\\ folder/folder\"\n      allow(machine.communicate).to receive(:execute).\n        with(any_args).and_yield(:stdout, path_with_spaces)\n\n      expect(machine.communicate).to receive(:execute).with(\"printf #{path_with_spaces}\")\n      cap.shell_expand_guest_path(machine, path)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/openwrt/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestOpenWrt::Cap::ChangeHostName\" do\n  let(:caps) do\n    VagrantPlugins::GuestOpenWrt::Plugin\n        .components\n        .guest_capabilities[:openwrt]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    let(:cap) { caps.get(:change_host_name) }\n\n    it \"changes the hostname if appropriate\" do\n      cap.change_host_name(machine, \"testhost\")\n\n      expect(comm.received_commands[0]).to match(/uci get system\\.@system\\[0\\].hostname | grep '^testhost$'/)\n      expect(comm.received_commands[1]).to match(/uci set system.@system\\[0\\].hostname='testhost'/)\n      expect(comm.received_commands[1]).to match(/uci commit system/)\n      expect(comm.received_commands[1]).to match(/\\/etc\\/init.d\\/system reload/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/openwrt/cap/halt_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestOpenWrt::Cap::Halt\" do\n  let(:plugin) { VagrantPlugins::GuestOpenWrt::Plugin.components.guest_capabilities[:openwrt].get(:halt) }\n  let(:machine) { double(\"machine\") }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:shutdown_command){ \"halt\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n  end\n\n  after do\n    communicator.verify_expectations!\n  end\n\n  describe \".halt\" do\n    it \"sends a shutdown signal\" do\n      communicator.expect_command(shutdown_command)\n      plugin.halt(machine)\n    end\n\n    it \"ignores an IOError\" do\n      communicator.stub_command(shutdown_command, raise: IOError)\n      expect {\n        plugin.halt(machine)\n      }.to_not raise_error\n    end\n\n    it \"ignores a Vagrant::Errors::SSHDisconnected\" do\n      communicator.stub_command(shutdown_command, raise: Vagrant::Errors::SSHDisconnected)\n      expect {\n        plugin.halt(machine)\n      }.to_not raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/openwrt/cap/insert_public_key_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestOpenWrt::Cap::InsertPublicKey\" do\n  let(:caps) do\n    VagrantPlugins::GuestOpenWrt::Plugin\n        .components\n        .guest_capabilities[:openwrt]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".insert_public_key\" do\n    let(:cap) { caps.get(:insert_public_key) }\n\n    it \"inserts the public key\" do\n      cap.insert_public_key(machine, \"ssh-rsa ...\")\n\n      expect(comm.received_commands[0]).to match(/printf 'ssh-rsa ...\\\\n' >> \\/etc\\/dropbear\\/authorized_keys/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/openwrt/cap/remove_public_key_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestOpenWrt::Cap::RemovePublicKey\" do\n  let(:caps) do\n    VagrantPlugins::GuestOpenWrt::Plugin\n      .components\n      .guest_capabilities[:openwrt]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".remove_public_key\" do\n    let(:cap) { caps.get(:remove_public_key) }\n\n    it \"removes the public key\" do\n      cap.remove_public_key(machine, \"ssh-rsa keyvalue comment\")\n      expect(comm.received_commands[0]).to match(/if test -f \\/etc\\/dropbear\\/authorized_keys ; then/)\n      expect(comm.received_commands[0]).to match(/sed -i '\\/\\^.*ssh-rsa keyvalue comment.*\\$\\/d' \\/etc\\/dropbear\\/authorized_keys/)\n      expect(comm.received_commands[0]).to match(/fi/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/openwrt/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::VagrantPlugins::Cap::Rsync\" do\n  let(:caps) do\n    VagrantPlugins::GuestOpenWrt::Plugin\n        .components\n        .guest_capabilities[:openwrt]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:guest_directory) { \"/guest/directory/path\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".rsync_installed\" do\n    let(:cap) { caps.get(:rsync_installed) }\n\n    describe \"when rsync is in the path\" do\n      it \"is true\" do\n        comm.stub_command(\"which rsync\", stdout: '/usr/bin/rsync', exit_code: 0)\n        expect(cap.rsync_installed(machine)).to be true\n      end\n    end\n\n    describe \"when rsync is not in the path\" do\n      it \"is false\" do\n        comm.stub_command(\"which rsync\", stdout: '', exit_code: 1)\n        expect(cap.rsync_installed(machine)).to be false\n      end\n    end\n  end\n\n  describe \".rsync_install\" do\n    let(:cap) { caps.get(:rsync_install) }\n\n    it \"installs rsync\" do\n      cap.rsync_install(machine)\n\n      expect(comm.received_commands[0]).to match(/opkg update/)\n      expect(comm.received_commands[0]).to match(/opkg install rsync/)\n    end\n  end\n\n  describe \".rsync_command\" do\n    let(:cap) { caps.get(:rsync_command) }\n\n    it \"provides the rsync command to use\" do\n      expect(cap.rsync_command(machine)).to eq(\"rsync -zz\")\n    end\n  end\n\n  describe \".rsync_pre\" do\n    let(:cap) { caps.get(:rsync_pre) }\n\n    it \"creates target directory on guest\" do\n      cap.rsync_pre(machine, :guestpath => guest_directory)\n      expect(comm.received_commands[0]).to match(/mkdir -p '\\/guest\\/directory\\/path'/)\n    end\n  end\n\n  describe \".rsync_post\" do\n    let(:cap) { caps.get(:rsync_post) }\n\n    it \"is a no-op\" do\n      cap.rsync_post(machine, {})\n      expect(comm).to_not receive(:execute)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/openwrt/guest_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n"
  },
  {
    "path": "test/unit/plugins/guests/photon/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestPhoton::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestPhoton::Plugin\n      .components\n      .guest_capabilities[:photon]\n      .get(:change_host_name)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:name) { \"banana-rama.example.com\" }\n  let(:basename) { \"banana-rama\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    context \"minimal network config\" do \n      let(:networks) { [\n        [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n      ] }\n\n      before do \n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n      end\n\n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n\n        described_class.change_host_name(machine, name)\n        expect(comm.received_commands[2]).to match(/hostname '#{name}'/)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n\n        described_class.change_host_name(machine, name)\n        expect(comm).to_not receive(:sudo).with(/hostname '#{name}'/)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/photon/cap/configure_networks_test.rb",
    "content": "# Copyright (c) 2015 VMware, Inc. All Rights Reserved.\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestPhoton::Cap:ConfigureNetworks\" do\n  let(:caps) do\n    VagrantPlugins::GuestPhoton::Plugin\n      .components\n      .guest_capabilities[:photon]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n    comm.stub_command(\"ifconfig | grep 'eth' | cut -f1 -d' '\",\n      stdout: \"eth1\\neth2\")\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".configure_networks\" do\n    let(:cap) { caps.get(:configure_networks) }\n\n    let(:network_1) do\n      {\n        interface: 0,\n        type: \"dhcp\",\n      }\n    end\n\n    let(:network_2) do\n      {\n        interface: 1,\n        type: \"static\",\n        ip: \"33.33.33.10\",\n        netmask: \"255.255.0.0\",\n        gateway: \"33.33.0.1\",\n      }\n    end\n\n    it \"creates and starts the networks\" do\n      cap.configure_networks(machine, [network_1, network_2])\n      expect(comm.received_commands[1]).to match(/ifconfig eth1/)\n      expect(comm.received_commands[1]).to match(/ifconfig eth2 33.33.33.10 netmask 255.255.0.0/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/photon/cap/docker_test.rb",
    "content": "# Copyright (c) 2015 VMware, Inc. All Rights Reserved.\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestPhoton::Cap:Docker\" do\n  let(:caps) do\n    VagrantPlugins::GuestPhoton::Plugin\n      .components\n      .guest_capabilities[:photon]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".docker_daemon_running\" do\n    let(:cap) { caps.get(:docker_daemon_running) }\n\n    it \"installs rsync\" do\n      comm.expect_command(\"test -S /run/docker.sock\")\n      cap.docker_daemon_running(machine)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/pld/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestPld::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestPld::Plugin\n      .components\n      .guest_capabilities[:pld]\n      .get(:change_host_name)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:name) { \"banana-rama.example.com\" }\n  let(:basename) { \"banana-rama\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    context \"minimal network config\" do \n      let(:networks) { [\n        [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n      ] }\n\n      before do \n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n      end\n\n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n\n        described_class.change_host_name(machine, name)\n        expect(comm.received_commands[-1]).to match(/service network restart/)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n\n        described_class.change_host_name(machine, name)\n        expect(comm).to_not receive(:sudo).with(/service network restart/)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/pld/cap/flavor_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestPld::Cap::Flavor\" do\n  let(:caps) do\n    VagrantPlugins::GuestPld::Plugin\n      .components\n      .guest_capabilities[:pld]\n  end\n\n  let(:machine) { double(\"machine\") }\n\n  describe \".flavor\" do\n    let(:cap) { caps.get(:flavor) }\n\n    let(:name) { \"banana-rama.example.com\" }\n\n    it \"is pld\" do\n      expect(cap.flavor(machine)).to be(:pld)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/pld/cap/network_scripts_dir_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestPld::Cap::NetworkScriptsDir\" do\n  let(:caps) do\n    VagrantPlugins::GuestPld::Plugin\n      .components\n      .guest_capabilities[:pld]\n  end\n\n  let(:machine) { double(\"machine\") }\n\n  describe \".network_scripts_dir\" do\n    let(:cap) { caps.get(:network_scripts_dir) }\n\n    let(:name) { \"banana-rama.example.com\" }\n\n    it \"is /etc/sysconfig/interfaces\" do\n      expect(cap.network_scripts_dir(machine)).to eq(\"/etc/sysconfig/interfaces\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/redhat/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestRedHat::Cap::ChangeHostName\" do\n  let(:caps) do\n    VagrantPlugins::GuestRedHat::Plugin\n      .components\n      .guest_capabilities[:redhat]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    let(:cap) { caps.get(:change_host_name) }\n    let(:name) { \"banana-rama.example.com\" }\n    let(:hostname_changed) { true }\n    let(:systemd) { true }\n    let(:hostnamectl) { true }\n    let(:networkd) { true }\n    let(:network_manager) { false }\n    let(:networks) { [\n      [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n    ] }\n\n    before do\n      comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: hostname_changed ? 1 : 0)\n      allow(cap).to receive(:systemd?).and_return(systemd)\n      allow(cap).to receive(:hostnamectl?).and_return(hostnamectl)\n      allow(cap).to receive(:systemd_networkd?).and_return(networkd)\n      allow(cap).to receive(:systemd_controlled?).with(anything, /NetworkManager/).and_return(network_manager)\n      allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n    end\n\n    context \"minimal network config\" do \n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n        cap.change_host_name(machine, name)\n        expect(comm.received_commands[1]).to match(/\\/etc\\/sysconfig\\/network/)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n        cap.change_host_name(machine, name)\n        expect(comm).to_not receive(:sudo).with(/\\/etc\\/sysconfig\\/network/)\n      end\n    end\n\n    context \"multiple networks configured with hostname\" do \n      it \"adds a new entry only for the hostname\" do \n        networks = [\n          [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}],\n          [:public_network, {:ip=>\"192.168.0.1\", :hostname=>true, :protocol=>\"tcp\", :id=>\"93a4ad88-0774-4127-a161-ceb715ff372f\"}],\n          [:public_network, {:ip=>\"192.168.0.2\", :protocol=>\"tcp\", :id=>\"5aebe848-7d85-4425-8911-c2003d924120\"}]\n        ]\n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n        expect(cap).to receive(:replace_host)\n        expect(cap).to_not receive(:add_hostname_to_loopback_interface)\n        cap.change_host_name(machine, name)\n      end\n\n      it \"appends an entry to the loopback interface\" do \n        networks = [\n          [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}],\n          [:public_network, {:ip=>\"192.168.0.1\", :protocol=>\"tcp\", :id=>\"93a4ad88-0774-4127-a161-ceb715ff372f\"}],\n          [:public_network, {:ip=>\"192.168.0.2\", :protocol=>\"tcp\", :id=>\"5aebe848-7d85-4425-8911-c2003d924120\"}]\n        ]\n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n        expect(cap).to_not receive(:replace_host)\n        expect(cap).to receive(:add_hostname_to_loopback_interface).once\n        cap.change_host_name(machine, name)\n      end\n    end\n\n    context \"when hostnamectl is in use\" do\n      let(:hostnamectl) { true }\n\n      it \"sets hostname with hostnamectl\" do\n        cap.change_host_name(machine, name)\n        expect(comm.received_commands[2]).to match(/hostnamectl/)\n      end\n    end\n\n    context \"when hostnamectl is not in use\" do\n      let(:hostnamectl) { false }\n\n      it \"sets hostname with hostname command\" do\n        cap.change_host_name(machine, name)\n        expect(comm.received_commands[2]).to match(/hostname -F/)\n      end\n    end\n\n    context \"when host name is already set\" do\n      let(:hostname_changed) { false }\n\n      it \"does not change the hostname\" do\n        cap.change_host_name(machine, name)\n        expect(comm.received_commands.size).to eq(2)\n      end\n    end\n\n    context \"restarts the network\" do\n      context \"when networkd is in use\" do\n        let(:networkd) { true }\n\n        it \"restarts networkd with systemctl\" do\n          cap.change_host_name(machine, name)\n          expect(comm.received_commands[3]).to match(/systemctl restart systemd-networkd/)\n        end\n      end\n\n      context \"when NetworkManager is in use\" do\n        let(:networkd) { false }\n        let(:network_manager) { true }\n\n        it \"restarts NetworkManager with systemctl\" do\n          cap.change_host_name(machine, name)\n          expect(comm.received_commands[3]).to match(/systemctl restart NetworkManager/)\n        end\n      end\n\n      context \"when networkd and NetworkManager are not in use\" do\n        let(:networkd) { false }\n        let(:network_manager) { false }\n\n        it \"restarts the network using service\" do\n          cap.change_host_name(machine, name)\n          expect(comm.received_commands[3]).to match(/service network restart/)\n        end\n      end\n\n      context \"when systemd is not in use\" do\n        let(:systemd) { false }\n\n        it \"restarts the network using service\" do\n          cap.change_host_name(machine, name)\n          expect(comm.received_commands[3]).to match(/service network restart/)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/redhat/cap/configure_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestRedHat::Cap::ConfigureNetworks\" do\n  let(:caps) do\n    VagrantPlugins::GuestRedHat::Plugin\n      .components\n      .guest_capabilities[:redhat]\n  end\n\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:config) { double(\"config\", vm: vm) }\n  let(:guest) { double(\"guest\") }\n  let(:machine) { double(\"machine\", guest: guest, config: config) }\n  let(:networks){ [[:public_network, network_1], [:private_network, network_2]] }\n  let(:vm){ double(\"vm\", networks: networks) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  context \"with systems-connections network configuration path\" do\n    let(:cap) { caps.get(:configure_networks) }\n\n    let(:network_1) do\n      {\n        interface: 0,\n        type: \"dhcp\",\n      }\n    end\n\n    let(:network_2) do\n      {\n        interface: 1,\n        type: \"static\",\n        ip: \"33.33.33.10\",\n        netmask: \"255.255.0.0\",\n        gateway: \"33.33.0.1\",\n      }\n    end\n\n    before do\n      allow(guest).to receive(:capability)\n                        .with(:flavor)\n                        .and_return(:rhel)\n\n      allow(guest).to receive(:capability)\n                        .with(:network_scripts_dir)\n                        .and_return(\"/system-connections\")\n    end\n\n    it \"should configure with network manager\" do\n      expect(cap).to receive(:configure_network_manager).with(machine, [network_1])\n      cap.configure_networks(machine, [network_1])\n    end\n  end\n\n  describe \".configure_networks\" do\n    context \"when version is less than 10\" do\n      let(:cap) { caps.get(:configure_networks) }\n\n      before do\n        allow(guest).to receive(:capability)\n                          .with(:flavor)\n                          .and_return(:rhel)\n\n        allow(guest).to receive(:capability)\n                          .with(:network_scripts_dir)\n                          .and_return(\"/network-scripts\")\n\n        allow(guest).to receive(:capability)\n                          .with(:network_interfaces)\n                          .and_return([\"eth1\", \"eth2\"])\n      end\n\n      let(:network_1) do\n        {\n          interface: 0,\n          type: \"dhcp\",\n        }\n      end\n\n      let(:network_2) do\n        {\n          interface: 1,\n          type: \"static\",\n          ip: \"33.33.33.10\",\n          netmask: \"255.255.0.0\",\n          gateway: \"33.33.0.1\",\n        }\n      end\n\n      let(:network_3) do\n        {\n          interface: 2,\n          type: \"static\",\n          ip: \"33.33.33.11\",\n          netmask: \"255.255.0.0\",\n          gateway: \"33.33.0.1\",\n        }\n      end\n\n      context \"network configuration file\" do\n        let(:networks){ [[:public_network, network_1], [:private_network, network_2], [:private_network, network_3]] }\n        let(:tempfile) { double(\"tempfile\") }\n\n        before do\n          allow(tempfile).to receive(:binmode)\n          allow(tempfile).to receive(:write)\n          allow(tempfile).to receive(:fsync)\n          allow(tempfile).to receive(:close)\n          allow(tempfile).to receive(:path)\n          allow(Tempfile).to receive(:open).and_yield(tempfile)\n        end\n\n        it \"should generate two configuration files\" do\n          expect(Tempfile).to receive(:open).twice\n          cap.configure_networks(machine, [network_1, network_2])\n        end\n\n        it \"should generate three configuration files\" do\n          expect(Tempfile).to receive(:open).thrice\n          cap.configure_networks(machine, [network_1, network_2, network_3])\n        end\n\n        it \"should generate configuration with network_2 IP address\" do\n          expect(tempfile).to receive(:write).with(/#{network_2[:ip]}/)\n          cap.configure_networks(machine, [network_1, network_2, network_3])\n        end\n\n        it \"should generate configuration with network_3 IP address\" do\n          expect(tempfile).to receive(:write).with(/#{network_3[:ip]}/)\n          cap.configure_networks(machine, [network_1, network_2, network_3])\n        end\n      end\n\n      context \"with NetworkManager installed\" do\n        let(:net1_nm_controlled) { true }\n        let(:net2_nm_controlled) { true }\n\n        let(:networks){ [\n          [:public_network, network_1.merge(nm_controlled: net1_nm_controlled)],\n          [:private_network, network_2.merge(nm_controlled: net2_nm_controlled)]\n        ] }\n\n        before do\n          allow(cap).to receive(:nmcli?).and_return true\n        end\n\n        context \"with devices managed by NetworkManager\" do\n          before do\n            allow(cap).to receive(:nm_controlled?).and_return true\n          end\n\n          context \"with nm_controlled option omitted\" do\n            let(:networks){ [\n              [:public_network, network_1],\n              [:private_network, network_2]\n            ] }\n\n            it \"creates and starts the networks via nmcli\" do\n              cap.configure_networks(machine, [network_1, network_2])\n              expect(comm.received_commands[0]).to match(/nmcli/)\n              expect(comm.received_commands[0]).to_not match(/(ifdown|ifup)/)\n            end\n          end\n\n          context \"with nm_controlled option set to true\" do\n            it \"creates and starts the networks via nmcli\" do\n              cap.configure_networks(machine, [network_1, network_2])\n              expect(comm.received_commands[0]).to match(/nmcli/)\n              expect(comm.received_commands[0]).to_not match(/(ifdown|ifup)/)\n            end\n          end\n\n          context \"with nm_controlled option set to false\" do\n            let(:net1_nm_controlled) { false }\n            let(:net2_nm_controlled) { false }\n\n            it \"creates and starts the networks via ifup and disables devices in NetworkManager\" do\n              cap.configure_networks(machine, [network_1, network_2])\n              expect(comm.received_commands[0]).to match(/nmcli.*disconnect/)\n              expect(comm.received_commands[0]).to match(/ifup/)\n              expect(comm.received_commands[0]).to_not match(/ifdown/)\n            end\n          end\n\n          context \"with nm_controlled option set to false on first device\" do\n            let(:net1_nm_controlled) { false }\n            let(:net2_nm_controlled) { true }\n\n            it \"creates and starts the networks with one managed manually and one NetworkManager controlled\" do\n              cap.configure_networks(machine, [network_1, network_2])\n              expect(comm.received_commands[0]).to match(/nmcli.*disconnect.*eth1/)\n              expect(comm.received_commands[0]).to match(/ifup.*eth1/)\n              expect(comm.received_commands[0]).to_not match(/ifdown/)\n            end\n          end\n        end\n\n        context \"with devices not managed by NetworkManager\" do\n          before do\n            allow(cap).to receive(:nm_controlled?).and_return false\n          end\n\n          context \"with nm_controlled option omitted\" do\n            let(:networks){ [\n              [:public_network, network_1],\n              [:private_network, network_2]\n            ] }\n\n            it \"creates and starts the networks manually\" do\n              cap.configure_networks(machine, [network_1, network_2])\n              expect(comm.received_commands[0]).to match(/ifdown/)\n              expect(comm.received_commands[0]).to match(/ifup/)\n              expect(comm.received_commands[0]).to_not match(/nmcli c up/)\n              expect(comm.received_commands[0]).to_not match(/nmcli d disconnect/)\n            end\n          end\n\n          context \"with nm_controlled option set to true\" do\n            let(:net1_nm_controlled) { true }\n            let(:net2_nm_controlled) { true }\n\n            it \"creates and starts the networks via nmcli\" do\n              cap.configure_networks(machine, [network_1, network_2])\n              expect(comm.received_commands[0]).to match(/NetworkManager/)\n              expect(comm.received_commands[0]).to match(/ifdown/)\n              expect(comm.received_commands[0]).to_not match(/ifup/)\n            end\n          end\n\n          context \"with nm_controlled option set to false\" do\n            let(:net1_nm_controlled) { false }\n            let(:net2_nm_controlled) { false }\n\n            it \"creates and starts the networks via ifup \" do\n              cap.configure_networks(machine, [network_1, network_2])\n              expect(comm.received_commands[0]).to match(/ifup/)\n              expect(comm.received_commands[0]).to match(/ifdown/)\n              expect(comm.received_commands[0]).to_not match(/nmcli c up/)\n              expect(comm.received_commands[0]).to_not match(/nmcli d disconnect/)\n            end\n          end\n\n          context \"with nm_controlled option set to false on first device\" do\n            let(:net1_nm_controlled) { false }\n            let(:net2_nm_controlled) { true }\n\n            it \"creates and starts the networks with one managed manually and one NetworkManager controlled\" do\n              cap.configure_networks(machine, [network_1, network_2])\n              expect(comm.received_commands[0]).to_not match(/nmcli.*disconnect/)\n              expect(comm.received_commands[0]).to match(/ifdown/)\n              expect(comm.received_commands[0]).to match(/ifup.*eth1/)\n            end\n          end\n        end\n      end\n\n      context \"without NetworkManager installed\" do\n        before do\n          allow(cap).to receive(:nmcli?).and_return false\n        end\n\n        context \"with nm_controlled option omitted\" do\n\n          it \"creates and starts the networks manually\" do\n            cap.configure_networks(machine, [network_1, network_2])\n            expect(comm.received_commands[0]).to match(/ifdown/)\n            expect(comm.received_commands[0]).to match(/ifup/)\n            expect(comm.received_commands[0]).to_not match(/nmcli/)\n          end\n        end\n\n        context \"with nm_controlled option set\" do\n          let(:networks){ [\n            [:public_network, network_1.merge(nm_controlled: true)],\n            [:private_network, network_2.merge(nm_controlled: true)]\n          ] }\n\n          it \"raises an error\" do\n            expect{ cap.configure_networks(machine, [network_1, network_2]) }.to raise_error(Vagrant::Errors::NetworkManagerNotInstalled)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/redhat/cap/flavor_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestRedHat::Cap::Flavor\" do\n  let(:caps) do\n    VagrantPlugins::GuestRedHat::Plugin\n      .components\n      .guest_capabilities[:redhat]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".flavor\" do\n    let(:cap) { caps.get(:flavor) }\n\n    # /etc/os-release was added in EL7+\n    context \"without /etc/os-release file\" do\n      {\n        \"\" => :rhel\n      }.each do |str, expected|\n        it \"returns #{expected} for #{str}\" do\n          comm.stub_command(\"test -f /etc/os-release\", exit_code: 1)\n          expect(cap.flavor(machine)).to be(expected)\n        end\n      end\n    end\n    context \"with /etc/os-release file\" do\n      {\n        \"7\" => :rhel_7,\n        \"8\" => :rhel_8,\n        \"9.0\" => :rhel_9,\n        \"9.1\" => :rhel_9,\n        \"\" => :rhel,\n        \"banana\" => :rhel,\n      }.each do |str, expected|\n        it \"returns #{expected} for #{str}\" do\n          comm.stub_command(\"test -f /etc/os-release\", exit_code: 0)\n          comm.stub_command(\"source /etc/os-release && printf $VERSION_ID\", stdout: str)\n          expect(cap.flavor(machine)).to be(expected)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/redhat/cap/network_scripts_dir_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestRedHat::Cap::NetworkScriptsDir\" do\n  let(:caps) do\n    VagrantPlugins::GuestRedHat::Plugin\n      .components\n      .guest_capabilities[:redhat]\n  end\n\n  let(:is_legacy) { false }\n  let(:communicator) { double(\"communicator\") }\n  let(:machine) { double(\"machine\", communicate: communicator) }\n\n  before do\n    allow(communicator).to receive(:test).with(\"test -d /etc/sysconfig/network-scripts\").and_return(is_legacy)\n  end\n\n  describe \".network_scripts_dir\" do\n    let(:cap) { caps.get(:network_scripts_dir) }\n\n    let(:name) { \"banana-rama.example.com\" }\n\n    it \"is /etc/NetworkManager/system-connections\" do\n      expect(cap.network_scripts_dir(machine)).to eq(\"/etc/NetworkManager/system-connections\")\n    end\n\n    context 'when version is legacy' do\n      let(:is_legacy) { true }\n\n      it \"is /etc/sysconfig/network-scripts\" do\n        expect(cap.network_scripts_dir(machine)).to eq(\"/etc/sysconfig/network-scripts\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/redhat/cap/nfs_client_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestRedHat::Cap:NFSClient\" do\n  let(:caps) do\n    VagrantPlugins::GuestRedHat::Plugin\n      .components\n      .guest_capabilities[:redhat]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".nfs_client_install\" do\n    let(:cap) { caps.get(:nfs_client_install) }\n\n    it \"installs nfs client\" do\n      cap.nfs_client_install(machine)\n      expect(comm.received_commands[0]).to match(/install nfs-utils/)\n      expect(comm.received_commands[0]).to match(/\\/bin\\/systemctl restart rpcbind nfs-server/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/redhat/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestRedHat::Cap:RSync\" do\n  let(:caps) do\n    VagrantPlugins::GuestRedHat::Plugin\n      .components\n      .guest_capabilities[:redhat]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".rsync_install\" do\n    let(:cap) { caps.get(:rsync_install) }\n\n    it \"installs rsync\" do\n      cap.rsync_install(machine)\n      expect(comm.received_commands[0]).to match(/install rsync/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/redhat/cap/smb_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestRedHat::Cap::SMB\" do\n  let(:described_class) do\n    VagrantPlugins::GuestRedHat::Plugin\n      .components\n      .guest_capabilities[:redhat]\n      .get(:smb_install)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".smb_install\" do\n    it \"installs smb when /sbin/mount.cifs does not exist\" do\n      comm.stub_command(\"test -f /sbin/mount.cifs\", exit_code: 1)\n      described_class.smb_install(machine)\n\n      expect(comm.received_commands[1]).to match(/if command -v dnf; then/)\n      expect(comm.received_commands[1]).to match(/dnf -y install cifs-utils/)\n    end\n\n    it \"does not install smb when /sbin/mount.cifs exists\" do\n      comm.stub_command(\"test -f /sbin/mount.cifs\", exit_code: 0)\n      described_class.smb_install(machine)\n\n      expect(comm.received_commands.join(\"\")).to_not match(/update/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/rocky/cap/flavor_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestRocky::Cap::Flavor\" do\n  let(:caps) do\n    VagrantPlugins::GuestRocky::Plugin\n      .components\n      .guest_capabilities[:rocky]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".flavor\" do\n    let(:cap) { caps.get(:flavor) }\n\n    {\n      \"\" => :rocky,\n      \"8.2\" => :rocky_8,\n      \"9\" => :rocky_9,\n      \"invalid\" => :rocky\n    }.each do |str, expected|\n      it \"returns #{expected} for #{str}\" do\n        comm.stub_command(\"source /etc/os-release && printf $VERSION_ID\", stdout: str)\n        expect(cap.flavor(machine)).to be(expected)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/slackware/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSlackware::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestSlackware::Plugin\n      .components\n      .guest_capabilities[:slackware]\n      .get(:change_host_name)\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:name) { \"banana-rama.example.com\" }\n  let(:basename) { \"banana-rama\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    context \"minimal network config\" do \n      let(:networks) { [\n        [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n      ] }\n\n      before do \n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n      end\n\n      it \"sets the hostname\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n\n        described_class.change_host_name(machine, name)\n        expect(comm.received_commands[2]).to match(/hostname -F \\/etc\\/hostname/)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 0)\n\n        described_class.change_host_name(machine, name)\n        expect(comm).to_not receive(:sudo).with(/hostname -F \\/etc\\/hostname/)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/slackware/cap/configure_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSlackware::Cap::ConfigureNetworks\" do\n  let(:caps) do\n    VagrantPlugins::GuestSlackware::Plugin\n      .components\n      .guest_capabilities[:slackware]\n  end\n\n  let(:guest) { double(\"guest\") }\n  let(:machine) { double(\"machine\", guest: guest) }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".configure_networks\" do\n    let(:cap) { caps.get(:configure_networks) }\n\n    before do\n      allow(guest).to receive(:capability).with(:network_interfaces)\n        .and_return([\"eth1\", \"eth2\"])\n    end\n\n    let(:network_1) do\n      {\n        interface: 0,\n        type: \"dhcp\",\n      }\n    end\n\n    let(:network_2) do\n      {\n        interface: 1,\n        type: \"static\",\n        ip: \"33.33.33.10\",\n        netmask: \"255.255.0.0\",\n        gateway: \"33.33.0.1\",\n      }\n    end\n\n    it \"creates and starts the networks\" do\n      cap.configure_networks(machine, [network_1, network_2])\n      expect(comm.received_commands[0]).to match(/\\/etc\\/rc.d\\/rc.inet1/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/smartos/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSmartos::Cap::ChangeHostName\" do\n  let(:caps) do\n    VagrantPlugins::GuestSmartos::Plugin\n        .components\n        .guest_capabilities[:smartos]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:config) { double(\"config\", smartos: VagrantPlugins::GuestSmartos::Config.new) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n    allow(machine).to receive(:config).and_return(config)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    let(:cap) { caps.get(:change_host_name) }\n\n    it \"changes the hostname if appropriate\" do\n      cap.change_host_name(machine, \"testhost\")\n\n      expect(comm.received_commands[0]).to match(/if hostname | grep 'testhost' ; then/)\n      expect(comm.received_commands[0]).to match(/exit 0/)\n      expect(comm.received_commands[0]).to match(/fi/)\n      expect(comm.received_commands[0]).to match(/if \\[ -d \\/usbkey \\] && \\[ \"\\$\\(zonename\\)\" == \"global\" \\] ; then/)\n      expect(comm.received_commands[0]).to match(/pfexec sed -i '' 's\\/hostname=\\.\\*\\/hostname=testhost\\/' \\/usbkey\\/config/)\n      expect(comm.received_commands[0]).to match(/fi/)\n      expect(comm.received_commands[0]).to match(/pfexec echo 'testhost' > \\/etc\\/nodename/)\n      expect(comm.received_commands[0]).to match(/pfexec hostname testhost/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/smartos/cap/configure_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::VagrantPlugins::Cap::ConfigureNetworks\" do\n  let(:plugin) { VagrantPlugins::GuestSmartos::Plugin.components.guest_capabilities[:smartos].get(:configure_networks) }\n  let(:machine) { double(\"machine\") }\n  let(:config) { double(\"config\", smartos: VagrantPlugins::GuestSmartos::Config.new) }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(machine).to receive(:config).and_return(config)\n  end\n\n  after do\n    communicator.verify_expectations!\n  end\n\n  describe \".configure_networks\" do\n    let(:interface) { \"eth0\" }\n    let(:device) { \"e1000g#{interface}\" }\n\n    describe 'dhcp' do\n      let(:network) { {interface: interface, type: :dhcp} }\n\n      it \"plumbs the device\" do\n        communicator.expect_command(%Q(pfexec /sbin/ifconfig #{device} plumb))\n        plugin.configure_networks(machine, [network])\n      end\n\n      it \"starts dhcp for the device\" do\n        communicator.expect_command(%Q(pfexec /sbin/ifconfig #{device} dhcp start))\n        plugin.configure_networks(machine, [network])\n      end\n    end\n\n    describe 'static' do\n      let(:network) { {interface: interface, type: :static, ip: '1.1.1.1', netmask: '255.255.255.0'} }\n\n      it \"plumbs the network\" do\n        communicator.expect_command(%Q(pfexec /sbin/ifconfig #{device} plumb))\n        plugin.configure_networks(machine, [network])\n      end\n\n      it \"starts sets netmask and IP for the device\" do\n        communicator.expect_command(%Q(pfexec /sbin/ifconfig #{device} inet 1.1.1.1 netmask 255.255.255.0))\n        plugin.configure_networks(machine, [network])\n      end\n\n      it \"starts enables the device\" do\n        communicator.expect_command(%Q(pfexec /sbin/ifconfig #{device} up))\n        plugin.configure_networks(machine, [network])\n      end\n\n      it \"starts writes out a hostname file\" do\n        communicator.expect_command(%Q(pfexec sh -c \"echo '1.1.1.1' > /etc/hostname.#{device}\"))\n        plugin.configure_networks(machine, [network])\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "test/unit/plugins/guests/smartos/cap/halt_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSmartos::Cap::Halt\" do\n  let(:plugin) { VagrantPlugins::GuestSmartos::Plugin.components.guest_capabilities[:smartos].get(:halt) }\n  let(:machine) { double(\"machine\") }\n  let(:config) { double(\"config\", smartos: double(\"smartos\", suexec_cmd: 'pfexec')) }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:shutdown_command){ \"pfexec /usr/sbin/poweroff\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(machine).to receive(:config).and_return(config)\n  end\n\n  after do\n    communicator.verify_expectations!\n  end\n\n  describe \".halt\" do\n    it \"sends a shutdown signal\" do\n      communicator.expect_command(shutdown_command)\n      plugin.halt(machine)\n    end\n\n    it \"ignores an IOError\" do\n      communicator.stub_command(shutdown_command, raise: IOError)\n      expect {\n        plugin.halt(machine)\n      }.to_not raise_error\n    end\n\n    it \"ignores a Vagrant::Errors::SSHDisconnected\" do\n      communicator.stub_command(shutdown_command, raise: Vagrant::Errors::SSHDisconnected)\n      expect {\n        plugin.halt(machine)\n      }.to_not raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/smartos/cap/insert_public_key_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSmartos::Cap::InsertPublicKey\" do\n  let(:caps) do\n    VagrantPlugins::GuestSmartos::Plugin\n        .components\n        .guest_capabilities[:smartos]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".insert_public_key\" do\n    let(:cap) { caps.get(:insert_public_key) }\n\n    it \"inserts the public key\" do\n      cap.insert_public_key(machine, \"ssh-rsa ...\")\n\n      expect(comm.received_commands[0]).to match(/if \\[ -d \\/usbkey \\] && \\[ \"\\$\\(zonename\\)\" == \"global\" \\] ; then/)\n      expect(comm.received_commands[0]).to match(/printf 'ssh-rsa ...\\\\n' >> \\/usbkey\\/config.inc\\/authorized_keys/)\n      expect(comm.received_commands[0]).to match(/cp \\/usbkey\\/config.inc\\/authorized_keys ~\\/.ssh\\/authorized_keys/)\n      expect(comm.received_commands[0]).to match(/else/)\n      expect(comm.received_commands[0]).to match(/mkdir -p ~\\/.ssh/)\n      expect(comm.received_commands[0]).to match(/chmod 0700 ~\\/.ssh/)\n      expect(comm.received_commands[0]).to match(/printf 'ssh-rsa ...\\\\n' >> ~\\/.ssh\\/authorized_keys/)\n      expect(comm.received_commands[0]).to match(/chmod 0600 ~\\/.ssh\\/authorized_keys/)\n      expect(comm.received_commands[0]).to match(/fi/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/smartos/cap/mount_nfs_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/guests/smartos/config\"\n\ndescribe \"VagrantPlugins::GuestSmartos::Cap::MountNFS\" do\n  let(:caps) do\n    VagrantPlugins::GuestSmartos::Plugin\n        .components\n        .guest_capabilities[:smartos]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:config) { double(\"config\", smartos: VagrantPlugins::GuestSmartos::Config.new) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n    allow(machine).to receive(:config).and_return(config)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".mount_nfs_folder\" do\n    let(:cap) { caps.get(:mount_nfs_folder) }\n\n    it \"mounts the folder\" do\n      cap.mount_nfs_folder(machine, '1.1.1.1', {'nfs' => {guestpath: '/mountpoint', hostpath: '/some/share'}})\n\n      expect(comm.received_commands[0]).to match(/if \\[ -d \\/usbkey \\] && \\[ \"\\$\\(zonename\\)\" == \"global\" \\] ; then/)\n      expect(comm.received_commands[0]).to match(/pfexec mkdir -p \\/usbkey\\/config.inc/)\n      expect(comm.received_commands[0]).to match(/printf '1\\.1\\.1\\.1:\\/some\\/share:\\/mountpoint' | pfexec tee -a \\/usbkey\\/config.inc\\/nfs_mounts/)\n      expect(comm.received_commands[0]).to match(/fi/)\n      expect(comm.received_commands[0]).to match(/pfexec mkdir -p \\/mountpoint/)\n      expect(comm.received_commands[0]).to match(/pfexec \\/usr\\/sbin\\/mount -F nfs '1\\.1\\.1\\.1:\\/some\\/share' '\\/mountpoint'/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/smartos/cap/remove_public_key_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSmartos::Cap::RemovePublicKey\" do\n  let(:caps) do\n    VagrantPlugins::GuestSmartos::Plugin\n      .components\n      .guest_capabilities[:smartos]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".remove_public_key\" do\n    let(:cap) { caps.get(:remove_public_key) }\n\n    it \"removes the public key\" do\n      cap.remove_public_key(machine, \"ssh-rsa keyvalue comment\")\n      expect(comm.received_commands[0]).to match(/if test -f \\/usbkey\\/config.inc\\/authorized_keys ; then/)\n      expect(comm.received_commands[0]).to match(/sed -i '' '\\/\\^.*ssh-rsa keyvalue comment.*\\$\\/d' \\/usbkey\\/config.inc\\/authorized_keys/)\n      expect(comm.received_commands[0]).to match(/fi/)\n      expect(comm.received_commands[0]).to match(/if test -f ~\\/.ssh\\/authorized_keys ; then/)\n      expect(comm.received_commands[0]).to match(/sed -i '' '\\/\\^.*ssh-rsa keyvalue comment.*\\$\\/d' ~\\/.ssh\\/authorized_keys/)\n      expect(comm.received_commands[0]).to match(/fi/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/smartos/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::VagrantPlugins::Cap::Rsync\" do\n  let(:plugin) { VagrantPlugins::GuestSmartos::Plugin.components.guest_capabilities[:smartos].get(:rsync_installed) }\n  let(:machine) { double(\"machine\") }\n  let(:config) { double(\"config\", smartos: VagrantPlugins::GuestSmartos::Config.new) }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(machine).to receive(:config).and_return(config)\n  end\n\n  after do\n    communicator.verify_expectations!\n  end\n\n  describe \".rsync_installed\" do\n    describe \"when rsync is in the path\" do\n      it \"is true\" do\n        communicator.stub_command(\"which rsync\", stdout: '/usr/bin/rsync', exit_code: 0)\n        expect(plugin.rsync_installed(machine)).to be true\n      end\n    end\n\n    describe \"when rsync is not in the path\" do\n      it \"is false\" do\n        communicator.stub_command(\"which rsync\", stdout: '', exit_code: 1)\n        expect(plugin.rsync_installed(machine)).to be false\n      end\n    end\n  end\n\n  describe \".rsync_pre\" do\n    it 'makes the guestpath directory with pfexec' do\n      communicator.expect_command(\"pfexec mkdir -p '/sync_dir'\")\n      plugin.rsync_pre(machine, guestpath: '/sync_dir')\n    end\n  end\n\n  describe \".rsync_post\" do\n    it 'chowns incorrectly owned files in sync dir' do\n      communicator.expect_command(\"pfexec find /sync_dir '!' -type l -a '(' ! -user somebody -or ! -group somegroup ')' -exec chown somebody:somegroup '{}' +\")\n      plugin.rsync_post(machine, guestpath: '/sync_dir', owner: 'somebody', group: 'somegroup')\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/solaris/cap/file_system_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSolaris::Cap::FileSystem\" do\n  let(:caps) do\n    VagrantPlugins::GuestSolaris::Plugin\n      .components\n      .guest_capabilities[:solaris]\n  end\n\n  let(:machine) { double(\"machine\", communicate: comm) }\n  let(:comm) { double(\"comm\") }\n\n  before { allow(comm).to receive(:execute) }\n\n  describe \".create_tmp_path\" do\n    let(:cap) { caps.get(:create_tmp_path) }\n    let(:opts) { {} }\n\n    it \"should generate path on guest\" do\n      expect(comm).to receive(:execute).with(/mktemp/)\n      cap.create_tmp_path(machine, opts)\n    end\n\n    it \"should capture path generated on guest\" do\n      expect(comm).to receive(:execute).with(/mktemp/).and_yield(:stdout, \"TMP_PATH\")\n      expect(cap.create_tmp_path(machine, opts)).to eq(\"TMP_PATH\")\n    end\n\n    it \"should strip newlines on path\" do\n      expect(comm).to receive(:execute).with(/mktemp/).and_yield(:stdout, \"TMP_PATH\\n\")\n      expect(cap.create_tmp_path(machine, opts)).to eq(\"TMP_PATH\")\n    end\n\n    context \"when type is a directory\" do\n      before { opts[:type] = :directory }\n\n      it \"should create guest path as a directory\" do\n        expect(comm).to receive(:execute).with(/-d/)\n        cap.create_tmp_path(machine, opts)\n      end\n    end\n  end\n\n  describe \".decompress_tgz\" do\n    let(:cap) { caps.get(:decompress_tgz) }\n    let(:comp) { \"compressed_file\" }\n    let(:dest) { \"path/to/destination\" }\n    let(:opts) { {} }\n\n    before { allow(cap).to receive(:create_tmp_path).and_return(\"TMP_DIR\") }\n    after{ cap.decompress_tgz(machine, comp, dest, opts) }\n\n    it \"should create temporary directory for extraction\" do\n      expect(cap).to receive(:create_tmp_path)\n    end\n\n    it \"should extract file with tar\" do\n      expect(comm).to receive(:execute).with(/tar/)\n    end\n\n    it \"should extract file to temporary directory\" do\n      expect(comm).to receive(:execute).with(/TMP_DIR/)\n    end\n\n    it \"should remove compressed file from guest\" do\n      expect(comm).to receive(:execute).with(/rm .*#{comp}/)\n    end\n\n    it \"should remove extraction directory from guest\" do\n      expect(comm).to receive(:execute).with(/rm .*TMP_DIR/)\n    end\n\n    it \"should create parent directories for destination\" do\n      expect(comm).to receive(:execute).with(/mkdir -p .*to'/)\n    end\n\n    context \"when type is directory\" do\n      before { opts[:type] = :directory }\n\n      it \"should create destination directory\" do\n        expect(comm).to receive(:execute).with(/mkdir -p .*destination'/)\n      end\n    end\n  end\n\n  describe \".decompress_zip\" do\n    let(:cap) { caps.get(:decompress_zip) }\n    let(:comp) { \"compressed_file\" }\n    let(:dest) { \"path/to/destination\" }\n    let(:opts) { {} }\n\n    before { allow(cap).to receive(:create_tmp_path).and_return(\"TMP_DIR\") }\n    after{ cap.decompress_zip(machine, comp, dest, opts) }\n\n    it \"should create temporary directory for extraction\" do\n      expect(cap).to receive(:create_tmp_path)\n    end\n\n    it \"should extract file with zip\" do\n      expect(comm).to receive(:execute).with(/zip/)\n    end\n\n    it \"should extract file to temporary directory\" do\n      expect(comm).to receive(:execute).with(/TMP_DIR/)\n    end\n\n    it \"should remove compressed file from guest\" do\n      expect(comm).to receive(:execute).with(/rm .*#{comp}/)\n    end\n\n    it \"should remove extraction directory from guest\" do\n      expect(comm).to receive(:execute).with(/rm .*TMP_DIR/)\n    end\n\n    it \"should create parent directories for destination\" do\n      expect(comm).to receive(:execute).with(/mkdir -p .*to'/)\n    end\n\n    context \"when type is directory\" do\n      before { opts[:type] = :directory }\n\n      it \"should create destination directory\" do\n        expect(comm).to receive(:execute).with(/mkdir -p .*destination'/)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/solaris/cap/halt_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSolaris::Cap::Halt\" do\n  let(:caps) do\n    VagrantPlugins::GuestSolaris::Plugin\n      .components\n      .guest_capabilities[:solaris]\n  end\n\n  let(:shutdown_command){ \"sudo /usr/sbin/shutdown -y -i5 -g0\" }\n  let(:machine) { double(\"machine\", config: double(\"config\", solaris: double(\"solaris\", suexec_cmd: 'sudo'))) }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".halt\" do\n    let(:cap) { caps.get(:halt) }\n\n    it \"runs the shutdown command\" do\n      comm.expect_command(shutdown_command)\n      cap.halt(machine)\n    end\n\n    it \"ignores an IOError\" do\n      comm.stub_command(shutdown_command, raise: IOError)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n\n    it \"ignores a Vagrant::Errors::SSHDisconnected\" do\n      comm.stub_command(shutdown_command, raise: Vagrant::Errors::SSHDisconnected)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/solaris11/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSolaris11::Cap::ChangeHostName\" do\n  let(:caps) do\n    VagrantPlugins::GuestSolaris11::Plugin\n      .components\n      .guest_capabilities[:solaris11]\n  end\n\n  let(:machine) { double(\"machine\", config: double(\"config\", solaris11: double(\"solaris11\", suexec_cmd: 'sudo'))) }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    let(:cap) { caps.get(:change_host_name) }\n    let(:name) { \"solaris11.domain.com\" }\n\n    it \"changes the hostname\" do\n      allow(machine.communicate).to receive(:test).and_return(false)\n      allow(machine.communicate).to receive(:execute)\n\n      expect(machine.communicate).to receive(:execute).with(\"sudo /usr/sbin/svccfg -s system/identity:node setprop config/nodename=\\\"#{name}\\\"\")\n      expect(machine.communicate).to receive(:execute).with(\"sudo /usr/sbin/svccfg -s system/identity:node setprop config/loopback=\\\"#{name}\\\"\")\n      expect(machine.communicate).to receive(:execute).with(\"sudo /usr/sbin/svccfg -s system/identity:node refresh \")\n      expect(machine.communicate).to receive(:execute).with(\"sudo /usr/sbin/svcadm restart system/identity:node \")\n      cap.change_host_name(machine, name)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/solaris11/cap/configure_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSolaris11::Cap::ConfigureNetworks\" do\n  let(:caps) do\n    VagrantPlugins::GuestSolaris11::Plugin\n      .components\n      .guest_capabilities[:solaris11]\n  end\n\n  let(:machine) { double(\"machine\", config: double(\"config\", solaris11: double(\"solaris11\", suexec_cmd: 'sudo', device: 'net'))) }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".configure_networks\" do\n    let(:cap) { caps.get(:configure_networks) }\n    let(:network_1) do\n      {\n        interface: 0,\n        type: \"dhcp\",\n      }\n    end\n\n    let(:network_2) do\n      {\n        interface: 1,\n        type: \"static\",\n        ip: \"33.33.33.10\",\n        netmask: \"255.255.0.0\",\n        gateway: \"33.33.0.1\",\n      }\n    end\n\n    let(:networks) { [network_1, network_2] }\n\n    it \"configures the guests network if static\" do\n      allow(machine.communicate).to receive(:test).and_return(true)\n\n      cap.configure_networks(machine, networks)\n      expect(comm.received_commands[1]).to eq(\"sudo ipadm delete-addr net1/v4\")\n      expect(comm.received_commands[2]).to eq(\"sudo ipadm create-addr -T static -a 33.33.33.10/16 net1/v4\")\n    end\n\n    it \"configures the guests network if dhcp\" do\n      allow(machine.communicate).to receive(:test).and_return(true)\n      cap.configure_networks(machine, networks)\n      expect(comm.received_commands[0]).to eq(\"sudo ipadm create-addr -T addrconf net0/v4\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/suse/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSUSE::Cap::ChangeHostName\" do\n  let(:caps) do\n    VagrantPlugins::GuestSUSE::Plugin\n      .components\n      .guest_capabilities[:suse]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:cap) { caps.get(:change_host_name) }\n  let(:name) { \"banana-rama.example.com\" }\n  let(:basename) { \"banana-rama\" }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n    allow(cap).to receive(:hostnamectl?).and_return(true)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    context \"minimal network config\" do \n      let(:networks) { [\n        [:forwarded_port, {:guest=>22, :host=>2222, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]\n      ] }\n\n      before do \n        allow(machine).to receive_message_chain(:config, :vm, :networks).and_return(networks)\n      end\n\n      it \"sets the hostname\" do\n        comm.stub_command('test \"$(hostnamectl --static status)\" = \"#{basename}\"', exit_code: 1)\n\n        cap.change_host_name(machine, name)\n        expect(comm.received_commands[2]).to match(/echo #{name} > \\/etc\\/HOSTNAME/)\n        expect(comm.received_commands[2]).to match(/hostnamectl set-hostname '#{basename}'/)\n      end\n\n      it \"does not change the hostname if already set\" do\n        comm.stub_command('test \"$(hostnamectl --static status)\" = \"#{basename}\"', exit_code: 0)\n\n        cap.change_host_name(machine, name)\n        expect(comm.received_commands.size).to eq(3)\n      end\n\n      context \"hostnamectl is not present\" do \n        before do\n          allow(cap).to receive(:hostnamectl?).and_return(false)\n        end\n  \n        it \"sets the hostname\" do\n          comm.stub_command(\"hostname -f | grep '^#{name}$'\", exit_code: 1)\n  \n          cap.change_host_name(machine, name)\n          expect(comm.received_commands[2]).to match(/echo #{name} > \\/etc\\/HOSTNAME/)\n          expect(comm.received_commands[2]).to match(/hostname '#{basename}'/)\n        end\n    end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/suse/cap/configure_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSUSE::Cap::ConfigureNetworks\" do\n  let(:caps) do\n    VagrantPlugins::GuestSUSE::Plugin\n      .components\n      .guest_capabilities[:suse]\n  end\n\n  let(:guest) { double(\"guest\") }\n  let(:machine) { double(\"machine\", guest: guest) }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".configure_networks\" do\n    let(:cap) { caps.get(:configure_networks) }\n\n    before do\n      allow(guest).to receive(:capability).with(:network_scripts_dir)\n        .and_return(\"/scripts\")\n      allow(guest).to receive(:capability).with(:network_interfaces)\n        .and_return([\"eth1\", \"eth2\"])\n    end\n\n    let(:network_1) do\n      {\n        interface: 0,\n        type: \"dhcp\",\n      }\n    end\n\n    let(:network_2) do\n      {\n        interface: 1,\n        type: \"static\",\n        ip: \"33.33.33.10\",\n        netmask: \"255.255.0.0\",\n        gateway: \"33.33.0.1\",\n      }\n    end\n\n    it \"creates and starts the networks\" do\n      cap.configure_networks(machine, [network_1, network_2])\n      expect(comm.received_commands[0]).to match(/\\/sbin\\/ifdown 'eth1'/)\n      expect(comm.received_commands[0]).to match(/\\/sbin\\/ifup 'eth1'/)\n      expect(comm.received_commands[0]).to match(/\\/sbin\\/ifdown 'eth2'/)\n      expect(comm.received_commands[0]).to match(/\\/sbin\\/ifup 'eth2'/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/suse/cap/halt_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSUSE::Cap::Halt\" do\n  let(:caps) do\n    VagrantPlugins::GuestSUSE::Plugin\n      .components\n      .guest_capabilities[:suse]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".halt\" do\n    let(:cap) { caps.get(:halt) }\n\n    it \"runs systemctl shutdown when systemctl is present\" do\n      comm.stub_command('test -e /usr/bin/systemctl', exit_code: 0)\n      comm.expect_command('test -e /usr/bin/systemctl')\n      comm.expect_command(\"/usr/bin/systemctl poweroff &\")\n      cap.halt(machine)\n    end\n\n    it \"runs shutdown when systemctl is not present\" do\n      comm.stub_command('test -e /usr/bin/systemctl', exit_code: 1)\n      comm.expect_command('test -e /usr/bin/systemctl')\n      comm.expect_command(\"/sbin/shutdown -h now &\")\n      cap.halt(machine)\n    end\n\n    it \"does not raise an IOError\" do\n      comm.stub_command('test -e /usr/bin/systemctl', exit_code: 0)\n      comm.stub_command(\"/usr/bin/systemctl poweroff &\", raise: IOError)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n\n    it \"ignores a Vagrant::Errors::SSHDisconnected\" do\n      comm.stub_command('test -e /usr/bin/systemctl', exit_code: 1)\n      comm.stub_command(\"/sbin/shutdown -h now &\", raise: Vagrant::Errors::SSHDisconnected)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/suse/cap/network_scripts_dir_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSUSE::Cap::NetworkScriptsDir\" do\n  let(:caps) do\n    VagrantPlugins::GuestSUSE::Plugin\n      .components\n      .guest_capabilities[:suse]\n  end\n\n  let(:machine) { double(\"machine\") }\n\n  describe \".network_scripts_dir\" do\n    let(:cap) { caps.get(:network_scripts_dir) }\n\n    it \"runs /etc/sysconfig/network\" do\n      expect(cap.network_scripts_dir(machine)).to eq(\"/etc/sysconfig/network\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/suse/cap/nfs_client_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSUSE::Cap::NFSClient\" do\n  let(:caps) do\n    VagrantPlugins::GuestSUSE::Plugin\n      .components\n      .guest_capabilities[:suse]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".nfs_client_install\" do\n    let(:cap) { caps.get(:nfs_client_install) }\n\n    it \"installs nfs client utilities\" do\n      cap.nfs_client_install(machine)\n      expect(comm.received_commands[0]).to match(/zypper -n install nfs-client/)\n      expect(comm.received_commands[0]).to match(/systemctl restart rpcbind/)\n      expect(comm.received_commands[0]).to match(/systemctl restart nfs-client.target/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/suse/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestSUSE::Cap::RSync\" do\n  let(:caps) do\n    VagrantPlugins::GuestSUSE::Plugin\n      .components\n      .guest_capabilities[:suse]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".rsync_install\" do\n    let(:cap) { caps.get(:rsync_install) }\n\n    it \"installs rsync\" do\n      comm.expect_command(\"zypper -n install rsync\")\n      cap.rsync_install(machine)\n    end\n  end\n\n  describe \".rsync_installed\" do\n    let(:cap) { caps.get(:rsync_installed) }\n\n    it \"checks if rsync is installed\" do\n      comm.expect_command(\"test -f /usr/bin/rsync\")\n      cap.rsync_installed(machine)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/tinycore/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestTinyCore::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestTinyCore::Plugin.components.guest_capabilities[:tinycore].get(:change_host_name)\n  end\n  let(:machine) { double(\"machine\") }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:old_hostname) { 'boot2docker' }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    communicator.stub_command('hostname -f', stdout: old_hostname)\n  end\n\n  after do\n    communicator.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n    it \"refreshes the hostname service with the sethostname command\" do\n      communicator.expect_command(%q(/usr/bin/sethostname newhostname.newdomain.tld))\n      described_class.change_host_name(machine, 'newhostname.newdomain.tld')\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/tinycore/cap/halt_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestTinyCore::Cap::Halt\" do\n  let(:caps) do\n    VagrantPlugins::GuestTinyCore::Plugin\n      .components\n      .guest_capabilities[:tinycore]\n  end\n\n  let(:shutdown_command){ \"poweroff\" }\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".halt\" do\n    let(:cap) { caps.get(:halt) }\n\n    it \"runs the shutdown command\" do\n      comm.expect_command(shutdown_command)\n      cap.halt(machine)\n    end\n\n    it \"ignores an IOError\" do\n      comm.stub_command(shutdown_command, raise: IOError)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n\n    it \"ignores a Vagrant::Errors::SSHDisconnected\" do\n      comm.stub_command(shutdown_command, raise: Vagrant::Errors::SSHDisconnected)\n      expect {\n        cap.halt(machine)\n      }.to_not raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/windows/cap/change_host_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/guests/windows/cap/change_host_name\")\n\ndescribe \"VagrantPlugins::GuestWindows::Cap::ChangeHostName\" do\n  let(:described_class) do\n    VagrantPlugins::GuestWindows::Plugin.components.guest_capabilities[:windows].get(:change_host_name)\n  end\n  let(:machine) { double(\"machine\", guest: guest) }\n  let(:guest) { double(\"guest\") }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n  end\n\n  after do\n    communicator.verify_expectations!\n  end\n\n  describe \".change_host_name\" do\n\n    let(:rename_script) { <<-EOH\n        $computer = Get-WmiObject -Class Win32_ComputerSystem\n        $retval = $computer.rename(\"newhostname\").returnvalue\n        if ($retval -eq 0) {\n          shutdown /r /t 5 /f /d p:4:1 /c \"Vagrant Rename Computer\"\n        }\n        exit $retval\n      EOH\n      }\n\n    it \"changes the hostname\" do\n      communicator.stub_command(\n        'if (!([System.Net.Dns]::GetHostName() -eq \\'newhostname\\')) { exit 0 } exit 1',\n        exit_code: 0)\n      communicator.stub_command(rename_script, exit_code: 0)\n      allow(machine.guest).to receive(:capability)\n      allow(machine.guest).to receive(:capability?)\n      described_class.change_host_name_and_wait(machine, 'newhostname', 0)\n    end\n\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/windows/cap/file_system_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::GuestWindows::Cap::FileSystem\" do\n  let(:caps) do\n    VagrantPlugins::GuestWindows::Plugin\n      .components\n      .guest_capabilities[:windows]\n  end\n\n  let(:machine) { double(\"machine\", communicate: comm) }\n  let(:comm) { double(\"comm\") }\n\n  before { allow(comm).to receive(:execute) }\n\n  describe \".create_tmp_path\" do\n    let(:cap) { caps.get(:create_tmp_path) }\n    let(:opts) { {} }\n\n    it \"should generate path on guest\" do\n      expect(comm).to receive(:execute).with(/GetRandomFileName/, any_args)\n      cap.create_tmp_path(machine, opts)\n    end\n\n    it \"should capture path generated on guest\" do\n      expect(comm).to receive(:execute).with(/Write-Output/, any_args).and_yield(:stdout, \"TMP_PATH\")\n      expect(cap.create_tmp_path(machine, opts)).to eq(\"TMP_PATH\")\n    end\n\n    it \"should strip newlines on path\" do\n      expect(comm).to receive(:execute).with(/Write-Output/, any_args).and_yield(:stdout, \"TMP_PATH\\r\\n\")\n      expect(cap.create_tmp_path(machine, opts)).to eq(\"TMP_PATH\")\n    end\n\n    context \"when type is a directory\" do\n      before { opts[:type] = :directory }\n\n      it \"should create guest path as a directory\" do\n        expect(comm).to receive(:execute).with(/CreateDirectory/, any_args)\n        cap.create_tmp_path(machine, opts)\n      end\n    end\n  end\n\n  describe \".decompress_zip\" do\n    let(:cap) { caps.get(:decompress_zip) }\n    let(:comp) { \"compressed_file\" }\n    let(:dest) { \"path/to/destination\" }\n    let(:opts) { {} }\n\n    before { allow(cap).to receive(:create_tmp_path).and_return(\"TMP_DIR\") }\n    after{ cap.decompress_zip(machine, comp, dest, opts) }\n\n    it \"should create temporary directory for extraction\" do\n      expect(cap).to receive(:create_tmp_path)\n    end\n\n    it \"should extract file with zip\" do\n      expect(comm).to receive(:execute).with(/copyhere/, any_args)\n    end\n\n    it \"should extract file to temporary directory\" do\n      expect(comm).to receive(:execute).with(/TMP_DIR/, any_args)\n    end\n\n    it \"should remove compressed file from guest\" do\n      expect(comm).to receive(:execute).with(/Remove-Item .*#{comp}/, any_args)\n    end\n\n    it \"should remove extraction directory from guest\" do\n      expect(comm).to receive(:execute).with(/Remove-Item .*TMP_DIR/, any_args)\n    end\n\n    it \"should create parent directories for destination\" do\n      expect(comm).to receive(:execute).with(/New-Item .*Directory .*to\\\\\"/, any_args)\n    end\n\n    context \"when type is directory\" do\n      before { opts[:type] = :directory }\n\n      it \"should create destination directory\" do\n        expect(comm).to receive(:execute).with(/New-Item .*Directory .*destination\"/, any_args)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/windows/cap/halt_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/guests/windows/cap/halt\")\n\ndescribe \"VagrantPlugins::GuestWindows::Cap::Halt\" do\n  let(:described_class) do\n    VagrantPlugins::GuestWindows::Plugin.components.guest_capabilities[:windows].get(:halt)\n  end\n  let(:machine) { double(\"machine\") }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n  end\n\n  after do\n    communicator.verify_expectations!\n  end\n\n  describe \".halt\" do\n  \n    it \"cancels any existing scheduled shut down\" do\n      communicator.expect_command(\"shutdown -a\")\n      described_class.halt(machine)\n    end\n\n    it \"shuts down immediately\" do\n      communicator.expect_command('shutdown /s /t 1 /c \"Vagrant Halt\" /f /d p:4:1')\n      described_class.halt(machine)\n    end\n\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/windows/cap/insert_public_key_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/communicators/winssh/communicator\"\n\ndescribe \"VagrantPlugins::GuestWindows::Cap::InsertPublicKey\" do\n  let(:caps) do\n    VagrantPlugins::GuestWindows::Plugin\n      .components\n      .guest_capabilities[:windows]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:auth_keys_check_result){ 1 }\n\n  before do\n    @tempfile = Tempfile.new(\"vagrant-test\")\n    allow(Tempfile).to receive(:new).and_return(@tempfile)\n    allow(comm).to receive(:is_a?).and_return(true)\n    allow(machine).to receive(:communicate).and_return(comm)\n\n    allow(comm).to receive(:execute).with(/Write-Output .+/, shell: \"powershell\").and_yield(:stdout, \"TEMP\\r\\nHOME\\r\\n\")\n    allow(comm).to receive(:execute).with(/New-Item -Path .+/, shell: \"powershell\")\n    allow(comm).to receive(:execute).with(/dir .+authorized_keys/, shell: \"cmd\", error_check: false).and_return(auth_keys_check_result)\n    allow(comm).to receive(:create_remote_directory)\n  end\n\n  after do\n    @tempfile.delete\n  end\n\n  describe \".insert_public_key\" do\n    let(:cap) { caps.get(:insert_public_key) }\n\n    context \"when authorized_keys exists on guest\" do\n      let(:auth_keys_check_result){ 0 }\n      before do\n        expect(@tempfile).to receive(:delete).and_return(true)\n        expect(@tempfile).to receive(:delete).and_call_original\n      end\n\n      it \"inserts the public key\" do\n        expect(comm).to receive(:download)\n        expect(comm).to receive(:upload)\n        expect(comm).to receive(:execute).with(/Set-Acl .*/, shell: \"powershell\")\n        cap.insert_public_key(machine, \"ssh-rsa ...\")\n        expect(File.read(@tempfile.path)).to include(\"ssh-rsa ...\")\n      end\n    end\n\n    context \"when authorized_keys does not exist on guest\" do\n      before do\n        expect(@tempfile).to receive(:delete).and_return(true)\n        expect(@tempfile).to receive(:delete).and_call_original\n      end\n\n      it \"inserts the public key\" do\n        expect(comm).to_not receive(:download)\n        expect(comm).to receive(:upload)\n        expect(comm).to receive(:execute).with(/Set-Acl .*/, shell: \"powershell\")\n        cap.insert_public_key(machine, \"ssh-rsa ...\")\n        expect(File.read(@tempfile.path)).to include(\"ssh-rsa ...\")\n      end\n    end\n\n    context \"when required directories cannot be fetched from the guest\" do\n      before do\n        expect(comm).to receive(:execute).with(/Write-Output .+/, shell: \"powershell\").and_yield(:stdout, \"TEMP\\r\\n\")\n      end\n\n      it \"should raise an error\" do\n        expect{ cap.insert_public_key(machine, \"ssh-rsa ...\") }.to raise_error(VagrantPlugins::GuestWindows::Errors::PublicKeyDirectoryFailure)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/windows/cap/mount_shared_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/guests/windows/cap/mount_shared_folder\")\n\ndescribe \"VagrantPlugins::GuestWindows::Cap::MountSharedFolder\" do\n\n  let(:machine) { double(\"machine\") }\n  let(:communicator) { double(:execute) }\n  let(:config) { double(\"config\") }\n  let(:vm) { double(\"vm\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(communicator).to receive(:execute)\n    allow(machine).to receive(:config).and_return(config)\n    allow(config).to receive(:vm).and_return(vm)\n    allow(vm).to receive(:communicator).and_return(:winrm)\n  end\n\n  describe \"virtualbox\" do\n\n    let(:described_class) do\n      VagrantPlugins::GuestWindows::Plugin.components.guest_capabilities[:windows].get(:mount_virtualbox_shared_folder)\n    end\n\n    describe \".mount_shared_folder\" do\n      it \"should call mount_volume script with correct args\" do\n        expect(Vagrant::Util::TemplateRenderer).to receive(:render).with(\n          /.+scripts\\/mount_volume.ps1/, options: {\n              mount_point: \"guestpath\",\n              share_name: \"name\",\n              vm_provider_unc_path: \"\\\\\\\\vboxsvr\\\\name\",\n            })\n        described_class.mount_virtualbox_shared_folder(machine, 'name', 'guestpath', {})\n      end\n\n      it \"should replace invalid Windows share chars\" do\n        expect(Vagrant::Util::TemplateRenderer).to receive(:render).with(\n          kind_of(String), options: {\n              mount_point: kind_of(String),\n              share_name: \"invalid-windows_sharename\",\n              vm_provider_unc_path: \"\\\\\\\\vboxsvr\\\\invalid-windows_sharename\",\n            })\n        described_class.mount_virtualbox_shared_folder(machine, \"/invalid-windows/sharename\", \"guestpath\", {})\n      end\n    end\n  end\n\n  describe \"vmware\" do\n\n    let(:described_class) do\n      VagrantPlugins::GuestWindows::Plugin.components.guest_capabilities[:windows].get(:mount_vmware_shared_folder)\n    end\n\n    describe \".mount_shared_folder\" do\n      it \"should call mount_volume script with correct args\" do\n        expect(Vagrant::Util::TemplateRenderer).to receive(:render).with(\n          /.+scripts\\/mount_volume.ps1/, options: {\n              mount_point: \"guestpath\",\n              share_name: \"name\",\n              vm_provider_unc_path: \"\\\\\\\\vmware-host\\\\Shared Folders\\\\name\",\n            })\n        described_class.mount_vmware_shared_folder(machine, 'name', 'guestpath', {})\n      end\n    end\n  end\n\n  describe \"parallels\" do\n\n    let(:described_class) do\n      VagrantPlugins::GuestWindows::Plugin.components.guest_capabilities[:windows].get(:mount_parallels_shared_folder)\n    end\n\n    describe \".mount_shared_folder\" do\n      it \"should call mount_volume script with correct args\" do\n        expect(Vagrant::Util::TemplateRenderer).to receive(:render).with(\n          /.+scripts\\/mount_volume.ps1/, options: {\n              mount_point: \"guestpath\",\n              share_name: \"name\",\n              vm_provider_unc_path: \"\\\\\\\\psf\\\\name\",\n            })\n        described_class.mount_parallels_shared_folder(machine, 'name', 'guestpath', {})\n      end\n    end\n  end\n\n  describe \"smb\" do\n\n    let(:described_class) do\n      VagrantPlugins::GuestWindows::Plugin.components.guest_capabilities[:windows].get(:mount_smb_shared_folder)\n    end\n\n    describe \".mount_shared_folder\" do\n      it \"should call mount_volume script with correct args\" do\n        expect(Vagrant::Util::TemplateRenderer).to receive(:render).with(\n          /.+scripts\\/mount_volume.ps1/, options: {\n              mount_point: \"guestpath\",\n              share_name: \"name\",\n              vm_provider_unc_path: \"\\\\\\\\host\\\\name\",\n            })\n        expect(machine.communicate).to receive(:execute).with(\"cmdkey /add:host /user:user /pass:\\\"pass\\\"\", {:shell=>:powershell, :elevated=>true})\n        described_class.mount_smb_shared_folder(machine, 'name', 'guestpath', {:smb_username => \"user\", :smb_password => \"pass\", :smb_host => \"host\"})\n      end\n    end\n  end\n\n  describe \"virtualbox-ssh\" do\n\n    let(:described_class) do\n      VagrantPlugins::GuestWindows::Plugin.components.guest_capabilities[:windows].get(:mount_virtualbox_shared_folder)\n    end\n\n    before do\n      allow(vm).to receive(:communicator).and_return(:ssh)\n    end\n\n    describe \".mount_shared_folder\" do\n      it \"should call mount_volume script via ssh\" do\n        expect(communicator).to receive(:execute).with(/powershell/, shell: \"sh\")\n        described_class.mount_virtualbox_shared_folder(machine, 'name', 'guestpath', {})\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/windows/cap/reboot_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/guests/windows/cap/reboot\")\n\ndescribe \"VagrantPlugins::GuestWindows::Cap::Reboot\" do\n  let(:described_class) do\n    VagrantPlugins::GuestWindows::Plugin.components.guest_capabilities[:windows].get(:wait_for_reboot)\n  end\n  let(:vm) { double(\"vm\") }\n  let(:config) { double(\"config\") }\n  let(:machine) { double(\"machine\", ui: ui) }\n  let(:guest) { double(\"guest\") }\n  let(:communicator) { double(\"communicator\") }\n  let(:ui) { Vagrant::UI::Silent.new }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(machine).to receive(:guest).and_return(guest)\n    allow(machine.guest).to receive(:ready?).and_return(true)\n    allow(machine).to receive(:config).and_return(config)\n    allow(config).to receive(:vm).and_return(vm)\n  end\n\n  describe \".reboot\" do\n    before do\n      allow(vm).to receive(:communicator).and_return(:winrm)\n    end\n\n    it \"reboots the vm\" do\n      allow(communicator).to receive(:execute)\n\n      expect(communicator).to receive(:test).with(/# Function/, { error_check: false, shell: :powershell }).and_return(0)\n      expect(communicator).to receive(:execute).with(/shutdown/, { shell: :powershell }).and_return(0)\n      expect(described_class).to receive(:wait_for_reboot)\n\n      described_class.reboot(machine)\n    end\n\n    context \"user output\" do\n      before do\n        allow(communicator).to receive(:execute)\n        allow(described_class).to receive(:wait_for_reboot)\n      end\n\n      after { described_class.reboot(machine) }\n\n      it \"sends message to user that guest is rebooting\" do\n        expect(communicator).to receive(:test).and_return(true)\n        expect(ui).to receive(:info).and_call_original\n      end\n    end\n\n    context \"with exceptions while waiting for reboot\" do\n      before { allow(described_class).to receive(:sleep) }\n\n      it \"should retry on any standard error\" do\n        allow(communicator).to receive(:execute)\n\n        expect(communicator).to receive(:test).with(/# Function/, { error_check: false, shell: :powershell }).and_return(0)\n        expect(communicator).to receive(:execute).with(/shutdown/, { shell: :powershell }).and_return(0)\n        expect(described_class).to receive(:wait_for_reboot).and_raise(StandardError)\n        expect(described_class).to receive(:wait_for_reboot)\n\n        described_class.reboot(machine)\n      end\n\n      it \"should not retry when exception is not a standard error\" do\n        allow(communicator).to receive(:execute)\n\n        expect(communicator).to receive(:test).with(/# Function/, { error_check: false, shell: :powershell }).and_return(0)\n        expect(communicator).to receive(:execute).with(/shutdown/, { shell: :powershell }).and_return(0)\n        expect(described_class).to receive(:wait_for_reboot).and_raise(Exception)\n\n        expect { described_class.reboot(machine) }.to raise_error(Exception)\n      end\n    end\n  end\n\n  describe \"winrm communicator\" do\n    before do\n      allow(vm).to receive(:communicator).and_return(:winrm)\n    end\n\n    describe \".wait_for_reboot\" do\n      it \"runs reboot detect script\" do\n        expect(communicator).to receive(:execute).with(/# Function/, { error_check: false, shell: :powershell }).and_return(0)\n        allow(communicator).to receive(:execute)\n\n        described_class.wait_for_reboot(machine)\n      end\n\n      it \"fixes symlinks to network shares\" do\n        allow(communicator).to receive(:execute).and_return(0)\n        expect(communicator).to receive(:execute).with('net use', { error_check: false, shell: :powershell })\n\n        described_class.wait_for_reboot(machine)\n      end\n    end\n  end\n\n  describe \"ssh communicator\" do\n    before do\n      allow(vm).to receive(:communicator).and_return(:ssh)\n    end\n\n    describe \".wait_for_reboot\" do\n      it \"does execute Windows reboot detect script\" do\n        expect(communicator).to receive(:execute).with(/# Function/, { error_check: false, shell: :powershell }).and_return(0)\n        expect(communicator).to receive(:execute).with('net use', { error_check: false, shell: :powershell })\n        described_class.wait_for_reboot(machine)\n      end\n    end\n  end\n\n  context \"reboot configuration\" do\n    before do\n      allow(communicator).to receive(:execute)\n      expect(communicator).to receive(:test).with(/# Function/, { error_check: false, shell: :powershell }).and_return(0)\n      expect(communicator).to receive(:execute).with(/shutdown/, { shell: :powershell }).and_return(0)\n      allow(described_class).to receive(:sleep)\n      allow(described_class).to receive(:wait_for_reboot).and_raise(StandardError)\n    end\n\n    context \"default retry duration value\" do\n      let(:max_retries) { (described_class::DEFAULT_MAX_REBOOT_RETRY_DURATION / described_class::WAIT_SLEEP_TIME) + 2 }\n\n      it \"should receive expected number of wait_for_reboot calls\" do\n        expect(described_class).to receive(:wait_for_reboot).exactly(max_retries).times\n        expect { described_class.reboot(machine) }.to raise_error(StandardError)\n      end\n    end\n\n    context \"with custom retry duration value\" do\n      let(:duration) { 10 }\n      let(:max_retries) { (duration / described_class::WAIT_SLEEP_TIME) + 2 }\n\n      before do\n        expect(ENV).to receive(:fetch).with(\"VAGRANT_MAX_REBOOT_RETRY_DURATION\", anything).and_return(duration)\n      end\n\n      it \"should receive expected number of wait_for_reboot calls\" do\n        expect(described_class).to receive(:wait_for_reboot).exactly(max_retries).times\n        expect { described_class.reboot(machine) }.to raise_error(StandardError)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/windows/cap/remove_public_key_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/communicators/winssh/communicator\"\n\ndescribe \"VagrantPlugins::GuestWindows::Cap::RemovePublicKey\" do\n  let(:caps) do\n    VagrantPlugins::GuestWindows::Plugin\n      .components\n      .guest_capabilities[:windows]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:public_key_insecure){ \"ssh-rsa...insecure\" }\n  let(:public_key_other){ \"ssh-rsa...other\" }\n\n  let(:auth_keys_check_result){ 1 }\n\n  before do\n    @tempfile = Tempfile.new(\"vagrant-test\")\n    @tempfile.puts(public_key_insecure)\n    @tempfile.puts(public_key_other)\n    @tempfile.flush\n    @tempfile.rewind\n    allow(Tempfile).to receive(:new).and_return(@tempfile)\n    allow(comm).to receive(:is_a?).and_return(true)\n    allow(machine).to receive(:communicate).and_return(comm)\n\n    allow(comm).to receive(:execute).with(/Write-Output .+/, shell: \"powershell\").and_yield(:stdout, \"TEMP\\r\\nHOME\\r\\n\")\n    allow(comm).to receive(:execute).with(/New-Item -Path/, shell: \"powershell\")\n    allow(comm).to receive(:execute).with(/dir .+\\.ssh/, shell: \"cmd\")\n    allow(comm).to receive(:execute).with(/dir .+authorized_keys/, shell: \"cmd\", error_check: false).and_return(auth_keys_check_result)\n    allow(comm).to receive(:create_remote_directory)\n  end\n\n  after do\n    @tempfile.delete\n  end\n\n  describe \".remove_public_key\" do\n    let(:cap) { caps.get(:remove_public_key) }\n\n    context \"when authorized_keys exists on guest\" do\n      let(:auth_keys_check_result){ 0 }\n      before do\n        expect(@tempfile).to receive(:delete).and_return(true)\n        expect(@tempfile).to receive(:delete).and_call_original\n      end\n\n      it \"removes the public key\" do\n        expect(comm).to receive(:download)\n        expect(comm).to receive(:upload)\n        expect(comm).to receive(:execute).with(/Set-Acl .*/, shell: \"powershell\")\n        cap.remove_public_key(machine, public_key_insecure)\n        expect(File.read(@tempfile.path)).to include(public_key_other)\n        expect(File.read(@tempfile.path)).to_not include(public_key_insecure)\n      end\n    end\n\n    context \"when authorized_keys does not exist on guest\" do\n      it \"does nothing\" do\n        expect(comm).to_not receive(:download)\n        expect(comm).to receive(:upload)\n        expect(comm).to receive(:execute).with(/Set-Acl .*/, shell: \"powershell\")\n        cap.remove_public_key(machine, public_key_insecure)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/windows/cap/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/guests/windows/cap/rsync\")\n\ndescribe \"VagrantPlugins::GuestWindows::Cap::RSync\" do\n  let(:described_class) do\n    VagrantPlugins::GuestWindows::Plugin.components.guest_capabilities[:windows].get(:rsync_pre)\n  end\n  let(:machine) { double(\"machine\") }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n  end\n\n  after do\n    communicator.verify_expectations!\n  end\n\n  describe \".rsync_pre\" do\n    it 'makes the guestpath directory with mkdir' do\n      communicator.expect_command(\"mkdir \\\"/sync_dir\\\" -force\")\n      described_class.rsync_pre(machine, guestpath: '/sync_dir')\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/windows/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/guests/windows/config\")\n\ndescribe VagrantPlugins::GuestWindows::Config do\n  let(:machine) { double(\"machine\") }\n\n  subject { described_class.new }\n\n  it \"is valid by default\" do\n    subject.finalize!\n    result = subject.validate(machine)\n    expect(result[\"Windows Guest\"]).to be_empty\n  end\n\n  describe \"default values\" do\n    before { subject.finalize! }\n    its(\"set_work_network\") { should == false }\n  end\n\n  describe \"attributes\" do\n    [:set_work_network].each do |attribute|\n      it \"should not default #{attribute} if overridden\" do\n        subject.send(\"#{attribute}=\".to_sym, 10)\n        subject.finalize!\n        expect(subject.send(attribute)).to be(10)\n      end\n\n      it \"should return error #{attribute} if nil\" do\n        subject.send(\"#{attribute}=\".to_sym, nil)\n        subject.finalize!\n        result = subject.validate(machine)\n        expect(result[\"Windows Guest\"]).to include(\"windows.#{attribute} cannot be nil.\")\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/guests/windows/guest_network_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/guests/windows/guest_network\")\n\ndescribe \"VagrantPlugins::GuestWindows::GuestNetwork\" do\n\n  let(:communicator) { double(\"communicator\") }\n  let(:subject) { VagrantPlugins::GuestWindows::GuestNetwork.new(communicator) }\n\n  describe \".is_dhcp_enabled\" do\n    it \"should query the NIC by ordinal index\" do\n      expect(communicator).to receive(:test).with(\n        /.+Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter \"Index=7 and DHCPEnabled=True\"/).\n        and_return(true)\n      expect(subject.is_dhcp_enabled(7)).to be(true)\n    end\n\n    it \"should return false for non-DHCP NICs\" do\n      expect(communicator).to receive(:test).with(\n        /.+Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter \"Index=8 and DHCPEnabled=True\"/).\n        and_return(false)\n      expect(subject.is_dhcp_enabled(8)).to be(false)\n    end\n  end\n\n  describe \".configure_static_interface\" do\n    it \"should configure IP using netsh\" do\n      expect(communicator).to receive(:execute).with(\n        \"netsh interface ip set address \\\"Local Area Connection 2\\\" static 192.168.33.10 255.255.255.0\").\n        and_return(0)\n      subject.configure_static_interface(7, \"Local Area Connection 2\", \"192.168.33.10\", \"255.255.255.0\")\n    end\n  end\n\n  describe \".configure_dhcp_interface\" do\n    it \"should configure DHCP when DHCP is disabled\" do\n      allow(communicator).to receive(:test).and_return(false) # is DHCP enabled?\n      expect(communicator).to receive(:execute).with(\n        \"netsh interface ip set address \\\"Local Area Connection 2\\\" dhcp\").\n        and_return(0)\n      subject.configure_dhcp_interface(7, \"Local Area Connection 2\")\n    end\n\n    it \"should not configure DHCP when DHCP is enabled\" do\n      allow(communicator).to receive(:test).and_return(true) # is DHCP enabled?\n      expect(communicator).to_not receive(:execute)\n      subject.configure_dhcp_interface(7, \"Local Area Connection 2\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/bsd/cap/nfs_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/hosts/bsd/cap/nfs\"\n\ndescribe VagrantPlugins::HostBSD::Cap::NFS do\n\n  include_context \"unit\"\n\n  describe \".nfs_export\" do\n    let(:environment) { double(\"environment\", host: host) }\n    let(:host) { double(\"host\") }\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:id) { \"UUID\" }\n    let(:ips) { [] }\n    let(:folders) { {} }\n\n    before do\n      allow(host).to receive(:capability).and_return(\"\")\n      allow(Vagrant::Util::TemplateRenderer).to receive(:render).and_return(\"\")\n      allow(described_class).to receive(:sleep)\n      allow(described_class).to receive(:nfs_cleanup)\n      allow(described_class).to receive(:system)\n      allow(described_class).to receive(:nfs_running?).and_return(true)\n      allow(File).to receive(:writable?).with(\"/etc/exports\")\n\n      allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"nfsd\", \"checkexports\").\n        and_return(Vagrant::Util::Subprocess::Result.new(0, \"\", \"\"))\n    end\n\n    it \"should execute successfully when no folders are defined\" do\n      expect { described_class.nfs_export(environment, ui, id, ips, folders) }.\n        not_to raise_error\n    end\n\n    context \"with single folder defined\" do\n      let(:folders) {\n        {\"/vagrant\" => {\n          type: :nfs, guestpath: \"/vagrant\", hostpath: \"/Users/vagrant/paths\", disabled: false}}\n      }\n\n      it \"should execute successfully\" do\n        expect { described_class.nfs_export(environment, ui, id, ips, folders) }.\n          not_to raise_error\n      end\n\n      it \"should resolve the host path\" do\n        expect(host).to receive(:capability).with(:resolve_host_path, folders[\"/vagrant\"][:hostpath]).and_return(\"\")\n        described_class.nfs_export(environment, ui, id, ips, folders)\n      end\n    end\n\n    context \"when nfsd is not running\" do \n      before {\n        allow(described_class).to receive(:nfs_running?).and_return(false)\n      }\n      it \"should restart nfsd\" do\n        expect(host).to receive(:capability).with(:nfs_restart_command).and_return([\"restart\"])\n        expect(described_class).to receive(:system).with(\"restart\")\n        described_class.nfs_export(environment, ui, id, ips, folders)\n      end\n    end\n\n    context \"when nfsd is running\" do \n      before {\n        allow(described_class).to receive(:nfs_running?).and_return(true)\n      }\n      it \"should update nfsd\" do\n        expect(host).to receive(:capability).with(:nfs_update_command).and_return([\"update\"])\n        expect(described_class).to receive(:system).with(\"update\")\n        described_class.nfs_export(environment, ui, id, ips, folders)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/bsd/cap/path_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/hosts/bsd/cap/path\"\n\ndescribe VagrantPlugins::HostBSD::Cap::Path do\n  describe \".resolve_host_path\" do\n    let(:env) { double(\"environment\") }\n    let(:path) { double(\"path\") }\n\n    it \"should return the path object provided\" do\n      expect(described_class.resolve_host_path(env, path)).to eq(path)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/bsd/cap/ssh_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire_relative \"../../../../../../plugins/hosts/bsd/cap/ssh\"\n\ndescribe VagrantPlugins::HostBSD::Cap::SSH do\n  let(:subject){ VagrantPlugins::HostBSD::Cap::SSH }\n\n  let(:env){ double(\"env\") }\n  let(:key_path){ double(\"key_path\") }\n\n  it \"should set file as user only read/write\" do\n    expect(key_path).to receive(:chmod).with(0600)\n    subject.set_ssh_key_permissions(env, key_path)\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/darwin/cap/configured_ip_addresses_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire_relative \"../../../../../../plugins/hosts/darwin/cap/configured_ip_addresses\"\n\ndescribe VagrantPlugins::HostDarwin::Cap::ConfiguredIPAddresses do\n\n  let(:subject){ VagrantPlugins::HostDarwin::Cap::ConfiguredIPAddresses }\n  let(:interfaces){ [\"192.168.1.2\"] }\n  before{ allow(Socket).to receive(:getifaddrs).and_return(\n    interfaces.map{|i| double(:socket, addr: Addrinfo.ip(i))}) }\n\n  it \"should get list of available addresses\" do\n    expect(subject.configured_ip_addresses(nil)).to eq([\"192.168.1.2\"])\n  end\n\n  context \"with loopback address\" do\n    let(:interfaces){ [\"192.168.1.2\", \"127.0.0.1\"] }\n\n    it \"should not include loopback address\" do\n      expect(subject.configured_ip_addresses(nil)).not_to include([\"127.0.0.1\"])\n    end\n  end\n\n  context \"with IPv6 address\" do\n    let(:interfaces){ [\"192.168.1.2\", \"2001:200:dff:fff1:216:3eff:feb1:44d7\"] }\n\n    it \"should not include IPv6 address\" do\n      expect(subject.configured_ip_addresses(nil)).not_to include([\"2001:200:dff:fff1:216:3eff:feb1:44d7\"])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/darwin/cap/fs_iso_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/hosts/darwin/cap/fs_iso\"\n\ndescribe VagrantPlugins::HostDarwin::Cap::FsISO do\n  include_context \"unit\"\n\n  let(:subject){ VagrantPlugins::HostDarwin::Cap::FsISO }\n  let(:env) { double(\"env\") }\n\n  describe \".isofs_available\" do\n    it \"finds iso building utility when available\" do\n      expect(Vagrant::Util::Which).to receive(:which).and_return(true)\n      expect(subject.isofs_available(env)).to eq(true)\n    end\n  end\n\n  describe \".create_iso\" do\n    let(:file_destination) { \"/woo/out.iso\" }\n\n    before do \n      allow(file_destination).to receive(:nil?).and_return(false)\n    end\n\n    it \"builds an iso\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\n        \"hdiutil\", \"makehybrid\", \"-iso\", \"-joliet\", \"-ov\", \"-o\", /.iso/, /\\/foo\\/src/\n      ).and_return(double(exit_code: 0))\n      expect(FileUtils).to receive(:mkdir_p).with(Pathname.new(file_destination).dirname)\n\n      output = subject.create_iso(env, \"/foo/src\", file_destination: file_destination)\n      expect(output.to_s).to eq(\"/woo/out.iso\")\n    end\n\n    it \"builds an iso with volume_id\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\n        \"hdiutil\", \"makehybrid\", \"-iso\", \"-joliet\", \"-ov\", \"-default-volume-name\", \"cidata\", \"-o\", /.iso/, /\\/foo\\/src/\n      ).and_return(double(exit_code: 0))\n      expect(FileUtils).to receive(:mkdir_p).with(Pathname.new(file_destination).dirname)\n\n      output = subject.create_iso(env, \"/foo/src\", file_destination: file_destination, volume_id: \"cidata\")\n      expect(output.to_s).to eq(\"/woo/out.iso\")\n    end\n\n    it \"builds an iso given a file destination without an extension\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\n        \"hdiutil\", \"makehybrid\", \"-iso\", \"-joliet\", \"-ov\", \"-o\", /.iso/, /\\/foo\\/src/\n      ).and_return(double(exit_code: 0))\n      expect(FileUtils).to receive(:mkdir_p).with(Pathname.new(\"/woo/out_dir\"))\n\n\n      output = subject.create_iso(env, \"/foo/src\", file_destination: \"/woo/out_dir\")\n      expect(output.to_s).to match(/\\/woo\\/out_dir\\/[\\w]{6}_vagrant.iso/)\n    end\n\n    it \"builds an iso when no file destination is given\" do\n      allow(Tempfile).to receive(:new).and_return(file_destination)\n      allow(file_destination).to receive(:path).and_return(file_destination)\n      allow(file_destination).to receive(:delete)\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\n        \"hdiutil\", \"makehybrid\", \"-iso\", \"-joliet\", \"-ov\", \"-o\", /.iso/, /\\/foo\\/src/\n      ).and_return(double(exit_code: 0))\n      # Should create a directory wherever Tempfile creates files by default\n      expect(FileUtils).to receive(:mkdir_p).with(Pathname.new(file_destination).dirname)\n      allow(file_destination).to receive(:close)\n      allow(file_destination).to receive(:unlink)\n      output = subject.create_iso(env, \"/foo/src\")\n      expect(output.to_s).to eq(file_destination)\n    end\n\n    it \"raises an error if iso build failed\" do\n      allow(Vagrant::Util::Subprocess).to receive(:execute).with(any_args).and_return(double(stdout: \"nope\", stderr: \"nope\", exit_code: 1))\n      expect(FileUtils).to receive(:mkdir_p).with(Pathname.new(file_destination).dirname)\n\n      expect{ subject.create_iso(env, \"/foo/src\", file_destination: file_destination) }.to raise_error(Vagrant::Errors::ISOBuildFailed)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/darwin/cap/nfs_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire_relative \"../../../../../../plugins/hosts/darwin/cap/nfs\"\n\ndescribe VagrantPlugins::HostDarwin::Cap::NFS do\n  include_context \"unit\"\n\n  let(:subject){ VagrantPlugins::HostDarwin::Cap::NFS }\n\n  it \"exists\" do\n    expect(subject).to_not be(nil)\n  end\n\n  it \"should use nfs/exports_darwin as its template\" do\n    expect(subject.nfs_exports_template(nil)).to eq(\"nfs/exports_darwin\")\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/darwin/cap/path_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/hosts/darwin/cap/path\"\nrequire_relative \"../../../../../../plugins/hosts/darwin/cap/version\"\n\ndescribe VagrantPlugins::HostDarwin::Cap::Path do\n  include_context \"unit\"\n\n  let(:caps) do\n    VagrantPlugins::HostDarwin::Plugin\n      .components\n      .host_capabilities[:darwin]\n  end\n\n  let(:host) { @host }\n  let(:hosts) {\n    {\n      darwin: [VagrantPlugins::HostDarwin::Host, :bsd]\n    }\n  }\n\n  let(:env) do\n    ienv = isolated_environment\n    ienv.vagrantfile(\"\")\n    env = ienv.create_vagrant_env\n    @host = Vagrant::Host.new(\n      :darwin,\n      hosts,\n      {darwin: caps},\n      env\n    )\n    env.instance_variable_set(:@host, @host)\n    env\n  end\n\n  describe \".resolve_host_path\" do\n    let(:cap) { caps.get(:resolve_host_path) }\n    let(:path) { \"/test/vagrant/path\" }\n    let(:firmlink_map) { {} }\n    let(:macos_version) { Gem::Version.new(\"10.15.1\") }\n    let(:result) {\n      Vagrant::Util::Subprocess::Result.new(0, macos_version, \"\")\n    }\n\n    before do\n      # allow(env).to receive(:host).\n      #   and_return(host)\n      # allow(host).to receive(:capability).\n      #   with(:version).\n      #   and_return(macos_version)\n      allow(Vagrant::Util::Subprocess).to receive(:execute).\n        with(\"sw_vers\", \"-productVersion\").\n        and_return(result)\n      allow(described_class).to receive(:firmlink_map).\n        and_return(firmlink_map)\n    end\n\n    it \"should not change the path when no firmlinks are defined\" do\n      expect(cap.resolve_host_path(env, path)).to eq(path)\n    end\n\n    context \"when firmlink map contains non-matching values\" do\n      let(:firmlink_map) { {\"/users\" => \"users\", \"/system\" => \"system\"} }\n\n      it \"should not change the path\" do\n        expect(cap.resolve_host_path(env, path)).to eq(path)\n      end\n    end\n\n    context \"when firmlink map contains matching value\" do\n      let(:firmlink_map) { {\"/users\" => \"users\", \"/test\" => \"test\"} }\n\n      it \"should update the path\" do\n        expect(cap.resolve_host_path(env, path)).not_to eq(path)\n      end\n\n      it \"should prefix the path with the defined data path\" do\n        expect(cap.resolve_host_path(env, path)).to start_with(described_class.const_get(:FIRMLINK_DATA_PATH))\n      end\n    end\n\n    context \"when firmlink map match points to different named target\" do\n      let(:firmlink_map) { {\"/users\" => \"users\", \"/test\" => \"other\"} }\n\n      it \"should update the path\" do\n        expect(cap.resolve_host_path(env, path)).not_to eq(path)\n      end\n\n      it \"should prefix the path with the defined data path\" do\n        expect(cap.resolve_host_path(env, path)).\n          to start_with(described_class.const_get(:FIRMLINK_DATA_PATH))\n      end\n\n      it \"should include the updated path name\" do\n        expect(cap.resolve_host_path(env, path)).to include(\"other\")\n      end\n    end\n\n    context \"when macos version is later than catalina\" do\n      let(:macos_version) { Gem::Version.new(\"10.16.1\") }\n\n      it \"should not update the path\" do\n        expect(cap.resolve_host_path(env, path)).to eq(path)\n      end\n\n      it \"should not prefix the path with the defined data path\" do\n        expect(cap.resolve_host_path(env, path)).\n          not_to start_with(described_class.const_get(:FIRMLINK_DATA_PATH))\n      end\n    end\n  end\n\n  describe \".firmlink_map\" do\n    let(:cap) { caps.get(:firmlink_map) }\n    before { described_class.reset! }\n\n    context \"when firmlink definition file does not exist\" do\n      before { expect(File).to receive(:exist?).\n          with(described_class.const_get(:FIRMLINK_DEFS)).and_return(false) }\n\n      it \"should return an empty hash\" do\n        expect(described_class.firmlink_map).to eq({})\n      end\n    end\n\n    context \"when firmlink definition file exists with values\" do\n      before do\n        expect(File).to receive(:exist?).with(described_class.const_get(:FIRMLINK_DEFS)).and_return(true)\n        expect(File).to receive(:readlines).with.(described_class.const_get(:FIRMLINK_DEFS)).\n          and_return([\"/System\\tSystem\\n\", \"/Users\\tUsers\\n\", \"/Library/Something\\tLibrary/Somethingelse\"])\n\n        it \"should generate a non-empty hash\" do\n          expect(described_class.firmlink_map).not_to be_empty\n        end\n\n        it \"should properly create entries\" do\n          result = described_class.firmlink_map\n          expect(result[\"/System\"]).to eq(\"System\")\n          expect(result[\"/Users\"]).to eq(\"Users\")\n          expect(result[\"/Library/Something\"]).to eq(\"Library/Somethingelse\")\n        end\n\n        it \"should only load values once\" do\n          describe_class.firmlink_app\n          expect(File).not_to receive(:readlines)\n          describe_class.firmlink_app\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/darwin/cap/rdp_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire_relative \"../../../../../../plugins/hosts/darwin/cap/rdp\"\n\ndescribe VagrantPlugins::HostDarwin::Cap::RDP do\n  let(:rdp_info) do\n    {\n      host: \"host\",\n      port: \"port\",\n      username: \"username\",\n    }\n  end\n\n  it \"includes the default options\" do\n    path = described_class.generate_config_file(rdp_info)\n    result = File.readlines(path).map(&:chomp)\n    expect(result).to include(\"drivestoredirect:s:*\")\n    expect(result).to include(\"full address:s:host:port\")\n    expect(result).to include(\"prompt for credentials:i:1\")\n    expect(result).to include(\"username:s:username\")\n  end\n\n  it \"includes extra RDP arguments\" do\n    rdp_info.merge!(extra_args: [\"screen mode id:i:0\"])\n    path = described_class.generate_config_file(rdp_info)\n    result = File.readlines(path).map(&:chomp)\n    expect(result).to include(\"screen mode id:i:0\")\n  end\n\n  it \"opens the RDP file\" do\n    env = double(:env)\n    allow(described_class).to receive(:generate_config_file).and_return(\"/path\")\n    expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"open\", \"/path\")\n    described_class.rdp_client(env, rdp_info)\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/darwin/cap/smb_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire_relative \"../../../../../../plugins/hosts/darwin/cap/smb\"\n\ndescribe VagrantPlugins::HostDarwin::Cap::SMB do\n  include_context \"unit\"\n\n  let(:subject){ VagrantPlugins::HostDarwin::Cap::SMB }\n  let(:machine){ double(:machine) }\n  let(:env){ double(:env) }\n  let(:options){ {} }\n  let(:result){ Vagrant::Util::Subprocess::Result }\n\n  before{ allow(subject).to receive(:machine_id).and_return(\"CUSTOM_ID\") }\n\n  describe \".smb_installed\" do\n    it \"is installed if sharing binary exists\" do\n      expect(File).to receive(:exist?).with(\"/usr/sbin/sharing\").and_return(true)\n      expect(subject.smb_installed(nil)).to be(true)\n    end\n\n    it \"is not installed if sharing binary does not exist\" do\n      expect(File).to receive(:exist?).with(\"/usr/sbin/sharing\").and_return(false)\n      expect(subject.smb_installed(nil)).to be(false)\n    end\n  end\n\n  describe \".smb_start\" do\n    before{ allow(Vagrant::Util::Subprocess).to receive(:execute)\n        .and_return(result.new(0, \"SMB-NT\", \"\")) }\n\n    it \"should check for NT compatible password\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"pwpolicy\", \"gethashtypes\").\n        and_return(result.new(0, \"SMB-NT\", \"\"))\n      subject.smb_start(env)\n    end\n\n    it \"should raise error if NT compatible password is not set\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"pwpolicy\", \"gethashtypes\").\n        and_return(result.new(0, \"\", \"\"))\n      expect{ subject.smb_start(env) }.to raise_error(VagrantPlugins::SyncedFolderSMB::Errors::SMBCredentialsMissing)\n    end\n\n    it \"should ignore if the command returns non-zero\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"pwpolicy\", \"gethashtypes\").\n        and_return(result.new(1, \"\", \"\"))\n      subject.smb_start(env)\n    end\n\n    it \"should not load smb preferences if it is already loaded\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"launchctl\", \"list\", /preferences/).and_return(result.new(0, \"\", \"\"))\n      expect(Vagrant::Util::Subprocess).not_to receive(:execute).with(/sudo/, /launchctl/, \"load\", \"-w\", /preferences/)\n      subject.smb_start(env)\n    end\n\n    it \"should load smb preferences if it is not already loaded\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"launchctl\", \"list\", /preferences/).and_return(result.new(1, \"\", \"\"))\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, /launchctl/, \"load\", \"-w\", /preferences/).and_return(result.new(0, \"\", \"\"))\n      subject.smb_start(env)\n    end\n\n    it \"should raise error if load smb preferences fails\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"launchctl\", \"list\", /preferences/).and_return(result.new(1, \"\", \"\"))\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, /launchctl/, \"load\", \"-w\", /preferences/).and_return(result.new(1, \"\", \"\"))\n      expect{ subject.smb_start(env) }.to raise_error(VagrantPlugins::SyncedFolderSMB::Errors::SMBStartFailed)\n    end\n\n    it \"should not load smbd if it is already loaded\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"launchctl\", \"list\", /smbd/).and_return(result.new(0, \"\", \"\"))\n      expect(Vagrant::Util::Subprocess).not_to receive(:execute).with(/sudo/, /launchctl/, \"load\", \"-w\", /smbd/)\n      subject.smb_start(env)\n    end\n\n    it \"should load smbd if it is not already loaded\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"launchctl\", \"list\", /smbd/).and_return(result.new(1, \"\", \"\"))\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, /launchctl/, \"load\", \"-w\", /smbd/).and_return(result.new(0, \"\", \"\"))\n      subject.smb_start(env)\n    end\n\n    it \"should raise error if load smbd fails\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"launchctl\", \"list\", /smbd/).and_return(result.new(1, \"\", \"\"))\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, /launchctl/, \"load\", \"-w\", /smbd/).and_return(result.new(1, \"\", \"\"))\n      expect{ subject.smb_start(env) }.to raise_error(VagrantPlugins::SyncedFolderSMB::Errors::SMBStartFailed)\n    end\n  end\n\n  describe \".smb_cleanup\" do\n    after{ subject.smb_cleanup(env, machine, options) }\n\n    it \"should search for shares with generated machine ID\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\n        \"/usr/bin/sudo\", /sharing/, \"-l\").and_return(result.new(0, \"\", \"\"))\n    end\n\n    it \"should remove shares individually\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).\n        with(\"/usr/bin/sudo\", /sharing/, \"-l\").\n        and_return(result.new(0, \"name: vgt-CUSTOM_ID-1\\nname: vgt-CUSTOM_ID-2\\n\", \"\"))\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, /sharing/, anything, /CUSTOM_ID/).\n        twice.and_return(result.new(0, \"\", \"\"))\n    end\n  end\n\n  describe \".smb_prepare\" do\n    let(:folders){ {\"/first/path\" => {hostpath: \"/first/host\", smb_id: \"ID1\"},\n      \"/second/path\" => {hostpath: \"/second/host\"}} }\n    before{ allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(result.new(0, \"\", \"\")) }\n    it \"should provide ID value if not set\" do\n      subject.smb_prepare(env, machine, folders, options)\n      expect(folders[\"/second/path\"][:smb_id]).to start_with(\"vgt-\")\n    end\n\n    it \"should not modify ID if already set\" do\n      subject.smb_prepare(env, machine, folders, options)\n      expect(folders[\"/first/path\"][:smb_id]).to eq(\"ID1\")\n    end\n\n    it \"should raise error when sharing command fails\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(result.new(1, \"\", \"\"))\n      expect{ subject.smb_prepare(env, machine, folders, options) }.to raise_error(\n        VagrantPlugins::SyncedFolderSMB::Errors::DefineShareFailed)\n    end\n\n    it \"should add shares individually\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, any_args).twice.and_return(result.new(0, \"\", \"\"))\n      subject.smb_prepare(env, machine, folders, options)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/darwin/cap/version_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/hosts/darwin/cap/version\"\n\ndescribe VagrantPlugins::HostDarwin::Cap::Version do\n  describe \".version\" do\n    let(:product_version) { \"10.5.1\" }\n    let(:env) { double(:env) }\n    let(:exit_code) { 0 }\n    let(:stderr) { \"\" }\n    let(:stdout) { product_version }\n    let(:result) {\n      Vagrant::Util::Subprocess::Result.new(exit_code, stdout, stderr)\n    }\n\n    before do\n      allow(Vagrant::Util::Subprocess).to receive(:execute).\n        with(\"sw_vers\", \"-productVersion\").\n        and_return(result)\n    end\n\n    it \"should return a Gem::Version\" do\n      expect(described_class.version(env)).to be_a(Gem::Version)\n    end\n\n    it \"should equal the defined version\" do\n      expect(described_class.version(env)).to eq(Gem::Version.new(product_version))\n    end\n\n    context \"when version cannot be parsed\" do\n      let(:product_version) { \"invalid\"  }\n\n      it \"should raise a failure error\" do\n        expect { described_class.version(env) }.\n          to raise_error(Vagrant::Errors::DarwinVersionFailed)\n      end\n    end\n\n    context \"when command execution fails\" do\n      let(:exit_code) { 1 }\n\n      it \"should raise a failure error\" do\n        expect { described_class.version(env) }.\n          to raise_error(Vagrant::Errors::DarwinVersionFailed)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/linux/cap/fs_iso_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/hosts/linux/cap/fs_iso\"\n\ndescribe VagrantPlugins::HostLinux::Cap::FsISO do\n  include_context \"unit\"\n\n  let(:subject){ VagrantPlugins::HostLinux::Cap::FsISO }\n  let(:env) { double(\"env\") }\n\n  describe \".isofs_available\" do\n    it \"finds iso building utility when available\" do\n      expect(Vagrant::Util::Which).to receive(:which).and_return(true)\n      expect(subject.isofs_available(env)).to eq(true)\n    end\n  end\n\n  describe \".create_iso\" do\n    let(:file_destination) { \"/woo/out.iso\" }\n\n    before do \n      allow(file_destination).to receive(:nil?).and_return(false)\n      allow(FileUtils).to receive(:mkdir_p)\n    end\n\n    it \"builds an iso\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\n        \"mkisofs\", \"-joliet\", \"-o\", /.iso/, /\\/foo\\/src/\n      ).and_return(double(exit_code: 0))\n\n      output = subject.create_iso(env, \"/foo/src\", file_destination: file_destination)\n      expect(output.to_s).to eq(file_destination)\n    end\n\n    it \"builds an iso with volume_id\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\n        \"mkisofs\", \"-joliet\", \"-volid\", \"cidata\", \"-o\", /.iso/, /\\/foo\\/src/\n      ).and_return(double(exit_code: 0))\n\n      output = subject.create_iso(env, \"/foo/src\", file_destination: file_destination, volume_id: \"cidata\")\n      expect(output.to_s).to eq(file_destination)\n    end\n\n    it \"builds an iso given a file destination without an extension\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\n        \"mkisofs\", \"-joliet\", \"-o\", /.iso/, /\\/foo\\/src/\n      ).and_return(double(exit_code: 0))\n\n      output = subject.create_iso(env, \"/foo/src\", file_destination: \"/woo/out_dir\")\n      expect(output.to_s).to match(/\\/woo\\/out_dir\\/[\\w]{6}_vagrant.iso/)\n    end\n\n    it \"raises an error if iso build failed\" do\n      allow(Vagrant::Util::Subprocess).to receive(:execute).with(any_args).and_return(double(stdout: \"nope\", stderr: \"nope\", exit_code: 1))\n      expect{ subject.create_iso(env, \"/foo/src\", file_destination: \"/woo/out.iso\") }.to raise_error(Vagrant::Errors::ISOBuildFailed)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/linux/cap/nfs_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/hosts/linux/cap/nfs\"\nrequire_relative \"../../../../../../lib/vagrant/util\"\n\ndescribe VagrantPlugins::HostLinux::Cap::NFS do\n\n  include_context \"unit\"\n\n  let(:caps) do\n    VagrantPlugins::HostLinux::Plugin\n      .components\n      .host_capabilities[:linux]\n  end\n\n  let(:tmp_exports_path) do\n    @tmp_exports ||= temporary_file\n  end\n  let(:exports_path){ VagrantPlugins::HostLinux::Cap::NFS::NFS_EXPORTS_PATH }\n  let(:env){ double(:env) }\n  let(:ui){ Vagrant::UI::Silent.new }\n  let(:host){ double(:host) }\n\n  before do\n    @original_exports_path = VagrantPlugins::HostLinux::Cap::NFS::NFS_EXPORTS_PATH\n    VagrantPlugins::HostLinux::Cap::NFS.send(:remove_const, :NFS_EXPORTS_PATH)\n    VagrantPlugins::HostLinux::Cap::NFS.const_set(:NFS_EXPORTS_PATH, tmp_exports_path.to_s)\n    allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"systemctl\", \"list-units\", any_args).\n      and_return(Vagrant::Util::Subprocess::Result.new(1, \"\", \"\"))\n    allow(Vagrant::Util::Platform).to receive(:systemd?).and_return(false)\n  end\n\n  after do\n    VagrantPlugins::HostLinux::Cap::NFS.send(:remove_const, :NFS_EXPORTS_PATH)\n    VagrantPlugins::HostLinux::Cap::NFS.const_set(:NFS_EXPORTS_PATH, @original_exports_path)\n    VagrantPlugins::HostLinux::Cap::NFS.reset!\n    File.unlink(tmp_exports_path.to_s) if File.exist?(tmp_exports_path.to_s)\n    @tmp_exports = nil\n  end\n\n  describe \".nfs_service_name_systemd\" do\n    let(:cap){ VagrantPlugins::HostLinux::Cap::NFS }\n\n    context \"without service match\" do\n      it \"should use default service name\" do\n        expect(cap.nfs_service_name_systemd).to eq(cap.const_get(:NFS_DEFAULT_NAME_SYSTEMD))\n      end\n    end\n\n    context \"with service match\" do\n      let(:custom_nfs_service_name){ \"custom-nfs-server-service-name\" }\n      before{ expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"systemctl\", \"list-units\", any_args).\n          and_return(Vagrant::Util::Subprocess::Result.new(0, custom_nfs_service_name, \"\")) }\n\n      it \"should use the matched service name\" do\n        expect(cap.nfs_service_name_systemd).to eq(custom_nfs_service_name)\n      end\n    end\n  end\n\n  describe \".nfs_service_name_sysv\" do\n    let(:cap){ VagrantPlugins::HostLinux::Cap::NFS }\n\n    context \"without service match\" do\n      it \"should use default service name\" do\n        expect(cap.nfs_service_name_sysv).to eq(cap.const_get(:NFS_DEFAULT_NAME_SYSV))\n      end\n    end\n\n    context \"with service match\" do\n      let(:custom_nfs_service_name){ \"/etc/init.d/custom-nfs-server-service-name\" }\n      before{ expect(Dir).to receive(:glob).with(/.+init\\.d.+/).and_return([custom_nfs_service_name]) }\n\n      it \"should use the matched service name\" do\n        expect(cap.nfs_service_name_sysv).to eq(File.basename(custom_nfs_service_name))\n      end\n    end\n  end\n\n  describe \".nfs_check_command\" do\n    let(:cap){ caps.get(:nfs_check_command) }\n\n    context \"without systemd\" do\n      before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(false) }\n\n      it \"should use init.d script\" do\n        expect(cap.nfs_check_command(env)).to include(\"init.d\")\n      end\n    end\n    context \"with systemd\" do\n      before do\n        expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(true)\n      end\n\n      it \"should use systemctl\" do\n        expect(cap.nfs_check_command(env)).to include(\"systemctl\")\n      end\n    end\n  end\n\n  describe \".nfs_start_command\" do\n    let(:cap){ caps.get(:nfs_start_command) }\n\n    context \"without systemd\" do\n      before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(false) }\n\n      it \"should use init.d script\" do\n        expect(cap.nfs_start_command(env)).to include(\"init.d\")\n      end\n    end\n    context \"with systemd\" do\n      before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(true) }\n\n      it \"should use systemctl\" do\n        expect(cap.nfs_start_command(env)).to include(\"systemctl\")\n      end\n    end\n  end\n\n  describe \".nfs_export\" do\n\n    let(:cap){ caps.get(:nfs_export) }\n\n    before do\n      allow(env).to receive(:host).and_return(host)\n      allow(host).to receive(:capability).with(:nfs_apply_command).and_return(\"/bin/true\")\n      allow(host).to receive(:capability).with(:nfs_check_command).and_return(\"/bin/true\")\n      allow(host).to receive(:capability).with(:nfs_start_command).and_return(\"/bin/true\")\n      allow(Vagrant::Util::Subprocess).to receive(:execute).and_call_original\n      allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"sudo\", \"/bin/true\").and_return(double(:result, exit_code: 0))\n      allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"/bin/true\").and_return(double(:result, exit_code: 0))\n    end\n\n    it \"should export new entries\" do\n      cap.nfs_export(env, ui, SecureRandom.uuid, [\"127.0.0.1\", \"127.0.0.1\"], \"tmp\" => {:hostpath => \"/tmp\"})\n      exports_content = File.read(exports_path)\n      expect(exports_content.scan(/\\/tmp.*127\\.0\\.0\\.1/).length).to be(1)\n    end\n\n    it \"should not remove existing entries\" do\n      File.write(exports_path, \"/custom/directory hostname1(rw,sync,no_subtree_check)\")\n      cap.nfs_export(env, ui, SecureRandom.uuid, [\"127.0.0.1\", \"127.0.0.1\"], \"tmp\" => {:hostpath => \"/tmp\"})\n      exports_content = File.read(exports_path)\n      expect(exports_content.scan(/\\/tmp.*127\\.0\\.0\\.1/).length).to be(1)\n      expect(exports_content).to match(/\\/custom\\/directory.*hostname1/)\n    end\n\n    it \"should remove entries no longer valid\" do\n      valid_id = SecureRandom.uuid\n      other_id = SecureRandom.uuid\n      content =<<-EOH\n# VAGRANT-BEGIN: #{Process.uid} #{other_id}\n\"/tmp\" 127.0.0.1(rw,no_subtree_check,all_squash,anonuid=,anongid=,fsid=)\n# VAGRANT-END: #{Process.uid} #{other_id}\n# VAGRANT-BEGIN: #{Process.uid} #{valid_id}\n\"/var\" 127.0.0.1(rw,no_subtree_check,all_squash,anonuid=,anongid=,fsid=)\n# VAGRANT-END: #{Process.uid} #{valid_id}\nEOH\n      File.write(exports_path, content)\n      cap.nfs_export(env, ui, valid_id, [\"127.0.0.1\"], \"home\" => {:hostpath => \"/home\"})\n      exports_content = File.read(exports_path)\n      expect(exports_content).to include(\"/home\")\n      expect(exports_content).to include(\"/tmp\")\n      expect(exports_content).not_to include(\"/var\")\n    end\n\n    it \"throws an exception with at least 2 different nfs options\" do\n      folders = {\"/vagrant\"=>\n                 {:hostpath=>\"/home/vagrant\",\n                  :linux__nfs_options=>[\"rw\",\"all_squash\"]},\n                 \"/var/www/project\"=>\n                 {:hostpath=>\"/home/vagrant\",\n                  :linux__nfs_options=>[\"rw\",\"sync\"]}}\n\n      expect { cap.nfs_export(env, ui, SecureRandom.uuid, [\"127.0.0.1\"], folders) }.\n        to raise_error Vagrant::Errors::NFSDupePerms\n    end\n\n    it \"writes only 1 hostpath for multiple exports\" do\n      folders = {\"/vagrant\"=>\n                 {:hostpath=>\"/home/vagrant\",\n                  :linux__nfs_options=>[\"rw\",\"all_squash\"]},\n                 \"/var/www/otherproject\"=>\n                 {:hostpath=>\"/newhome/otherproject\",\n                  :linux__nfs_options=>[\"rw\",\"all_squash\"]},\n                 \"/var/www/project\"=>\n                 {:hostpath=>\"/home/vagrant\",\n                  :linux__nfs_options=>[\"rw\",\"all_squash\"]}}\n      valid_id = SecureRandom.uuid\n      content =<<-EOH\n# VAGRANT-BEGIN: #{Process.uid} #{valid_id}\n\"/home/vagrant\" 127.0.0.1(rw,all_squash,anonuid=,anongid=,fsid=)\n\"/newhome/otherproject\" 127.0.0.1(rw,all_squash,anonuid=,anongid=,fsid=)\n# VAGRANT-END: #{Process.uid} #{valid_id}\nEOH\n\n      cap.nfs_export(env, ui, valid_id, [\"127.0.0.1\"], folders)\n      exports_content = File.read(exports_path)\n      expect(exports_content).to eq(content)\n    end\n\n  end\n\n  describe \".nfs_prune\" do\n\n    let(:cap){ caps.get(:nfs_prune) }\n\n    before do\n      allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"mv\", any_args).\n        and_call_original\n    end\n\n    it \"should remove entries no longer valid\" do\n      invalid_id = SecureRandom.uuid\n      valid_id = SecureRandom.uuid\n      content =<<-EOH\n# VAGRANT-BEGIN: #{Process.uid} #{invalid_id}\n\"/tmp\" 127.0.0.1(rw,no_subtree_check,all_squash,anonuid=,anongid=,fsid=)\n# VAGRANT-END: #{Process.uid} #{invalid_id}\n# VAGRANT-BEGIN: #{Process.uid} #{valid_id}\n\"/var\" 127.0.0.1(rw,no_subtree_check,all_squash,anonuid=,anongid=,fsid=)\n# VAGRANT-END: #{Process.uid} #{valid_id}\nEOH\n      File.write(exports_path, content)\n      cap.nfs_prune(env, ui, [valid_id])\n      exports_content = File.read(exports_path)\n      expect(exports_content).to include(valid_id)\n      expect(exports_content).not_to include(invalid_id)\n      expect(exports_content).to include(\"/var\")\n      expect(exports_content).not_to include(\"/tmp\")\n    end\n  end\n\n  describe \".nfs_write_exports\" do\n\n    before do\n      File.write(tmp_exports_path, \"original content\")\n      allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"mv\", any_args).\n        and_call_original\n    end\n\n    it \"should write updated contents to file\" do\n      described_class.nfs_write_exports(\"new content\")\n      exports_content = File.read(exports_path)\n      expect(exports_content).to include(\"new content\")\n      expect(exports_content).not_to include(\"original content\")\n    end\n\n    it \"should only update contents if different\" do\n      original_stat = File.stat(exports_path)\n      described_class.nfs_write_exports(\"original content\")\n      updated_stat = File.stat(exports_path)\n      expect(original_stat).to eq(updated_stat)\n    end\n\n    it \"should retain existing file permissions\" do\n      File.chmod(0600, exports_path)\n      original_stat = File.stat(exports_path)\n      described_class.nfs_write_exports(\"original content\")\n      updated_stat = File.stat(exports_path)\n      expect(original_stat.mode).to eq(updated_stat.mode)\n    end\n\n    it \"should raise exception when failing to move new exports file\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(\n        Vagrant::Util::Subprocess::Result.new(1, \"Failed to move file\", \"\")\n      )\n      expect{ described_class.nfs_write_exports(\"new content\") }.to raise_error(Vagrant::Errors::NFSExportsFailed)\n    end\n\n    context \"exports file modification\" do\n      let(:tmp_stat) { double(\"tmp_stat\", uid: 100, gid: 100, mode: tmp_mode) }\n      let(:tmp_mode) { 0 }\n      let(:exports_stat) {\n        double(\"stat\", uid: exports_uid, gid: exports_gid,\n               mode: exports_mode, :directory? => true, :writable? => true,\n               :world_writable? => true, :sticky? => true)\n      }\n      let(:exports_uid) { -1 }\n      let(:exports_gid) { -1 }\n      let(:exports_mode) { 0 }\n      let(:new_exports_file) { double(\"new_exports_file\", path: \"/dev/null/exports\") }\n      let(:new_exports_path) { new_exports_file.path }\n\n      before do\n        allow(File).to receive(:stat).and_call_original\n        allow(File).to receive(:join).with(Dir.tmpdir, \"vagrant-exports\").and_return(new_exports_path)\n        allow(File).to receive(:open).with(new_exports_path, \"w+\").and_return(new_exports_file)\n        allow(File).to receive(:stat).with(new_exports_path).and_return(tmp_stat)\n        allow(File).to receive(:stat).with(tmp_exports_path.to_s).and_return(exports_stat)\n        allow(new_exports_file).to receive(:puts)\n        allow(new_exports_file).to receive(:close)\n        allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(Vagrant::Util::Subprocess::Result.new(0, \"\", \"\"))\n      end\n\n      it \"should retain existing file owner and group IDs\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|\n          expect(args).to include(\"sudo\")\n          expect(args).to include(\"chown\")\n        }.and_return(Vagrant::Util::Subprocess::Result.new(0, \"\", \"\"))\n        described_class.nfs_write_exports(\"new content\")\n      end\n\n      it \"should raise custom exception when chown fails\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|\n          expect(args).to include(\"sudo\")\n          expect(args).to include(\"chown\")\n        }.and_return(Vagrant::Util::Subprocess::Result.new(1, \"\", \"\"))\n        expect { described_class.nfs_write_exports(\"new content\") }.to raise_error(Vagrant::Errors::NFSExportsFailed)\n      end\n\n      context \"when user has write access to exports file\" do\n        let(:file_writable?) { true }\n        let(:dir_writable?) { false }\n        let(:exports_pathname) { double(\"exports_pathname\", writable?: file_writable?, dirname: exports_dir_pathname) }\n        let(:exports_dir_pathname) { double(\"exports_dir_pathname\", writable?: dir_writable?) }\n\n        before do\n          allow(File).to receive(:stat).and_return(exports_stat)\n          allow(File).to receive(:exist?).and_return(false)\n          allow(Pathname).to receive(:new).with(tmp_exports_path.to_s).and_return(exports_pathname)\n        end\n\n        it \"should use sudo when moving new file\" do\n          expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|\n            expect(args).to include(\"sudo\")\n            expect(args).to include(\"mv\")\n          }.and_return(Vagrant::Util::Subprocess::Result.new(0, \"\", \"\"))\n          described_class.nfs_write_exports(\"new content\")\n        end\n\n        context \"and write access to exports parent directory\" do\n          let(:dir_writable?) { true }\n\n          it \"should not use sudo when moving new file\" do\n            expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|\n              expect(args).not_to include(\"sudo\")\n              expect(args).to include(\"mv\")\n            }.and_return(Vagrant::Util::Subprocess::Result.new(0, \"\", \"\"))\n            described_class.nfs_write_exports(\"new content\")\n          end\n        end\n      end\n    end\n  end\n\n  describe \".modinfo_path\" do\n    let(:cap){ VagrantPlugins::HostLinux::Cap::NFS }\n\n    context \"with modinfo on PATH\" do\n      before do\n        expect(Vagrant::Util::Which).to receive(:which).with(\"modinfo\").and_return(\"/usr/bin/modinfo\")\n      end\n\n      it \"should use full path to modinfo\" do\n        expect(cap.modinfo_path).to eq(\"/usr/bin/modinfo\")\n      end\n    end\n\n    context \"with modinfo at /sbin/modinfo\" do\n      before do\n        expect(Vagrant::Util::Which).to receive(:which).with(\"modinfo\").and_return(nil)\n        expect(File).to receive(:file?).with(\"/sbin/modinfo\").and_return(true)\n      end\n\n      it \"should use /sbin/modinfo\" do\n        expect(cap.modinfo_path).to eq(\"/sbin/modinfo\")\n      end\n    end\n\n    context \"modinfo not found\" do\n      before do\n        expect(Vagrant::Util::Which).to receive(:which).with(\"modinfo\").and_return(nil)\n        expect(File).to receive(:file?).with(\"/sbin/modinfo\").and_return(false)\n      end\n\n      it \"should use modinfo\" do\n        expect(cap.modinfo_path).to eq(\"modinfo\")\n      end\n    end\n\n    context \"with cached value for modinfo_path\" do\n      before do\n        cap.instance_variable_set(:@_modinfo_path, \"/usr/local/bin/modinfo\")\n      end\n\n      it \"should use cached value\" do\n        expect(cap.modinfo_path).to eq(\"/usr/local/bin/modinfo\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/linux/cap/ssh_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire_relative \"../../../../../../plugins/hosts/linux/cap/ssh\"\n\ndescribe VagrantPlugins::HostLinux::Cap::SSH do\n  let(:subject){ VagrantPlugins::HostLinux::Cap::SSH }\n\n  let(:env){ double(\"env\") }\n  let(:key_path){ double(\"key_path\") }\n\n  it \"should set file as user only read/write\" do\n    expect(key_path).to receive(:chmod).with(0600)\n    subject.set_ssh_key_permissions(env, key_path)\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/void/cap/nfs_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/hosts/void/cap/nfs\"\nrequire_relative \"../../../../../../lib/vagrant/util\"\n\ndescribe VagrantPlugins::HostVoid::Cap::NFS do\n\n  include_context \"unit\"\n\n  let(:caps) do\n    VagrantPlugins::HostVoid::Plugin\n      .components\n      .host_capabilities[:void]\n  end\n\n  let(:env) { double(\"env\") }\n\n  context \".nfs_check_command\" do\n    it \"should provide nfs_check_command capability\" do\n      expect(caps.get(:nfs_check_command)).to eq(described_class)\n    end\n\n    it \"should return command to execute\" do\n      expect(caps.get(:nfs_check_command).nfs_check_command(env)).to be_a(String)\n    end\n  end\n\n  context \".nfs_start_command\" do\n    it \"should provide nfs_start_command capability\" do\n      expect(caps.get(:nfs_start_command)).to eq(described_class)\n    end\n\n    it \"should return command to execute\" do\n      expect(caps.get(:nfs_start_command).nfs_start_command(env)).to be_a(String)\n    end\n  end\n\n  context \".nfs_installed\" do\n    let(:exit_code) { 0 }\n    let(:result) { Vagrant::Util::Subprocess::Result.new(exit_code, \"\", \"\") }\n\n    before { allow(Vagrant::Util::Subprocess).to receive(:execute).\n        with(\"/usr/bin/xbps-query\", \"nfs-utils\").and_return(result) }\n\n    it \"should provide nfs_installed capability\" do\n      expect(caps.get(:nfs_installed)).to eq(described_class)\n    end\n\n    context \"when installed\" do\n      it \"should return true\" do\n        expect(caps.get(:nfs_installed).nfs_installed(env)).to be_truthy\n      end\n    end\n\n    context \"when not installed\" do\n      let(:exit_code) { 1 }\n\n      it \"should return false\" do\n        expect(caps.get(:nfs_installed).nfs_installed(env)).to be_falsey\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/windows/cap/configure_ip_addresses_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire_relative \"../../../../../../plugins/hosts/windows/cap/configured_ip_addresses\"\n\ndescribe VagrantPlugins::HostWindows::Cap::ConfiguredIPAddresses do\n\n  let(:subject){ VagrantPlugins::HostWindows::Cap::ConfiguredIPAddresses }\n  let(:result){ Vagrant::Util::Subprocess::Result }\n  let(:addresses){ [] }\n  let(:execute_result){ result.new(0, {ip_addresses: addresses}.to_json, \"\") }\n\n  before{ allow(Vagrant::Util::PowerShell).to receive(:execute).\n      and_return(execute_result) }\n\n  it \"should return an array\" do\n    expect(subject.configured_ip_addresses(nil)).to be_kind_of(Array)\n  end\n\n  context \"with single address returned\" do\n    let(:addresses){ \"ADDRESS\" }\n\n    it \"should return an array\" do\n      expect(subject.configured_ip_addresses(nil)).to eq([addresses])\n    end\n  end\n\n  context \"with multiple addresses returned\" do\n    let(:addresses){ [\"ADDRESS1\", \"ADDRESS2\"] }\n\n    it \"should return an array\" do\n      expect(subject.configured_ip_addresses(nil)).to eq(addresses)\n    end\n  end\n\n  context \"with failed script execution\" do\n    let(:execute_result){ result.new(1, \"\", \"\") }\n\n    it \"should raise error\" do\n      expect{ subject.configured_ip_addresses(nil) }.to raise_error(\n        Vagrant::Errors::PowerShellError)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/windows/cap/fs_iso_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/hosts/windows/cap/fs_iso\"\n\ndescribe VagrantPlugins::HostWindows::Cap::FsISO do\n  include_context \"unit\"\n\n  let(:subject){ VagrantPlugins::HostWindows::Cap::FsISO }\n  let(:env) { double(\"env\") }\n\n  before do\n    allow(Vagrant::Util::Which).to receive(:which).and_return(true)\n  end\n\n  describe \".isofs_available\" do\n    it \"finds iso building utility when available\" do\n      expect(Vagrant::Util::Which).to receive(:which).and_return(true)\n      expect(subject.isofs_available(env)).to eq(true)\n    end\n  end\n\n  describe \".create_iso\" do\n    let(:file_destination) { \"/woo/out.iso\" }\n\n    before do \n      allow(file_destination).to receive(:nil?).and_return(false)\n      allow(FileUtils).to receive(:mkdir_p)\n    end\n\n    it \"builds an iso\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\n        \"oscdimg.exe\", \"-j1\", \"-o\", \"-m\", /\\/foo\\/src/, /.iso/\n      ).and_return(double(exit_code: 0))\n\n      output = subject.create_iso(env, \"/foo/src\", file_destination: file_destination)\n      expect(output.to_s).to eq(file_destination)\n    end\n\n    it \"builds an iso with volume_id\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\n        \"oscdimg.exe\", \"-j1\", \"-o\", \"-m\", \"-ltest\", /\\/foo\\/src/, /.iso/\n      ).and_return(double(exit_code: 0))\n\n      output = subject.create_iso(env, \"/foo/src\", file_destination: file_destination, volume_id: \"test\")\n      expect(output.to_s).to eq(file_destination)\n    end\n\n    it \"builds an iso given a file destination without an extension\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\n        \"oscdimg.exe\", \"-j1\", \"-o\", \"-m\", /\\/foo\\/src/, /.iso/\n      ).and_return(double(exit_code: 0))\n\n      output = subject.create_iso(env, \"/foo/src\", file_destination: \"/woo/out_dir\")\n      expect(output.to_s).to match(/\\/woo\\/out_dir\\/[\\w]{6}_vagrant.iso/)\n    end\n\n    it \"raises an error if iso build failed\" do\n      allow(Vagrant::Util::Subprocess).to receive(:execute).with(any_args).and_return(double(stdout: \"nope\", stderr: \"nope\", exit_code: 1))\n      expect{ subject.create_iso(env, \"/foo/src\", file_destination: file_destination) }.to raise_error(Vagrant::Errors::ISOBuildFailed)\n    end\n  end\n\n  describe \".oscdimg_path\" do\n    it \"returns executable name when found in PATH\" do\n      expect(Vagrant::Util::Which).to receive(:which).and_return(true)\n      expect(subject.oscdimg_path).to eq(\"oscdimg.exe\")\n    end\n\n    it \"returns full path when executable detected\" do\n      expect(Vagrant::Util::Which).to receive(:which).and_return(false)\n      expect(File).to receive(:executable?).with(/.+oscdimg.exe$/).and_return(true)\n      expect(subject.oscdimg_path).to match(/.+oscdimg.exe$/)\n    end\n\n    it \"raises an error when not found\" do\n      expect(Vagrant::Util::Which).to receive(:which).and_return(false)\n      expect(File).to receive(:executable?).with(/.+oscdimg.exe$/).and_return(false)\n      expect { subject.oscdimg_path }.to raise_error(Vagrant::Errors::OscdimgCommandMissingError)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/windows/cap/smb_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire_relative \"../../../../../../plugins/hosts/windows/cap/smb\"\n\ndescribe VagrantPlugins::HostWindows::Cap::SMB do\n  let(:subject){ VagrantPlugins::HostWindows::Cap::SMB }\n  let(:machine){ double(:machine, env: double(:machine_env, ui: Vagrant::UI::Silent.new)) }\n  let(:env){ double(:env) }\n  let(:options){ {} }\n  let(:result){ Vagrant::Util::Subprocess::Result }\n  let(:powershell_version){ \"3\" }\n  let(:smblist){ <<-EOF\nName        : vgt-CUSTOM_ID-1\nPath        : /a/path\nDescription : vgt-CUSTOM_ID-1\n\nName        : vgt-CUSTOM_ID-2\nPath        : /other/path\nDescription : vgt-CUSTOM_ID-2\n\nName        : my-share\nPath        : /my/path\nDescription : Not Vagrant Owned\n\nName        : scoped-share\nScope       : *\nPath        : /scoped/path\nDescription : Scoped Path\n    EOF\n  }\n  let(:netsharelist){ <<-EOF\n\nShare name        Resource     Remark\n-----------------------------------------------\nvgt-CUSTOM_ID-1   /a/path      vgt-CUSTOM_ID-1\nvgt-CUSTOM_ID-2   /other/path  vgt-CUSTOM_ID-2\nmy-share          /my/path     Not Vagran...\nThe command completed successfully.\n    EOF\n  }\n  let(:netshare1){ <<-EOF\nShare name vgt-CUSTOM_ID-1\nPath       /a/path\nRemark     vgt-CUSTOM_ID-1\n    EOF\n  }\n  let(:netshare2){ <<-EOF\nShare name vgt-CUSTOM_ID-2\nPath       /other/path\nRemark     vgt-CUSTOM_ID-2\n    EOF\n  }\n  let(:netshare_my){ <<-EOF\nShare name my-share\nPath       /my/path\nRemark     Not Vagrant Owned\n    EOF\n  }\n\n\n  before do\n    allow(subject).to receive(:machine_id).and_return(\"CUSTOM_ID\")\n    allow(Vagrant::Util::PowerShell).to receive(:version).and_return(powershell_version)\n    allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_return(\"\")\n    allow(subject).to receive(:sleep)\n  end\n\n  describe \".smb_mount_options\" do\n    it \"should provide smb version of at least 2\" do\n      result = subject.smb_mount_options(nil)\n      ver = result.detect{|i| i.start_with?(\"vers\") }.to_s.split(\"=\", 2).last.to_s.to_i\n      expect(ver).to be >= 2\n    end\n  end\n\n  describe \".smb_installed\" do\n    context \"when powershell version is greater than 2\" do\n      it \"is valid installation\" do\n        expect(subject.smb_installed(nil)).to eq(true)\n      end\n    end\n\n    context \"when powershell version is less than 3\" do\n      let(:powershell_version){ \"2\" }\n\n      it \"is not a valid installation\" do\n        expect(subject.smb_installed(nil)).to eq(false)\n      end\n    end\n  end\n\n  describe \".smb_cleanup\" do\n    before do\n      allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).with(/Get-SmbShare/).\n        and_return(smblist)\n      allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).with(/net share/).and_return(netsharelist)\n      allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).with(/net share vgt-CUSTOM_ID-1/).and_return(netshare1)\n      allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).with(/net share vgt-CUSTOM_ID-2/).and_return(netshare2)\n      allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).with(/net share my/).and_return(netshare_my)\n      allow(Vagrant::Util::PowerShell).to receive(:execute).and_return(result.new(0, \"\", \"\"))\n    end\n    after{ subject.smb_cleanup(env, machine, options) }\n\n    it \"should pause after warning user\" do\n      expect(machine.env.ui).to receive(:warn).and_call_original\n      expect(subject).to receive(:sleep)\n    end\n\n    it \"should remove owned shares\" do\n      expect(Vagrant::Util::PowerShell).to receive(:execute) do |*args|\n        expect(args).to include(\"vgt-CUSTOM_ID-1\")\n        expect(args).to include(\"vgt-CUSTOM_ID-2\")\n        result.new(0, \"\", \"\")\n      end\n    end\n\n    it \"should not remove owned shares\" do\n      expect(Vagrant::Util::PowerShell).to receive(:execute) do |*args|\n        expect(args).not_to include(\"my-share\")\n        result.new(0, \"\", \"\")\n      end\n    end\n\n    it \"should remove all shares in single call\" do\n      expect(Vagrant::Util::PowerShell).to receive(:execute).with(any_args, sudo: true).once\n    end\n\n    context \"when no shares are defined\" do\n      before do\n        expect(Vagrant::Util::PowerShell).to receive(:execute_cmd).with(/Get-SmbShare/).\n          and_return(\"\")\n      end\n\n      it \"should not attempt to remove shares\" do\n        expect(Vagrant::Util::PowerShell).not_to receive(:execute).with(any_args, sudo: true)\n      end\n\n      it \"should not warn user\" do\n        expect(machine.env.ui).not_to receive(:warn)\n      end\n    end\n\n    context \"when Get-SmbShare is not available\" do\n      before do\n        expect(Vagrant::Util::PowerShell).to receive(:execute_cmd).with(/Get-SmbShare/).and_return(nil)\n      end\n\n      it \"should fetch list using net.exe\" do\n        expect(Vagrant::Util::PowerShell).to receive(:execute_cmd).with(/net share/).and_return(\"\")\n      end\n\n      it \"should remove owned shares\" do\n        expect(Vagrant::Util::PowerShell).to receive(:execute) do |*args|\n          expect(args).to include(\"vgt-CUSTOM_ID-1\")\n          expect(args).to include(\"vgt-CUSTOM_ID-2\")\n          result.new(0, \"\", \"\")\n        end\n      end\n\n      it \"should not remove owned shares\" do\n        expect(Vagrant::Util::PowerShell).to receive(:execute) do |*args|\n          expect(args).not_to include(\"my-share\")\n          result.new(0, \"\", \"\")\n        end\n      end\n    end\n  end\n\n  describe \".smb_prepare\" do\n    let(:folders){ {\"/first/path\" => {hostpath: \"/host/1\"}, \"/second/path\" => {hostpath: \"/host/2\", smb_id: \"ID1\"}} }\n    let(:options){ {} }\n\n    before{ allow(Vagrant::Util::PowerShell).to receive(:execute).and_return(result.new(0, \"\", \"\")) }\n\n    it \"should add ID when not defined\" do\n      subject.smb_prepare(env, machine, folders, options)\n      expect(folders[\"/first/path\"][:smb_id]).to start_with(\"vgt-\")\n    end\n\n    it \"should not modify ID when defined\" do\n      subject.smb_prepare(env, machine, folders, options)\n      expect(folders[\"/second/path\"][:smb_id]).to eq(\"ID1\")\n    end\n\n    it \"should pause after warning user\" do\n      expect(machine.env.ui).to receive(:warn).and_call_original\n      expect(subject).to receive(:sleep)\n      subject.smb_prepare(env, machine, folders, options)\n    end\n\n    it \"should add all shares in single call\" do\n      expect(Vagrant::Util::PowerShell).to receive(:execute).with(any_args, sudo: true).once\n      subject.smb_prepare(env, machine, folders, options)\n    end\n\n    context \"when share already exists\" do\n      let(:shares){ {\"ID1\" => {\"Path\" => \"/host/2\"}} }\n      before do\n        allow(File).to receive(:expand_path).and_call_original\n        expect(subject).to receive(:existing_shares).and_return(shares)\n      end\n\n      it \"should expand paths when comparing existing to requested\" do\n        expect(File).to receive(:expand_path).at_least(2).with(\"/host/2\").and_return(\"expanded_path\")\n        subject.smb_prepare(env, machine, folders, options)\n      end\n\n      context \"with different path\" do\n        let(:shares){ {\"ID1\" => {\"Path\" => \"/host/3\"}} }\n\n        it \"should raise an error\" do\n          expect{\n            subject.smb_prepare(env, machine, folders, options)\n          }.to raise_error(VagrantPlugins::SyncedFolderSMB::Errors::SMBNameError)\n        end\n      end\n    end\n\n    context \"when no shared are defined\" do\n      after{ subject.smb_prepare(env, machine, {}, options) }\n\n      it \"should not attempt to add shares\" do\n        expect(Vagrant::Util::PowerShell).not_to receive(:execute).with(any_args, sudo: true)\n      end\n\n      it \"should not warn user\" do\n        expect(machine.env.ui).not_to receive(:warn)\n      end\n    end\n\n    context \"when more than 10 shares are defined\" do\n      let(:folders) {\n        Hash[12.times.map{|i| [\"/path#{i}\", {hostpath: \"/host#{i}\"}]}]\n      }\n\n      after{ subject.smb_prepare(env, machine, folders, options) }\n\n      it \"should execute multiple powershell commands\" do\n        expect(Vagrant::Util::PowerShell).to receive(:execute).twice.with(any_args, sudo: true)\n      end\n    end\n  end\n\n  describe \".get_smbshares\" do\n    before { expect(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_return(smblist) }\n\n    it \"should return a Hash of share information\" do\n      expect(subject.get_smbshares).to be_a(Hash)\n    end\n\n    it \"should provide name and description for share\" do\n      shares = subject.get_smbshares\n      expect(shares[\"vgt-CUSTOM_ID-1\"]).to be_a(Hash)\n      expect(shares[\"vgt-CUSTOM_ID-1\"][\"Path\"]).to eq(\"/a/path\")\n      expect(shares[\"vgt-CUSTOM_ID-1\"][\"Description\"]).to eq(\"vgt-CUSTOM_ID-1\")\n    end\n\n    it \"should properly handle share with scope information\" do\n      shares = subject.get_smbshares\n      expect(shares[\"scoped-share\"]).to be_a(Hash)\n      expect(shares[\"scoped-share\"][\"Path\"]).to eq(\"/scoped/path\")\n      expect(shares[\"scoped-share\"][\"Description\"]).to eq(\"Scoped Path\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/hosts/windows/cap/ssh_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire_relative \"../../../../../../plugins/hosts/windows/cap/ssh\"\n\ndescribe VagrantPlugins::HostWindows::Cap::SSH do\n  let(:subject){ VagrantPlugins::HostWindows::Cap::SSH }\n  let(:result){ Vagrant::Util::Subprocess::Result.new(exit_code, stdout, stderr) }\n  let(:exit_code){ 0 }\n  let(:stdout){ \"\" }\n  let(:stderr){ \"\" }\n\n  let(:key_path){ double(\"keypath\", to_s: \"keypath\") }\n  let(:env){ double(\"env\") }\n\n  before do\n    allow(Vagrant::Util::PowerShell).to receive(:execute).and_return(result)\n  end\n\n  it \"should execute PowerShell script\" do\n    expect(Vagrant::Util::PowerShell).to receive(:execute).with(\n      /set_ssh_key_permissions.ps1/, \"-KeyPath\", key_path.to_s, any_args\n    ).and_return(result)\n    subject.set_ssh_key_permissions(env, key_path)\n  end\n\n  it \"should return the result\" do\n\n    expect(subject.set_ssh_key_permissions(env, key_path)).to eq(result)\n  end\n\n  context \"when command fails\" do\n    let(:exit_code){ 1 }\n\n    it \"should raise an error\" do\n      expect{ subject.set_ssh_key_permissions(env, key_path) }.to raise_error(Vagrant::Errors::PowerShellError)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/kernel_v2/config/cloud_init_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/cloud_init\")\n\ndescribe VagrantPlugins::Kernel_V2::VagrantConfigCloudInit do\n  include_context \"unit\"\n\n  subject { described_class.new(:user_data) }\n\n  let(:provider) { double(\"provider\") }\n  let(:machine) { double(\"machine\", name: \"rspec\", provider: provider,\n                         env: Vagrant::Environment.new) }\n\n\n  def assert_invalid\n    errors = subject.validate(machine)\n    if errors.empty?\n      raise \"No errors: #{errors.inspect}\"\n    end\n  end\n\n  def assert_valid\n    errors = subject.validate(machine)\n    if !errors.empty?\n      raise \"Errors: #{errors.inspect}\"\n    end\n  end\n\n  before do\n    env = double(\"env\")\n\n    subject.content_type = \"text/cloud-config\"\n    subject.inline = <<-CONFIG\n    package_update: true\n    CONFIG\n  end\n\n  describe \"#validate\" do\n    context \"with defaults\" do\n      it \"is a valid config\" do\n        subject.finalize!\n        assert_valid\n      end\n\n      it \"sets a content_type\" do\n        subject.finalize!\n        expect(subject.content_type).to eq(\"text/cloud-config\")\n      end\n\n      context \"with no type set\" do\n        let(:type_subject) { described_class.new }\n\n        before do\n          type_subject.content_type = \"text/cloud-config\"\n          type_subject.inline = <<-CONFIG\n          package_update: true\n          CONFIG\n        end\n\n        it \"defaults to a type\" do\n          type_subject.finalize!\n          expect(type_subject.type).to eq(:user_data)\n        end\n      end\n    end\n\n    context \"with an invalid option set\" do\n      before do\n        subject.content_type = \"text/not-real-option\"\n      end\n\n      it \"is an invalid config\" do\n        subject.finalize!\n        assert_invalid\n      end\n    end\n\n    context \"with both path and inline set\" do\n      before do\n        subject.path = \"path/to/option\"\n        subject.inline = \"package_update: true\"\n      end\n\n      it \"is an invalid config\" do\n        subject.finalize!\n        assert_invalid\n      end\n    end\n\n    context \"with inline set as an invalid type\" do\n      before do\n        subject.path = :i_am_a_symbol\n      end\n\n      it \"is an invalid config\" do\n        subject.finalize!\n        assert_invalid\n      end\n    end\n\n    context \"with path set as an invalid type\" do\n      before do\n        subject.inline = :i_am_a_symbol\n      end\n\n      it \"is an invalid config\" do\n        subject.finalize!\n        assert_invalid\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/kernel_v2/config/disk_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/disk\")\n\ndescribe VagrantPlugins::Kernel_V2::VagrantConfigDisk do\n  include_context \"unit\"\n\n  let(:type) { :disk }\n\n  subject { described_class.new(type) }\n\n  let(:ui) { Vagrant::UI::Silent.new }\n  let(:env) { double(\"env\", ui: ui) }\n  let(:provider) { double(\"provider\") }\n  let(:machine) { double(\"machine\", name: \"name\", provider: provider, env: env,\n                         provider_name: :virtualbox) }\n\n  def assert_invalid\n    errors = subject.validate(machine)\n    if errors.empty?\n      raise \"No errors: #{errors.inspect}\"\n    end\n  end\n\n  def assert_valid\n    errors = subject.validate(machine)\n    if !errors.empty?\n      raise \"Errors: #{errors.inspect}\"\n    end\n  end\n\n  before do\n    allow(provider).to receive(:capability?).with(:validate_disk_ext).and_return(true)\n    allow(provider).to receive(:capability).with(:validate_disk_ext, \"vdi\").and_return(true)\n    allow(provider).to receive(:capability?).with(:set_default_disk_ext).and_return(true)\n    allow(provider).to receive(:capability).with(:set_default_disk_ext).and_return(\"vdi\")\n  end\n\n  describe \"with defaults\" do\n    before do\n      subject.name = \"foo\"\n      subject.size = 100\n    end\n\n    it \"is valid with test defaults\" do\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"sets a disk type\" do\n      subject.finalize!\n      expect(subject.type).to eq(type)\n    end\n\n    it \"defaults to non-primary disk\" do\n      subject.finalize!\n      expect(subject.primary).to eq(false)\n    end\n  end\n\n  describe \"with an invalid config\" do\n    before do\n      subject.name = \"bar\"\n    end\n\n    it \"raises an error if size not set\" do\n      subject.finalize!\n      assert_invalid\n    end\n\n    context \"with an invalid disk extension\" do\n      before do\n        subject.size = 100\n        subject.disk_ext = \"fake\"\n\n        allow(provider).to receive(:capability?).with(:validate_disk_ext).and_return(true)\n        allow(provider).to receive(:capability).with(:validate_disk_ext, \"fake\").and_return(false)\n        allow(provider).to receive(:capability?).with(:default_disk_exts).and_return(true)\n        allow(provider).to receive(:capability).with(:default_disk_exts).and_return([\"vdi\", \"vmdk\"])\n      end\n\n      it \"raises an error\" do\n        subject.finalize!\n        assert_invalid\n      end\n    end\n  end\n\n  describe \"config for dvd type\" do\n    let(:iso_path) { \"/tmp/untitled.iso\" }\n\n    before do\n      subject.type = :dvd\n      subject.name = \"untitled\"\n      allow(File).to receive(:file?).with(iso_path).and_return(true)\n      subject.file = iso_path\n    end\n\n    it \"is valid with test defaults\" do\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"is invalid if file path is unset\" do\n      subject.file = nil\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"is invalid if primary\" do\n      subject.primary = true\n      subject.finalize!\n      assert_invalid\n    end\n  end\n\n  describe \"#add_provider_config\" do\n    it \"normalizes provider config\" do\n      test_provider_config = {provider__something: \"special\" }\n      subject.add_provider_config(**test_provider_config)\n      expect(subject.provider_config).to eq( { provider: {something: \"special\" }} )\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/kernel_v2/config/package_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/package\")\n\ndescribe VagrantPlugins::Kernel_V2::PackageConfig do\n  subject { described_class.new }\n\n  describe \"#name\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.name).to be_nil\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/kernel_v2/config/push_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/push\")\n\ndescribe VagrantPlugins::Kernel_V2::PushConfig do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  describe \"#define\" do\n    let(:pushes) { subject.instance_variable_get(:@__defined_pushes) }\n\n    it \"pushes the strategy and block onto the defined pushes array\" do\n      subject.define(\"foo\") { \"bar\" }\n      subject.define(\"foo\") { \"zip\" }\n      subject.define(\"foo\") { \"zap\" }\n\n      expect(pushes.size).to eq(1)\n      expect(pushes[:foo].size).to eq(3)\n      expect(pushes[:foo][0]).to be_a(Array)\n      expect(pushes[:foo][0][0]).to eq(:foo)\n      expect(pushes[:foo][0][1]).to be_a(Proc)\n    end\n\n    context \"when no strategy is given\" do\n      it \"defaults to the name\" do\n        subject.define(\"foo\") { \"bar\" }\n\n        expect(pushes.size).to eq(1)\n        expect(pushes[:foo].size).to eq(1)\n        expect(pushes[:foo][0]).to be_a(Array)\n        expect(pushes[:foo][0][0]).to eq(:foo)\n        expect(pushes[:foo][0][1]).to be_a(Proc)\n      end\n    end\n\n    context \"when a strategy is given\" do\n      it \"uses the strategy\" do\n        subject.define(\"foo\", strategy: \"bacon\") { \"bar\" }\n\n        expect(pushes.size).to eq(1)\n        expect(pushes[:foo].size).to eq(1)\n        expect(pushes[:foo][0]).to be_a(Array)\n        expect(pushes[:foo][0][0]).to eq(:bacon)\n        expect(pushes[:foo][0][1]).to be_a(Proc)\n      end\n    end\n  end\n\n  describe \"#merge\" do\n    it \"appends defined pushes\" do\n      a = described_class.new.tap do |i|\n        i.define(\"foo\") { \"bar\" }\n        i.define(\"bar\") { \"bar\" }\n      end\n      b = described_class.new.tap do |i|\n        i.define(\"foo\") { \"zip\" }\n      end\n\n      result = a.merge(b)\n      pushes = result.instance_variable_get(:@__defined_pushes)\n\n      expect(pushes[:foo]).to be_a(Array)\n      expect(pushes[:foo].size).to eq(2)\n\n      expect(pushes[:bar]).to be_a(Array)\n      expect(pushes[:bar].size).to eq(1)\n    end\n  end\n\n  describe \"#__compiled_pushes\" do\n    it \"raises an exception if not finalized\" do\n      subject.instance_variable_set(:@__finalized, false)\n      expect { subject.__compiled_pushes }.to raise_error(RuntimeError)\n    end\n\n    it \"returns a copy of the compiled pushes\" do\n      pushes =  { foo: \"bar\" }\n      subject.instance_variable_set(:@__finalized, true)\n      subject.instance_variable_set(:@__compiled_pushes, pushes)\n\n      expect(subject.__compiled_pushes).to_not be(pushes)\n      expect(subject.__compiled_pushes).to eq(pushes)\n    end\n  end\n\n  describe \"#finalize!\" do\n    let(:pushes) { a.merge(b).tap { |r| r.finalize! }.__compiled_pushes }\n    let(:key)    { pushes[:foo][0] }\n    let(:config) { pushes[:foo][1] }\n    let(:unset)  { Vagrant.plugin(\"2\", :config).const_get(:UNSET_VALUE) }\n    let(:dummy_klass) { Vagrant::Config::V2::DummyConfig }\n\n    before do\n      register_plugin(\"2\") do |plugin|\n        plugin.name \"foo\"\n\n        plugin.push(:foo) do\n          Class.new(Vagrant.plugin(\"2\", :push))\n        end\n\n        plugin.config(:foo, :push) do\n          Class.new(Vagrant.plugin(\"2\", :config)) do\n            attr_accessor :bar\n            attr_accessor :zip\n\n            def initialize\n              @bar = self.class.const_get(:UNSET_VALUE)\n              @zip = self.class.const_get(:UNSET_VALUE)\n            end\n          end\n        end\n      end\n    end\n\n    it \"compiles the proper configuration with a single strategy\" do\n      instance = described_class.new.tap do |i|\n        i.define \"foo\"\n      end\n\n      instance.finalize!\n\n      pushes = instance.__compiled_pushes\n      strategy, config = pushes[:foo]\n      expect(strategy).to eq(:foo)\n      expect(config.bar).to be(unset)\n    end\n\n    it \"compiles the proper configuration with a single strategy and block\" do\n      instance = described_class.new.tap do |i|\n        i.define \"foo\" do |b|\n          b.bar = 42\n        end\n      end\n\n      instance.finalize!\n\n      pushes = instance.__compiled_pushes\n      strategy, config = pushes[:foo]\n      expect(strategy).to eq(:foo)\n      expect(config.bar).to eq(42)\n    end\n\n    it \"compiles the proper config with a name and explicit strategy\" do\n      instance = described_class.new.tap do |i|\n        i.define \"bar\", strategy: \"foo\"\n      end\n\n      instance.finalize!\n\n      pushes = instance.__compiled_pushes\n      strategy, config = pushes[:bar]\n      expect(strategy).to eq(:foo)\n      expect(config.bar).to be(unset)\n    end\n\n    it \"compiles the proper config with a name and explicit strategy with block\" do\n      instance = described_class.new.tap do |i|\n        i.define \"bar\", strategy: \"foo\" do |b|\n          b.bar = 42\n        end\n      end\n\n      instance.finalize!\n\n      pushes = instance.__compiled_pushes\n      strategy, config = pushes[:bar]\n      expect(strategy).to eq(:foo)\n      expect(config.bar).to eq(42)\n    end\n\n    context \"with the same name but different strategy\" do\n      context \"with no block\" do\n        let(:a) do\n          described_class.new.tap do |i|\n            i.define(\"foo\", strategy: \"bar\")\n          end\n        end\n\n        let(:b) do\n          described_class.new.tap do |i|\n            i.define(\"foo\", strategy: \"zip\")\n          end\n        end\n\n        it \"chooses the last config\" do\n          expect(key).to eq(:zip)\n          expect(config).to be_kind_of(dummy_klass)\n        end\n      end\n\n      context \"with a block\" do\n        let(:a) do\n          described_class.new.tap do |i|\n            i.define(\"foo\", strategy: \"bar\") do |p|\n              p.bar = \"a\"\n            end\n          end\n        end\n\n        let(:b) do\n          described_class.new.tap do |i|\n            i.define(\"foo\", strategy: \"zip\") do |p|\n              p.zip = \"b\"\n            end\n          end\n        end\n\n        it \"chooses the last config\" do\n          expect(key).to eq(:zip)\n          expect(config).to be_kind_of(dummy_klass)\n        end\n      end\n\n      context \"with a block, then no block\" do\n        let(:a) do\n          described_class.new.tap do |i|\n            i.define(\"foo\", strategy: \"bar\") do |p|\n              p.bar, p.zip = \"a\", \"a\"\n            end\n          end\n        end\n\n        let(:b) do\n          described_class.new.tap do |i|\n            i.define(\"foo\", strategy: \"zip\")\n          end\n        end\n\n        it \"chooses the last config\" do\n          expect(key).to eq(:zip)\n          expect(config).to be_kind_of(dummy_klass)\n        end\n      end\n\n      context \"with no block, then a block\" do\n        let(:a) do\n          described_class.new.tap do |i|\n            i.define(\"foo\", strategy: \"bar\")\n          end\n        end\n\n        let(:b) do\n          described_class.new.tap do |i|\n            i.define(\"foo\", strategy: \"zip\") do |p|\n              p.bar, p.zip = \"b\", \"b\"\n            end\n          end\n        end\n\n        it \"chooses the last config\" do\n          expect(key).to eq(:zip)\n          expect(config).to be_kind_of(dummy_klass)\n        end\n      end\n    end\n\n    context \"with the same name twice\" do\n      context \"with no block\" do\n        let(:a) do\n          described_class.new.tap do |i|\n            i.define(\"foo\")\n          end\n        end\n\n        let(:b) do\n          described_class.new.tap do |i|\n            i.define(\"foo\")\n          end\n        end\n\n        it \"merges the configs\" do\n          expect(key).to eq(:foo)\n          expect(config.bar).to be(unset)\n          expect(config.zip).to be(unset)\n        end\n      end\n\n      context \"with a block\" do\n        let(:a) do\n          described_class.new.tap do |i|\n            i.define(\"foo\") do |p|\n              p.bar = \"a\"\n            end\n          end\n        end\n\n        let(:b) do\n          described_class.new.tap do |i|\n            i.define(\"foo\") do |p|\n              p.zip = \"b\"\n            end\n          end\n        end\n\n        it \"merges the configs\" do\n          expect(key).to eq(:foo)\n          expect(config.bar).to eq(\"a\")\n          expect(config.zip).to eq(\"b\")\n        end\n      end\n\n      context \"with a block, then no block\" do\n        let(:a) do\n          described_class.new.tap do |i|\n            i.define(\"foo\") do |p|\n              p.bar = \"a\"\n            end\n          end\n        end\n\n        let(:b) do\n          described_class.new.tap do |i|\n            i.define(\"foo\")\n          end\n        end\n\n        it \"merges the configs\" do\n          expect(key).to eq(:foo)\n          expect(config.bar).to eq(\"a\")\n          expect(config.zip).to be(unset)\n        end\n      end\n\n      context \"with no block, then a block\" do\n        let(:a) do\n          described_class.new.tap do |i|\n            i.define(\"foo\", strategy: \"bar\")\n          end\n        end\n\n        let(:b) do\n          described_class.new.tap do |i|\n            i.define(\"foo\", strategy: \"zip\") do |p|\n              p.zip = \"b\"\n            end\n          end\n        end\n\n        it \"merges the configs\" do\n          expect(key).to eq(:zip)\n          expect(config).to be_kind_of(dummy_klass)\n        end\n      end\n    end\n\n    it \"sets @__finalized to true\" do\n      subject.finalize!\n      expect(subject.instance_variable_get(:@__finalized)).to be(true)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/kernel_v2/config/ssh_connect_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/ssh_connect\")\n\ndescribe VagrantPlugins::Kernel_V2::SSHConnectConfig do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  subject { described_class.new }\n\n  describe \"#verify_host_key\" do\n    it \"defaults to :never\" do\n      subject.finalize!\n      expect(subject.verify_host_key).to eq(:never)\n    end\n\n    it \"should modify true value to :accepts_new_or_local_tunnel\" do\n      subject.verify_host_key = true\n      subject.finalize!\n      expect(subject.verify_host_key).to eq(:accepts_new_or_local_tunnel)\n    end\n\n    it \"should modify :very value to :accept_new\" do\n      subject.verify_host_key = :very\n      subject.finalize!\n      expect(subject.verify_host_key).to eq(:accept_new)\n    end\n\n    it \"should modify :secure to :always\" do\n      subject.verify_host_key = :secure\n      subject.finalize!\n      expect(subject.verify_host_key).to eq(:always)\n    end\n  end\n\n  describe \"#key_type\" do\n    it \"defaults to :auto\" do\n      subject.finalize!\n      expect(subject.key_type).to eq(:auto)\n    end\n\n    it \"should allow supported key type\" do\n      subject.key_type = :ed25519\n      subject.finalize!\n      errors = subject.validate(machine)\n      expect(errors).to be_empty\n    end\n\n    it \"should not allow unsupported key type\" do\n      subject.key_type = :unknown_type\n      subject.finalize!\n      errors = subject.validate(machine)\n      expect(errors).not_to be_empty\n    end\n\n    it \"should convert string values to symbol\" do\n      subject.key_type = \"ecdsa521\"\n      subject.finalize!\n      expect(subject.key_type).to eq(:ecdsa521)\n    end\n  end\n\n  describe \"#config\" do\n    let(:config_file) { \"/path/to/config\" }\n\n    before do\n      # NOTE: The machine instance must be initialized before\n      #       any mocks on File are registered. Otherwise it\n      #       will cause a failure attempting to create the\n      #       instance\n      machine\n      allow(File).to receive(:file?).\n        with(/#{Regexp.escape(config_file)}/).\n        and_return(true)\n    end\n\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.config).to be_nil\n    end\n\n    it \"should return the set path\" do\n      subject.config = config_file\n      subject.finalize!\n      expect(subject.config).to eq(config_file)\n    end\n\n    it \"should validate when path exists\" do\n      subject.config = config_file\n      subject.finalize!\n      machine\n      expect(File).to receive(:file?).\n        with(/#{Regexp.escape(config_file)}/).\n        and_return(true)\n      expect(subject.validate(machine)).to be_empty\n    end\n\n    it \"should not validate when path does not exist\" do\n      subject.config = config_file\n      subject.finalize!\n      expect(File).to receive(:file?).\n        with(/#{Regexp.escape(config_file)}/).\n        and_return(false)\n      expect(subject.validate(machine)).not_to be_empty\n    end\n  end\n\n  describe \"#remote_user\" do\n    let(:username) { double(\"username\") }\n    let(:remote_user) { double(\"remote_user\") }\n\n    it \"should default to username value\" do\n      subject.username = username\n      subject.finalize!\n      expect(subject.remote_user).to eq(subject.username)\n    end\n\n    it \"should be set to provided value\" do\n      subject.username = username\n      subject.remote_user = remote_user\n      subject.finalize!\n      expect(subject.remote_user).to eq(remote_user)\n    end\n  end\n\n  describe \"#connect_timeout\" do\n    let(:timeout_value) { 1 }\n\n    it \"should default to the default value\" do\n      subject.finalize!\n      expect(subject.connect_timeout).\n        to eq(described_class.const_get(:DEFAULT_SSH_CONNECT_TIMEOUT))\n    end\n\n    it \"should be set to provided value\" do\n      subject.connect_timeout = timeout_value\n      subject.finalize!\n      expect(subject.connect_timeout).to eq(timeout_value)\n    end\n\n    it \"should cast given value to integer\" do\n      subject.connect_timeout = timeout_value.to_s\n      subject.finalize!\n      expect(subject.connect_timeout).to eq(timeout_value)\n    end\n\n    it \"should properly validate\" do\n      subject.connect_timeout = timeout_value\n      subject.finalize!\n      expect(subject.validate(machine)).to be_empty\n    end\n\n    context \"when value cannot be cast\" do\n      let(:timeout_value) { :value }\n\n      it \"should not raise an error\" do\n        subject.connect_timeout = timeout_value\n        expect { subject.finalize! }.not_to raise_error\n      end\n\n      it \"should not validate\" do\n        subject.connect_timeout = timeout_value\n        subject.finalize!\n        expect(subject.validate(machine)).not_to be_empty\n      end\n    end\n\n    context \"when value is less than 1\" do\n      let(:timeout_value) { 0 }\n\n      it \"should not raise an error\" do\n        subject.connect_timeout = timeout_value\n        expect { subject.finalize! }.not_to raise_error\n      end\n\n      it \"should not validate\" do\n        subject.connnect_timeout = timeout_value\n        subject.finalize!\n        expect(subject.validate(machine)).not_to be_empty\n      end\n    end\n  end\n\n  describe \"#connect_retries\" do\n    let(:retry_value) { 1 }\n\n    it \"should default to the default value\" do\n      subject.finalize!\n      expect(subject.connect_retries).\n        to eq(described_class.const_get(:DEFAULT_SSH_CONNECT_RETRIES))\n    end\n\n    it \"should be set to the provided value\" do\n      subject.connect_retries = retry_value\n      subject.finalize!\n      expect(subject.connect_retries).to eq(retry_value)\n    end\n\n    it \"should properly validate\" do\n      subject.connect_retries = retry_value\n      subject.finalize!\n      expect(subject.validate(machine)).to be_empty\n    end\n\n    context \"when value is a float\" do\n      let(:retry_value) { 2.3 }\n\n      it \"should not raise an error\" do\n        subject.connect_retries = retry_value\n        expect { subject.finalize! }.not_to raise_error\n      end\n\n      it \"should not validate\" do\n        subject.connect_retries = retry_value\n        subject.finalize!\n        expect(subject.validate(machine)).not_to be_empty\n      end\n    end\n\n    context \"when value is not numeric\" do\n      let(:retry_value) { \"2\" }\n\n      it \"should not raise an error\" do\n        subject.connect_retries = retry_value\n        expect { subject.finalize! }.not_to raise_error\n      end\n\n      it \"should not validate\" do\n        subject.connect_retries = retry_value\n        subject.finalize!\n        expect(subject.validate(machine)).not_to be_empty\n      end\n    end\n\n    context \"when value is less than 0\" do\n      let(:retry_value) { -1 }\n\n      it \"should not validate\" do\n        subject.connect_retries = retry_value\n        subject.finalize!\n        expect(subject.validate(machine)).not_to be_empty\n      end\n    end\n  end\n\n  describe \"#connect_retry_delay\" do\n    let(:delay_value) { 1 }\n\n    it \"should default to the default value\" do\n      subject.finalize!\n      expect(subject.connect_retry_delay).\n        to eq(described_class.const_get(:DEFAULT_SSH_CONNECT_RETRY_DELAY))\n    end\n\n    it \"should be set to the provided value\" do\n      subject.connect_retry_delay = delay_value\n      subject.finalize!\n      expect(subject.connect_retry_delay).to eq(delay_value)\n    end\n\n    it \"should properly validate\" do\n      subject.connect_retry_delay = delay_value\n      subject.finalize!\n      expect(subject.validate(machine)).to be_empty\n    end\n\n    context \"when value is a float\" do\n      let(:delay_value) { 2.3 }\n\n      it \"should validate\" do\n        subject.connect_retry_delay = delay_value\n        subject.finalize!\n        expect(subject.validate(machine)).to be_empty\n      end\n    end\n\n    context \"when value is not numeric\" do\n      let(:delay_value) { \"2\" }\n\n      it \"should not raise an error\" do\n        subject.connect_retry_delay = delay_value\n        expect { subject.finalize! }.not_to raise_error\n      end\n\n      it \"should not validate\" do\n        subject.connect_retry_delay = delay_value\n        subject.finalize!\n        expect(subject.validate(machine)).not_to be_empty\n      end\n    end\n\n    context \"when value is less than 0\" do\n      let(:delay_value) { -1 }\n\n      it \"should not validate\" do\n        subject.connect_retry_delay = delay_value\n        subject.finalize!\n        expect(subject.validate(machine)).not_to be_empty\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/kernel_v2/config/ssh_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/ssh\")\n\ndescribe VagrantPlugins::Kernel_V2::SSHConfig do\n  subject { described_class.new }\n\n  describe \"#default\" do\n    it \"defaults to vagrant username\" do\n      subject.finalize!\n      expect(subject.default.port).to eq(22)\n      expect(subject.default.username).to eq(\"vagrant\")\n    end\n  end\n\n  describe \"#sudo_command\" do\n    it \"defaults properly\" do\n      subject.finalize!\n      expect(subject.sudo_command).to eq(\"sudo -E -H %c\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/kernel_v2/config/trigger_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/trigger\")\n\ndescribe VagrantPlugins::Kernel_V2::TriggerConfig do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  let(:machine) { double(\"machine\") }\n\n  def assert_invalid\n    errors = subject.validate(machine)\n    if !errors.values.any? { |v| !v.empty? }\n      raise \"No errors: #{errors.inspect}\"\n    end\n  end\n\n  def assert_valid\n    errors = subject.validate(machine)\n    if !errors.values.all? { |v| v.empty? }\n      raise \"Errors: #{errors.inspect}\"\n    end\n  end\n\n  before do\n    env = double(\"env\")\n    allow(env).to receive(:root_path).and_return(nil)\n    allow(machine).to receive(:env).and_return(env)\n    allow(machine).to receive(:provider_config).and_return(nil)\n    allow(machine).to receive(:provider_options).and_return({})\n  end\n\n  it \"is valid with test defaults\" do\n    subject.finalize!\n    assert_valid\n  end\n\n  let (:hash_block) { {info: \"hi\", run: {inline: \"echo 'hi'\"}} }\n  let (:splat) { [:up, :destroy, :halt] }\n  let (:arr) { [[:up, :destroy, :halt]] }\n\n  describe \"creating a before trigger\" do\n    it \"creates a trigger with the splat syntax\" do\n      subject.before(:up, hash_block)\n      bf_trigger = subject.instance_variable_get(:@_before_triggers)\n      expect(bf_trigger.size).to eq(1)\n      expect(bf_trigger.first).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)\n    end\n\n    it \"creates a trigger with the array syntax\" do\n      subject.before([:up], hash_block)\n      bf_trigger = subject.instance_variable_get(:@_before_triggers)\n      expect(bf_trigger.size).to eq(1)\n      expect(bf_trigger.first).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)\n    end\n\n    it \"creates a trigger with the block syntax\" do\n      subject.before :up do |trigger|\n        trigger.name = \"rspec\"\n      end\n      bf_trigger = subject.instance_variable_get(:@_before_triggers)\n      expect(bf_trigger.size).to eq(1)\n      expect(bf_trigger.first).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)\n    end\n\n    it \"creates multiple triggers with the splat syntax\" do\n      subject.before(splat, hash_block)\n      bf_trigger = subject.instance_variable_get(:@_before_triggers)\n      expect(bf_trigger.size).to eq(3)\n      bf_trigger.map { |t| expect(t).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger) }\n    end\n\n    it \"creates multiple triggers with the block syntax\" do\n      subject.before splat do |trigger|\n        trigger.name = \"rspec\"\n      end\n      bf_trigger = subject.instance_variable_get(:@_before_triggers)\n      expect(bf_trigger.size).to eq(3)\n      bf_trigger.map { |t| expect(t).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger) }\n    end\n\n    it \"creates multiple triggers with the array syntax\" do\n      subject.before(arr, hash_block)\n      bf_trigger = subject.instance_variable_get(:@_before_triggers)\n      expect(bf_trigger.size).to eq(3)\n      bf_trigger.map { |t| expect(t).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger) }\n    end\n  end\n\n  describe \"creating an after trigger\" do\n    it \"creates a trigger with the splat syntax\" do\n      subject.after(:up, hash_block)\n      af_trigger = subject.instance_variable_get(:@_after_triggers)\n      expect(af_trigger.size).to eq(1)\n      expect(af_trigger.first).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)\n    end\n\n    it \"creates a trigger with the array syntax\" do\n      subject.after([:up], hash_block)\n      af_trigger = subject.instance_variable_get(:@_after_triggers)\n      expect(af_trigger.size).to eq(1)\n      expect(af_trigger.first).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)\n    end\n\n    it \"creates a trigger with the block syntax\" do\n      subject.after :up do |trigger|\n        trigger.name = \"rspec\"\n      end\n      af_trigger = subject.instance_variable_get(:@_after_triggers)\n      expect(af_trigger.size).to eq(1)\n      expect(af_trigger.first).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)\n    end\n\n    it \"creates multiple triggers with the splat syntax\" do\n      subject.after(splat, hash_block)\n      af_trigger = subject.instance_variable_get(:@_after_triggers)\n      expect(af_trigger.size).to eq(3)\n      af_trigger.map { |t| expect(t).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger) }\n    end\n\n    it \"creates multiple triggers with the block syntax\" do\n      subject.after splat do |trigger|\n        trigger.name = \"rspec\"\n      end\n      af_trigger = subject.instance_variable_get(:@_after_triggers)\n      expect(af_trigger.size).to eq(3)\n      af_trigger.map { |t| expect(t).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger) }\n    end\n\n    it \"creates multiple triggers with the array syntax\" do\n      subject.after(arr, hash_block)\n      af_trigger = subject.instance_variable_get(:@_after_triggers)\n      expect(af_trigger.size).to eq(3)\n      af_trigger.map { |t| expect(t).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger) }\n    end\n  end\n\n  describe \"#create_trigger\" do\n    let(:command) { :up }\n    let(:hash_block) { {info: \"hi\", run: {inline: \"echo 'hi'\"}} }\n\n    it \"returns a new VagrantConfigTrigger object if given a hash\" do\n      trigger = subject.create_trigger(command, hash_block)\n      expect(trigger).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)\n    end\n\n    it \"returns a new VagrantConfigTrigger object if given a block\" do\n      block = Proc.new { |b| b.info = \"test\"}\n\n      trigger = subject.create_trigger(command, block)\n      expect(trigger).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)\n    end\n  end\n\n  describe \"#merge\" do\n    it \"merges defined triggers\" do\n      a = described_class.new()\n      b = described_class.new()\n\n      a.before(splat, hash_block)\n      a.after(arr, hash_block)\n      b.before(splat, hash_block)\n      b.after(arr, hash_block)\n\n      result = a.merge(b)\n      bf_trigger = result.instance_variable_get(:@_before_triggers)\n      af_trigger = result.instance_variable_get(:@_after_triggers)\n\n      expect(bf_trigger).to be_a(Array)\n      expect(af_trigger).to be_a(Array)\n      expect(bf_trigger.size).to eq(6)\n      expect(af_trigger.size).to eq(6)\n    end\n\n    it \"merges the other triggers if a class is empty\" do\n      a = described_class.new()\n      b = described_class.new()\n\n      a.before(splat, hash_block)\n      a.after(arr, hash_block)\n\n      b_bf_trigger = b.instance_variable_get(:@_before_triggers)\n      b_af_trigger = b.instance_variable_get(:@_after_triggers)\n\n      result = a.merge(b)\n      bf_trigger = result.instance_variable_get(:@_before_triggers)\n      af_trigger = result.instance_variable_get(:@_after_triggers)\n\n      expect(bf_trigger.size).to eq(3)\n      expect(af_trigger.size).to eq(3)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/kernel_v2/config/vagrant_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/vagrant\")\n\ndescribe VagrantPlugins::Kernel_V2::VagrantConfig do\n  subject { described_class.new }\n\n  let(:machine){ double(\"machine\") }\n\n  describe \"#host\" do\n    it \"defaults to :detect\" do\n      subject.finalize!\n      expect(subject.host).to eq(:detect)\n    end\n\n    it \"symbolizes\" do\n      subject.host = \"foo\"\n      subject.finalize!\n      expect(subject.host).to eq(:foo)\n    end\n  end\n\n  describe \"#sensitive\" do\n    after{ Vagrant::Util::CredentialScrubber.reset! }\n\n    it \"accepts string value\" do\n      subject.sensitive = \"test\"\n      subject.finalize!\n      expect(subject.sensitive).to eq(\"test\")\n    end\n\n    it \"accepts array of values\" do\n      subject.sensitive = [\"test1\", \"test2\"]\n      subject.finalize!\n      expect(subject.sensitive).to eq([\"test1\", \"test2\"])\n    end\n\n    it \"does not accept non-string values\" do\n      subject.sensitive = 1\n      subject.finalize!\n      result = subject.validate(machine)\n      expect(result).to be_a(Hash)\n      expect(result.values).not_to be_empty\n    end\n\n    it \"registers single sensitive value to be scrubbed\" do\n      subject.sensitive = \"test\"\n      expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(\"test\")\n      subject.finalize!\n    end\n\n    it \"registers multiple sensitive values to be scrubbed\" do\n      subject.sensitive = [\"test1\", \"test2\"]\n      expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(\"test1\")\n      expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(\"test2\")\n      subject.finalize!\n    end\n  end\n\n  describe \"#plugins\" do\n    it \"converts string into hash of plugins\" do\n      subject.plugins = \"vagrant-plugin\"\n      subject.finalize!\n      expect(subject.plugins).to be_a(Hash)\n    end\n\n    it \"converts array of strings into hash of plugins\" do\n      subject.plugins = [\"vagrant-plugin\", \"vagrant-other-plugin\"]\n      subject.finalize!\n      expect(subject.plugins).to be_a(Hash)\n      expect(subject.plugins.keys).to eq([\"vagrant-plugin\", \"vagrant-other-plugin\"])\n    end\n\n    it \"does not convert hash\" do\n      plugins = {\"vagrant-plugin\" => {}}\n      subject.plugins = plugins\n      subject.finalize\n      expect(subject.plugins).to eq(plugins)\n    end\n\n    it \"converts array of mixed strings and hashes\" do\n      subject.plugins = [\"vagrant-plugin\", {\"vagrant-other-plugin\" => {:version => \"1\"}}]\n      subject.finalize!\n      expect(subject.plugins[\"vagrant-plugin\"]).to eq({})\n      expect(subject.plugins[\"vagrant-other-plugin\"]).to eq({\"version\" => \"1\"})\n    end\n\n    it \"generates a validation error when incorrect type is provided\" do\n      subject.plugins = 0\n      subject.finalize!\n      result = subject.validate(machine)\n      expect(result.values).not_to be_empty\n    end\n\n    it \"generates a validation error when invalid option is provided\" do\n      subject.plugins = {\"vagrant-plugin\" => {\"badkey\" => true}}\n      subject.finalize!\n      result = subject.validate(machine)\n      expect(result.values).not_to be_empty\n    end\n\n    it \"generates a validation error when options are incorrect type\" do\n      subject.plugins = {\"vagrant-plugin\" => 1}\n      subject.finalize!\n      result = subject.validate(machine)\n      expect(result.values).not_to be_empty\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/kernel_v2/config/vm_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/vm\")\n\ndescribe VagrantPlugins::Kernel_V2::VMConfig do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  let(:provider) { double(\"provider\") }\n  let(:machine) { double(\"machine\", provider: provider, provider_name: \"provider\", name: \"default\") }\n\n  def assert_invalid\n    errors = subject.validate(machine)\n    if !errors.values.any? { |v| !v.empty? }\n      raise \"No errors: #{errors.inspect}\"\n    end\n  end\n\n  def assert_valid\n    errors = subject.validate(machine)\n    if !errors.values.all? { |v| v.empty? }\n      raise \"Errors: #{errors.inspect}\"\n    end\n  end\n\n  def find_network(name)\n    network_definitions = subject.networks.map do |n|\n      n[1]\n    end\n    network_definitions.find {|n| n[:id] == name}\n  end\n\n  before do\n    env = double(\"env\")\n    allow(env).to receive(:root_path).and_return(nil)\n    allow(machine).to receive(:env).and_return(env)\n    allow(machine).to receive(:provider_config).and_return(nil)\n    allow(machine).to receive(:provider_options).and_return({})\n    allow(machine).to receive_message_chain(:synced_folders, :types).and_return( {} )\n    allow(provider).to receive(:capability?).with(:validate_disk_ext).and_return(true)\n    allow(provider).to receive(:capability).with(:validate_disk_ext, \"vdi\").and_return(true)\n    allow(provider).to receive(:capability?).with(:set_default_disk_ext).and_return(true)\n    allow(provider).to receive(:capability).with(:set_default_disk_ext).and_return(\"vdi\")\n\n    subject.box = \"foo\"\n  end\n\n  it \"is valid with test defaults\" do\n    subject.finalize!\n    assert_valid\n  end\n\n  it \"validates disables_host_modification option\" do\n    subject.allow_hosts_modification = true\n    subject.finalize!\n    assert_valid\n\n    subject.allow_hosts_modification = false\n    subject.finalize!\n    assert_valid\n\n    subject.allow_hosts_modification = \"truthy\"\n    subject.finalize!\n    assert_invalid\n  end\n\n  it \"does not check for fstab caps if already set\" do\n    expect(machine).to_not receive(:synced_folder_types)\n    subject.allow_fstab_modification = true\n    subject.finalize!\n    assert_valid\n  end\n\n  describe \"#base_mac\" do\n    it \"defaults properly\" do\n      subject.finalize!\n      expect(subject.base_mac).to be_nil\n    end\n  end\n\n  describe \"#base_address\" do\n    it \"defaults properly\" do\n      subject.finalize!\n      expect(subject.base_address).to be_nil\n    end\n  end\n\n  describe \"#box\" do\n    it \"is required\" do\n      subject.box = nil\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"cannot be an empty string\" do\n      subject.box = \"\"\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"is not required if the provider says so\" do\n      machine.provider_options[:box_optional] = true\n      subject.box = nil\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"is invalid if clone is set\" do\n      subject.clone = \"foo\"\n      subject.finalize!\n      assert_invalid\n    end\n  end\n\n  describe \"#box_architecture\" do\n    it \"is not required\" do\n      subject.box_architecture = nil\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"is :auto by default\" do\n      subject.finalize!\n      assert_valid\n      expect(subject.box_architecture).to eq(:auto)\n    end\n\n    it \"can be set to custom value\" do\n      subject.box_architecture = \"test-arch\"\n      subject.finalize!\n      assert_valid\n      expect(subject.box_architecture).to eq(\"test-arch\")\n    end\n\n    it \"is converted to string\" do\n      subject.box_architecture = :test_arch\n      subject.finalize!\n      assert_valid\n      expect(subject.box_architecture).to eq(\"test_arch\")\n    end\n  end\n\n  context \"#box_check_update\" do\n    it \"defaults to true\" do\n      with_temp_env(\"VAGRANT_BOX_UPDATE_CHECK_DISABLE\" => \"\") do\n        subject.finalize!\n        expect(subject.box_check_update).to be(true)\n      end\n    end\n\n    it \"is false if VAGRANT_BOX_UPDATE_CHECK_DISABLE is set\" do\n      with_temp_env(\"VAGRANT_BOX_UPDATE_CHECK_DISABLE\" => \"1\") do\n        subject.finalize!\n        expect(subject.box_check_update).to be(false)\n      end\n    end\n  end\n\n  describe \"#box_url\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n\n      expect(subject.box_url).to be_nil\n    end\n\n    it \"turns into an array\" do\n      subject.box_url = \"foo\"\n      subject.finalize!\n\n      expect(subject.box_url).to eq(\n        [\"foo\"])\n    end\n\n    it \"keeps in array\" do\n      subject.box_url = [\"foo\", \"bar\"]\n      subject.finalize!\n\n      expect(subject.box_url).to eq(\n        [\"foo\", \"bar\"])\n    end\n  end\n\n  context \"#box_version\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n\n      expect(subject.box_version).to be_nil\n    end\n\n    it \"errors if invalid version\" do\n      subject.box_version = \"nope\"\n      subject.finalize!\n\n      expect { assert_valid }.to raise_error(RuntimeError)\n    end\n\n    it \"can have complex constraints\" do\n      subject.box_version = \">= 0, ~> 1.0\"\n      subject.finalize!\n\n      assert_valid\n    end\n\n    [\"1\", 1, \"1.0\", 1.0].each do |valid|\n      it \"is valid: #{valid}\" do\n        subject.box_version = valid\n        subject.finalize!\n        assert_valid\n      end\n    end\n  end\n\n  describe \"#communicator\" do\n    it \"is nil by default\" do\n      subject.finalize!\n      expect(subject.communicator).to be_nil\n    end\n  end\n\n  describe \"#guest\" do\n    it \"is nil by default\" do\n      subject.finalize!\n      expect(subject.guest).to be_nil\n    end\n\n    it \"is symbolized\" do\n      subject.guest = \"foo\"\n      subject.finalize!\n      expect(subject.guest).to eq(:foo)\n    end\n  end\n\n  describe \"#hostname\" do\n    [\"a\", \"foo\", \"foo-bar\", \"baz0\"].each do |valid|\n      it \"is valid: #{valid}\" do\n        subject.hostname = valid\n        subject.finalize!\n        assert_valid\n      end\n    end\n  end\n\n  describe \"#network(s)\" do\n    it \"defaults to forwarding SSH by default\" do\n      subject.finalize!\n      n = subject.networks\n      expect(n.length).to eq(1)\n      expect(n[0][0]).to eq(:forwarded_port)\n      expect(n[0][1][:guest]).to eq(22)\n      expect(n[0][1][:host]).to eq(2222)\n      expect(n[0][1][:host_ip]).to eq(\"127.0.0.1\")\n      expect(n[0][1][:id]).to eq(\"ssh\")\n    end\n\n    it \"defaults to forwarding WinRM if communicator is winrm\" do\n      subject.communicator = \"winrm\"\n      subject.finalize!\n      n = subject.networks\n      expect(n.length).to eq(3)\n\n      expect(n[0][0]).to eq(:forwarded_port)\n      expect(n[0][1][:guest]).to eq(5985)\n      expect(n[0][1][:host]).to eq(55985)\n      expect(n[0][1][:host_ip]).to eq(\"127.0.0.1\")\n      expect(n[0][1][:id]).to eq(\"winrm\")\n\n      expect(n[1][0]).to eq(:forwarded_port)\n      expect(n[1][1][:guest]).to eq(5986)\n      expect(n[1][1][:host]).to eq(55986)\n      expect(n[1][1][:host_ip]).to eq(\"127.0.0.1\")\n      expect(n[1][1][:id]).to eq(\"winrm-ssl\")\n    end\n\n    it \"forwards ssh even if the communicator is winrm\" do\n      subject.communicator = \"winrm\"\n      subject.finalize!\n      n = subject.networks\n      expect(n.length).to eq(3)\n\n      expect(n[0][0]).to eq(:forwarded_port)\n      expect(n[0][1][:guest]).to eq(5985)\n      expect(n[0][1][:host]).to eq(55985)\n      expect(n[0][1][:host_ip]).to eq(\"127.0.0.1\")\n      expect(n[0][1][:id]).to eq(\"winrm\")\n\n      expect(n[1][0]).to eq(:forwarded_port)\n      expect(n[1][1][:guest]).to eq(5986)\n      expect(n[1][1][:host]).to eq(55986)\n      expect(n[1][1][:host_ip]).to eq(\"127.0.0.1\")\n      expect(n[1][1][:id]).to eq(\"winrm-ssl\")\n\n      expect(n[2][0]).to eq(:forwarded_port)\n      expect(n[2][1][:guest]).to eq(22)\n      expect(n[2][1][:host]).to eq(2222)\n      expect(n[2][1][:host_ip]).to eq(\"127.0.0.1\")\n      expect(n[2][1][:id]).to eq(\"ssh\")\n\n    end\n\n    it \"allows overriding SSH\" do\n      subject.network \"forwarded_port\",\n        guest: 22, host: 14100, id: \"ssh\"\n      subject.finalize!\n\n      n = subject.networks\n      expect(n.length).to eq(1)\n      expect(n[0][0]).to eq(:forwarded_port)\n      expect(n[0][1][:guest]).to eq(22)\n      expect(n[0][1][:host]).to eq(14100)\n      expect(n[0][1][:id]).to eq(\"ssh\")\n    end\n\n    it \"allows overriding WinRM\" do\n      subject.communicator = :winrm\n      subject.network \"forwarded_port\",\n        guest: 5985, host: 14100, id: \"winrm\"\n      subject.finalize!\n\n      winrm_network = find_network 'winrm'\n      expect(winrm_network[:guest]).to eq(5985)\n      expect(winrm_network[:host]).to eq(14100)\n      expect(winrm_network[:id]).to eq(\"winrm\")\n    end\n\n    it \"allows overriding WinRM SSL\" do\n      subject.communicator = :winrm\n      subject.network \"forwarded_port\",\n        guest: 5986, host: 14100, id: \"winrm-ssl\"\n      subject.finalize!\n\n      winrmssl_network = find_network 'winrm-ssl'\n      expect(winrmssl_network[:guest]).to eq(5986)\n      expect(winrmssl_network[:host]).to eq(14100)\n      expect(winrmssl_network[:id]).to eq(\"winrm-ssl\")\n    end\n\n    it \"turns all forwarded port ports to ints\" do\n      subject.network \"forwarded_port\",\n        guest: \"45\", host: \"4545\", id: \"test\"\n      subject.finalize!\n      n = subject.networks.find do |type, data|\n        type == :forwarded_port && data[:id] == \"test\"\n      end\n      expect(n).to_not be_nil\n      expect(n[1][:guest]).to eq(45)\n      expect(n[1][:host]).to eq(4545)\n    end\n\n    it \"is an error if forwarding a port too low\" do\n      subject.network \"forwarded_port\",\n        guest: \"45\", host: \"-5\"\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"is an error if forwarding a port too high\" do\n      subject.network \"forwarded_port\",\n        guest: \"45\", host: \"74545\"\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"is an error if multiple networks set hostname\" do\n      subject.network \"public_network\", ip: \"192.168.0.1\", hostname: true\n      subject.network \"public_network\", ip: \"192.168.0.2\", hostname: true\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"is an error if networks set hostname without ip\" do\n      subject.network \"public_network\", hostname: true\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"is not an error if hostname non-bool\" do\n      subject.network \"public_network\",  ip: \"192.168.0.1\", hostname: \"true\"\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"is not an error if one hostname is true\" do\n      subject.network \"public_network\",  ip: \"192.168.0.1\", hostname: true\n      subject.network \"public_network\",  ip: \"192.168.0.2\", hostname: false\n      subject.finalize!\n      assert_valid\n    end\n  end\n\n  describe \"#post_up_message\" do\n    it \"defaults to empty string\" do\n      subject.finalize!\n      expect(subject.post_up_message).to eq(\"\")\n    end\n\n    it \"can be set\" do\n      subject.post_up_message = \"foo\"\n      subject.finalize!\n      expect(subject.post_up_message).to eq(\"foo\")\n    end\n  end\n\n  describe \"#provider and #__providers\" do\n    it \"returns the providers in order\" do\n      subject.provider \"foo\"\n      subject.provider \"bar\"\n      subject.finalize!\n\n      expect(subject.__providers).to eq([:foo, :bar])\n    end\n\n    describe \"merging\" do\n      it \"prioritizes new orders in later configs\" do\n        subject.provider \"foo\"\n\n        other = described_class.new\n        other.provider \"bar\"\n\n        merged = subject.merge(other)\n\n        expect(merged.__providers).to eq([:foo, :bar])\n      end\n\n      it \"prioritizes duplicates in new orders in later configs\" do\n        subject.provider \"foo\"\n\n        other = described_class.new\n        other.provider \"bar\"\n        other.provider \"foo\"\n\n        merged = subject.merge(other)\n\n        expect(merged.__providers).to eq([:foo, :bar])\n      end\n    end\n  end\n\n  describe \"#provider and #get_provider_config\" do\n    it \"compiles the configurations for a provider\" do\n      subject.provider \"virtualbox\" do |vb|\n        vb.gui = true\n      end\n\n      subject.provider \"virtualbox\" do |vb|\n        vb.name = \"foo\"\n      end\n\n      subject.finalize!\n\n      config = subject.get_provider_config(:virtualbox)\n      expect(config.name).to eq(\"foo\")\n      expect(config.gui).to be(true)\n    end\n\n    it \"raises an exception if there is a problem loading\" do\n      subject.provider \"virtualbox\" do |vb|\n        # Purposeful bad variable\n        vm.foo = \"bar\"\n      end\n\n      expect { subject.finalize! }.\n        to raise_error(Vagrant::Errors::VagrantfileLoadError)\n    end\n\n    it \"ignores providers entirely if flag is provided\" do\n      subject.provider \"virtualbox\" do |vb|\n        vb.nope = true\n      end\n\n      subject.provider \"virtualbox\" do |vb|\n        vb.not_real = \"foo\"\n      end\n\n      subject.finalize!\n      errors = subject.validate(machine, true)\n      expect(errors).to eq({\"vm\"=>[]})\n    end\n  end\n\n  describe \"#provision\" do\n    it \"stores the provisioners\" do\n      subject.provision(\"shell\", inline: \"foo\")\n      subject.provision(\"shell\", inline: \"bar\", run: \"always\") { |s| s.path = \"baz\" }\n      subject.provision(\"shell\", inline: \"foo\", communicator_required: false)\n      subject.finalize!\n\n      r = subject.provisioners\n      expect(r.length).to eql(3)\n      expect(r[0].run).to be_nil\n      expect(r[0].config.inline).to eql(\"foo\")\n      expect(r[1].config.inline).to eql(\"bar\")\n      expect(r[1].config.path).to eql(\"baz\")\n      expect(r[1].run).to eql(:always)\n      expect(r[1].communicator_required).to eql(true)\n      expect(r[2].communicator_required).to eql(false)\n    end\n\n    it \"allows provisioner settings to be overridden\" do\n      subject.provision(\"s\", path: \"foo\", type: \"shell\") { |s| s.inline = \"foo\" }\n      subject.provision(\"s\", inline: \"bar\", type: \"shell\") { |s| s.args = \"bar\" }\n      subject.finalize!\n\n      r = subject.provisioners\n      expect(r.length).to eql(1)\n      expect(r[0].config.args).to eql(\"bar\")\n      expect(r[0].config.inline).to eql(\"bar\")\n      expect(r[0].config.path).to eql(\"foo\")\n    end\n\n    it \"marks as invalid if a bad name\" do\n      subject.provision(\"nope\", inline: \"foo\")\n      subject.finalize!\n\n      r = subject.provisioners\n      expect(r.length).to eql(1)\n      expect(r[0]).to be_invalid\n    end\n\n    it \"allows provisioners that don't define any config\" do\n      register_plugin(\"2\") do |p|\n        p.name \"foo\"\n        # This plugin registers a dummy provisioner\n        # without registering a provisioner config\n        p.provisioner(:foo) do\n          Class.new Vagrant::plugin(\"2\", :provisioner)\n        end\n      end\n\n      subject.provision(\"foo\") do |c|\n        c.bar = \"baz\"\n      end\n\n      # This should succeed without errors\n      expect{ subject.finalize! }.to_not raise_error\n    end\n\n    it \"generates a uuid if no name was provided\" do\n      allow(SecureRandom).to receive(:uuid).and_return(\"MY_CUSTOM_VALUE\")\n\n      subject.provision(\"shell\", path: \"foo\") { |s| s.inline = \"foo\" }\n      subject.finalize!\n\n      r = subject.provisioners\n      expect(r[0].id).to eq(\"MY_CUSTOM_VALUE\")\n    end\n\n    it \"sets id as name if a name was provided\" do\n      subject.provision(\"ghost\", type: \"shell\", path: \"motoko\") { |s| s.inline = \"motoko\" }\n      subject.finalize!\n\n      r = subject.provisioners\n      expect(r[0].id).to eq(:ghost)\n    end\n\n    describe \"merging\" do\n      it \"ignores non-overriding runs\" do\n        subject.provision(\"shell\", inline: \"foo\", run: \"once\")\n\n        other = described_class.new\n        other.provision(\"shell\", inline: \"bar\", run: \"always\")\n\n        merged = subject.merge(other)\n        merged_provs = merged.provisioners\n\n        expect(merged_provs.length).to eql(2)\n        expect(merged_provs[0].run).to eq(\"once\")\n        expect(merged_provs[1].run).to eq(\"always\")\n      end\n\n      it \"does not merge duplicate provisioners\" do\n        subject.provision(\"shell\", inline: \"foo\")\n        subject.provision(\"shell\", inline: \"bar\")\n\n        merged = subject.merge(subject)\n        merged_provs = merged.provisioners\n\n        expect(merged_provs.length).to eql(2)\n      end\n\n      it \"copies the configs\" do\n        subject.provision(\"shell\", inline: \"foo\")\n        subject_provs = subject.provisioners\n\n        other = described_class.new\n        other.provision(\"shell\", inline: \"bar\")\n\n        merged = subject.merge(other)\n        merged_provs = merged.provisioners\n\n        expect(merged_provs.length).to eql(2)\n        expect(merged_provs[0].config.inline).\n          to eq(subject_provs[0].config.inline)\n        expect(merged_provs[0].config.object_id).\n          to_not eq(subject_provs[0].config.object_id)\n      end\n\n      it \"uses the proper order when merging overrides\" do\n        subject.provision(\"original\", inline: \"foo\", type: \"shell\")\n        subject.provision(\"other\", inline: \"other\", type: \"shell\")\n\n        other = described_class.new\n        other.provision(\"shell\", inline: \"bar\")\n        other.provision(\"original\", inline: \"foo-overload\", type: \"shell\")\n\n        merged = subject.merge(other)\n        merged_provs = merged.provisioners\n\n        expect(merged_provs.length).to eql(3)\n        expect(merged_provs[0].config.inline).\n          to eq(\"other\")\n        expect(merged_provs[1].config.inline).\n          to eq(\"bar\")\n        expect(merged_provs[2].config.inline).\n          to eq(\"foo-overload\")\n      end\n\n      it \"can preserve order for overrides\" do\n        subject.provision(\"original\", inline: \"foo\", type: \"shell\")\n        subject.provision(\"other\", inline: \"other\", type: \"shell\")\n\n        other = described_class.new\n        other.provision(\"shell\", inline: \"bar\")\n        other.provision(\n          \"original\", inline: \"foo-overload\", type: \"shell\",\n          preserve_order: true)\n\n        merged = subject.merge(other)\n        merged_provs = merged.provisioners\n\n        expect(merged_provs.length).to eql(3)\n        expect(merged_provs[0].config.inline).\n          to eq(\"foo-overload\")\n        expect(merged_provs[1].config.inline).\n          to eq(\"other\")\n        expect(merged_provs[2].config.inline).\n          to eq(\"bar\")\n      end\n    end\n  end\n\n  describe \"#disk\" do\n    before(:each) do\n      allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).\n        with(\"disks\").and_return(\"true\")\n    end\n\n    it \"stores the disks\" do\n      subject.disk(:disk, size: 100, primary: true)\n      subject.disk(:disk, size: 1000, name: \"storage\")\n      subject.finalize!\n\n      assert_valid\n\n      d = subject.disks\n      expect(d.length).to eql(2)\n      expect(d[0].size).to eql(100)\n      expect(d[1].size).to eql(1000)\n      expect(d[1].name).to eql(\"storage\")\n    end\n\n    it \"raises an error with duplicate names\" do\n      subject.disk(:disk, size: 100, name: \"foo\")\n      subject.disk(:disk, size: 1000, name: \"foo\", primary: false)\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"raises an error with duplicate disk files\" do\n      allow(File).to receive(:file?).with(\"bar.vmdk\").and_return(true)\n      subject.disk(:disk, size: 100, name: \"foo1\", file: \"bar.vmdk\")\n      subject.disk(:disk, size: 100, name: \"foo2\", file: \"bar.vmdk\")\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"does not merge duplicate disks\" do\n      subject.disk(:disk, size: 1000, primary: false, name: \"storage\")\n      subject.disk(:disk, size: 1000, primary: false, name: \"backup\")\n\n      merged = subject.merge(subject)\n      merged_disks = merged.disks\n\n      expect(merged_disks.length).to eql(2)\n    end\n\n    it \"ignores non-overriding runs\" do\n      subject.disk(:disk, name: \"foo\")\n\n      other = described_class.new\n      other.disk(:disk, name: \"bar\", primary: false)\n\n      merged = subject.merge(other)\n      merged_disks = merged.disks\n\n      expect(merged_disks.length).to eql(2)\n      expect(merged_disks[0].name).to eq(\"foo\")\n      expect(merged_disks[1].name).to eq(\"bar\")\n    end\n\n    it \"adds provider config with `__` config form\" do\n      subject.disk(:disk, size: 1000, primary: false, name: \"storage\",  provider__something: \"special\")\n      expect(subject.disks[0].provider_config).to eq({:provider=>{:something=>\"special\"}})\n    end\n\n    it \"adds provider config with Hash config form\" do\n      subject.disk(:disk, size: 1000, primary: false, name: \"storage\",  provider: {something: \"special\"})\n      expect(subject.disks[0].provider_config).to eq({:provider=>{:something=>\"special\"}})\n    end\n  end\n\n  describe \"#synced_folder(s)\" do\n    it \"defaults to sharing the current directory\" do\n      subject.finalize!\n      sf = subject.synced_folders\n      expect(sf.length).to eq(1)\n      expect(sf).to have_key(\"/vagrant\")\n      expect(sf[\"/vagrant\"][:disabled]).to_not be\n    end\n\n    it \"allows overriding settings on the /vagrant sf\" do\n      subject.synced_folder(\".\", \"/vagrant\", disabled: true)\n      subject.finalize!\n      sf = subject.synced_folders\n      expect(sf.length).to eq(1)\n      expect(sf).to have_key(\"/vagrant\")\n      expect(sf[\"/vagrant\"][:disabled]).to be(true)\n    end\n\n    it \"allows overriding previously set options\" do\n      subject.synced_folder(\".\", \"/vagrant\", disabled: true)\n      subject.synced_folder(\".\", \"/vagrant\", foo: :bar)\n      subject.finalize!\n      sf = subject.synced_folders\n      expect(sf.length).to eq(1)\n      expect(sf).to have_key(\"/vagrant\")\n      expect(sf[\"/vagrant\"][:disabled]).to be(false)\n      expect(sf[\"/vagrant\"][:foo]).to eq(:bar)\n    end\n\n    # This is a little bit of a special case since nfs can be specified\n    # as `type: \"nfs\"` or `nfs: true`\n    it \"properly overrides nfs\" do\n      subject.synced_folder(\".\", \"/vagrant\", nfs: true)\n      subject.synced_folder(\".\", \"/vagrant\", type: \"rsync\")\n      subject.finalize!\n      sf = subject.synced_folders\n      expect(sf.length).to eq(1)\n      expect(sf).to have_key(\"/vagrant\")\n      expect(sf[\"/vagrant\"][:type]).to be(:rsync)\n      expect(sf[\"/vagrant\"][:nfs]).to eq(nil)\n    end\n\n    it \"is not an error if guest path is empty but name is not\" do\n      subject.synced_folder(\".\", \"\", name: \"my-vagrant-folder\")\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"allows providing custom name via options\" do\n      subject.synced_folder(\".\", \"/vagrant\", name: \"my-vagrant-folder\")\n      sf = subject.synced_folders\n      expect(sf).to have_key(\"my-vagrant-folder\")\n      expect(sf[\"my-vagrant-folder\"][:guestpath]).to eq(\"/vagrant\")\n      expect(sf[\"my-vagrant-folder\"][:hostpath]).to eq(\".\")\n    end\n\n    it \"allows providing custom name without guest path\" do\n      subject.synced_folder(\".\", name: \"my-vagrant-folder\")\n      sf = subject.synced_folders\n      expect(sf).to have_key(\"my-vagrant-folder\")\n      expect(sf[\"my-vagrant-folder\"][:hostpath]).to eq(\".\")\n    end\n\n    it \"requires either guest path or name\" do\n      subject.synced_folder(\".\", name: nil, guestpath: nil)\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"keeps nil guest path if not provided\" do\n      subject.synced_folder(\".\", name: \"my-vagrant-folder\")\n      sf = subject.synced_folders\n      expect(sf[\"my-vagrant-folder\"][:guestpath]).to be_nil\n    end\n\n    context \"WSL host paths\" do\n      let(:valid_path){ \"/mnt/c/path\" }\n      let(:invalid_path){ \"/home/vagrant/path\" }\n      let(:synced_folder_impl){ double(\"synced_folder_impl\", new: double(\"synced_folder_inst\", usable?: true, _initialize: true)) }\n      let(:fs_config){ double(\"fs_config\", vm: double(\"fs_vm\", allowed_synced_folder_types: nil)) }\n      let(:plugin){ double(\"plugin\", manager: manager) }\n      let(:manager){ double(\"manager\", synced_folders: {sf_impl: [synced_folder_impl, 1]}) }\n\n      let(:stub_pathname){ double(\"stub_pathname\", directory?: true, relative?: false) }\n\n      before do\n        allow(Pathname).to receive(:new).and_return(stub_pathname)\n        allow(stub_pathname).to receive(:expand_path).and_return(stub_pathname)\n        allow(Vagrant::Util::Platform).to receive(:wsl?).and_return(true)\n        allow(Vagrant::Util::Platform).to receive(:wsl_drvfs_path?).with(valid_path).and_return(true)\n        allow(Vagrant::Util::Platform).to receive(:wsl_drvfs_path?).with(invalid_path).and_return(false)\n        allow(machine).to receive(:config).and_return(fs_config)\n        allow(Vagrant).to receive(:plugin).with(\"2\").and_return(plugin)\n        subject.synced_folder(\".\", \"/vagrant\", disabled: true)\n      end\n\n      it \"is valid when located on DrvFs\" do\n        subject.synced_folder(valid_path, \"/guest/path\")\n        subject.finalize!\n        assert_valid\n      end\n\n      it \"is invalid when not located on DrvFs\" do\n        subject.synced_folder(invalid_path, \"/guest/path\")\n        subject.finalize!\n        assert_invalid\n      end\n\n      context \"when synced folder defines support for non-DrvFs\" do\n        let(:support_nondrvfs){ true }\n\n        before do\n          allow(synced_folder_impl).to receive(:respond_to?).with(:wsl_allow_non_drvfs?).and_return(true)\n          allow(synced_folder_impl).to receive(:wsl_allow_non_drvfs?).and_return(support_nondrvfs)\n        end\n\n        context \"and is supported\" do\n          it \"is valid when located on DrvFs\" do\n            subject.synced_folder(valid_path, \"/guest/path\")\n            subject.finalize!\n            assert_valid\n          end\n\n          it \"is valid when not located on DrvFs\" do\n            subject.synced_folder(invalid_path, \"/guest/path\")\n            subject.finalize!\n            assert_valid\n          end\n        end\n\n        context \"and is not supported\" do\n          let(:support_nondrvfs){ false }\n\n          it \"is valid when located on DrvFs\" do\n            subject.synced_folder(valid_path, \"/guest/path\")\n            subject.finalize!\n            assert_valid\n          end\n\n          it \"is invalid when not located on DrvFs\" do\n            subject.synced_folder(invalid_path, \"/guest/path\")\n            subject.finalize!\n            assert_invalid\n          end\n        end\n      end\n    end\n  end\n\n  describe \"#usable_port_range\" do\n    it \"defaults properly\" do\n      subject.finalize!\n      expect(subject.usable_port_range).to eq(\n        Range.new(2200, 2250))\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/kernel_v2/config/vm_trigger_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/vm_trigger\")\n\ndescribe VagrantPlugins::Kernel_V2::VagrantConfigTrigger do\n  include_context \"unit\"\n\n  let(:command) { :up }\n\n  subject { described_class.new(command) }\n\n  let(:machine) { double(\"machine\") }\n\n  def assert_invalid\n    errors = subject.validate(machine)\n    if errors.empty?\n      raise \"No errors: #{errors.inspect}\"\n    end\n  end\n\n  def assert_valid\n    errors = subject.validate(machine)\n    if !errors.empty?\n      raise \"Errors: #{errors.inspect}\"\n    end\n  end\n\n  before do\n    env = double(\"env\")\n    allow(env).to receive(:root_path).and_return(nil)\n    allow(machine).to receive(:env).and_return(env)\n    allow(machine).to receive(:provider_config).and_return(nil)\n    allow(machine).to receive(:provider_options).and_return({})\n\n    subject.name = \"foo\"\n    subject.info = \"Hello there\"\n    subject.warn = \"Warning!!\"\n    subject.ignore = :up\n    subject.only_on = \"guest\"\n    subject.ruby do |env,machine|\n      var = 'test'\n      math = 1+1\n    end\n    subject.run = {inline: \"apt-get update\"}\n    subject.run_remote = {inline: \"apt-get update\", env: {\"VAR\"=>\"VAL\"}}\n  end\n\n  describe \"with defaults\" do\n    it \"is valid with test defaults\" do\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"sets a command\" do\n      subject.finalize!\n      expect(subject.command).to eq(command)\n    end\n\n    it \"uses default error behavior\" do\n      subject.finalize!\n      expect(subject.on_error).to eq(:halt)\n    end\n  end\n\n  describe \"defining a new config that needs to match internal restraints\" do\n    let(:cmd) { :destroy }\n    let(:cfg) { described_class.new(cmd) }\n    let(:arr_cfg) { described_class.new(cmd) }\n\n    before do\n      cfg.only_on = :guest\n      cfg.ignore = \"up\"\n      cfg.abort = true\n      cfg.type = \"action\"\n      cfg.ruby do\n        var = 1+1\n      end\n      arr_cfg.only_on = [\"guest\", /other/]\n      arr_cfg.ignore = [\"up\", \"destroy\"]\n    end\n\n    it \"ensures only_on is an array\" do\n      cfg.finalize!\n      arr_cfg.finalize!\n\n      expect(cfg.only_on).to be_a(Array)\n      expect(arr_cfg.only_on).to be_a(Array)\n    end\n\n    it \"ensures ignore is an array of symbols\" do\n      cfg.finalize!\n      arr_cfg.finalize!\n\n      expect(cfg.ignore).to be_a(Array)\n      expect(arr_cfg.ignore).to be_a(Array)\n\n      cfg.ignore.each do |a|\n        expect(a).to be_a(Symbol)\n      end\n\n      arr_cfg.ignore.each do |a|\n        expect(a).to be_a(Symbol)\n      end\n    end\n\n    it \"ensures ruby is a proc\" do\n      cfg.finalize!\n      expect(cfg.ruby_block).to be_a(Proc)\n    end\n\n    it \"converts aborts true to exit code 0\" do\n      cfg.finalize!\n\n      expect(cfg.abort).to eq(1)\n    end\n\n    it \"converts types to symbols\" do\n      cfg.finalize!\n      expect(cfg.type).to eq(:action)\n    end\n  end\n\n  describe \"defining a basic trigger config\" do\n    let(:cmd) { :up }\n    let(:cfg) { described_class.new(cmd) }\n\n    before do\n      cfg.info = \"Hello there\"\n      cfg.warn = \"Warning!!\"\n      cfg.on_error = :continue\n      cfg.ignore = :up\n      cfg.abort = 3\n      cfg.only_on = \"guest\"\n      cfg.ruby = proc{ var = 1+1 }\n      cfg.run = {inline: \"apt-get update\"}\n      cfg.run_remote = {inline: \"apt-get update\", env: {\"VAR\"=>\"VAL\"}}\n    end\n\n    it \"sets the options\" do\n      cfg.finalize!\n      expect(cfg.info).to eq(\"Hello there\")\n      expect(cfg.warn).to eq(\"Warning!!\")\n      expect(cfg.on_error).to eq(:continue)\n      expect(cfg.ignore).to eq([:up])\n      expect(cfg.only_on).to eq([\"guest\"])\n      expect(cfg.run).to be_a(VagrantPlugins::Shell::Config)\n      expect(cfg.run_remote).to be_a(VagrantPlugins::Shell::Config)\n      expect(cfg.abort).to eq(3)\n      expect(cfg.ruby_block).to be_a(Proc)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/docker/action/compare_synced_folders_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/providers/docker/action/compare_synced_folders\"\n\ndescribe VagrantPlugins::DockerProvider::Action::CompareSyncedFolders do\n  include_context \"unit\"\n  include_context \"virtualbox\"\n\n  let(:sandbox) { isolated_environment }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    sandbox.vagrantfile(\"\")\n    sandbox.create_vagrant_env\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :virtualbox).tap do |m|\n      allow(m.provider).to receive(:driver).and_return(driver)\n    end\n  end\n\n  let(:env)    {{ machine: machine, ui: machine.ui, root_path: Pathname.new(\".\") }}\n  let(:app)    { lambda { |*args| }}\n  let(:driver) { double(\"driver\") }\n\n  subject { described_class.new(app, env) }\n\n  after do\n    sandbox.close\n  end\n\n  describe \"#call\" do\n    let(:cached) { {:docker=>{\"/vagrant\"=>{:guestpath=>\"/vagrant\", :hostpath=>\"/home/hashicorp/code/vagrant-sandbox\", :disabled=>false, :__vagrantfile=>true}}} }\n    let(:fresh) { {:docker=>{\"/vagrant\"=>{:guestpath=>\"/vagrant\", :hostpath=>\".\", :disabled=>false, :__vagrantfile=>true}}} }\n\n    let(:existing) { {\"/vagrant\"=>\"/home/hashicorp/code/vagrant-sandbox\"} }\n\n\n    it \"calls the next action in the chain\" do\n      allow(machine.provider).to receive(:host_vm?).and_return(false)\n      called = false\n      app = ->(*args) { called = true }\n\n      action = described_class.new(app, env)\n      action.call(env)\n\n      expect(called).to eq(true)\n    end\n\n    context \"invalid or existing entries\" do\n      let(:cached) { {:docker=>{\"/vagrant\"=>{:guestpath=>\"/not-real\", :hostpath=>\"/home/hashicorp/code/vagrant-sandbox\", :disabled=>false, :__vagrantfile=>true}}} }\n      let(:fresh) { {:docker=>{\"/vagrant\"=>{:guestpath=>\"/vagrant\", :hostpath=>\".\", :disabled=>false, :__vagrantfile=>true}}} }\n      it \"shows a warning\" do\n        allow(machine.provider).to receive(:host_vm?).and_return(false)\n\n        called = false\n        app = ->(*args) { called = true }\n        action = described_class.new(app, env)\n\n        expect(action).to receive(:synced_folders).\n          with(machine, cached: true).and_return(cached)\n        expect(action).to receive(:synced_folders).\n          with(machine).and_return(fresh)\n\n        expect(machine.ui).to receive(:warn)\n\n        action.call(env)\n        expect(called).to eq(true)\n      end\n    end\n\n    it \"shows no warning comparing synced folders\" do\n      allow(machine.provider).to receive(:host_vm?).and_return(false)\n\n      called = false\n      app = ->(*args) { called = true }\n      action = described_class.new(app, env)\n\n      expect(action).to receive(:synced_folders).\n        with(machine, cached: true).and_return(cached)\n      expect(action).to receive(:synced_folders).\n        with(machine).and_return(fresh)\n\n      action.call(env)\n      expect(machine.ui).not_to receive(:warn)\n      expect(called).to eq(true)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/docker/action/connect_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/providers/docker/action/connect_networks\"\n\n\ndescribe VagrantPlugins::DockerProvider::Action::ConnectNetworks do\n  include_context \"unit\"\n\n  let(:sandbox) { isolated_environment }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    sandbox.vagrantfile(\"\")\n    sandbox.create_vagrant_env\n  end\n\n  let(:vm_config) { double(\"machine_vm_config\") }\n\n  let(:machine_config) do\n    double(\"machine_config\").tap do |top_config|\n      allow(top_config).to receive(:vm).and_return(vm_config)\n    end\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :docker).tap do |m|\n      allow(m).to receive(:vagrantfile).and_return(vagrantfile)\n      allow(m).to receive(:config).and_return(machine_config)\n      allow(m).to receive(:id).and_return(\"12345\")\n      allow(m.provider).to receive(:driver).and_return(driver)\n      allow(m.provider).to receive(:host_vm?).and_return(false)\n      allow(m.config.vm).to receive(:networks).and_return(networks)\n    end\n  end\n\n  let(:docker_connects) { {0=>\"vagrant_network_172.20.0.0/16\", 1=>\"vagrant_network_public_wlp4s0\", 2=>\"vagrant_network_2a02:6b8:b010:9020:1::/80\"} }\n\n  let(:vagrantfile) { double(\"vagrantfile\") }\n\n  let(:env)    {{ machine: machine, ui: machine.ui, root_path: Pathname.new(\".\"),\n                  docker_connects: docker_connects, vagrantfile: vagrantfile }}\n  let(:app)    { lambda { |*args| }}\n  let(:driver) { double(\"driver\", create: \"abcd1234\") }\n\n  let(:networks) { [[:private_network,\n          {:ip=>\"172.20.128.2\",\n           :subnet=>\"172.20.0.0/16\",\n           :driver=>\"bridge\",\n           :internal=>\"true\",\n           :alias=>\"mynetwork\",\n           :protocol=>\"tcp\",\n           :id=>\"80e017d5-388f-4a2f-a3de-f8dce8156a58\"}],\n           [:public_network,\n            {:ip=>\"172.30.130.2\",\n             :subnet=>\"172.30.0.0/16\",\n             :driver=>\"bridge\",\n             :id=>\"30e017d5-488f-5a2f-a3ke-k8dce8246b60\"}],\n         [:private_network,\n          {:type=>\"dhcp\",\n           :ipv6=>\"true\",\n           :subnet=>\"2a02:6b8:b010:9020:1::/80\",\n           :protocol=>\"tcp\",\n           :id=>\"b8f23054-38d5-45c3-99ea-d33fc5d1b9f2\"}],\n         [:forwarded_port,\n          {:guest=>22, :host=>2200, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]]\n  }\n\n  subject { described_class.new(app, env) }\n\n  let(:subprocess_result) do\n    double(\"subprocess_result\").tap do |result|\n      allow(result).to receive(:exit_code).and_return(0)\n      allow(result).to receive(:stdout).and_return(\"\")\n      allow(result).to receive(:stderr).and_return(\"\")\n    end\n  end\n\n  before do\n    allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"docker\", \"version\", an_instance_of(Hash)).and_return(subprocess_result)\n  end\n\n  after do\n    sandbox.close\n  end\n\n  describe \"#call\" do\n    it \"calls the next action in the chain\" do\n      allow(driver).to receive(:host_vm?).and_return(false)\n      allow(driver).to receive(:connect_network).and_return(true)\n\n      called = false\n      app = ->(*args) { called = true }\n\n      action = described_class.new(app, env)\n\n      action.call(env)\n\n      expect(called).to eq(true)\n    end\n\n    it \"connects all of the available networks to a container\" do\n      expect(driver).to receive(:connect_network).with(\"vagrant_network_172.20.0.0/16\", \"12345\", [\"--ip\", \"172.20.128.2\", \"--alias\", \"mynetwork\"])\n      expect(driver).to receive(:connect_network).with(\"vagrant_network_public_wlp4s0\", \"12345\", [\"--ip\", \"172.30.130.2\"])\n      expect(driver).to receive(:connect_network).with(\"vagrant_network_2a02:6b8:b010:9020:1::/80\", \"12345\", [])\n\n      subject.call(env)\n    end\n\n    context \"with missing env values\" do\n      it \"raises an error if the network name is missing\" do\n        env[:docker_connects] = {}\n\n        expect{subject.call(env)}.to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkNameMissing)\n      end\n    end\n  end\n\n  describe \"#generate_connect_cli_arguments\" do\n    let(:network_options) {\n            {:ip=>\"172.20.128.2\",\n             :subnet=>\"172.20.0.0/16\",\n             :driver=>\"bridge\",\n             :internal=>\"true\",\n             :alias=>\"mynetwork\",\n             :protocol=>\"tcp\",\n             :id=>\"80e017d5-388f-4a2f-a3de-f8dce8156a58\"} }\n\n    let(:false_network_options) {\n            {:ip=>\"172.20.128.2\",\n             :subnet=>\"172.20.0.0/16\",\n             :driver=>\"bridge\",\n             :internal=>\"false\",\n             :alias=>\"mynetwork\",\n             :protocol=>\"tcp\",\n             :id=>\"80e017d5-388f-4a2f-a3de-f8dce8156a58\"} }\n\n    it \"removes false values\" do\n      cli_args = subject.generate_connect_cli_arguments(false_network_options)\n      expect(cli_args).to eq([\"--ip\", \"172.20.128.2\", \"--subnet\", \"172.20.0.0/16\", \"--driver\", \"bridge\", \"--alias\", \"mynetwork\", \"--protocol\", \"tcp\", \"--id\", \"80e017d5-388f-4a2f-a3de-f8dce8156a58\"])\n    end\n\n    it \"removes true and leaves flag value in arguments\" do\n      cli_args = subject.generate_connect_cli_arguments(network_options)\n      expect(cli_args).to eq([\"--ip\", \"172.20.128.2\", \"--subnet\", \"172.20.0.0/16\", \"--driver\", \"bridge\", \"--internal\", \"--alias\", \"mynetwork\", \"--protocol\", \"tcp\", \"--id\", \"80e017d5-388f-4a2f-a3de-f8dce8156a58\"])\n    end\n\n    it \"takes options and generates cli flags\" do\n      cli_args = subject.generate_connect_cli_arguments(network_options)\n      expect(cli_args).to eq([\"--ip\", \"172.20.128.2\", \"--subnet\", \"172.20.0.0/16\", \"--driver\", \"bridge\", \"--internal\", \"--alias\", \"mynetwork\", \"--protocol\", \"tcp\", \"--id\", \"80e017d5-388f-4a2f-a3de-f8dce8156a58\"])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/docker/action/create_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/providers/docker/action/create\"\n\ndescribe VagrantPlugins::DockerProvider::Action::Create do\n  include_context \"unit\"\n  include_context \"virtualbox\"\n\n  let(:sandbox) { isolated_environment }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    sandbox.vagrantfile(\"\")\n    sandbox.create_vagrant_env\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :virtualbox).tap do |m|\n      allow(m.provider).to receive(:driver).and_return(driver)\n    end\n  end\n\n  let(:env)    {{ machine: machine, ui: machine.ui, root_path: Pathname.new(\".\") }}\n  let(:app)    { lambda { |*args| }}\n  let(:driver) { double(\"driver\", create: \"abcd1234\") }\n\n  subject { described_class.new(app, env) }\n\n  after do\n    sandbox.close\n  end\n\n  describe \"#call\" do\n    it \"calls the next action in the chain\" do\n      called = false\n      app = ->(*args) { called = true }\n\n      action = described_class.new(app, env)\n      action.call(env)\n\n      expect(called).to eq(true)\n    end\n  end\n\n  describe \"#forwarded_ports\" do\n    it \"does not clobber ports with different protocols\" do\n      subject.instance_variable_set(:@machine, machine)\n      machine.config.vm.network \"forwarded_port\", guest: 8125, host: 8125, protocol: \"tcp\"\n      machine.config.vm.network \"forwarded_port\", guest: 8125, host: 8125, protocol: \"udp\"\n\n      result = subject.forwarded_ports(false)\n\n      expect(result).to eq([\"8125:8125\", \"8125:8125/udp\"])\n    end\n  end\n\n  describe \"#generate_container_name\" do\n    let(:env) {{root_path: root_path}}\n    let(:root_path) {Pathname.new(\"/path/to/root\")}\n\n    before do\n      subject.instance_variable_set(:@env, env)\n      subject.instance_variable_set(:@machine, machine)\n    end\n\n    it \"should not remove any characters\" do\n      expect(subject.generate_container_name).to start_with(\"root\")\n    end\n\n    context \"when root path starts with underscores\" do\n      let(:root_path) {Pathname.new(\"/path/to/__root\")}\n      it \"should remove underscores\" do\n        expect(subject.generate_container_name).to start_with(\"root\")\n      end\n    end\n\n    context \"when root path starts with dashes\" do\n      let(:root_path) {Pathname.new(\"/path/to/--root\")}\n      it \"should remove dashes\" do\n        expect(subject.generate_container_name).to start_with(\"root\")\n      end\n    end\n\n    context \"when root path starts with combination of invalid starting characters\" do\n      let(:root_path) {Pathname.new(\"/path/to/_.-_-root\")}\n      it \"should remove invalid starting characters\" do\n        expect(subject.generate_container_name).to start_with(\"root\")\n      end\n    end\n\n    context \"when root path ends with underscores\" do\n      let(:root_path) {Pathname.new(\"/path/to/root__\")}\n      it \"should not remove trailing underscroes\" do\n        expect(subject.generate_container_name).to start_with(\"root_\")\n      end\n    end\n\n    context \"when root path ends with invalid characters\" do\n      let(:root_path) {Pathname.new(\"/path/to/root_.\")}\n      it \"should remove invalid trailing characters\" do\n        expect(subject.generate_container_name).to start_with(\"root_\")\n      end\n    end\n\n    context \"when root path contains invalid characters\" do\n      let(:root_path) {Pathname.new(\"/path/to/root-.path_.\")}\n      it \"should only remove invalid characters\" do\n        expect(subject.generate_container_name).to start_with(\"root-path_\")\n      end\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/docker/action/destroy_network_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/providers/docker/action/destroy_network\"\n\ndescribe VagrantPlugins::DockerProvider::Action::DestroyNetwork do\n  include_context \"unit\"\n\n  let(:sandbox) { isolated_environment }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    sandbox.vagrantfile(\"\")\n    sandbox.create_vagrant_env\n  end\n\n  let(:vm_config) { double(\"machine_vm_config\") }\n\n  let(:machine_config) do\n    double(\"machine_config\").tap do |top_config|\n      allow(top_config).to receive(:vm).and_return(vm_config)\n    end\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :docker).tap do |m|\n      allow(m).to receive(:vagrantfile).and_return(vagrantfile)\n      allow(m).to receive(:config).and_return(machine_config)\n      allow(m.provider).to receive(:driver).and_return(driver)\n      allow(m.config.vm).to receive(:networks).and_return(networks)\n    end\n  end\n\n  let(:vagrantfile) { double(\"vagrantfile\") }\n\n  let(:env)    {{ machine: machine, ui: machine.ui, root_path: Pathname.new(\".\"), vagrantfile: vagrantfile }}\n  let(:app)    { lambda { |*args| }}\n  let(:driver) { double(\"driver\", create: \"abcd1234\") }\n\n  let(:networks) { [[:private_network,\n          {:ip=>\"172.20.128.2\",\n           :subnet=>\"172.20.0.0/16\",\n           :driver=>\"bridge\",\n           :internal=>\"true\",\n           :alias=>\"mynetwork\",\n           :protocol=>\"tcp\",\n           :id=>\"80e017d5-388f-4a2f-a3de-f8dce8156a58\"}],\n         [:private_network,\n          {:type=>\"dhcp\",\n           :ipv6=>\"true\",\n           :subnet=>\"2a02:6b8:b010:9020:1::/80\",\n           :protocol=>\"tcp\",\n           :id=>\"b8f23054-38d5-45c3-99ea-d33fc5d1b9f2\"}],\n         [:forwarded_port,\n          {:guest=>22, :host=>2200, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]]\n  }\n\n  subject { described_class.new(app, env) }\n\n  let(:subprocess_result) do\n    double(\"subprocess_result\").tap do |result|\n      allow(result).to receive(:exit_code).and_return(0)\n      allow(result).to receive(:stdout).and_return(\"\")\n      allow(result).to receive(:stderr).and_return(\"\")\n    end\n  end\n\n  before do\n    allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"docker\", \"version\", an_instance_of(Hash)).and_return(subprocess_result)\n  end\n\n  after do\n    sandbox.close\n  end\n\n  describe \"#call\" do\n    let(:network_names) { [\"vagrant_network_172.20.0.0/16\", \"vagrant_network_2a02:6b8:b010:9020:1::/80\"] }\n\n    it \"calls the next action in the chain\" do\n      allow(driver).to receive(:host_vm?).and_return(false)\n      allow(driver).to receive(:existing_network?).and_return(true)\n      allow(driver).to receive(:network_used?).and_return(true)\n      allow(driver).to receive(:list_network_names).and_return([])\n\n      called = false\n      app = ->(*args) { called = true }\n\n      action = described_class.new(app, env)\n      action.call(env)\n\n      expect(called).to eq(true)\n    end\n\n    it \"calls the proper driver method to destroy the network\" do\n      allow(driver).to receive(:list_network_names).and_return(network_names)\n      allow(driver).to receive(:host_vm?).and_return(false)\n      allow(driver).to receive(:existing_named_network?).with(\"vagrant_network_172.20.0.0/16\").\n                                                         and_return(true)\n      allow(driver).to receive(:network_used?).with(\"vagrant_network_172.20.0.0/16\").\n                                                         and_return(false)\n      allow(driver).to receive(:existing_named_network?).with(\"vagrant_network_2a02:6b8:b010:9020:1::/80\").\n                                                         and_return(true)\n      allow(driver).to receive(:network_used?).with(\"vagrant_network_2a02:6b8:b010:9020:1::/80\").\n                                                         and_return(false)\n\n      expect(driver).to receive(:rm_network).with(\"vagrant_network_172.20.0.0/16\").twice\n      expect(driver).to receive(:rm_network).with(\"vagrant_network_2a02:6b8:b010:9020:1::/80\").twice\n\n      subject.call(env)\n    end\n\n    it \"doesn't destroy the network if another container is still using it\" do\n      allow(driver).to receive(:host_vm?).and_return(false)\n      allow(driver).to receive(:list_network_names).and_return(network_names)\n      allow(driver).to receive(:existing_named_network?).with(\"vagrant_network_172.20.0.0/16\").\n                                                         and_return(true)\n      allow(driver).to receive(:network_used?).with(\"vagrant_network_172.20.0.0/16\").\n                                                         and_return(true)\n      allow(driver).to receive(:existing_named_network?).with(\"vagrant_network_2a02:6b8:b010:9020:1::/80\").\n                                                         and_return(true)\n      allow(driver).to receive(:network_used?).with(\"vagrant_network_2a02:6b8:b010:9020:1::/80\").\n                                                         and_return(true)\n\n      expect(driver).not_to receive(:rm_network).with(\"vagrant_network_172.20.0.0/16\")\n      expect(driver).not_to receive(:rm_network).with(\"vagrant_network_2a02:6b8:b010:9020:1::/80\")\n\n      subject.call(env)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/docker/action/host_machine_sync_folders_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/providers/docker/action/host_machine_sync_folders\"\n\ndescribe VagrantPlugins::DockerProvider::Action::HostMachineSyncFolders do\n  include_context \"unit\"\n  include_context \"virtualbox\"\n\n  let(:sandbox) { isolated_environment }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    sandbox.vagrantfile(\"\")\n    sandbox.create_vagrant_env\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :virtualbox).tap do |m|\n      allow(m.provider).to receive(:driver).and_return(driver)\n    end\n  end\n\n  let(:env)    {{ machine: machine, ui: machine.ui, root_path: Pathname.new(\".\") }}\n  let(:app)    { lambda { |*args| }}\n  let(:driver) { double(\"driver\") }\n\n  subject { described_class.new(app, env) }\n\n  after do\n    sandbox.close\n  end\n\n  describe \"#call\" do\n    it \"calls the next action in the chain\" do\n      allow(machine.provider).to receive(:host_vm?).and_return(false)\n      called = false\n      app = ->(*args) { called = true }\n\n      action = described_class.new(app, env)\n      action.call(env)\n\n      expect(called).to eq(true)\n    end\n\n    context \"with a host vm\" do\n      it \"calls the next action in the chain\" do\n        allow(machine.provider).to receive(:host_vm?).and_return(true)\n        allow(machine.provider).to receive(:host_vm).and_return(machine)\n        called = false\n        app = ->(*args) { called = true }\n\n        expect(machine.provider).to receive(:host_vm_lock).and_return(true)\n        action = described_class.new(app, env)\n        action.call(env)\n\n        expect(called).to eq(true)\n      end\n    end\n  end\n\n  describe \"#setup_synced_folders\" do\n    it \"syncs folders on the guest machine with a given id\" do\n      allow(Digest::MD5).to receive(:hexdigest).and_return(\"4e9414d72abee585b3d6263e50248e37\")\n      expect(machine).to receive(:action).with(:sync_folders, {:synced_folders_config => anything})\n      expect(env[:machine].config.vm).to receive(:synced_folder).\n        with(\"/var/lib/docker/docker_4e9414d72abee585b3d6263e50248e37\",\n             \"/vagrant\", anything)\n      subject.send(:setup_synced_folders, machine, env)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/docker/action/login_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/providers/docker/action/login\"\n\n\ndescribe VagrantPlugins::DockerProvider::Action::Login do\n  include_context \"unit\"\n\n  let(:sandbox) { isolated_environment }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    sandbox.vagrantfile(\"\")\n    sandbox.create_vagrant_env\n  end\n\n  let(:provider_config) { double(\"provider_config\", username: \"docker\", password: \"\") }\n\n  let(:vm_config) { double(\"machine_vm_config\") }\n\n  let(:machine_config) do\n    double(\"machine_config\").tap do |top_config|\n      allow(top_config).to receive(:vm).and_return(vm_config)\n    end\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :docker).tap do |m|\n      allow(m).to receive(:id).and_return(\"12345\")\n      allow(m).to receive(:config).and_return(machine_config)\n      allow(m).to receive(:provider_config).and_return(provider_config)\n      allow(m).to receive(:vagrantfile).and_return(vagrantfile)\n      allow(m.provider).to receive(:driver).and_return(driver)\n      allow(m.provider).to receive(:host_vm?).and_return(false)\n    end\n  end\n\n  let(:vagrantfile) { double(\"vagrantfile\") }\n\n  let(:env)    {{ machine: machine, ui: machine.ui, root_path: Pathname.new(\".\"), vagrantfile: vagrantfile }}\n  let(:app)    { lambda { |*args| }}\n  let(:driver) { double(\"driver\", create: \"abcd1234\") }\n\n\n  subject { described_class.new(app, env) }\n\n  let(:subprocess_result) do\n    double(\"subprocess_result\").tap do |result|\n      allow(result).to receive(:exit_code).and_return(0)\n      allow(result).to receive(:stdout).and_return(\"\")\n      allow(result).to receive(:stderr).and_return(\"\")\n    end\n  end\n\n  before do\n    allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"docker\", \"version\", an_instance_of(Hash)).and_return(subprocess_result)\n  end\n\n  after do\n    sandbox.close\n  end\n\n  describe \"#call\" do\n    it \"calls the next action in the chain\" do\n      allow(driver).to receive(:host_vm?).and_return(false)\n\n      called = false\n      app = ->(*args) { called = true }\n\n      action = described_class.new(app, env)\n\n      action.call(env)\n\n      expect(called).to eq(true)\n    end\n\n    it \"uses a host vm lock if host_vm is true and password is set\" do\n      allow(driver).to receive(:host_vm?).and_return(true)\n      allow(driver).to receive(:login).and_return(true)\n      allow(driver).to receive(:logout).and_return(true)\n\n      allow(machine.provider).to receive(:host_vm?).and_return(true)\n      allow(machine.provider).to receive(:host_vm_lock) { |&block| block.call }\n\n      allow(provider_config).to receive(:password).and_return(\"docker\")\n      allow(provider_config).to receive(:email).and_return(\"docker\")\n      allow(provider_config).to receive(:auth_server).and_return(\"docker\")\n\n      called = false\n      app = ->(*args) { called = true }\n\n      action = described_class.new(app, env)\n\n      action.call(env)\n\n      expect(called).to eq(true)\n    end\n\n    it \"doesn't use the host vm if not set\" do\n      allow(driver).to receive(:host_vm?).and_return(false)\n      allow(driver).to receive(:login).and_return(true)\n      allow(driver).to receive(:logout).and_return(true)\n\n      allow(machine.provider).to receive(:host_vm?).and_return(false)\n\n      allow(provider_config).to receive(:password).and_return(\"docker\")\n      allow(provider_config).to receive(:email).and_return(\"docker\")\n      allow(provider_config).to receive(:auth_server).and_return(\"docker\")\n\n      called = false\n      app = ->(*args) { called = true }\n\n      action = described_class.new(app, env)\n\n      action.call(env)\n\n      expect(called).to eq(true)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/docker/action/prepare_networks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/providers/docker/action/prepare_networks\"\n\ndescribe VagrantPlugins::DockerProvider::Action::PrepareNetworks do\n  include_context \"unit\"\n\n  let(:sandbox) { isolated_environment }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    sandbox.vagrantfile(\"\")\n    sandbox.create_vagrant_env\n  end\n\n  let(:vm_config) { double(\"machine_vm_config\") }\n\n  let(:machine_config) do\n    double(\"machine_config\").tap do |top_config|\n      allow(top_config).to receive(:vm).and_return(vm_config)\n    end\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :docker).tap do |m|\n      allow(m).to receive(:vagrantfile).and_return(vagrantfile)\n      allow(m).to receive(:config).and_return(machine_config)\n      allow(m.provider).to receive(:driver).and_return(driver)\n      allow(m.config.vm).to receive(:networks).and_return(networks)\n    end\n  end\n\n  let(:vagrantfile) { double(\"vagrantfile\") }\n\n  let(:env)    {{ machine: machine, ui: machine.ui, root_path: Pathname.new(\".\"), vagrantfile: vagrantfile }}\n  let(:app)    { lambda { |*args| }}\n  let(:driver) { double(\"driver\", create: \"abcd1234\") }\n\n  let(:networks) { [[:private_network,\n          {:ip=>\"172.20.128.2\",\n           :subnet=>\"172.20.0.0/16\",\n           :driver=>\"bridge\",\n           :internal=>\"true\",\n           :alias=>\"mynetwork\",\n           :protocol=>\"tcp\",\n           :id=>\"80e017d5-388f-4a2f-a3de-f8dce8156a58\"}],\n           [:public_network,\n            {:ip=>\"172.30.130.2\",\n             :subnet=>\"172.30.0.0/16\",\n             :driver=>\"bridge\",\n             :id=>\"30e017d5-488f-5a2f-a3ke-k8dce8246b60\"}],\n         [:private_network,\n          {:type=>\"dhcp\",\n           :ipv6=>\"true\",\n           :subnet=>\"2a02:6b8:b010:9020:1::/80\",\n           :protocol=>\"tcp\",\n           :id=>\"b8f23054-38d5-45c3-99ea-d33fc5d1b9f2\"}],\n         [:forwarded_port,\n          {:guest=>22, :host=>2200, :host_ip=>\"127.0.0.1\", :id=>\"ssh\", :auto_correct=>true, :protocol=>\"tcp\"}]]\n  }\n\n  let(:invalid_network) {\n         [[:private_network,\n          {:ipv6=>\"true\",\n           :protocol=>\"tcp\",\n           :id=>\"b8f23054-38d5-45c3-99ea-d33fc5d1b9f2\"}]]\n        }\n\n  subject { described_class.new(app, env) }\n\n  let(:subprocess_result) do\n    double(\"subprocess_result\").tap do |result|\n      allow(result).to receive(:exit_code).and_return(0)\n      allow(result).to receive(:stdout).and_return(\"\")\n      allow(result).to receive(:stderr).and_return(\"\")\n    end\n  end\n\n  before do\n    allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"docker\", \"version\", an_instance_of(Hash)).and_return(subprocess_result)\n  end\n\n  after do\n    sandbox.close\n  end\n\n  describe \"#call\" do\n    it \"calls the next action in the chain\" do\n      allow(driver).to receive(:host_vm?).and_return(false)\n      allow(driver).to receive(:existing_named_network?).and_return(false)\n      allow(driver).to receive(:create_network).and_return(true)\n\n      called = false\n      app = ->(*args) { called = true }\n\n      action = described_class.new(app, env)\n\n      allow(action).to receive(:process_public_network).and_return([\"name\", {}])\n      allow(action).to receive(:process_private_network).and_return([\"name\", {}])\n\n      action.call(env)\n\n      expect(called).to eq(true)\n    end\n\n    it \"calls the proper driver methods to setup a network\" do\n      allow(driver).to receive(:host_vm?).and_return(false)\n      allow(driver).to receive(:existing_named_network?).and_return(false)\n      allow(driver).to receive(:network_containing_address).\n        with(\"172.20.128.2\").and_return(nil)\n      allow(driver).to receive(:network_containing_address).\n        with(\"192.168.1.1\").and_return(nil)\n      allow(driver).to receive(:network_defined?).with(\"172.20.128.0/24\").\n        and_return(false)\n      allow(driver).to receive(:network_defined?).with(\"172.30.128.0/24\").\n        and_return(false)\n      allow(driver).to receive(:network_defined?).with(\"2a02:6b8:b010:9020:1::/80\").\n        and_return(false)\n\n      allow(subject).to receive(:request_public_gateway).and_return(\"1234\")\n      allow(subject).to receive(:request_public_iprange).and_return(\"1234\")\n\n      expect(subject).to receive(:process_private_network).with(networks[0][1], {}, env).\n        and_return([\"vagrant_network_172.20.128.0/24\", {:ipv6=>false, :subnet=>\"172.20.128.0/24\"}])\n\n      expect(subject).to receive(:process_public_network).with(networks[1][1], {}, env).\n        and_return([\"vagrant_network_public_wlp4s0\", {\"opt\"=>\"parent=wlp4s0\", \"subnet\"=>\"192.168.1.0/24\", \"driver\"=>\"macvlan\", \"gateway\"=>\"1234\", \"ipv6\"=>false, \"ip_range\"=>\"1234\"}])\n\n      expect(subject).to receive(:process_private_network).with(networks[2][1], {}, env).\n        and_return([\"vagrant_network_2a02:6b8:b010:9020:1::/80\", {:ipv6=>true, :subnet=>\"2a02:6b8:b010:9020:1::/80\"}])\n\n      allow(machine.ui).to receive(:ask).and_return(\"1\")\n\n      expect(driver).to receive(:create_network).\n        with(\"vagrant_network_172.20.128.0/24\", [\"--subnet\", \"172.20.128.0/24\"])\n      expect(driver).to receive(:create_network).\n        with(\"vagrant_network_public_wlp4s0\", [\"--opt\", \"parent=wlp4s0\", \"--subnet\", \"192.168.1.0/24\", \"--driver\", \"macvlan\", \"--gateway\", \"1234\", \"--ip-range\", \"1234\"])\n      expect(driver).to receive(:create_network).\n        with(\"vagrant_network_2a02:6b8:b010:9020:1::/80\", [\"--ipv6\", \"--subnet\", \"2a02:6b8:b010:9020:1::/80\"])\n\n      subject.call(env)\n\n      expect(env[:docker_connects]).to eq({0=>\"vagrant_network_172.20.128.0/24\", 1=>\"vagrant_network_public_wlp4s0\", 2=>\"vagrant_network_2a02:6b8:b010:9020:1::/80\"})\n    end\n\n    it \"uses an existing network if a matching subnet is found\" do\n      allow(driver).to receive(:host_vm?).and_return(false)\n      allow(driver).to receive(:network_containing_address).\n        with(\"172.20.128.2\").and_return(nil)\n      allow(driver).to receive(:network_containing_address).\n        with(\"192.168.1.1\").and_return(nil)\n      allow(driver).to receive(:network_defined?).with(\"172.20.128.0/24\").\n        and_return(\"vagrant_network_172.20.128.0/24\")\n      allow(driver).to receive(:network_defined?).with(\"172.30.128.0/24\").\n        and_return(\"vagrant_network_public_wlp4s0\")\n      allow(driver).to receive(:network_defined?).with(\"2a02:6b8:b010:9020:1::/80\").\n        and_return(\"vagrant_network_2a02:6b8:b010:9020:1::/80\")\n      allow(machine.ui).to receive(:ask).and_return(\"1\")\n\n      expect(driver).to receive(:existing_named_network?).\n        with(\"vagrant_network_172.20.128.0/24\").and_return(true)\n      expect(driver).to receive(:existing_named_network?).\n        with(\"vagrant_network_public_wlp4s0\").and_return(true)\n      expect(driver).to receive(:existing_named_network?).\n        with(\"vagrant_network_2a02:6b8:b010:9020:1::/80\").and_return(true)\n\n      expect(subject).to receive(:process_private_network).with(networks[0][1], {}, env).\n        and_return([\"vagrant_network_172.20.128.0/24\", {:ipv6=>false, :subnet=>\"172.20.128.0/24\"}])\n\n      expect(subject).to receive(:process_public_network).with(networks[1][1], {}, env).\n        and_return([\"vagrant_network_public_wlp4s0\", {\"opt\"=>\"parent=wlp4s0\", \"subnet\"=>\"192.168.1.0/24\", \"driver\"=>\"macvlan\", \"gateway\"=>\"1234\", \"ipv6\"=>false, \"ip_range\"=>\"1234\"}])\n\n      expect(subject).to receive(:process_private_network).with(networks[2][1], {}, env).\n        and_return([\"vagrant_network_2a02:6b8:b010:9020:1::/80\", {:ipv6=>true, :subnet=>\"2a02:6b8:b010:9020:1::/80\"}])\n      expect(driver).not_to receive(:create_network)\n\n      expect(subject).to receive(:validate_network_configuration!).\n        with(\"vagrant_network_172.20.128.0/24\", networks[0][1],\n            {:ipv6=>false, :subnet=>\"172.20.128.0/24\"}, driver)\n\n      expect(subject).to receive(:validate_network_configuration!).\n        with(\"vagrant_network_public_wlp4s0\", networks[1][1],\n             {\"opt\"=>\"parent=wlp4s0\", \"subnet\"=>\"192.168.1.0/24\", \"driver\"=>\"macvlan\", \"gateway\"=>\"1234\", \"ipv6\"=>false, \"ip_range\"=>\"1234\"}, driver)\n\n      expect(subject).to receive(:validate_network_configuration!).\n        with(\"vagrant_network_2a02:6b8:b010:9020:1::/80\", networks[2][1],\n            {:ipv6=>true, :subnet=>\"2a02:6b8:b010:9020:1::/80\"}, driver)\n\n      subject.call(env)\n    end\n\n    it \"raises an error if an inproper network configuration is given\" do\n      allow(machine.config.vm).to receive(:networks).and_return(invalid_network)\n      allow(driver).to receive(:host_vm?).and_return(false)\n      allow(driver).to receive(:existing_network?).and_return(false)\n\n      expect{ subject.call(env) }.to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkIPAddressRequired)\n    end\n  end\n\n  describe \"#list_interfaces\" do\n    let(:interfaces){ [\"192.168.1.2\", \"192.168.10.10\"] }\n\n    it \"returns an array of interfaces to use\" do\n      allow(Socket).to receive(:getifaddrs).\n            and_return(interfaces.map{|i| double(:socket, addr: Addrinfo.ip(i))})\n      interfaces = subject.list_interfaces\n\n      expect(subject.list_interfaces.size).to eq(2)\n    end\n\n    it \"does not include an interface with the address is nil\" do\n      allow(Socket).to receive(:getifaddrs).\n        and_return(interfaces.map{|i| double(:socket, addr: nil)})\n\n      expect(subject.list_interfaces.size).to eq(0)\n    end\n  end\n\n  describe \"#generate_create_cli_arguments\" do\n    let(:network_options) {\n            {:ip=>\"172.20.128.2\",\n             :subnet=>\"172.20.0.0/16\",\n             :driver=>\"bridge\",\n             :internal=>\"true\",\n             :alias=>\"mynetwork\",\n             :protocol=>\"tcp\",\n             :id=>\"80e017d5-388f-4a2f-a3de-f8dce8156a58\"} }\n\n    let(:false_network_options) {\n            {:ip=>\"172.20.128.2\",\n             :subnet=>\"172.20.0.0/16\",\n             :driver=>\"bridge\",\n             :internal=>\"false\",\n             :alias=>\"mynetwork\",\n             :protocol=>\"tcp\",\n             :id=>\"80e017d5-388f-4a2f-a3de-f8dce8156a58\"} }\n\n    it \"returns an array of cli arguments\" do\n      cli_args = subject.generate_create_cli_arguments(network_options)\n      expect(cli_args).to eq( [\"--ip\", \"172.20.128.2\", \"--subnet\", \"172.20.0.0/16\", \"--driver\", \"bridge\", \"--internal\", \"--alias\", \"mynetwork\", \"--protocol\", \"tcp\", \"--id\", \"80e017d5-388f-4a2f-a3de-f8dce8156a58\"])\n    end\n\n    it \"removes option if set to false\" do\n      cli_args = subject.generate_create_cli_arguments(false_network_options)\n      expect(cli_args).to eq( [\"--ip\", \"172.20.128.2\", \"--subnet\", \"172.20.0.0/16\", \"--driver\", \"bridge\", \"--alias\", \"mynetwork\", \"--protocol\", \"tcp\", \"--id\", \"80e017d5-388f-4a2f-a3de-f8dce8156a58\"])\n    end\n  end\n\n  describe \"#validate_network_name!\" do\n    let(:netname) { \"vagrant_network\" }\n\n    it \"returns true if name exists\" do\n      allow(driver).to receive(:existing_named_network?).with(netname).\n        and_return(true)\n\n      expect(subject.validate_network_name!(netname, env)).to be_truthy\n    end\n\n    it \"raises an error if name does not exist\" do\n      allow(driver).to receive(:existing_named_network?).with(netname).\n        and_return(false)\n\n      expect{subject.validate_network_name!(netname, env)}.to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkNameUndefined)\n    end\n  end\n\n  describe \"#validate_network_configuration!\" do\n    let(:netname) { \"vagrant_network_172.20.128.0/24\" }\n    let(:options) { {:ip=>\"172.20.128.2\", :subnet=>\"172.20.0.0/16\", :driver=>\"bridge\", :internal=>\"true\", :alias=>\"mynetwork\", :protocol=>\"tcp\", :id=>\"80e017d5-388f-4a2f-a3de-f8dce8156a58\", :netmask=>24} }\n    let(:network_options) { {:ipv6=>false, :subnet=>\"172.20.128.0/24\"} }\n\n    it \"returns true if all options are valid\" do\n      allow(driver).to receive(:network_containing_address).with(options[:ip]).\n                                                                         and_return(netname)\n      allow(driver).to receive(:network_containing_address).with(network_options[:subnet]).\n                                                                         and_return(netname)\n\n      expect(subject.validate_network_configuration!(netname, options, network_options, driver)).\n        to be_truthy\n    end\n\n    it \"raises an error of the address is invalid\" do\n      allow(driver).to receive(:network_containing_address).with(options[:ip]).\n                                                                         and_return(\"fakename\")\n      expect{subject.validate_network_configuration!(netname, options, network_options, driver)}.\n        to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkAddressInvalid)\n    end\n\n    it \"raises an error of the subnet is invalid\" do\n      allow(driver).to receive(:network_containing_address).with(options[:ip]).\n                                                                         and_return(netname)\n      allow(driver).to receive(:network_containing_address).with(network_options[:subnet]).\n                                                                         and_return(\"fakename\")\n\n      expect{subject.validate_network_configuration!(netname, options, network_options, driver)}.\n        to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkSubnetInvalid)\n    end\n  end\n\n  describe \"#process_private_network\" do\n    let(:options) { {:ip=>\"172.20.128.2\", :subnet=>\"172.20.0.0/16\", :driver=>\"bridge\", :internal=>\"true\", :alias=>\"mynetwork\", :protocol=>\"tcp\", :id=>\"80e017d5-388f-4a2f-a3de-f8dce8156a58\", :netmask=>24} }\n    let(:dhcp_options) { {type: \"dhcp\"} }\n    let(:bad_options) { {driver: \"bridge\"} }\n\n    it \"generates a network name and config for a dhcp private network\" do\n      network_name, network_options = subject.process_private_network(dhcp_options, {}, env)\n\n      expect(network_name).to eq(\"vagrant_network\")\n      expect(network_options).to eq({})\n    end\n\n    it \"generates a network name and options for a static ip\" do\n      allow(driver).to receive(:network_defined?).and_return(nil)\n      network_name, network_options = subject.process_private_network(options, {}, env)\n      expect(network_name).to eq(\"vagrant_network_172.20.0.0/16\")\n      expect(network_options).to eq({:ipv6=>false, :subnet=>\"172.20.0.0/16\"})\n    end\n\n    it \"raises an error if no ip address or type `dhcp` was given\" do\n      expect{subject.process_private_network(bad_options, {}, env)}.\n        to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkIPAddressRequired)\n    end\n  end\n\n  describe \"#process_public_network\" do\n    let(:options) { {:ip=>\"172.30.130.2\", :subnet=>\"172.30.0.0/16\", :driver=>\"bridge\", :id=>\"30e017d5-488f-5a2f-a3ke-k8dce8246b60\"} }\n    let(:addr) { double(\"addr\", ip: true, ip_address: \"192.168.1.139\") }\n    let(:netmask) { double(\"netmask\", ip_unpack: [\"255.255.255.0\"]) }\n    let(:ipaddr) { double(\"ipaddr\", prefix: 22, succ: \"10.1.10.2\", ipv4?: true,\n                          ipv6?: false, to_i: 4294967040, name: \"ens20u1u2\",\n                          addr: addr, netmask: netmask) }\n\n    it \"raises an error if there are no network interfaces\" do\n      expect(subject).to receive(:list_interfaces).and_return([])\n\n      expect{subject.process_public_network(options, {}, env)}.\n        to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkNoInterfaces)\n    end\n\n    it \"generates a network name and configuration\" do\n      allow(machine.ui).to receive(:ask).and_return(\"1\")\n      allow(subject).to receive(:request_public_gateway).and_return(\"1234\")\n      allow(subject).to receive(:request_public_iprange).and_return(\"1234\")\n      allow(IPAddr).to receive(:new).and_return(ipaddr)\n      allow(driver).to receive(:existing_named_network?).and_return(false)\n      allow(driver).to receive(:network_containing_address).\n        with(\"10.1.10.2\").and_return(\"vagrant_network_public\")\n\n      # mock the call to PrepareNetworks.list_interfaces so that we don't depend\n      # on the current network interfaces\n      allow(subject).to receive(:list_interfaces).\n        and_return([ipaddr])\n\n      network_name, _network_options = subject.process_public_network(options, {}, env)\n      expect(network_name).to eq(\"vagrant_network_public\")\n    end\n  end\n\n  describe \"#request_public_gateway\" do\n    let(:options) { {:ip=>\"172.30.130.2\", :subnet=>\"172.30.0.0/16\", :driver=>\"bridge\", :id=>\"30e017d5-488f-5a2f-a3ke-k8dce8246b60\"} }\n    let(:ipaddr) { double(\"ipaddr\", to_s: \"172.30.130.2\", prefix: 22, succ: \"172.30.130.3\",\n                          ipv4?: true, ipv6?: false) }\n\n    it \"requests a gateway\" do\n      allow(IPAddr).to receive(:new).and_return(ipaddr)\n      allow(ipaddr).to receive(:include?).and_return(false)\n      allow(machine.ui).to receive(:ask).and_return(\"1\")\n\n      addr = subject.request_public_gateway(options, \"bridge\", env)\n\n      expect(addr).to eq(\"172.30.130.2\")\n    end\n  end\n\n  describe \"#request_public_iprange\" do\n    let(:options) { {:ip=>\"172.30.130.2\", :subnet=>\"172.30.0.0/16\", :driver=>\"bridge\", :id=>\"30e017d5-488f-5a2f-a3ke-k8dce8246b60\"} }\n    let(:ipaddr) { double(\"ipaddr\", to_s: \"172.30.100.2\", prefix: 22, succ: \"172.30.100.3\",\n                          ipv4?: true, ipv6?: false) }\n    let(:subnet) { double(\"ipaddr\", to_s: \"172.30.130.2\", prefix: 22, succ: \"172.30.130.3\",\n                          ipv6?: false) }\n\n    let(:ipaddr_prefix) { double(\"ipaddr_prefix\", to_s: \"255.255.255.255/255.255.255.0\",\n                                 to_i: 4294967040 ) }\n\n    let(:netmask) { double(\"netmask\", ip_unpack: [\"255.255.255.0\", 0]) }\n    let(:interface) { double(\"interface\", name: \"bridge\", netmask: netmask) }\n\n    it \"requests a public ip range\" do\n      allow(IPAddr).to receive(:new).with(options[:subnet]).and_return(subnet)\n      allow(IPAddr).to receive(:new).with(\"172.30.130.2\").and_return(ipaddr)\n      allow(IPAddr).to receive(:new).with(\"255.255.255.255/255.255.255.0\").and_return(ipaddr_prefix)\n      allow(subnet).to receive(:include?).and_return(true)\n      allow(machine.ui).to receive(:ask).and_return(options[:ip])\n\n      addr = subject.request_public_iprange(options, interface, env)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/docker/command/exec_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../../../../../plugins/providers/docker/command/exec\"\n\ndescribe VagrantPlugins::DockerProvider::Command::Exec do\n  include_context \"unit\"\n  include_context \"command plugin helpers\"\n\n  let(:env) { {\n    action_runner: action_runner,\n    machine: machine,\n    ui: Vagrant::UI::Silent.new,\n  } }\n\n  subject { described_class.new(app, env) }\n  let(:argv) { [] }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    isolated_environment.tap do |env|\n      env.vagrantfile(\"\")\n    end\n  end\n\n  let(:iso_vagrant_env) { iso_env.create_vagrant_env }\n\n  let(:action_runner) { double(\"action_runner\") }\n  let(:box) do\n    box_dir = iso_env.box3(\"foo\", \"1.0\", :virtualbox)\n    Vagrant::Box.new(\"foo\", :virtualbox, \"1.0\", box_dir)\n  end\n  let(:machine) { iso_vagrant_env.machine(iso_vagrant_env.machine_names[0], :dummy) }\n\n  subject { described_class.new(argv, env) }\n\n  before(:all) do\n    I18n.load_path << Vagrant.source_root.join(\"templates/locales/providers_docker.yml\")\n    I18n.reload!\n  end\n\n  before do\n    allow(Vagrant.plugin(\"2\").manager).to receive(:commands).and_return({})\n  end\n\n  describe \"#exec_command\" do\n    describe \"with -t option\" do\n      let(:command) { [\"/bin/bash\"] }\n      let(:options) { {pty: \"true\"} }\n\n      it \"calls Safe Exec\" do\n        allow(Kernel).to receive(:exec).and_return(true)\n        expect(Vagrant::Util::SafeExec).to receive(:exec).with(\"docker\", \"exec\", \"-t\", anything, \"/bin/bash\")\n        subject.exec_command(machine, command, options)\n      end\n    end\n    describe \"with options\" do\n      let(:command) { [\"ls\"] }\n      let(:options) { {etc: \"something\"} }\n\n      it \"passes the options successfully\" do\n        driver = instance_double(\"Driver\")\n        expect(driver).to receive(:execute).with(\"docker\", \"exec\", nil, \"ls\", {etc: \"something\"})\n        allow(machine.provider).to receive(:driver) { driver }\n        subject.exec_command(machine, command, options)\n      end\n    end\n    describe \"without a command\" do\n      let(:argv) { [] }\n\n      it \"raises an error\" do\n        expect {\n          subject.execute\n        }.to raise_error(VagrantPlugins::DockerProvider::Errors::ExecCommandRequired)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/docker/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/platform\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/docker/config\")\n\ndescribe VagrantPlugins::DockerProvider::Config do\n  include_context \"unit\"\n\n  let(:machine) { double(\"machine\") }\n\n  let(:build_dir) do\n    Dir.mktmpdir(\"vagrant-test-docker-provider-build-dir\").tap do |dir|\n      File.open(File.join(dir, \"Dockerfile\"), \"wb+\") do |f|\n        f.write(\"Hello\")\n      end\n    end\n  end\n\n  after do\n    FileUtils.rm_rf(build_dir)\n  end\n\n  def assert_invalid\n    errors = subject.validate(machine)\n    if !errors.values.any? { |v| !v.empty? }\n      raise \"No errors: #{errors.inspect}\"\n    end\n  end\n\n  def assert_valid\n    errors = subject.validate(machine)\n    if !errors.values.all? { |v| v.empty? }\n      raise \"Errors: #{errors.inspect}\"\n    end\n  end\n\n  def valid_defaults\n    subject.image = \"foo\"\n  end\n\n  describe \"defaults\" do\n    before { subject.finalize! }\n\n    its(:build_dir) { should be_nil }\n    its(:git_repo) { should be_nil }\n    its(:expose) { should eq([]) }\n    its(:cmd) { should eq([]) }\n    its(:env) { should eq({}) }\n    its(:force_host_vm) { should be(false) }\n    its(:host_vm_build_dir_options) { should be_nil }\n    its(:image) { should be_nil }\n    its(:name) { should be_nil }\n    its(:privileged) { should be(false) }\n    its(:stop_timeout) { should eq(1) }\n    its(:vagrant_machine) { should be_nil }\n    its(:vagrant_vagrantfile) { should be_nil }\n\n    its(:auth_server) { should be_nil }\n    its(:email) { should eq(\"\") }\n    its(:username) { should eq(\"\") }\n    its(:password) { should eq(\"\") }\n  end\n\n  before do\n    # By default lets be Linux for validations\n    allow(Vagrant::Util::Platform).to receive(:linux).and_return(true)\n    allow(Vagrant::Util::Platform).to receive(:linux?).and_return(true)\n  end\n\n  describe \"should be invalid if any two or more of build dir, git repo and image are set\" do\n    it \"build dir and image\" do\n      subject.build_dir = build_dir\n      subject.image = \"foo\"\n      subject.git_repo = nil\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"build dir and git repo\" do\n      subject.build_dir = build_dir\n      subject.git_repo = \"http://example.com/something.git#branch:dir\"\n      subject.image = nil\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"git repo dir and image\" do\n      subject.build_dir = nil\n      subject.git_repo = \"http://example.com/something.git#branch:dir\"\n      subject.image = \"foo\"\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"build dir, git repo and image\" do\n      subject.build_dir = build_dir\n      subject.git_repo = \"http://example.com/something.git#branch:dir\"\n      subject.image = \"foo\"\n      subject.finalize!\n      assert_invalid\n    end\n  end\n\n  describe \"#build_dir\" do\n    it \"should be valid if not set with image or git repo\" do\n      subject.build_dir = nil\n      subject.git_repo = nil\n      subject.image = \"foo\"\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"should be valid with a valid directory\" do\n      subject.build_dir = build_dir\n      subject.finalize!\n      assert_valid\n    end\n  end\n\n  describe \"#git_repo\" do\n    it \"should be valid if not set with image or build dir\" do\n      subject.build_dir = nil\n      subject.git_repo = \"http://example.com/something.git#branch:dir\"\n      subject.image = nil\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"should be valid with a http git url\" do\n      subject.git_repo = \"http://example.com/something.git#branch:dir\"\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"should be valid with a git@ url\" do\n      subject.git_repo = \"git@example.com:somebody/something\"\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"should be valid with a git:// url\" do\n      subject.git_repo = \"git://example.com/something\"\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"should be valid with a short url beginning with github.com url\" do\n      subject.git_repo = \"github.com/somebody/something\"\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"should be invalid with an non-git url\" do\n      subject.git_repo = \"http://foo.bar.com\"\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"should be invalid with an non url\" do\n      subject.git_repo = \"http||://foo.bar.com sdfs\"\n      subject.finalize!\n      assert_invalid\n    end\n  end\n\n  describe \"#compose\" do\n    before do\n      valid_defaults\n    end\n\n    it \"should be valid when enabled\" do\n      subject.compose = true\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"should be invalid when force_host_vm is enabled\" do\n      subject.compose = true\n      subject.force_host_vm = true\n      subject.finalize!\n      assert_invalid\n    end\n  end\n\n  describe \"#create_args\" do\n    before do\n      valid_defaults\n    end\n\n    it \"is invalid if it isn't an array\" do\n      subject.create_args = \"foo\"\n      subject.finalize!\n      assert_invalid\n    end\n  end\n\n  describe \"#expose\" do\n    before do\n      valid_defaults\n    end\n\n    it \"uniqs the ports\" do\n      subject.expose = [1, 1, 4, 5]\n      subject.finalize!\n      assert_valid\n\n      expect(subject.expose).to eq([1, 4, 5])\n    end\n  end\n\n  describe \"#image\" do\n    it \"should be valid if set\" do\n      subject.image = \"foo\"\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"should be invalid if not set\" do\n      subject.image = nil\n      subject.finalize!\n      assert_invalid\n    end\n  end\n\n  describe \"#link\" do\n    before do\n      valid_defaults\n    end\n\n    it \"should be valid with good links\" do\n      subject.link \"foo:bar\"\n      subject.link \"db:blah\"\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"should be invalid if not name:alias\" do\n      subject.link \"foo\"\n      subject.finalize!\n      assert_invalid\n    end\n\n    it \"should be invalid if too many colons\" do\n      subject.link \"foo:bar:baz\"\n      subject.finalize!\n      assert_invalid\n    end\n  end\n\n  describe \"#merge\" do\n    let(:one) { described_class.new }\n    let(:two) { described_class.new }\n\n    subject { one.merge(two) }\n\n    context \"#build_dir, #git_repo and #image\" do\n      it \"overrides image if build_dir is set previously\" do\n        one.build_dir = \"foo\"\n        two.image = \"bar\"\n\n        expect(subject.build_dir).to be_nil\n        expect(subject.image).to eq(\"bar\")\n      end\n\n      it \"overrides image if git_repo is set previously\" do\n        one.git_repo = \"foo\"\n        two.image = \"bar\"\n\n        expect(subject.image).to eq(\"bar\")\n        expect(subject.git_repo).to be_nil\n      end\n\n      it \"overrides build_dir if image is set previously\" do\n        one.image = \"foo\"\n        two.build_dir = \"bar\"\n\n        expect(subject.build_dir).to eq(\"bar\")\n        expect(subject.image).to be_nil\n      end\n\n      it \"overrides build_dir if git_repo is set previously\" do\n        one.git_repo = \"foo\"\n        two.build_dir = \"bar\"\n\n        expect(subject.build_dir).to eq(\"bar\")\n        expect(subject.git_repo).to be_nil\n      end\n\n      it \"overrides git_repo if build_dir is set previously\" do\n        one.build_dir = \"foo\"\n        two.git_repo = \"bar\"\n\n        expect(subject.build_dir).to be_nil\n        expect(subject.git_repo).to eq(\"bar\")\n      end\n\n      it \"overrides git_repo if image is set previously\" do\n        one.image = \"foo\"\n        two.git_repo = \"bar\"\n\n        expect(subject.image).to be_nil\n        expect(subject.git_repo).to eq(\"bar\")\n      end\n\n      it \"preserves if both image and build_dir are set\" do\n        one.image = \"foo\"\n        two.image = \"baz\"\n        two.build_dir = \"bar\"\n\n        expect(subject.build_dir).to eq(\"bar\")\n        expect(subject.image).to eq(\"baz\")\n      end\n\n      it \"preserves if both image and git_repo are set\" do\n        one.image = \"foo\"\n        two.image = \"baz\"\n        two.git_repo = \"bar\"\n\n        expect(subject.image).to eq(\"baz\")\n        expect(subject.git_repo).to eq(\"bar\")\n      end\n\n      it \"preserves if both build_dir and git_repo are set\" do\n        one.build_dir = \"foo\"\n        two.build_dir = \"baz\"\n        two.git_repo = \"bar\"\n\n        expect(subject.build_dir).to eq(\"baz\")\n        expect(subject.git_repo).to eq(\"bar\")\n      end\n    end\n\n    context \"env vars\" do\n      it \"should merge the values\" do\n        one.env[\"foo\"] = \"bar\"\n        two.env[\"bar\"] = \"baz\"\n\n        expect(subject.env).to eq({\n          \"foo\" => \"bar\",\n          \"bar\" => \"baz\",\n        })\n      end\n    end\n\n    context \"exposed ports\" do\n      it \"merges the exposed ports\" do\n        one.expose << 1234\n        two.expose = [42, 54]\n\n        expect(subject.expose).to eq([\n          1234, 42, 54])\n      end\n    end\n\n    context \"links\" do\n      it \"should merge the links\" do\n        one.link \"foo\"\n        two.link \"bar\"\n\n        expect(subject._links).to eq([\n          \"foo\", \"bar\"])\n      end\n    end\n  end\n\n  describe \"#vagrant_machine\" do\n    before { valid_defaults }\n\n    it \"should convert to a symbol\" do\n      subject.vagrant_machine = \"foo\"\n      subject.finalize!\n      assert_valid\n      expect(subject.vagrant_machine).to eq(:foo)\n    end\n  end\n\n  describe \"#vagrant_vagrantfile\" do\n    before { valid_defaults }\n\n    it \"should be valid if set to a file\" do\n      subject.vagrant_vagrantfile = temporary_file.to_s\n      subject.finalize!\n      assert_valid\n    end\n\n    it \"should not be valid if set to a non-existent place\" do\n      subject.vagrant_vagrantfile = \"/i/shouldnt/exist\"\n      subject.finalize!\n      assert_invalid\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/docker/driver_compose_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"yaml\"\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"lib/vagrant/util/deep_merge\")\nrequire Vagrant.source_root.join(\"plugins/providers/docker/driver\")\n\ndescribe VagrantPlugins::DockerProvider::Driver::Compose do\n  let(:cmd_executed) { @cmd }\n  let(:execute_result) {\n    double(\"execute_result\",\n      exit_code: exit_code,\n      stderr: stderr,\n      stdout: stdout\n    )\n  }\n  let(:exit_code) { 0 }\n  let(:stderr) { \"\" }\n  let(:stdout) { \"\" }\n\n  let(:cid)          { 'side-1-song-10' }\n  let(:docker_yml){ double(\"docker-yml\", path: \"/tmp-file\") }\n  let(:machine){ double(\"machine\", env: env, name: :docker_1, id: :docker_id, provider_config: provider_config) }\n  let(:compose_configuration){ {} }\n  let(:provider_config) do\n    double(\"provider-config\",\n      compose: true,\n      compose_configuration: compose_configuration\n    )\n  end\n  let(:env) do\n    double(\"env\",\n      cwd: Pathname.new(\"/compose/cwd\"),\n      local_data_path: local_data_path\n    )\n  end\n  let(:composition_content){ \"--- {}\\n\" }\n  let(:composition_path) do\n    double(\"composition-path\",\n      to_s: \"docker-compose.yml\",\n      exist?: true,\n      read: composition_content,\n      delete: true\n    )\n  end\n  let(:data_directory){ double(\"data-directory\", join: composition_path) }\n  let(:local_data_path){ double(\"local-data-path\") }\n  let(:compose_execute_up){ [\"docker-compose\", \"-f\", \"docker-compose.yml\", \"-p\", \"cwd\", \"up\", \"--remove-orphans\", \"-d\", any_args] }\n  let(:compose_execute_up_regex) { /docker-compose -f docker-compose.yml -p cwd up --remove-orphans -d/ }\n\n  subject{ described_class.new(machine) }\n\n  before do\n    @cmd = []\n    allow(Vagrant::Util::Subprocess).to receive(:execute) { |*args|\n      if args.last.is_a?(Hash)\n        args = args[0, args.size - 1]\n      end\n      invalid = args.detect { |a| !a.is_a?(String) }\n      if invalid\n        raise TypeError,\n          \"Vagrant::Util::Subprocess#execute only accepts signle option Hash and String arguments, received `#{invalid.class}'\"\n      end\n      @cmd << args.join(\" \")\n    }.and_return(execute_result)\n    allow_any_instance_of(Vagrant::Errors::VagrantError).\n      to receive(:translate_error) { |*args| args.join(\" \") }\n\n    allow(Vagrant::Util::Which).to receive(:which).and_return(\"/dev/null/docker-compose\")\n    allow(env).to receive(:lock).and_yield\n    allow(Pathname).to receive(:new).with(local_data_path).and_return(local_data_path)\n    allow(Pathname).to receive(:new).with('/host/path').and_call_original\n    allow(local_data_path).to receive(:join).and_return(data_directory)\n    allow(data_directory).to receive(:mkpath)\n    allow(FileUtils).to receive(:mv)\n    allow(Tempfile).to receive(:new).with(\"vagrant-docker-compose\").and_return(docker_yml)\n    allow(docker_yml).to receive(:write)\n    allow(docker_yml).to receive(:close)\n  end\n\n  describe '#build' do\n    it 'creates a compose config with no extra options' do\n      expect(subject).to receive(:update_composition)\n      subject.build(composition_path)\n    end\n\n    it 'creates a compose config when given an array for build-arg' do\n      expect(subject).to receive(:update_composition)\n      subject.build(composition_path, extra_args: [\"foo\", \"bar\"])\n    end\n\n    it 'creates a compose config when given a hash for build-arg' do\n      expect(subject).to receive(:update_composition)\n      subject.build(composition_path, extra_args: {\"foo\"=>\"bar\"})\n    end\n  end\n\n  describe '#create' do\n    let(:params) { {\n      image:      'jimi/hendrix:electric-ladyland',\n      cmd:        ['play', 'voodoo-chile'],\n      ports:      '8080:80',\n      volumes:    '/host/path:guest/path',\n      detach:     true,\n      links:      [[:janis, 'joplin'], [:janis, 'janis']],\n      env:        {key: 'value'},\n      name:       cid,\n      hostname:   'jimi-hendrix',\n      privileged: true\n    } }\n\n    after {\n      subject.create(params)\n      expect(cmd_executed.first).to match(compose_execute_up_regex)\n    }\n\n    it 'sets container name' do\n      expect(docker_yml).to receive(:write).with(/#{machine.name}/)\n    end\n\n    it 'forwards ports' do\n      expect(docker_yml).to receive(:write).with(/#{params[:ports]}/)\n    end\n\n    it 'shares folders' do\n      expect(docker_yml).to receive(:write).with(/#{params[:volumes]}/)\n    end\n\n    context 'when links are provided as strings' do\n      before{ params[:links] = [\"linkl1:linkr1\", \"linkl2:linkr2\"] }\n\n      it 'links containers' do\n        params[:links].flatten.map{|l| l.split(':')}.each do |link|\n          expect(docker_yml).to receive(:write).with(/#{link}/)\n        end\n        subject.create(params)\n      end\n    end\n\n    context 'with relative path in share folders' do\n      before do\n        params[:volumes] = './path:guest/path'\n        allow(Pathname).to receive(:new).with('./path').and_call_original\n        allow(Pathname).to receive(:new).with('/compose/cwd/path').and_call_original\n      end\n\n      it 'should expand the relative host directory' do\n        expect(docker_yml).to receive(:write).with(%r{/compose/cwd/path})\n      end\n    end\n\n    context 'with a volumes key in use for mounting' do\n      let(:compose_config) { {\"volumes\"=>{\"my_volume_key\"=>\"data\"}} }\n\n      before do\n        params[:volumes] = 'my_volume_key:my/guest/path'\n        allow(Pathname).to receive(:new).with('./path').and_call_original\n        allow(Pathname).to receive(:new).with('my_volume_key').and_call_original\n        allow(Pathname).to receive(:new).with('/compose/cwd/my_volume_key').and_call_original\n        allow(subject).to receive(:get_composition).and_return(compose_config)\n      end\n\n      it 'should not expand the relative host directory' do\n        expect(docker_yml).to receive(:write).with(%r{my_volume_key})\n      end\n    end\n\n    it 'links containers' do\n      params[:links].each do |link|\n        expect(docker_yml).to receive(:write).with(/#{link}/)\n      end\n      subject.create(params)\n    end\n\n    it 'sets environmental variables' do\n      expect(docker_yml).to receive(:write).with(/key.*value/)\n    end\n\n    it 'is able to run a privileged container' do\n      expect(docker_yml).to receive(:write).with(/privileged/)\n    end\n\n    it 'sets the hostname if specified' do\n      expect(docker_yml).to receive(:write).with(/#{params[:hostname]}/)\n    end\n\n    it 'executes the provided command' do\n      expect(docker_yml).to receive(:write).with(/#{params[:image]}/)\n    end\n  end\n\n  describe '#created?' do\n    let(:result) { subject.created?(cid) }\n\n    it 'performs the check on all containers list' do\n      subject.created?(cid)\n      expect(cmd_executed.first).to match(/docker ps \\-a \\-q/)\n    end\n\n    context 'when container exists' do\n      let(:stdout) { \"foo\\n#{cid}\\nbar\" }\n      it { expect(result).to be_truthy }\n    end\n\n    context 'when container does not exist' do\n      let(:stdout) { \"foo\\n#{cid}extra\\nbar\" }\n      it { expect(result).to be_falsey }\n    end\n  end\n\n  describe '#pull' do\n    it 'should pull images' do\n      subject.pull('foo')\n      expect(cmd_executed.first).to eq(\"docker pull foo\")\n    end\n  end\n\n  describe '#running?' do\n    let(:result) { subject.running?(cid) }\n\n    it 'performs the check on the running containers list' do\n      subject.running?(cid)\n      expect(cmd_executed.first).to match(/docker ps \\-q/)\n      expect(cmd_executed.first).to_not include('-a')\n    end\n\n    context 'when container exists' do\n      let(:stdout) { \"foo\\n#{cid}\\nbar\" }\n      it { expect(result).to be_truthy }\n    end\n\n    context 'when container does not exist' do\n      let(:stdout) { \"foo\\n#{cid}extra\\nbar\" }\n      it { expect(result).to be_falsey }\n    end\n  end\n\n  describe '#privileged?' do\n    it 'identifies privileged containers' do\n      allow(subject).to receive(:inspect_container)\n        .and_return({'HostConfig' => {\"Privileged\" => true}})\n      expect(subject).to be_privileged(cid)\n    end\n\n    it 'identifies unprivileged containers' do\n      allow(subject).to receive(:inspect_container)\n        .and_return({'HostConfig' => {\"Privileged\" => false}})\n      expect(subject).to_not be_privileged(cid)\n    end\n  end\n\n  describe '#start' do\n    context 'when container is running' do\n      before { allow(subject).to receive(:running?).and_return(true) }\n\n      it 'does not start the container' do\n        subject.start(cid)\n        expect(cmd_executed).to be_empty\n      end\n    end\n\n    context 'when container is not running' do\n      before { allow(subject).to receive(:running?).and_return(false) }\n\n      it 'starts the container' do\n        subject.start(cid)\n        expect(cmd_executed.first).to eq(\"docker start #{cid}\")\n      end\n    end\n  end\n\n  describe '#stop' do\n    context 'when container is running' do\n      before { allow(subject).to receive(:running?).and_return(true) }\n\n      it 'stops the container' do\n        subject.stop(cid, 1)\n        expect(cmd_executed.first).to eq(\"docker stop -t 1 #{cid}\")\n      end\n\n      it \"stops the container with the set timeout\" do\n        subject.stop(cid, 5)\n        expect(cmd_executed.first).to eq(\"docker stop -t 5 #{cid}\")\n      end\n    end\n\n    context 'when container is not running' do\n      before { allow(subject).to receive(:running?).and_return(false) }\n\n      it 'does not stop container' do\n        expect(subject).not_to receive(:execute).with('docker', 'stop', '-t', '1', cid)\n        subject.stop(cid, 1)\n        expect(cmd_executed).to be_empty\n      end\n    end\n  end\n\n  describe '#rm' do\n    context 'when container has been created' do\n      before { allow(subject).to receive(:created?).and_return(true) }\n\n      it 'removes the container' do\n        subject.rm(cid)\n        expect(cmd_executed.first).to match(/docker-compose -f docker-compose.yml -p cwd rm -f docker_1/)\n      end\n    end\n\n    context 'when container has not been created' do\n      before { allow(subject).to receive(:created?).and_return(false) }\n\n      it 'does not attempt to remove the container' do\n        subject.rm(cid)\n        expect(cmd_executed).to be_empty\n      end\n    end\n  end\n\n  describe '#inspect_container' do\n    let(:stdout) { '[{\"json\": \"value\"}]' }\n\n    it 'inspects the container' do\n      subject.inspect_container(cid)\n      expect(cmd_executed.first).to eq(\"docker inspect #{cid}\")\n    end\n\n    it 'parses the json output' do\n      expect(subject.inspect_container(cid)).to eq('json' => 'value')\n    end\n  end\n\n  describe '#all_containers' do\n    let(:stdout) { \"container1\\ncontainer2\" }\n\n    it 'returns an array of all known containers' do\n      expect(subject.all_containers).to eq(['container1', 'container2'])\n      expect(cmd_executed.first).to eq(\"docker ps -a -q --no-trunc\")\n    end\n  end\n\n  describe '#docker_bridge_ip' do\n    let(:stdout) { \" inet 123.456.789.012/16 \" }\n\n    it 'returns the bridge ip' do\n      expect(subject.docker_bridge_ip).to eq('123.456.789.012')\n      expect(cmd_executed.first).to eq(\"docker network inspect bridge\")\n      expect(cmd_executed.last).to eq(\"ip -4 addr show scope global docker0\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/docker/driver_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/docker/driver\")\n\ndescribe VagrantPlugins::DockerProvider::Driver do\n  let(:cmd_executed) { @cmd }\n  let(:cid)          { 'side-1-song-10' }\n  let(:execute_result) {\n    double(\"execute_result\",\n      exit_code: exit_code,\n      stderr: stderr,\n      stdout: stdout\n    )\n  }\n  let(:exit_code) { 0 }\n  let(:stderr) { \"\" }\n  let(:stdout) { \"\" }\n\n  before do\n    allow(Vagrant::Util::Subprocess).to receive(:execute) { |*args|\n      if args.last.is_a?(Hash)\n        args = args[0, args.size - 1]\n      end\n      invalid = args.detect { |a| !a.is_a?(String) }\n      if invalid\n        raise TypeError,\n          \"Vagrant::Util::Subprocess#execute only accepts signle option Hash and String arguments, received `#{invalid.class}'\"\n      end\n      @cmd = args.join(\" \")\n    }.and_return(execute_result)\n    allow_any_instance_of(Vagrant::Errors::VagrantError).\n      to receive(:translate_error) { |*args| args.join(\" \") }\n  end\n\n  let(:docker_network_struct) {\n[\n    {\n        \"Name\": \"bridge\",\n        \"Id\": \"ae74f6cc18bbcde86326937797070b814cc71bfc4a6d8e3e8cf3b2cc5c7f4a7d\",\n        \"Created\": \"2019-03-20T14:10:06.313314662-07:00\",\n        \"Scope\": \"local\",\n        \"Driver\": \"bridge\",\n        \"EnableIPv6\": false,\n        \"IPAM\": {\n            \"Driver\": \"default\",\n            \"Options\": nil,\n            \"Config\": [\n                {\n                    \"Subnet\": \"172.17.0.0/16\",\n                    \"Gateway\": \"172.17.0.1\"\n                }\n            ]\n        },\n        \"Internal\": false,\n        \"Attachable\": false,\n        \"Ingress\": false,\n        \"ConfigFrom\": {\n            \"Network\": \"\"\n        },\n        \"ConfigOnly\": false,\n        \"Containers\": {\n            \"a1ee9b12bcea8268495b1f43e8d1285df1925b7174a695075f6140adb9415d87\": {\n                \"Name\": \"vagrant-sandbox_docker-1_1553116237\",\n                \"EndpointID\": \"fc1b0ed6e4f700cf88bb26a98a0722655191542e90df3e3492461f4d1f3c0cae\",\n                \"MacAddress\": \"02:42:ac:11:00:02\",\n                \"IPv4Address\": \"172.17.0.2/16\",\n                \"IPv6Address\": \"\"\n            }\n        },\n        \"Options\": {\n            \"com.docker.network.bridge.default_bridge\": \"true\",\n            \"com.docker.network.bridge.enable_icc\": \"true\",\n            \"com.docker.network.bridge.enable_ip_masquerade\": \"true\",\n            \"com.docker.network.bridge.host_binding_ipv4\": \"0.0.0.0\",\n            \"com.docker.network.bridge.name\": \"docker0\",\n            \"com.docker.network.driver.mtu\": \"1500\"\n        },\n        \"Labels\": {}\n    },\n    {\n        \"Name\": \"host\",\n        \"Id\": \"2a2845e77550e33bf3e97bda8b71477ac7d3ccf78bc9102585fdb6056fb84cbf\",\n        \"Created\": \"2018-09-28T10:54:08.633543196-07:00\",\n        \"Scope\": \"local\",\n        \"Driver\": \"host\",\n        \"EnableIPv6\": false,\n        \"IPAM\": {\n            \"Driver\": \"default\",\n            \"Options\": nil,\n            \"Config\": []\n        },\n        \"Internal\": false,\n        \"Attachable\": false,\n        \"Ingress\": false,\n        \"ConfigFrom\": {\n            \"Network\": \"\"\n        },\n        \"ConfigOnly\": false,\n        \"Containers\": {},\n        \"Options\": {},\n        \"Labels\": {}\n    },\n    {\n        \"Name\": \"vagrant_network\",\n        \"Id\": \"93385d4fd3cf7083a36e62fa72a0ad0a21203d0ddf48409c32b550cd8462b3ba\",\n        \"Created\": \"2019-03-20T14:10:36.828235585-07:00\",\n        \"Scope\": \"local\",\n        \"Driver\": \"bridge\",\n        \"EnableIPv6\": false,\n        \"IPAM\": {\n            \"Driver\": \"default\",\n            \"Options\": {},\n            \"Config\": [\n                {\n                    \"Subnet\": \"172.18.0.0/16\",\n                    \"Gateway\": \"172.18.0.1\"\n                }\n            ]\n        },\n        \"Internal\": false,\n        \"Attachable\": false,\n        \"Ingress\": false,\n        \"ConfigFrom\": {\n            \"Network\": \"\"\n        },\n        \"ConfigOnly\": false,\n        \"Containers\": {\n            \"a1ee9b12bcea8268495b1f43e8d1285df1925b7174a695075f6140adb9415d87\": {\n                \"Name\": \"vagrant-sandbox_docker-1_1553116237\",\n                \"EndpointID\": \"9502cd9d37ae6815e3ffeb0bc2de9b84f79e7223e8a1f8f4ccc79459e96c7914\",\n                \"MacAddress\": \"02:42:ac:12:00:02\",\n                \"IPv4Address\": \"172.18.0.2/16\",\n                \"IPv6Address\": \"\"\n            }\n        },\n        \"Options\": {},\n        \"Labels\": {}\n    },\n    {\n        \"Name\": \"vagrant_network_172.20.0.0/16\",\n        \"Id\": \"649f0ab3ef0eef6f2a025c0d0398bd7b9b4d05ec88b0d7bd573b44153d903cfb\",\n        \"Created\": \"2019-03-20T14:10:37.088885647-07:00\",\n        \"Scope\": \"local\",\n        \"Driver\": \"bridge\",\n        \"EnableIPv6\": false,\n        \"IPAM\": {\n            \"Driver\": \"default\",\n            \"Options\": {},\n            \"Config\": [\n                {\n                    \"Subnet\": \"172.20.0.0/16\"\n                }\n            ]\n        },\n        \"Internal\": false,\n        \"Attachable\": false,\n        \"Ingress\": false,\n        \"ConfigFrom\": {\n            \"Network\": \"\"\n        },\n        \"ConfigOnly\": false,\n        \"Containers\": {\n            \"a1ee9b12bcea8268495b1f43e8d1285df1925b7174a695075f6140adb9415d87\": {\n                \"Name\": \"vagrant-sandbox_docker-1_1553116237\",\n                \"EndpointID\": \"e19156f8018f283468227fa97c145f4ea0eaba652fb7e977a0c759b1c3ec168a\",\n                \"MacAddress\": \"02:42:ac:14:80:02\",\n                \"IPv4Address\": \"172.20.0.2/16\",\n                \"IPv6Address\": \"\"\n            }\n        },\n        \"Options\": {},\n        \"Labels\": {}\n    }\n].to_json }\n\n\n  describe '#build' do\n    let(:stdout) { \"Successfully built other_package\\nSuccessfully built 1a2b3c4d\" }\n    let(:cid) { \"1a2b3c4d\" }\n\n    it \"builds a container with standard docker\" do\n      container_id = subject.build(\"/tmp/fakedir\")\n\n      expect(container_id).to eq(cid)\n    end\n\n    context \"using buildkit\" do\n      let(:stdout) { \"writing image sha256:1a2b3c4d 0.0s done\" }\n\n      it \"builds a container with buildkit docker\" do\n        container_id = subject.build(\"/tmp/fakedir\")\n\n        expect(container_id).to eq(cid)\n      end\n    end\n\n    context \"using buildkit with old output\" do\n      let(:stdout) { \"writing image sha256:1a2b3c4d done\" }\n\n      it \"builds a container with buildkit docker (old output)\" do\n        container_id = subject.build(\"/tmp/fakedir\")\n\n        expect(container_id).to eq(cid)\n      end\n    end\n\n    context \"using buildkit with containerd backend output\" do\n      let(:stdout) { \"exporting manifest list sha256:1a2b3c4d done\" }\n\n      it \"builds a container with buildkit docker (containerd)\" do\n        container_id = subject.build(\"/tmp/fakedir\")\n\n        expect(container_id).to eq(cid)\n      end\n    end\n\n    context \"using podman emulating docker CLI\" do\n      let(:stdout) { \"1a2b3c4d5e6f7g8h9i10j11k12l13m14n16o17p18q19r20s21t22u23v24w25x2\" }\n\n      it \"builds a container with podman emulating docker CLI\" do\n        allow(subject).to receive(:podman?).and_return(true)\n\n        container_id = subject.build(\"/tmp/fakedir\")\n\n        expect(container_id).to eq(cid)\n      end\n\n      context \"if output contains extra trailing information\" do\n        let(:stdout) { \"1a2b3c4d5e6f7g8h9i10j11k12l13m14n16o17p18q19r20s21t22u23v24w25x2\\nextra content\\n\" }\n        it \"builds a container with podman emulating docker CLI\" do\n          allow(subject).to receive(:podman?).and_return(true)\n\n          container_id = subject.build(\"/tmp/fakedir\")\n\n          expect(container_id).to eq(cid)\n        end\n      end\n    end\n  end\n\n  describe '#podman?' do\n    context \"when docker is used\" do\n      let(:stdout) { \"Docker version 1.8.1, build d12ea79\" }\n\n      it 'returns false' do\n        expect(subject.podman?).to be false\n      end\n    end\n    context \"when podman is used\" do\n      let(:stdout) { \"podman version 1.7.1-dev\" }\n\n      it 'returns true' do\n        expect(subject.podman?).to be true\n      end\n    end\n  end\n\n  describe '#create' do\n    let(:params) { {\n      image:      'jimi/hendrix:electric-ladyland',\n      cmd:        ['play', 'voodoo-chile'],\n      ports:      '8080:80',\n      volumes:    '/host/path:guest/path',\n      detach:     true,\n      links:      [[:janis, 'joplin'], [:janis, 'janis']],\n      env:        {key: 'value'},\n      name:       cid,\n      hostname:   'jimi-hendrix',\n      privileged: true\n    } }\n\n    before { subject.create(params) }\n\n    it 'runs a detached docker image' do\n      expect(cmd_executed).to match(/^docker run .+ -d .+ #{Regexp.escape params[:image]}/)\n    end\n\n    it 'sets container name' do\n      expect(cmd_executed).to match(/--name #{Regexp.escape params[:name]}/)\n    end\n\n    it 'forwards ports' do\n      expect(cmd_executed).to match(/-p #{params[:ports]} .+ #{Regexp.escape params[:image]}/)\n    end\n\n    it 'shares folders' do\n      expect(cmd_executed).to match(/-v #{params[:volumes]} .+ #{Regexp.escape params[:image]}/)\n    end\n\n    it 'links containers' do\n      params[:links].each do |link|\n        expect(cmd_executed).to match(/--link #{link.join(':')} .+ #{Regexp.escape params[:image]}/)\n      end\n    end\n\n    it 'sets environmental variables' do\n      expect(cmd_executed).to match(/-e key=value .+ #{Regexp.escape params[:image]}/)\n    end\n\n    it 'is able to run a privileged container' do\n      expect(cmd_executed).to match(/--privileged .+ #{Regexp.escape params[:image]}/)\n    end\n\n    it 'sets the hostname if specified' do\n      expect(cmd_executed).to match(/-h #{params[:hostname]} #{Regexp.escape params[:image]}/)\n    end\n\n    it 'executes the provided command' do\n      expect(cmd_executed).to match(/#{Regexp.escape params[:image]} #{Regexp.escape params[:cmd].join(' ')}/)\n    end\n  end\n\n  describe '#create windows' do\n    let(:params) { {\n      image:      'jimi/hendrix:eletric-ladyland',\n      cmd:        ['play', 'voodoo-chile'],\n      ports:      '8080:80',\n      volumes:    'C:/Users/BobDylan/AllAlong:/The/Watchtower',\n      detach:     true,\n      links:      [[:janis, 'joplin'], [:janis, 'janis']],\n      env:        {key: 'value'},\n      name:       cid,\n      hostname:   'jimi-hendrix',\n      privileged: true\n    } }\n\n    let(:translated_path) { \"//c/Users/BobDylan/AllAlong:/The/Watchtower\" }\n\n    before do\n      allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n      subject.create(params)\n    end\n\n    it 'shares folders' do\n      expect(cmd_executed).to match(/-v #{translated_path} .+ #{Regexp.escape params[:image]}/)\n    end\n  end\n\n\n  describe '#created?' do\n    let(:result) { subject.created?(cid) }\n\n    it 'performs the check on all containers list' do\n      subject.created?(cid)\n      expect(cmd_executed).to match(/docker ps \\-a \\-q/)\n    end\n\n    context 'when container exists' do\n      let(:stdout) { \"foo\\n#{cid}\\nbar\" }\n\n      it { expect(result).to be_truthy }\n    end\n\n    context 'when container does not exist' do\n      let(:stdout) { \"foo\\n#{cid}extra\\nbar\" }\n\n      it { expect(result).to be_falsey }\n    end\n  end\n\n  describe '#image?' do\n    let(:result) { subject.image?(cid) }\n\n    it 'performs the check on all images list' do\n      subject.image?(cid)\n      expect(cmd_executed).to match(/docker images \\-q \\--no-trunc/)\n    end\n\n    context 'when image id exists' do\n      let(:stdout) { \"foo\\n#{cid}\\nbar\" }\n\n      it { expect(result).to be_truthy }\n    end\n\n    context 'when sha265 image id exists' do\n      let(:stdout) { \"sha256:foo\\nsha256:#{cid}\\nsha256:bar\" }\n\n      it { expect(result).to be_truthy }\n    end\n\n    context 'when image does not exist' do\n      let(:stdout) { \"foo\\n#{cid}extra\\nbar\" }\n\n      it { expect(result).to be_falsey }\n    end\n  end\n\n\n  describe '#pull' do\n    it 'should pull images' do\n      subject.pull('foo')\n      expect(cmd_executed).to match(/docker pull foo/)\n    end\n  end\n\n  describe '#read_used_ports' do\n    let(:all_containers) { [\"container1\\ncontainer2\"] }\n    let(:container_info) { {\"Name\"=>\"/container\", \"State\"=>{\"Running\"=>true}, \"HostConfig\"=>{\"PortBindings\"=>{}}} }\n    let(:empty_used_ports) { {} }\n\n    context \"with existing port forwards\" do\n      let(:container_info) { {\"Name\"=>\"/container\",\"State\"=>{\"Running\"=>true}, \"HostConfig\"=>{\"PortBindings\"=>{\"22/tcp\"=>[{\"HostIp\"=>\"127.0.0.1\",\"HostPort\"=>\"2222\"}] }}} }\n      let(:used_ports_set) { {\"2222\"=>Set[\"127.0.0.1\"]} }\n\n      context \"with active containers\" do\n        it 'should read all port bindings and return a hash of sets' do\n          allow(subject).to receive(:all_containers).and_return(all_containers)\n          allow(subject).to receive(:inspect_container).and_return(container_info)\n\n          used_ports = subject.read_used_ports\n          expect(used_ports).to eq(used_ports_set)\n        end\n      end\n\n      context \"with inactive containers\" do\n        let(:container_info) { {\"Name\"=>\"/container\", \"State\"=>{\"Running\"=>false}, \"HostConfig\"=>{\"PortBindings\"=>{\"22/tcp\"=>[{\"HostIp\"=>\"127.0.0.1\",\"HostPort\"=>\"2222\"}] }}} }\n\n        it 'returns empty' do\n          allow(subject).to receive(:all_containers).and_return(all_containers)\n          allow(subject).to receive(:inspect_container).and_return(container_info)\n\n          used_ports = subject.read_used_ports\n          expect(used_ports).to eq(empty_used_ports)\n        end\n      end\n    end\n\n    it 'returns empty if no ports are already bound' do\n      allow(subject).to receive(:all_containers).and_return(all_containers)\n      allow(subject).to receive(:inspect_container).and_return(container_info)\n\n      used_ports = subject.read_used_ports\n      expect(used_ports).to eq(empty_used_ports)\n    end\n  end\n\n  describe '#running?' do\n    let(:result) { subject.running?(cid) }\n\n    it 'performs the check on the running containers list' do\n      subject.running?(cid)\n      expect(cmd_executed).to match(/docker ps \\-q/)\n      expect(cmd_executed).to_not include('-a')\n    end\n\n    context 'when container exists' do\n      let(:stdout) { \"foo\\n#{cid}\\nbar\" }\n      it { expect(result).to be_truthy }\n    end\n\n    context 'when container does not exist' do\n      let(:stdout) { \"foo\\n#{cid}extra\\nbar\" }\n      it { expect(result).to be_falsey }\n    end\n  end\n\n  describe '#privileged?' do\n    it 'identifies privileged containers' do\n      allow(subject).to receive(:inspect_container).and_return({'HostConfig' => {\"Privileged\" => true}})\n      expect(subject).to be_privileged(cid)\n    end\n\n    it 'identifies unprivileged containers' do\n      allow(subject).to receive(:inspect_container).and_return({'HostConfig' => {\"Privileged\" => false}})\n      expect(subject).to_not be_privileged(cid)\n    end\n  end\n\n  describe '#start' do\n    context 'when container is running' do\n      before { allow(subject).to receive(:running?).and_return(true) }\n\n      it 'does not start the container' do\n        subject.start(cid)\n        expect(cmd_executed).to be_nil\n      end\n    end\n\n    context 'when container is not running' do\n      before { allow(subject).to receive(:running?).and_return(false) }\n\n      it 'starts the container' do\n        subject.start(cid)\n        expect(cmd_executed).to eq(\"docker start #{cid}\")\n      end\n    end\n  end\n\n  describe '#stop' do\n    context 'when container is running' do\n      before { allow(subject).to receive(:running?).and_return(true) }\n\n      it 'stops the container' do\n        subject.stop(cid, 1)\n        expect(cmd_executed).to eq(\"docker stop -t 1 #{cid}\")\n      end\n\n      it \"stops the container with the set timeout\" do\n        subject.stop(cid, 5)\n        expect(cmd_executed).to eq(\"docker stop -t 5 #{cid}\")\n      end\n    end\n\n    context 'when container is not running' do\n      before { allow(subject).to receive(:running?).and_return(false) }\n\n      it 'does not stop container' do\n        subject.stop(cid, 1)\n        expect(cmd_executed).to be_nil\n      end\n    end\n  end\n\n  describe '#rm' do\n    context 'when container has been created' do\n      before { allow(subject).to receive(:created?).and_return(true) }\n\n      it 'removes the container' do\n        subject.rm(cid)\n        expect(cmd_executed).to eq(\"docker rm -f -v #{cid}\")\n      end\n    end\n\n    context 'when container has not been created' do\n      before { allow(subject).to receive(:created?).and_return(false) }\n\n      it 'does not attempt to remove the container' do\n        subject.rm(cid)\n        expect(cmd_executed).to be_nil\n      end\n    end\n  end\n\n  describe '#rmi' do\n    let(:id) { 'asdg21ew' }\n\n    context 'image exists' do\n      it \"removes the image\" do\n        subject.rmi(id)\n        expect(cmd_executed).to eq(\"docker rmi #{id}\")\n      end\n    end\n\n    context 'image is being used by running container' do\n      before { allow(subject).to receive(:execute).and_raise(\"image is being used by running container\") }\n\n      it 'does not remove the image' do\n        expect(subject.rmi(id)).to eq(false)\n        subject.rmi(id)\n      end\n    end\n\n    context 'image is being used by stopped container' do\n      before { allow(subject).to receive(:execute).and_raise(\"image is being used by stopped container\") }\n\n      it 'does not remove the image' do\n        expect(subject.rmi(id)).to eq(false)\n        subject.rmi(id)\n      end\n    end\n\n    context 'container is using it' do\n      before { allow(subject).to receive(:execute).and_raise(\"container is using it\") }\n\n      it 'does not remove the image' do\n        expect(subject.rmi(id)).to eq(false)\n        subject.rmi(id)\n      end\n    end\n\n    context 'image does not exist' do\n      before { allow(subject).to receive(:execute).and_raise(\"No such image\") }\n\n      it 'raises an error' do\n        expect(subject.rmi(id)).to eq(nil)\n        subject.rmi(id)\n      end\n    end\n\n    context 'image is in use by a container' do\n      before { allow(subject).to receive(:execute).and_raise(\"image is in use by a container\") }\n\n      it 'does not remove the image' do\n        expect(subject.rmi(id)).to eq(false)\n        subject.rmi(id)\n      end\n    end\n  end\n\n  describe '#inspect_container' do\n    let(:stdout) { '[{\"json\": \"value\"}]' }\n\n    it 'inspects the container' do\n      subject.inspect_container(cid)\n      expect(cmd_executed).to eq(\"docker inspect #{cid}\")\n    end\n\n    it 'parses the json output' do\n      expect(subject.inspect_container(cid)).to eq('json' => 'value')\n    end\n  end\n\n  describe '#all_containers' do\n    let(:stdout) { \"container1\\ncontainer2\" }\n\n    it 'returns an array of all known containers' do\n      expect(subject.all_containers).to eq(['container1', 'container2'])\n      expect(cmd_executed).to eq(\"docker ps -a -q --no-trunc\")\n    end\n  end\n\n  describe '#docker_bridge_ip' do\n    context 'from docker network command' do \n    let(:network_struct) {\n    [{\n        \"Name\": \"bridge\",\n        \"Id\": \"ae74f6cc18bbcde86326937797070b814cc71bfc4a6d8e3e8cf3b2cc5c7f4a7d\",\n        \"Created\": \"2019-03-20T14:10:06.313314662-07:00\",\n        \"Scope\": \"local\",\n        \"Driver\": \"bridge\",\n        \"EnableIPv6\": false,\n        \"IPAM\": {\n            \"Driver\": \"default\",\n            \"Options\": nil,\n            \"Config\": [\n                {\n                    \"Subnet\": \"172.17.0.0/16\",\n                    \"Gateway\": \"172.17.0.1\"\n                }\n            ]\n        },\n        \"Internal\": false,\n        \"Attachable\": false,\n        \"Ingress\": false,\n        \"ConfigFrom\": {\n            \"Network\": \"\"\n        },\n        \"ConfigOnly\": false,\n        \"Containers\": {\n            \"a1ee9b12bcea8268495b1f43e8d1285df1925b7174a695075f6140adb9415d87\": {\n                \"Name\": \"vagrant-sandbox_docker-1_1553116237\",\n                \"EndpointID\": \"fc1b0ed6e4f700cf88bb26a98a0722655191542e90df3e3492461f4d1f3c0cae\",\n                \"MacAddress\": \"02:42:ac:11:00:02\",\n                \"IPv4Address\": \"172.17.0.2/16\",\n                \"IPv6Address\": \"\"\n            }\n        },\n        \"Options\": {\n            \"com.docker.network.bridge.default_bridge\": \"true\",\n            \"com.docker.network.bridge.enable_icc\": \"true\",\n            \"com.docker.network.bridge.enable_ip_masquerade\": \"true\",\n            \"com.docker.network.bridge.host_binding_ipv4\": \"0.0.0.0\",\n            \"com.docker.network.bridge.name\": \"docker0\",\n            \"com.docker.network.driver.mtu\": \"1500\"\n        },\n        \"Labels\": {}\n    }].to_json\n    }\n      it \"returns the bridge gateway ip\" do\n        allow(subject).to receive(:inspect_network).and_return(JSON.load(network_struct))\n        expect(subject.docker_bridge_ip).to eq('172.17.0.1')\n      end\n    end\n\n    context 'when falling back to ip' do\n      let(:stdout) { \" inet 123.456.789.012/16 \" }\n\n      it 'returns the bridge ip' do\n        expect(subject.docker_bridge_ip).to eq('123.456.789.012')\n        expect(cmd_executed).to eq(\"ip -4 addr show scope global docker0\")\n      end\n    end\n  end\n\n  describe '#docker_connect_network' do\n    let(:opts) { [\"--ip\", \"172.20.128.2\"] }\n\n    it 'connects a network to a container' do\n      subject.connect_network(\"vagrant_network\", cid, opts)\n      expect(cmd_executed).to eq(\"docker network connect vagrant_network #{cid} --ip 172.20.128.2\")\n    end\n  end\n\n  describe '#docker_create_network' do\n    let(:opts) { [\"--subnet\", \"172.20.0.0/16\"] }\n\n    it 'creates a network' do\n      subject.create_network(\"vagrant_network\", opts)\n      expect(cmd_executed).to eq(\"docker network create vagrant_network --subnet 172.20.0.0/16\")\n    end\n  end\n\n  describe '#docker_disconnet_network' do\n    it 'disconnects a network from a container' do\n      subject.disconnect_network(\"vagrant_network\", cid)\n      expect(cmd_executed).to eq(\"docker network disconnect vagrant_network #{cid} --force\")\n    end\n  end\n\n  describe '#docker_inspect_network' do\n    it 'gets info about a network' do\n      subject.inspect_network(\"vagrant_network\")\n      expect(cmd_executed).to eq(\"docker network inspect vagrant_network\")\n    end\n  end\n\n  describe '#docker_list_network' do\n    it 'lists docker networks' do\n      subject.list_network()\n      expect(cmd_executed).to eq(\"docker network ls\")\n    end\n  end\n\n  describe '#docker_rm_network' do\n    it 'deletes a docker network' do\n      subject.rm_network(\"vagrant_network\")\n      expect(cmd_executed).to eq(\"docker network rm vagrant_network\")\n    end\n  end\n\n  describe '#network_defined?' do\n    let(:subnet_string) { \"172.20.0.0/16\" }\n    let(:network_names) { [\"vagrant_network_172.20.0.0/16\", \"bridge\", \"null\" ] }\n\n    before do\n      allow(subject).to receive(:list_network_names).and_return(network_names)\n      allow(subject).to receive(:inspect_network).and_return(JSON.load(docker_network_struct))\n    end\n\n    it \"returns network name if defined\" do\n      network_name = subject.network_defined?(subnet_string)\n      expect(network_name).to eq(\"vagrant_network_172.20.0.0/16\")\n    end\n\n    it \"returns nil name if not defined\" do\n      network_name = subject.network_defined?(\"120.20.0.0/24\")\n      expect(network_name).to eq(nil)\n    end\n\n    context \"when config information is missing\" do\n      let(:docker_network_struct) do\n        [\n          {\n            \"Name\": \"bridge\",\n            \"Id\": \"ae74f6cc18bbcde86326937797070b814cc71bfc4a6d8e3e8cf3b2cc5c7f4a7d\",\n            \"Created\": \"2019-03-20T14:10:06.313314662-07:00\",\n            \"Scope\": \"local\",\n            \"Driver\": \"bridge\",\n            \"EnableIPv6\": false,\n            \"IPAM\": {\n              \"Driver\": \"default\",\n              \"Options\": nil,\n            },\n            \"Internal\": false,\n            \"Attachable\": false,\n            \"Ingress\": false,\n            \"ConfigFrom\": {\n              \"Network\": \"\"\n            },\n            \"ConfigOnly\": false,\n            \"Containers\": {\n              \"a1ee9b12bcea8268495b1f43e8d1285df1925b7174a695075f6140adb9415d87\": {\n                \"Name\": \"vagrant-sandbox_docker-1_1553116237\",\n                \"EndpointID\": \"fc1b0ed6e4f700cf88bb26a98a0722655191542e90df3e3492461f4d1f3c0cae\",\n                \"MacAddress\": \"02:42:ac:11:00:02\",\n                \"IPv4Address\": \"172.17.0.2/16\",\n                \"IPv6Address\": \"\"\n              },\n              \"Options\": {\n                \"com.docker.network.bridge.default_bridge\": \"true\",\n                \"com.docker.network.bridge.enable_icc\": \"true\",\n                \"com.docker.network.bridge.enable_ip_masquerade\": \"true\",\n                \"com.docker.network.bridge.host_binding_ipv4\": \"0.0.0.0\",\n                \"com.docker.network.bridge.name\": \"docker0\",\n                \"com.docker.network.driver.mtu\": \"1500\"\n              },\n              \"Labels\": {}\n            },\n          }\n        ].to_json\n      end\n\n      it \"should not raise an error\" do\n        expect { subject.network_defined?(subnet_string) }.not_to raise_error\n      end\n    end\n\n    context \"when IPAM information is missing\" do\n      let(:docker_network_struct) do\n        [\n          {\n            \"Name\": \"bridge\",\n            \"Id\": \"ae74f6cc18bbcde86326937797070b814cc71bfc4a6d8e3e8cf3b2cc5c7f4a7d\",\n            \"Created\": \"2019-03-20T14:10:06.313314662-07:00\",\n            \"Scope\": \"local\",\n            \"Driver\": \"bridge\",\n            \"EnableIPv6\": false,\n            \"Internal\": false,\n            \"Attachable\": false,\n            \"Ingress\": false,\n            \"ConfigFrom\": {\n              \"Network\": \"\"\n            },\n            \"ConfigOnly\": false,\n            \"Containers\": {\n              \"a1ee9b12bcea8268495b1f43e8d1285df1925b7174a695075f6140adb9415d87\": {\n                \"Name\": \"vagrant-sandbox_docker-1_1553116237\",\n                \"EndpointID\": \"fc1b0ed6e4f700cf88bb26a98a0722655191542e90df3e3492461f4d1f3c0cae\",\n                \"MacAddress\": \"02:42:ac:11:00:02\",\n                \"IPv4Address\": \"172.17.0.2/16\",\n                \"IPv6Address\": \"\"\n              },\n              \"Options\": {\n                \"com.docker.network.bridge.default_bridge\": \"true\",\n                \"com.docker.network.bridge.enable_icc\": \"true\",\n                \"com.docker.network.bridge.enable_ip_masquerade\": \"true\",\n                \"com.docker.network.bridge.host_binding_ipv4\": \"0.0.0.0\",\n                \"com.docker.network.bridge.name\": \"docker0\",\n                \"com.docker.network.driver.mtu\": \"1500\"\n              },\n              \"Labels\": {}\n            },\n          }\n        ].to_json\n      end\n\n      it \"should not raise an error\" do\n        expect { subject.network_defined?(subnet_string) }.not_to raise_error\n      end\n    end\n  end\n\n  describe '#network_containing_address' do\n    let(:address) { \"172.20.128.2\" }\n    let(:network_names) { [\"vagrant_network_172.20.0.0/16\", \"bridge\", \"null\" ] }\n\n    it \"returns the network name if it contains the requested address\" do\n      allow(subject).to receive(:list_network_names).and_return(network_names)\n      allow(subject).to receive(:inspect_network).and_return(JSON.load(docker_network_struct))\n\n      network_name = subject.network_containing_address(address)\n      expect(network_name).to eq(\"vagrant_network_172.20.0.0/16\")\n    end\n\n    it \"returns nil if no networks contain the requested address\" do\n      allow(subject).to receive(:list_network_names).and_return(network_names)\n      allow(subject).to receive(:inspect_network).and_return(JSON.load(docker_network_struct))\n\n      network_name = subject.network_containing_address(\"127.0.0.1\")\n      expect(network_name).to eq(nil)\n    end\n  end\n\n  describe '#existing_named_network?' do\n    let(:network_names) { [\"vagrant_network_172.20.0.0/16\", \"bridge\", \"null\" ] }\n\n    it \"returns true if the network exists\" do\n      allow(subject).to receive(:list_network_names).and_return(network_names)\n\n      expect(subject.existing_named_network?(\"vagrant_network_172.20.0.0/16\")).to be_truthy\n    end\n\n    it \"returns false if the network does not exist\" do\n      allow(subject).to receive(:list_network_names).and_return(network_names)\n\n      expect(subject.existing_named_network?(\"vagrant_network_17.0.0/16\")).to be_falsey\n    end\n  end\n\n  describe '#list_network_names' do\n    let(:unparsed_network_names) { \"vagrant_network_172.20.0.0/16\\nbridge\\nnull\" }\n    let(:network_names) { [\"vagrant_network_172.20.0.0/16\", \"bridge\", \"null\" ] }\n\n    it \"lists the network names\" do\n      allow(subject).to receive(:list_network).with(\"--format={{.Name}}\").\n        and_return(unparsed_network_names)\n\n      expect(subject.list_network_names).to eq(network_names)\n    end\n  end\n\n  describe '#network_used?' do\n    let(:network_name) { \"vagrant_network_172.20.0.0/16\" }\n    it \"returns nil if no networks\" do\n      allow(subject).to receive(:inspect_network).with(network_name).and_return(nil)\n\n      expect(subject.network_used?(network_name)).to eq(nil)\n    end\n\n    it \"returns true if network has containers in use\" do\n      allow(subject).to receive(:inspect_network).with(network_name).and_return([JSON.load(docker_network_struct).last])\n\n      expect(subject.network_used?(network_name)).to be_truthy\n    end\n\n    it \"returns false if network has containers in use\" do\n      allow(subject).to receive(:inspect_network).with(\"host\").and_return([JSON.load(docker_network_struct)[1]])\n\n      expect(subject.network_used?(\"host\")).to be_falsey\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/docker/provider_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/docker/provider\")\n\ndescribe VagrantPlugins::DockerProvider::Provider do\n  let(:driver_obj){ double(\"driver\") }\n  let(:provider){ double(\"provider\", driver: driver_obj) }\n  let(:provider_config){ double(\"provider_config\", force_host_vm: false) }\n  let(:ssh) { double(\"ssh\", guest_port: 22) }\n  let(:config) { double(\"config\", ssh: ssh) }\n  let(:machine){ double(\"machine\", provider: provider, provider_config: provider_config, config: config) }\n\n\n  let(:platform)   { double(\"platform\") }\n\n  subject { described_class.new(machine) }\n\n  before do\n    stub_const(\"Vagrant::Util::Platform\", platform)\n    allow(machine).to receive(:id).and_return(\"foo\")\n  end\n\n  describe \".usable?\" do\n    subject { described_class }\n\n    it \"returns true if usable\" do\n      allow(VagrantPlugins::DockerProvider::Driver).to receive(:new).and_return(driver_obj)\n      allow(provider_config).to receive(:compose).and_return(false)\n      allow(driver_obj).to receive(:execute).with(\"docker\", \"version\").and_return(true)\n      expect(subject).to be_usable\n    end\n\n    it \"raises an exception if docker is not available\" do\n      allow(VagrantPlugins::DockerProvider::Driver).to receive(:new).and_return(driver_obj)\n      allow(provider_config).to receive(:compose).and_return(false)\n      allow(platform).to receive(:windows?).and_return(false)\n      allow(platform).to receive(:darwin?).and_return(false)\n\n      allow(driver_obj).to receive(:execute).with(\"docker\", \"version\").\n        and_raise(Vagrant::Errors::CommandUnavailable, file: \"docker\")\n\n      expect { subject.usable?(true) }.\n        to raise_error(Vagrant::Errors::CommandUnavailable)\n    end\n  end\n\n  describe \"#driver\" do\n    it \"is initialized\" do\n      allow(provider_config).to receive(:compose).and_return(false)\n      allow(platform).to receive(:windows?).and_return(false)\n      allow(platform).to receive(:darwin?).and_return(false)\n      expect(subject.driver).to be_kind_of(VagrantPlugins::DockerProvider::Driver)\n    end\n  end\n\n  describe \"#state\" do\n    before { allow(subject).to receive(:driver).and_return(driver_obj) }\n\n    it \"returns not_created if no ID\" do\n      allow(machine).to receive(:id).and_return(nil)\n      expect(subject.state.id).to eq(:not_created)\n    end\n\n    it \"calls an action to determine the ID\" do\n      allow(provider_config).to receive(:compose).and_return(false)\n      allow(platform).to receive(:windows?).and_return(false)\n      allow(platform).to receive(:darwin?).and_return(false)\n      expect(machine).to receive(:id).and_return(\"foo\")\n      expect(driver_obj).to receive(:created?).with(\"foo\").and_return(false)\n\n      expect(subject.state.id).to eq(:not_created)\n    end\n  end\n\n  describe \"#host_vm\" do\n    let(:host_env) { double(\"host_env\", root_path: \"/vagrant.d\", default_provider: :virtualbox) }\n\n    it \"returns the host machine object\" do\n      allow(machine.provider_config).to receive(:vagrant_vagrantfile).and_return(\"/path/to/Vagrantfile\")\n      allow(machine.provider_config).to receive(:vagrant_machine).and_return(:default)\n      allow(machine).to receive(:env).and_return(double(\"env\"))\n      allow(machine.env).to receive(:root_path).and_return(\"/.vagrant.d\")\n      allow(machine.env).to receive(:home_path).and_return(\"/path/to\")\n      allow(machine.env).to receive(:ui_class).and_return(true)\n\n      expect(Vagrant::Environment).to receive(:new).and_return(host_env)\n\n      allow(host_env).to receive(:machine).and_return(true)\n      subject.host_vm\n    end\n  end\n\n  describe \"#ssh_info\" do\n    let(:result) { \"127.0.0.1\" }\n    let(:exit_code) { 0 }\n    let(:ssh_info) {{:host=>result,:port=>22}}\n\n    let(:network_settings) { {\"NetworkSettings\" => {\"Bridge\"=>\"\", \"SandboxID\"=>\"randomid\", \"HairpinMode\"=>false, \"LinkLocalIPv6Address\"=>\"\", \"LinkLocalIPv6PrefixLen\"=>0, \"Ports\"=>{\"443/tcp\"=>nil, \"80/tcp\"=>nil}, \"SandboxKey\"=>\"/var/run/docker/netns/158b7024a9e4\", \"SecondaryIPAddresses\"=>nil, \"SecondaryIPv6Addresses\"=>nil, \"EndpointID\"=>\"randomEndpointID\", \"Gateway\"=>\"172.17.0.1\", \"GlobalIPv6Address\"=>\"\", \"GlobalIPv6PrefixLen\"=>0, \"IPAddress\"=>\"127.0.0.1\", \"IPPrefixLen\"=>16, \"IPv6Gateway\"=>\"\", \"MacAddress\"=>\"02:42:ac:11:00:02\", \"Networks\"=>{\"bridge\"=>{\"IPAMConfig\"=>nil, \"Links\"=>nil, \"Aliases\"=>nil, \"NetworkID\"=>\"networkIDVar\", \"EndpointID\"=>\"endpointIDVar\", \"Gateway\"=>\"127.0.0.1\", \"IPAddress\"=>\"127.0.0.1\", \"IPPrefixLen\"=>16, \"IPv6Gateway\"=>\"\", \"GlobalIPv6Address\"=>\"\", \"GlobalIPv6PrefixLen\"=>0, \"MacAddress\"=>\"02:42:ac:11:00:02\", \"DriverOpts\"=>nil}}}} }\n\n    let(:empty_network_settings) { {\"NetworkSettings\" => {\"Bridge\"=>\"\", \"SandboxID\"=>\"randomid\", \"HairpinMode\"=>false, \"LinkLocalIPv6Address\"=>\"\", \"LinkLocalIPv6PrefixLen\"=>0, \"Ports\"=>\"\", \"SandboxKey\"=>\"/var/run/docker/netns/158b7024a9e4\", \"SecondaryIPAddresses\"=>nil, \"SecondaryIPv6Addresses\"=>nil, \"EndpointID\"=>\"randomEndpointID\", \"Gateway\"=>\"172.17.0.1\", \"GlobalIPv6Address\"=>\"\", \"GlobalIPv6PrefixLen\"=>0, \"IPAddress\"=>\"\", \"IPPrefixLen\"=>16, \"IPv6Gateway\"=>\"\", \"MacAddress\"=>\"02:42:ac:11:00:02\", \"Networks\"=>{\"bridge\"=>{\"IPAMConfig\"=>nil, \"Links\"=>nil, \"Aliases\"=>nil, \"NetworkID\"=>\"networkIDVar\", \"EndpointID\"=>\"endpointIDVar\", \"Gateway\"=>\"127.0.0.1\", \"IPAddress\"=>\"127.0.0.1\", \"IPPrefixLen\"=>16, \"IPv6Gateway\"=>\"\", \"GlobalIPv6Address\"=>\"\", \"GlobalIPv6PrefixLen\"=>0, \"MacAddress\"=>\"02:42:ac:11:00:02\", \"DriverOpts\"=>nil}}}} }\n\n    before do\n      allow(VagrantPlugins::DockerProvider::Driver).to receive(:new).and_return(driver_obj)\n      allow(machine).to receive(:action).with(:read_state).and_return(machine_state_id: :running)\n    end\n\n    it \"returns nil if a port info is nil from the driver\" do\n      allow(provider_config).to receive(:compose).and_return(false)\n      allow(platform).to receive(:windows?).and_return(false)\n      allow(platform).to receive(:darwin?).and_return(false)\n      allow(driver_obj).to receive(:created?).and_return(true)\n      allow(driver_obj).to receive(:state).and_return(:running)\n\n      allow(driver_obj).to receive(:inspect_container).and_return(empty_network_settings)\n\n      expect(subject.ssh_info).to eq(nil)\n    end\n\n    it \"should receive a valid address\" do\n      allow(provider_config).to receive(:compose).and_return(false)\n      allow(platform).to receive(:windows?).and_return(false)\n      allow(platform).to receive(:darwin?).and_return(false)\n      allow(driver_obj).to receive(:created?).and_return(true)\n      allow(driver_obj).to receive(:state).and_return(:running)\n      allow(driver_obj).to receive(:execute).with(:get_network_config).and_return(result)\n      allow(driver_obj).to receive(:inspect_container).and_return(network_settings)\n\n      expect(subject.ssh_info).to eq(ssh_info)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/docker/synced_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/docker/synced_folder\")\n\ndescribe VagrantPlugins::DockerProvider::SyncedFolder do\n  subject { described_class.new }\n\n  let(:provider_config) { double(\"provider_config\", volumes: []) }\n  let(:machine) { double(\"machine\") }\n\n  before do\n    allow(machine).to receive(:provider_name).and_return(:docker)\n    allow(machine).to receive(:provider_config).and_return(provider_config)\n  end\n\n  describe \"#usable?\" do\n    it \"is usable\" do\n      expect(subject).to be_usable(machine)\n    end\n\n    it \"is not usable if provider isn't docker\" do\n      allow(machine).to receive(:provider_name).and_return(:virtualbox)\n      expect(subject).to_not be_usable(machine)\n    end\n\n    it \"raises an error if bad provider if specified\" do\n      allow(machine).to receive(:provider_name).and_return(:virtualbox)\n      expect { subject.usable?(machine, true) }.\n        to raise_error(VagrantPlugins::DockerProvider::Errors::SyncedFolderNonDocker)\n    end\n  end\n\n  describe \"#prepare\" do\n    let(:folders) {{\"/guest/dir1\"=>\n                    {:guestpath=>\"/guest/dir1\",\n                     :hostpath=>\"/Users/brian/code/vagrant-sandbox\",\n                     :disabled=>false,\n                     :__vagrantfile=>true},\n                     \"/dev/vagrant\"=>\n                    {:guestpath=>\"/dev/vagrant\",\n                     :hostpath=>\"/Users/brian/code/vagrant\",\n                     :disabled=>false,\n                     :__vagrantfile=>true}}}\n\n    let(:consistency_folders) {{\"/guest/dir1\"=>\n                                {:docker_consistency=>\"cached\",\n                                 :guestpath=>\"/guest/dir1\",\n                                 :hostpath=>\"/Users/brian/code/vagrant-sandbox\",\n                                 :disabled=>false,\n                                 :__vagrantfile=>true},\n                                 \"/dev/vagrant\"=>\n                                {:docker_consistency=>\"delegated\",\n                                 :guestpath=>\"/dev/vagrant\",\n                                 :hostpath=>\"/Users/brian/code/vagrant\",\n                                 :disabled=>false,\n                                 :__vagrantfile=>true}}}\n    let(:options) { {} }\n\n    let(:volumes) { [\"/Users/brian/code/vagrant-sandbox:/guest/dir1\",\n                     \"/Users/brian/code/vagrant:/dev/vagrant\"] }\n    let(:consistency_volumes) { [\"/Users/brian/code/vagrant-sandbox:/guest/dir1:cached\",\n                                 \"/Users/brian/code/vagrant:/dev/vagrant:delegated\"] }\n\n    it \"prepares folders to mount\" do\n      subject.prepare(machine, folders, options)\n      expect(machine.provider_config.volumes).to eq(volumes)\n    end\n\n    it \"sets volume consistency if specified\" do\n      subject.prepare(machine, consistency_folders, options)\n      expect(machine.provider_config.volumes).to eq(consistency_volumes)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/action/check_enabled_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/action/check_enabled\")\n\ndescribe VagrantPlugins::HyperV::Action::CheckEnabled do\n  let(:app){ double(\"app\") }\n  let(:env){ {ui: ui, machine: machine} }\n  let(:ui){ Vagrant::UI::Silent.new }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:driver){ double(\"driver\") }\n  let(:machine){ double(\"machine\", provider: provider) }\n  let(:subject){ described_class.new(app, env) }\n\n  it \"should continue when Hyper-V is enabled\" do\n    expect(driver).to receive(:execute).and_return(\"result\" => true)\n    expect(app).to receive(:call)\n    subject.call(env)\n  end\n\n  it \"should raise error when Hyper-V is not enabled\" do\n    expect(driver).to receive(:execute).and_return(\"result\" => false)\n    expect(app).not_to receive(:call)\n    expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::PowerShellFeaturesDisabled)\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/action/configure_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/action/configure\")\n\ndescribe VagrantPlugins::HyperV::Action::Configure do\n  let(:app){ double(\"app\") }\n  let(:env){ {ui: ui, machine: machine} }\n  let(:ui){ Vagrant::UI::Silent.new }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:driver){ double(\"driver\") }\n  let(:machine){ double(\"machine\", provider: provider, config: config, provider_config: provider_config, data_dir: data_dir, id: \"machineID\") }\n  let(:data_dir){ double(\"data_dir\") }\n  let(:config){ double(\"config\", vm: vm) }\n  let(:vm){ double(\"vm\", networks: networks) }\n  let(:networks){ [] }\n  let(:switches){ [\n    {\"Name\" => \"Switch1\", \"Id\" => \"ID1\"},\n    {\"Name\" => \"Switch2\", \"Id\" => \"ID2\"}\n  ]}\n  let(:sentinel){ double(\"sentinel\") }\n  let(:provider_config){\n    double(\"provider_config\",\n      memory: \"1024\",\n      maxmemory: \"1024\",\n      cpus: 1,\n      auto_start_action: \"Nothing\",\n      auto_stop_action: \"Save\",\n      enable_checkpoints: false,\n      enable_automatic_checkpoints: true,\n      enable_virtualization_extensions: false,\n      vm_integration_services: vm_integration_services,\n      enable_enhanced_session_mode: enable_enhanced_session_mode\n    )\n  }\n  let(:vm_integration_services){ {} }\n  let(:enable_enhanced_session_mode){ false }\n\n  let(:subject){ described_class.new(app, env) }\n\n  before do\n    allow(driver).to receive(:execute)\n    allow(app).to receive(:call)\n    expect(driver).to receive(:execute).with(:get_switches).and_return(switches)\n    allow(ui).to receive(:ask).and_return(\"1\")\n    allow(data_dir).to receive(:join).and_return(sentinel)\n    allow(sentinel).to receive(:file?).and_return(false)\n    allow(sentinel).to receive(:open)\n    allow(driver).to receive(:set_enhanced_session_transport_type).with(\"VMBus\")\n  end\n\n  it \"should call the app on success\" do\n    expect(app).to receive(:call)\n    subject.call(env)\n  end\n\n  context \"with missing switch sentinel file\" do\n    it \"should prompt for switch to use\" do\n      expect(ui).to receive(:ask)\n      subject.call(env)\n    end\n\n    it \"should write sentinel file\" do\n      expect(sentinel).to receive(:open)\n      subject.call(env)\n    end\n  end\n\n  context \"with existing switch sentinel file\" do\n    before{ allow(sentinel).to receive(:file?).twice.and_return(true) }\n\n    it \"should not prompt for switch to use\" do\n      expect(ui).not_to receive(:ask)\n      subject.call(env)\n    end\n\n    it \"should not write sentinel file\" do\n      expect(sentinel).not_to receive(:open)\n      subject.call(env)\n    end\n  end\n\n  context \"with bridge defined in networks\" do\n    context \"with valid bridge switch name\" do\n      let(:networks){ [[:public_network, {bridge: \"Switch1\"}]] }\n\n      it \"should not prompt for switch\" do\n        expect(ui).not_to receive(:ask)\n        subject.call(env)\n      end\n    end\n\n    context \"with valid bridge switch ID\" do\n      let(:networks){ [[:public_network, {bridge: \"ID1\"}]] }\n\n      it \"should not prompt for switch\" do\n        expect(ui).not_to receive(:ask)\n        subject.call(env)\n      end\n    end\n\n    context \"with invalid bridge switch name\" do\n      let(:networks){ [[:public_network, {bridge: \"UNKNOWN\"}]] }\n\n      it \"should prompt for switch\" do\n        expect(ui).to receive(:ask)\n        subject.call(env)\n      end\n    end\n  end\n\n  context \"with integration services enabled\" do\n    let(:vm_integration_services){ {service: true} }\n\n    it \"should call the driver to set the services\" do\n      expect(driver).to receive(:set_vm_integration_services)\n      subject.call(env)\n    end\n  end\n\n  context \"without enhanced session transport type\" do\n    it \"should call the driver to set enhanced session transport type back to default\" do\n      expect(driver).to receive(:set_enhanced_session_transport_type).with(\"VMBus\")\n      subject.call(env)\n    end\n  end\n\n  context \"with enhanced session transport type\" do\n    let(:enable_enhanced_session_mode) { true }\n\n    it \"should call the driver to set enhanced session transport type\" do\n      expect(driver).to receive(:set_enhanced_session_transport_type).with(\"HvSocket\")\n      subject.call(env)\n    end\n  end\n\n  context \"without available switches\" do\n    let(:switches){ [] }\n\n    it \"should raise an error\" do\n      expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::NoSwitches)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/action/delete_vm_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/action/delete_vm\")\n\ndescribe VagrantPlugins::HyperV::Action::DeleteVM do\n  let(:app){ double(\"app\") }\n  let(:env){ {ui: ui, machine: machine} }\n  let(:ui){ Vagrant::UI::Silent.new }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:driver){ double(\"driver\") }\n  let(:machine){ double(\"machine\", provider: provider, data_dir: \"/dev/null\") }\n  let(:subject){ described_class.new(app, env) }\n\n  before do\n    allow(app).to receive(:call)\n    allow(driver).to receive(:delete_vm)\n    allow(FileUtils).to receive(:rm_rf)\n    allow(FileUtils).to receive(:mkdir_p)\n  end\n\n  it \"should call the app on success\" do\n    expect(app).to receive(:call)\n    subject.call(env)\n  end\n\n  it \"should call the driver to delete the vm\" do\n    expect(driver).to receive(:delete_vm)\n    subject.call(env)\n  end\n\n  it \"should delete the data directory\" do\n    expect(FileUtils).to receive(:rm_rf).with(machine.data_dir)\n    subject.call(env)\n  end\n\n  it \"should recreate the data directory\" do\n    expect(FileUtils).to receive(:mkdir_p).with(machine.data_dir)\n    subject.call(env)\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/action/export_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/action/export\")\n\ndescribe VagrantPlugins::HyperV::Action::Export do\n  let(:app){ double(\"app\") }\n  let(:env){ {ui: ui, machine: machine} }\n  let(:ui){ Vagrant::UI::Silent.new }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:driver){ double(\"driver\") }\n  let(:machine){ double(\"machine\", provider: provider, state: state) }\n  let(:state){ double(\"state\", id: machine_state) }\n  let(:machine_state){ :off }\n\n  let(:subject){ described_class.new(app, env) }\n\n  before do\n    allow(app).to receive(:call)\n    allow(driver).to receive(:export)\n  end\n\n  it \"should call the app on success\" do\n    expect(app).to receive(:call)\n    subject.call(env)\n  end\n\n  it \"should call the driver to perform the export\" do\n    expect(driver).to receive(:export)\n    subject.call(env)\n  end\n\n  context \"with invalid machine state\" do\n    let(:machine_state){ :on }\n\n    it \"should raise an error\" do\n      expect{ subject.call(env) }.to raise_error(Vagrant::Errors::VMPowerOffToPackage)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/action/import_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/action/import\")\n\ndescribe VagrantPlugins::HyperV::Action::Import do\n  let(:app){ double(\"app\") }\n  let(:env){ {ui: ui, machine: machine} }\n  let(:ui){ Vagrant::UI::Silent.new }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:driver){ double(\"driver\") }\n  let(:machine){ double(\"machine\", provider: provider, provider_config: provider_config, box: box, data_dir: data_dir, name: \"machname\") }\n  let(:provider_config){\n    double(\"provider_config\",\n      linked_clone: false,\n      vmname: \"VMNAME\",\n      cpus: nil,\n      memory: nil,\n      maxmemory: nil,\n    )\n  }\n  let(:box){ double(\"box\", directory: box_directory) }\n  let(:box_directory){ double(\"box_directory\") }\n  let(:data_dir){ double(\"data_dir\") }\n  let(:vm_dir){ double(\"vm_dir\") }\n  let(:hd_dir){ double(\"hd_dir\") }\n\n  let(:subject){ described_class.new(app, env) }\n\n  before do\n    allow(app).to receive(:call)\n    allow(box_directory).to receive(:join).with(\"Virtual Machines\").and_return(vm_dir)\n    allow(box_directory).to receive(:join).with(\"Virtual Hard Disks\").and_return(hd_dir)\n    allow(vm_dir).to receive(:directory?).and_return(true)\n    allow(vm_dir).to receive(:each_child).and_yield(Pathname.new(\"file.txt\"))\n    allow(hd_dir).to receive(:directory?).and_return(true)\n    allow(hd_dir).to receive(:each_child).and_yield(Pathname.new(\"file.txt\"))\n    allow(driver).to receive(:has_vmcx_support?).and_return(true)\n    allow(data_dir).to receive(:join).and_return(data_dir)\n    allow(data_dir).to receive(:to_s).and_return(\"DATA_DIR_PATH\")\n    allow(driver).to receive(:import).and_return(\"id\" => \"VMID\")\n    allow(machine).to receive(:id=)\n  end\n\n  context \"with missing virtual machines directory\" do\n    before{ expect(vm_dir).to receive(:directory?).and_return(false) }\n\n    it \"should raise an error\" do\n      expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid)\n    end\n  end\n\n  context \"with missing hard disks directory\" do\n    before{ expect(hd_dir).to receive(:directory?).and_return(false) }\n\n    it \"should raise an error\" do\n      expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid)\n    end\n  end\n\n  context \"with missing configuration file\" do\n    before do\n      allow(hd_dir).to receive(:each_child).and_yield(Pathname.new(\"image.vhd\"))\n    end\n\n    it \"should raise an error\" do\n      expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid)\n    end\n  end\n\n  context \"with missing image file\" do\n    before do\n      allow(vm_dir).to receive(:each_child).and_yield(Pathname.new(\"config.xml\"))\n    end\n\n    it \"should raise an error\" do\n      expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid)\n    end\n  end\n\n  context \"with image and config files\" do\n    before do\n      allow(vm_dir).to receive(:each_child).and_yield(Pathname.new(\"config.xml\"))\n      allow(hd_dir).to receive(:each_child).and_yield(Pathname.new(\"image.vhd\"))\n    end\n\n    it \"should call the app on success\" do\n      expect(app).to receive(:call)\n      subject.call(env)\n    end\n\n    it \"should request import via the driver\" do\n      expect(driver).to receive(:import).and_return(\"id\" => \"VMID\")\n      subject.call(env)\n    end\n\n    it \"should set the machine ID after import\" do\n      expect(machine).to receive(:id=).with(\"VMID\")\n      subject.call(env)\n    end\n\n    context \"VM ID result is Array\" do\n      before do\n        expect(driver).to receive(:import).and_return(\"id\" => \"VMID\")\n      end\n\n      it \"should properly set the machine ID\" do\n        expect(machine).to receive(:id=).with(\"VMID\")\n        subject.call(env)\n      end\n    end\n\n    context \"with no vmcx support\" do\n      before do\n        expect(driver).to receive(:has_vmcx_support?).and_return(false)\n      end\n\n      it \"should match XML config file\" do\n        subject.call(env)\n      end\n\n      it \"should not match VMCX config file\" do\n        expect(vm_dir).to receive(:each_child).and_yield(Pathname.new(\"config.vmcx\"))\n        expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid)\n      end\n    end\n\n    context \"with vmcx support\" do\n      before do\n        expect(driver).to receive(:has_vmcx_support?).and_return(true)\n      end\n\n      it \"should match XML config file\" do\n        subject.call(env)\n      end\n\n      it \"should match VMCX config file\" do\n        expect(vm_dir).to receive(:each_child).and_yield(Pathname.new(\"config.vmcx\"))\n        subject.call(env)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/action/is_windows_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/action/is_windows\")\n\ndescribe VagrantPlugins::HyperV::Action::IsWindows do\n  let(:app){ double(\"app\") }\n  let(:env){ {ui: ui, machine: machine} }\n  let(:ui){ Vagrant::UI::Silent.new }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:driver){ double(\"driver\") }\n  let(:machine){ double(\"machine\", provider: provider, config: config) }\n  let(:config){ double(\"config\", vm: vm) }\n  let(:vm){ double(\"vm\", guest: :windows) }\n  let(:subject){ described_class.new(app, env) }\n\n  before do\n    allow(app).to receive(:call)\n    allow(env).to receive(:[]=)\n  end\n\n  it \"should call the app on success\" do\n    expect(app).to receive(:call)\n    subject.call(env)\n  end\n\n  it \"should update the env with the result\" do\n    expect(env).to receive(:[]=).with(:result, true)\n    subject.call(env)\n  end\n\n  it \"should set the result to false when not windows\" do\n    expect(vm).to receive(:guest).and_return(:linux)\n    expect(env).to receive(:[]=).with(:result, false)\n    subject.call(env)\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/action/net_set_mac_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/action/net_set_mac\")\n\ndescribe VagrantPlugins::HyperV::Action::NetSetMac do\n  let(:app){ double(\"app\") }\n  let(:env){ {ui: ui, machine: machine} }\n  let(:ui){ Vagrant::UI::Silent.new }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:driver){ double(\"driver\") }\n  let(:machine){ double(\"machine\", provider: provider, provider_config: provider_config) }\n  let(:provider_config){ double(\"provider_config\", mac: mac) }\n  let(:mac){ \"ADDRESS\" }\n  let(:subject){ described_class.new(app, env) }\n\n  before do\n    allow(driver).to receive(:net_set_mac)\n    allow(app).to receive(:call)\n  end\n\n  it \"should call the app on success\" do\n    expect(app).to receive(:call)\n    subject.call(env)\n  end\n\n  it \"should call the driver to set the MAC address\" do\n    expect(driver).to receive(:net_set_mac).with(mac)\n    subject.call(env)\n  end\n\n  context \"with no MAC address provided\" do\n    let(:mac){ nil }\n\n    it \"should not call driver to set the MAC address\" do\n      expect(driver).not_to receive(:net_set_mac)\n      subject.call(env)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/action/net_set_vlan_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/action/net_set_vlan\")\n\ndescribe VagrantPlugins::HyperV::Action::NetSetVLan do\n  let(:app){ double(\"app\") }\n  let(:env){ {ui: ui, machine: machine} }\n  let(:ui){ Vagrant::UI::Silent.new }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:driver){ double(\"driver\") }\n  let(:machine){ double(\"machine\", provider: provider, provider_config: provider_config) }\n  let(:provider_config){ double(\"provider_config\", vlan_id: vlan_id) }\n  let(:vlan_id){ \"VID\" }\n  let(:subject){ described_class.new(app, env) }\n\n  before do\n    allow(driver).to receive(:net_set_vlan)\n    allow(app).to receive(:call)\n  end\n\n  it \"should call the app on success\" do\n    expect(app).to receive(:call)\n    subject.call(env)\n  end\n\n  it \"should call the driver to set the vlan id\" do\n    expect(driver).to receive(:net_set_vlan).with(vlan_id)\n    subject.call(env)\n  end\n\n  context \"with no vlan id provided\" do\n    let(:vlan_id){ nil }\n\n    it \"should not call driver to set the vlan id\" do\n      expect(driver).not_to receive(:net_set_vlan)\n      subject.call(env)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/action/read_guest_ip_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/action/read_guest_ip\")\n\ndescribe VagrantPlugins::HyperV::Action::ReadGuestIP do\n  let(:app){ double(\"app\") }\n  let(:env){ {ui: ui, machine: machine} }\n  let(:ui){ Vagrant::UI::Silent.new }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:driver){ double(\"driver\") }\n  let(:machine){ double(\"machine\", provider: provider) }\n  let(:subject){ described_class.new(app, env) }\n\n  before do\n    allow(app).to receive(:call)\n    allow(env).to receive(:[]=)\n    allow(machine).to receive(:id)\n  end\n\n  it \"should call the app on success\" do\n    expect(app).to receive(:call)\n    subject.call(env)\n  end\n\n  context \"with machine ID set\" do\n    before{ allow(machine).to receive(:id).and_return(\"VMID\") }\n\n    it \"should request guest IP from the driver\" do\n      expect(driver).to receive(:read_guest_ip).and_return(\"ip\" => \"ADDRESS\")\n      subject.call(env)\n    end\n\n    it \"should set the host information into the env\" do\n      expect(env).to receive(:[]=).with(:machine_ssh_info, { host: \"ADDRESS\" })\n      expect(driver).to receive(:read_guest_ip).and_return(\"ip\" => \"ADDRESS\")\n      subject.call(env)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/action/read_state_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/action/read_state\")\n\ndescribe VagrantPlugins::HyperV::Action::ReadState do\n  let(:app){ double(\"app\") }\n  let(:env){ {ui: ui, machine: machine, machine_state_id: state_id} }\n  let(:ui){ Vagrant::UI::Silent.new }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:driver){ double(\"driver\") }\n  let(:machine){ double(\"machine\", provider: provider) }\n  let(:state_id){ nil }\n\n  let(:subject){ described_class.new(app, env) }\n\n  before do\n    allow(app).to receive(:call)\n    allow(env).to receive(:[]=)\n    allow(machine).to receive(:id)\n  end\n\n  it \"should call the app on success\" do\n    expect(app).to receive(:call)\n    subject.call(env)\n  end\n\n  it \"should set machine state into the env as not created\" do\n    expect(env).to receive(:[]=).with(:machine_state_id, :not_created)\n    subject.call(env)\n  end\n\n  context \"with machine ID set\" do\n    before{ allow(machine).to receive(:id).and_return(\"VMID\") }\n\n    it \"should request machine state from the driver\" do\n      expect(driver).to receive(:get_current_state).and_return(\"state\" => \"running\")\n      subject.call(env)\n    end\n\n    it \"should set machine state into the env\" do\n      expect(driver).to receive(:get_current_state).and_return(\"state\" => \"running\")\n      expect(env).to receive(:[]=).with(:machine_state_id, :running)\n      subject.call(env)\n    end\n\n    context \"with machine state ID as not_created\" do\n      let(:state_id){ :not_created }\n\n      it \"should clear the machine ID\" do\n        expect(driver).to receive(:get_current_state).and_return(\"state\" => \"not_created\")\n        expect(machine).to receive(:id=).with(nil)\n        subject.call(env)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/action/set_name_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/action/set_name\")\n\ndescribe VagrantPlugins::HyperV::Action::SetName do\n  let(:app){ double(\"app\") }\n  let(:env){ {ui: ui, machine: machine, root_path: Pathname.new(\"path\")} }\n  let(:ui){ Vagrant::UI::Silent.new }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:driver){ double(\"driver\") }\n  let(:machine){ double(\"machine\", provider: provider, provider_config: provider_config, data_dir: data_dir, name: \"machname\") }\n  let(:data_dir){ double(\"data_dir\") }\n  let(:provider_config){ double(\"provider_config\", vmname: vmname) }\n  let(:vmname){ \"VMNAME\" }\n  let(:sentinel){ double(\"sentinel\") }\n\n  let(:subject){ described_class.new(app, env) }\n\n  before do\n    allow(driver).to receive(:set_name)\n    allow(app).to receive(:call)\n    allow(data_dir).to receive(:join).and_return(sentinel)\n    allow(sentinel).to receive(:file?).and_return(false)\n    allow(sentinel).to receive(:open)\n  end\n\n  it \"should call the app on success\" do\n    expect(app).to receive(:call)\n    subject.call(env)\n  end\n\n  it \"should call the driver to set the name\" do\n    expect(driver).to receive(:set_name)\n    subject.call(env)\n  end\n\n  it \"should use the configured name when setting\" do\n    expect(driver).to receive(:set_name).with(vmname)\n    subject.call(env)\n  end\n\n  it \"should write sentinel after name is set\" do\n    expect(sentinel).to receive(:open)\n    subject.call(env)\n  end\n\n  context \"when no name is provided in the config\" do\n    let(:vmname){ nil }\n\n    it \"should generate a name based on path and machine\" do\n      expect(driver).to receive(:set_name).with(/^#{env[:root_path].to_s}_#{machine.name}_.+/)\n      subject.call(env)\n    end\n\n    it \"should not set name if sentinel exists\" do\n      expect(sentinel).to receive(:file?).and_return(true)\n      subject.call(env)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/action/wait_for_ip_address_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/action/wait_for_ip_address\")\n\ndescribe VagrantPlugins::HyperV::Action::WaitForIPAddress do\n  let(:app){ double(\"app\") }\n  let(:env){ {ui: ui, machine: machine} }\n  let(:ui){ Vagrant::UI::Silent.new }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:driver){ double(\"driver\") }\n  let(:machine){ double(\"machine\", provider: provider, provider_config: provider_config) }\n  let(:provider_config){ double(\"provider_config\", ip_address_timeout: ip_address_timeout) }\n  let(:ip_address_timeout){ 1 }\n\n  let(:subject){ described_class.new(app, env) }\n\n  before do\n    allow(driver).to receive(:read_guest_ip).and_return(\"ip\" => \"127.0.0.1\")\n    allow(app).to receive(:call)\n  end\n\n  it \"should call the app on success\" do\n    expect(app).to receive(:call)\n    subject.call(env)\n  end\n\n  it \"should set a timeout for waiting\" do\n    expect(Timeout).to receive(:timeout).with(ip_address_timeout)\n    subject.call(env)\n  end\n\n  it \"should retry until it receives a valid address\" do\n    expect(driver).to receive(:read_guest_ip).and_return(\"ip\" => \"ADDRESS\")\n    expect(driver).to receive(:read_guest_ip).and_return(\"ip\" => \"127.0.0.1\")\n    expect(subject).to receive(:sleep)\n    subject.call(env)\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/cap/cleanup_disks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/cap/cleanup_disks\")\n\ndescribe VagrantPlugins::HyperV::Cap::CleanupDisks do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:driver) { double(\"driver\") }\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|\n      allow(m.provider).to receive(:driver).and_return(driver)\n      allow(m).to receive(:state).and_return(state)\n    end\n  end\n\n  let(:state) do\n    double(:state)\n  end\n\n  let(:subject) { described_class }\n\n  let(:disk_meta_file) { {disk: [], floppy: [], dvd: []} }\n  let(:defined_disks) { {} }\n\n  context \"#cleanup_disks\" do\n    it \"returns if there's no data in meta file\" do\n      subject.cleanup_disks(machine, defined_disks, disk_meta_file)\n      expect(subject).not_to receive(:handle_cleanup_disk)\n    end\n\n    describe \"with disks to clean up\" do\n      let(:disk_meta_file) do\n        {\n          \"disk\" => [\n            {\n              \"UUID\" => \"1234\",\n              \"Path\" => \"c:\\\\users\\\\vagrant\\\\storage.vhdx\",\n              \"Name\" => \"storage\"\n            }\n          ],\n          \"floppy\" => [],\n          \"dvd\" => []\n        }\n      end\n\n      before { allow(driver).to receive(:read_scsi_controllers).and_return([]) }\n\n      it \"calls the cleanup method if a disk_meta file is defined\" do\n        expect(subject).to receive(:handle_cleanup_disk).\n          with(machine, defined_disks, disk_meta_file[\"disk\"]).\n          and_return(true)\n\n        subject.cleanup_disks(machine, defined_disks, disk_meta_file)\n      end\n\n      context \"with dvd to clean up\" do\n        let(:disk_meta_file) do\n          {\n            \"disk\" => [],\n            \"floppy\" => [],\n            \"dvd\" => [\n              {\n                \"Path\" => \"test.iso\"\n              }\n            ]\n          }\n\n          it \"calls the cleamup method if a disk_meta file is defined\" do\n            expect(subject).to receive(:handle_cleanup_dvd).\n              with(machine, defined_disks, disk_meta_file[\"dvd\"]).\n              and_return(true)\n\n            subject.cleanup_disks(machine, defined_disks, disk_meta_file)\n          end\n        end\n\n      end\n    end\n  end\n\n  context \"handle_cleanup_dvd\" do\n    let(:disk_meta_file) do\n      {\n        \"disk\" => [],\n        \"floppy\" => [],\n        \"dvd\" => []\n      }\n    end\n    let(:scsi_controllers) do\n      [\n        {\n          \"ControllerNumber\" => 0,\n          \"Name\" => \"SCSI Controller\",\n          \"Drives\" => drives\n        }\n      ]\n    end\n    let(:drives) do\n      [\n        {\n          \"DvdMediaType\" => 1,\n          \"Path\" => \"test.iso\",\n          \"ControllerLocation\" => 1,\n          \"ControllerNumber\" => 0,\n          \"ControllerType\" => 1\n        }\n      ]\n    end\n    let(:defined_disks) { [] }\n\n    before do\n      allow(driver).to receive(:read_scsi_controllers).and_return(scsi_controllers)\n    end\n\n    it \"should not remove disk\" do\n      expect(driver).not_to receive(:detach_dvd)\n\n      subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[\"dvd\"])\n    end\n\n    context \"when disk is defined in meta file\" do\n      let(:disk_meta_file) do\n        {\n          \"disk\" => [],\n          \"floppy\" => [],\n          \"dvd\" => [\n            \"Path\" => \"test.iso\",\n            \"ControllerLocation\" => 1,\n            \"ControllerNumber\" => 0,\n            \"ControllerType\" => 1\n          ]\n        }\n      end\n\n      it \"should remove the disk\" do\n        expect(driver).to receive(:detach_dvd).with(1, 0)\n\n        subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[\"dvd\"])\n      end\n\n      context \"when disk is defined in defined disks\" do\n        let(:defined_disks) do\n          [\n            double(\"dvd\", name: \"test-dvd\", type: :dvd, file: \"test.iso\")\n          ]\n        end\n\n        it \"should not remove disk\" do\n          expect(driver).not_to receive(:detach_dvd)\n\n          subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[\"dvd\"])\n        end\n      end\n    end\n  end\n\n  context \"#handle_cleanup_disk\" do\n    let(:disk_meta_file) do\n      {\n        \"disk\" => [\n          {\n            \"UUID\" => \"1234\",\n            \"Path\" => \"c:\\\\users\\\\vagrant\\\\storage.vhdx\",\n            \"Name\" => \"storage\"\n          }\n        ],\n        \"floppy\" => [],\n        \"dvd\" => []\n      }\n    end\n    let(:defined_disks) { [] }\n    let(:all_disks) do\n      [\n        {\n          \"UUID\" => \"1234\",\n          \"Path\" => \"c:\\\\users\\\\vagrant\\\\storage.vhdx\",\n          \"Name\"=>\"storage\",\n          \"ControllerType\" => \"IDE\",\n          \"ControllerNumber\" => 1,\n          \"ControllerLocation\" => 0\n        }\n      ]\n    end\n    let(:path) { \"C:\\\\Users\\\\vagrant\\\\storage.vhdx\" }\n\n    it \"removes and closes medium from guest\" do\n      expect(driver).to receive(:list_hdds).and_return(all_disks)\n      expect(driver).to receive(:remove_disk).with(\"IDE\", 1, 0, \"c:\\\\users\\\\vagrant\\\\storage.vhdx\").and_return(true)\n\n      subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[\"disk\"])\n    end\n\n    it \"displays a warning if the disk could not be determined\" do\n      expect(driver).to receive(:list_hdds).and_return(all_disks)\n      expect(File).to receive(:realdirpath).and_return(path)\n      expect(File).to receive(:realdirpath).and_return(\"\")\n      expect(driver).not_to receive(:remove_disk)\n      expect(machine.ui).to receive(:warn).twice\n\n      subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[\"disk\"])\n    end\n\n    describe \"when windows paths mix cases\" do\n      let(:disk_meta_file) do\n        {\n          \"disk\" => [\n            {\n              \"UUID\" => \"1234\",\n              \"Path\" => \"c:\\\\users\\\\vagrant\\\\storage.vhdx\",\n              \"Name\" => \"storage\"\n            }\n          ],\n          \"floppy\" => [],\n          \"dvd\" => []\n        }\n      end\n      let(:defined_disks) { [] }\n      let(:all_disks) do\n        [\n          {\n            \"UUID\" => \"1234\",\n            \"Path\" => \"C:\\\\Users\\\\vagrant\\\\storage.vhdx\",\n            \"Name\" => \"storage\",\n            \"ControllerType\" => \"IDE\",\n            \"ControllerNumber\" => 1,\n            \"ControllerLocation\" => 0\n          }\n        ]\n      end\n\n      let(:path) { \"C:\\\\Users\\\\vagrant\\\\storage.vhdx\" }\n\n      it \"still removes and closes the medium from the guest\" do\n        expect(driver).to receive(:list_hdds).and_return(all_disks)\n        expect(File).to receive(:realdirpath).twice.and_return(path)\n        expect(driver).to receive(:remove_disk).with(\"IDE\", 1, 0, path).and_return(true)\n\n        subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[\"disk\"])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/cap/configure_disks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/cap/configure_disks\")\n\ndescribe VagrantPlugins::HyperV::Cap::ConfigureDisks do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:driver) { double(\"driver\") }\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|\n      allow(m.provider).to receive(:driver).and_return(driver)\n      allow(m).to receive(:state).and_return(state)\n    end\n  end\n\n  let(:state) do\n    double(:state)\n  end\n\n  let(:defined_disks) do\n    [\n      double(\"disk\", name: \"vagrant_primary\", size: \"5GB\", primary: true, type: :disk),\n      double(\"disk\", name: \"disk-0\", size: \"5GB\", primary: false, type: :disk),\n      double(\"disk\", name: \"disk-1\", size: \"5GB\", primary: false, type: :disk),\n      double(\"disk\", name: \"disk-2\", size: \"5GB\", primary: false, type: :disk)\n    ]\n  end\n\n  let(:subject) { described_class }\n\n  let(:all_disks) do\n    [\n      {\n        \"UUID\"=>\"12345\",\n        \"Path\"=>\"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx\",\n        \"ControllerLocation\"=>0,\n        \"ControllerNumber\"=>0\n      },\n      {\n        \"UUID\"=>\"67890\",\n        \"Name\"=>\"disk-0\",\n        \"Path\"=>\"C:/Users/vagrant/disks/disk-0.vhdx\",\n        \"ControllerLocation\"=>1,\n        \"ControllerNumber\"=>0\n      },\n      {\n        \"UUID\"=>\"324bbb53-d5ad-45f8-9bfa-1f2468b199a8\",\n        \"Path\"=>\"C:/Users/vagrant/disks/disk-1.vhdx\",\n        \"Name\"=>\"disk-1\",\n        \"ControllerLocation\"=>2,\n        \"ControllerNumber\"=>0\n      }\n    ]\n  end\n\n  context \"#configure_disks\" do\n    let(:dsk_data) do\n      {\n        \"UUID\"=>\"1234\",\n        \"Name\"=>\"disk\",\n        \"Path\"=> \"C:/Users/vagrant/storage.vhdx\"\n      }\n    end\n\n    it \"configures disks and returns the disks defined\" do\n      allow(driver).to receive(:list_hdds).and_return([])\n      expect(subject).to receive(:handle_configure_disk).exactly(4).and_return(dsk_data)\n\n      subject.configure_disks(machine, defined_disks)\n    end\n\n    describe \"with no disks to configure\" do\n      let(:defined_disks) { {} }\n      it \"returns empty hash if no disks to configure\" do\n        expect(subject.configure_disks(machine, defined_disks)).to eq({})\n      end\n    end\n\n    context \"with dvd\" do\n      before do\n        defined_disks.push(\n          double(\"dvd\", name: \"test-dvd\", type: :dvd, file: \"test.iso\")\n        )\n      end\n\n      it \"should configure the dvd disk\" do\n        allow(driver).to receive(:list_hdds).and_return([])\n        allow(subject).to receive(:handle_configure_disk).and_return({})\n        expect(subject).to receive(:handle_configure_dvd).and_return({})\n\n        subject.configure_disks(machine, defined_disks)\n      end\n    end\n  end\n\n  context \"#get_current_disk\" do\n    it \"gets primary disk uuid if disk to configure is primary\" do\n      expect(driver).to receive(:get_disk).with(all_disks.first[\"Path\"]).and_return(all_disks.first)\n      primary_disk = subject.get_current_disk(machine, defined_disks.first, all_disks)\n      expect(primary_disk).to eq(all_disks.first)\n    end\n\n    it \"finds the disk to configure\" do\n      disk = subject.get_current_disk(machine, defined_disks[1], all_disks)\n      expect(disk).to eq(all_disks[1])\n    end\n\n    it \"returns nil if disk is not found\" do\n      disk = subject.get_current_disk(machine, defined_disks[3], all_disks)\n      expect(disk).to be_nil\n    end\n\n    context \"when primary disk is not located at 0 0\" do\n      let(:all_disks) do\n        [\n          {\n            \"UUID\"=>\"12345\",\n            \"Path\"=>\"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx\",\n            \"ControllerLocation\"=>1,\n            \"ControllerNumber\"=>0\n          },\n          {\n            \"UUID\"=>\"67890\",\n            \"Name\"=>\"disk-0\",\n            \"Path\"=>\"C:/Users/vagrant/disks/disk-0.vhdx\",\n            \"ControllerLocation\"=>2,\n            \"ControllerNumber\"=>0\n          },\n          {\n            \"UUID\"=>\"324bbb53-d5ad-45f8-9bfa-1f2468b199a8\",\n            \"Path\"=>\"C:/Users/vagrant/disks/disk-1.vhdx\",\n            \"Name\"=>\"disk-1\",\n            \"ControllerLocation\"=>3,\n            \"ControllerNumber\"=>0\n          }\n        ]\n      end\n\n      it \"should return the primary disk\" do\n        expect(driver).to receive(:get_disk).with(all_disks.first[\"Path\"]).and_return(all_disks.first)\n        primary_disk = subject.get_current_disk(machine, defined_disks.first, all_disks)\n        expect(primary_disk).to eq(all_disks.first)\n      end\n\n      context \"when disks are unsorted\" do\n        let(:all_disks) do\n          [\n            {\n              \"UUID\"=>\"67890\",\n              \"Name\"=>\"disk-0\",\n              \"Path\"=>\"C:/Users/vagrant/disks/disk-0.vhdx\",\n              \"ControllerLocation\"=>2,\n              \"ControllerNumber\"=>0\n            },\n            {\n              \"UUID\"=>\"12345\",\n              \"Path\"=>\"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx\",\n              \"ControllerLocation\"=>1,\n              \"ControllerNumber\"=>0\n            },\n            {\n              \"UUID\"=>\"324bbb53-d5ad-45f8-9bfa-1f2468b199a8\",\n              \"Path\"=>\"C:/Users/vagrant/disks/disk-1.vhdx\",\n              \"Name\"=>\"disk-1\",\n              \"ControllerLocation\"=>3,\n              \"ControllerNumber\"=>0\n            }\n          ]\n        end\n\n        it \"should return the primary disk\" do\n          expect(driver).to receive(:get_disk).with(all_disks[1][\"Path\"]).and_return(all_disks[1])\n          primary_disk = subject.get_current_disk(machine, defined_disks.first, all_disks)\n          expect(primary_disk).to eq(all_disks[1])\n        end\n      end\n    end\n  end\n\n  context \"#handle_configure_dvd\" do\n    let(:scsi_controllers_current) do\n      [\n        {\n          \"ControllerNumber\" => 0,\n          \"Name\" => \"SCSI Controller\",\n          \"Drives\" => drives_current\n        }\n      ]\n    end\n    let(:drives_current) { [] }\n    let(:scsi_controllers_updated) do\n      [\n        {\n          \"ControllerNumber\" => 0,\n          \"Name\" => \"SCSI Controller\",\n          \"Drives\" => drives_updated\n        }\n      ]\n    end\n    let(:drives_updated) do\n      [\n        {\n          \"DvdMediaType\" => 1,\n          \"Path\" => \"test.iso\",\n          \"ControllerLocation\" => 1,\n          \"ControllerNumber\" => 0,\n          \"ControllerType\" => 1\n        }\n      ]\n    end\n\n    let(:defined_disk) do\n      double(\"dvd\", name: \"test-dvd\", type: :dvd, file: \"test.iso\")\n    end\n\n    it \"should add disk to guest\" do\n      expect(driver).to receive(:read_scsi_controllers).and_return(scsi_controllers_current)\n      expect(driver).to receive(:read_scsi_controllers).and_return(scsi_controllers_updated)\n      expect(driver).to receive(:attach_dvd).with(/test.iso$/)\n\n      subject.handle_configure_dvd(machine, defined_disk)\n    end\n\n    context \"when disk is already attached\" do\n      let(:drives_current) do\n        [\n          {\n            \"DvdMediaType\" => 1,\n            \"Path\" => \"test.iso\",\n            \"ControllerLocation\" => 1,\n            \"ControllerNumber\" => 0,\n            \"ControllerType\" => 1\n          }\n        ]\n      end\n\n      it \"should not add disk to guest\" do\n        expect(driver).to receive(:read_scsi_controllers).and_return(scsi_controllers_current)\n        expect(driver).not_to receive(:attach_dvd)\n\n        subject.handle_configure_dvd(machine, defined_disk)\n      end\n\n      context \"when additional disk is defined\" do\n        let(:defined_disk) do\n          double(\"dvd\", name: \"other-dvd\", type: :dvd, file: \"other-test.iso\")\n        end\n\n        let(:drives_updated) do\n          [\n            {\n              \"DvdMediaType\" => 1,\n              \"Path\" => \"test.iso\",\n              \"ControllerLocation\" => 1,\n              \"ControllerNumber\" => 0,\n              \"ControllerType\" => 1\n            },\n            {\n              \"DvdMediaType\" => 1,\n              \"Path\" => \"other-test.iso\",\n              \"ControllerLocation\" => 2,\n              \"ControllerNumber\" => 0,\n              \"ControllerType\" => 1\n            }\n          ]\n        end\n\n\n        it \"should add disk to guest\" do\n          expect(driver).to receive(:read_scsi_controllers).and_return(scsi_controllers_current)\n          expect(driver).to receive(:read_scsi_controllers).and_return(scsi_controllers_updated)\n          expect(driver).to receive(:attach_dvd).with(/other-test.iso$/)\n\n          subject.handle_configure_dvd(machine, defined_disk)\n        end\n      end\n    end\n  end\n\n  context \"#handle_configure_disk\" do\n    describe \"when creating a new disk\" do\n      let(:all_disks) do\n        [\n          {\n            \"UUID\"=>\"12345\",\n            \"Path\"=>\"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx\",\n            \"ControllerLocation\"=>0,\n            \"ControllerNumber\"=>0\n          }\n        ]\n      end\n\n      let(:disk_meta) do\n        {\n          \"UUID\" => \"12345\",\n          \"Name\" => \"vagrant_primary\",\n          \"Path\" => \"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx\"\n        }\n      end\n\n      it \"creates a new disk if it doesn't yet exist\" do\n        expect(subject).to receive(:create_disk).with(machine, defined_disks[1])\n          .and_return(disk_meta)\n\n        subject.handle_configure_disk(machine, defined_disks[1], all_disks)\n      end\n    end\n\n    describe \"when a disk needs to be resized\" do\n      let(:all_disks) do\n        [\n          {\"UUID\"=>\"12345\",\n           \"Path\"=>\"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx\",\n           \"ControllerLocation\"=>0,\n           \"ControllerNumber\"=>0\n          },\n          {\n            \"UUID\"=>\"67890\",\n            \"Name\"=>\"disk-0\",\n            \"Path\"=>\"C:/Users/vagrant/disks/disk-0.vhdx\",\n            \"ControllerLocation\"=>1,\n            \"ControllerNumber\"=>0\n          },\n          {\n            \"UUID\"=>\"324bbb53-d5ad-45f8-9bfa-1f2468b199a8\",\n            \"Path\"=>\"C:/Users/vagrant/disks/disk-1.vhdx\",\n            \"Name\"=>\"disk-1\",\n            \"ControllerLocation\"=>2,\n            \"ControllerNumber\"=>0\n          }\n        ]\n      end\n\n      it \"resizes a disk\" do\n        expect(subject).to receive(:get_current_disk).\n          with(machine, defined_disks[1], all_disks).and_return(all_disks[1])\n\n        expect(subject).to receive(:compare_disk_size).\n          with(machine, defined_disks[1], all_disks[1]).and_return(true)\n\n        expect(subject).to receive(:resize_disk).\n          with(machine, defined_disks[1], all_disks[1]).and_return(true)\n\n        subject.handle_configure_disk(machine, defined_disks[1], all_disks)\n      end\n    end\n\n    describe \"if no additional disk configuration is required\" do\n      let(:all_disks) do\n        [\n          {\n            \"UUID\"=>\"12345\",\n            \"Path\"=>\"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx\",\n            \"ControllerLocation\"=>0,\n            \"ControllerNumber\"=>0\n          },\n          {\n            \"UUID\"=>\"67890\",\n            \"Name\"=>\"disk-0\",\n            \"Path\"=>\"C:/Users/vagrant/disks/disk-0.vhdx\",\n            \"ControllerLocation\"=>1,\n            \"ControllerNumber\"=>0\n          },\n          {\n            \"UUID\"=>\"324bbb53-d5ad-45f8-9bfa-1f2468b199a8\",\n            \"Path\"=>\"C:/Users/vagrant/disks/disk-1.vhdx\",\n            \"Name\"=>\"disk-1\",\n            \"ControllerLocation\"=>2,\n            \"ControllerNumber\"=>0\n          }\n        ]\n      end\n\n      it \"does nothing if all disks are properly configured\" do\n        expect(subject).to receive(:get_current_disk).\n          with(machine, defined_disks[1], all_disks).and_return(all_disks[1])\n\n        expect(subject).to receive(:compare_disk_size).\n          with(machine, defined_disks[1], all_disks[1]).and_return(false)\n\n        subject.handle_configure_disk(machine, defined_disks[1], all_disks)\n      end\n    end\n  end\n\n  context \"#compare_disk_size\" do\n    let(:disk_config_small) do\n      double(\"disk\",\n        name: \"disk-0\",\n        size: 41824.0,\n        primary: false,\n        type: :disk\n      )\n    end\n    let(:disk_config_large) do\n      double(\"disk\",\n        name: \"disk-0\",\n        size: 123568719476736.0,\n        primary: false,\n        type: :disk\n      )\n    end\n\n    let(:disk_large) do\n      [\n        {\n          \"UUID\" => \"12345\",\n          \"Path\" => \"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx\",\n          \"ControllerLocation\" => 0,\n          \"ControllerNumber\" => 0\n        }\n      ]\n    end\n\n    let(:disk_small) do\n      {\n        \"UUID\" => \"67890\",\n        \"Path\" => \"C:/Users/vagrant/disks/small_disk.vhd\",\n        \"Size\" => 1073741824.0,\n        \"ControllerLocation\" => 1,\n        \"ControllerNumber\" => 0\n      }\n    end\n\n    it \"shows a warning if user attempts to shrink size of a vhd disk\" do\n      expect(machine.ui).to receive(:warn)\n      expect(driver).to receive(:get_disk).with(all_disks[1][\"Path\"]).and_return(disk_small)\n\n      expect(subject.compare_disk_size(machine, disk_config_small, all_disks[1])).to be_falsey\n    end\n\n    it \"returns true if requested size is bigger than current size\" do\n      expect(driver).to receive(:get_disk).with(all_disks[2][\"Path\"]).and_return(disk_small)\n      expect(subject.compare_disk_size(machine, disk_config_large, all_disks[2])).to be_truthy\n    end\n  end\n\n  context \"#create_disk\" do\n    let(:disk_provider_config) { {} }\n    let(:disk_config) do\n      double(\"disk\",\n        name: \"disk-0\",\n        size: 1073741824.0,\n        primary: false,\n        type: :disk,\n        disk_ext: \"vhdx\",\n        provider_config: disk_provider_config,\n        file: nil\n      )\n    end\n\n    let(:disk_file) { \"C:/Users/vagrant/disks/Virtual Hard Disks/disk-0.vhdx\" }\n\n    let(:data_dir) { Pathname.new(\"C:/Users/vagrant/disks\") }\n\n    let(:disk) do\n      {\n        \"DiskIdentifier\" => \"12345\",\n        \"Path\" => \"C:/Users/vagrant/disks/Virtual Hard Disks/disk-0.vhdx\",\n        \"ControllerLocation\" => 1,\n        \"ControllerNumber\" => 0\n      }\n    end\n\n    it \"creates a disk and attaches it to a guest\" do\n      expect(machine).to receive(:data_dir).and_return(data_dir)\n      expect(driver).to receive(:create_disk).with(disk_file, disk_config.size)\n      expect(driver).to receive(:get_disk).with(disk_file).and_return(disk)\n\n      expect(driver).to receive(:attach_disk).with(disk_file)\n\n      subject.create_disk(machine, disk_config)\n    end\n  end\n\n  context \"#convert_size_vars!\" do\n    let(:disk_provider_config) do\n      {\n        BlockSizeBytes: \"128MB\",\n        LogicalSectorSizeBytes: 512,\n        PhysicalSectorSizeBytes: 4096\n      }\n    end\n\n    it \"converts certain powershell arguments into something usable\" do\n      updated_config = subject.convert_size_vars!(disk_provider_config)\n\n      expect(updated_config[:BlockSizeBytes]).to eq(134217728)\n      expect(updated_config[:LogicalSectorSizeBytes]).to eq(512)\n      expect(updated_config[:PhysicalSectorSizeBytes]).to eq(4096)\n    end\n  end\n\n  context \"#resize_disk\" do\n    let(:disk_config) do\n      double(\"disk\",\n        name: \"disk-0\",\n        size: 1073741824.0,\n        primary: false,\n        type: :disk,\n        disk_ext: \"vhdx\",\n        provider_config: nil,\n        file: nil\n      )\n    end\n\n    let(:disk) do\n      {\n        \"DiskIdentifier\" => \"12345\",\n        \"Path\" => \"C:/Users/vagrant/disks/disk-0.vhdx\",\n        \"ControllerLocation\" => 1,\n        \"ControllerNumber\" => 0\n      }\n    end\n\n    let(:disk_file) { \"C:/Users/vagrant/disks/disk-0.vhdx\" }\n\n    it \"resizes the disk\" do\n      expect(driver).to receive(:get_disk).with(disk_file).and_return(disk)\n      expect(driver).to receive(:resize_disk).with(disk_file, disk_config.size.to_i).and_return(true)\n\n      subject.resize_disk(machine, disk_config, all_disks[1])\n    end\n  end\nend\n\nval =<<-EOF\n{\n    \"ControllerNumber\":  0,\n    \"IsTemplate\":  false,\n    \"Drives\":  [\n                   {\n                       \"Path\":  \"C:\\\\Users\\\\vagrant\\\\project\\\\.vagrant\\\\machines\\\\default\\\\hyperv\\\\Virtual Hard Disks\\\\ubuntu-18.04-amd64.vhdx\",\n                       \"DiskNumber\":  null,\n                       \"MaximumIOPS\":  0,\n                       \"MinimumIOPS\":  0,\n                       \"QoSPolicyID\":  \"00000000-0000-0000-0000-000000000000\",\n                       \"SupportPersistentReservations\":  false,\n                       \"WriteHardeningMethod\":  0,\n                       \"ControllerLocation\":  0,\n                       \"ControllerNumber\":  0,\n                       \"ControllerType\":  1,\n                       \"Name\":  \"Hard Drive on SCSI controller number 0 at location 0\",\n                       \"PoolName\":  \"Primordial\",\n                       \"Id\":  \"Microsoft:6F225311-B793-49CF-98A3-0A32108E49BB\\\\6AEC67E1-3135-401C-BB23-9FE1C4E34560\\\\0\\\\0\\\\D\",\n                       \"VMId\":  \"6f225311-b793-49cf-98a3-0a32108e49bb\",\n                       \"VMName\":  \"project_default_1744059721263_2993\",\n                       \"VMSnapshotId\":  \"00000000-0000-0000-0000-000000000000\",\n                       \"VMSnapshotName\":  \"\",\n                       \"CimSession\":  {\n                                          \"ComputerName\":  null,\n                                          \"InstanceId\":  \"899e8c1f-5c4f-4ba4-86a4-f72dc887885f\"\n                                      },\n                       \"ComputerName\":  \"DESKTOP-GICAJ17\",\n                       \"IsDeleted\":  false\n                   },\n                   {\n                       \"DvdMediaType\":  1,\n                       \"Path\":  \"C:\\\\Users\\\\Vagrant\\\\deb2.iso\",\n                       \"ControllerLocation\":  2,\n                       \"ControllerNumber\":  0,\n                       \"ControllerType\":  1,\n                       \"Name\":  \"DVD Drive on SCSI controller number 0 at location 2\",\n                       \"PoolName\":  \"Primordial\",\n                       \"Id\":  \"Microsoft:6F225311-B793-49CF-98A3-0A32108E49BB\\\\6AEC67E1-3135-401C-BB23-9FE1C4E34560\\\\0\\\\2\\\\D\",\n                       \"VMId\":  \"6f225311-b793-49cf-98a3-0a32108e49bb\",\n                       \"VMName\":  \"project_default_1744059721263_2993\",\n                       \"VMSnapshotId\":  \"00000000-0000-0000-0000-000000000000\",\n                       \"VMSnapshotName\":  \"\",\n                       \"CimSession\":  {\n                                          \"ComputerName\":  null,\n                                          \"InstanceId\":  \"899e8c1f-5c4f-4ba4-86a4-f72dc887885f\"\n                                      },\n                       \"ComputerName\":  \"DESKTOP-GICAJ17\",\n                       \"IsDeleted\":  false\n                   }\n               ],\n    \"Name\":  \"SCSI Controller\",\n    \"Id\":  \"Microsoft:6F225311-B793-49CF-98A3-0A32108E49BB\\\\6AEC67E1-3135-401C-BB23-9FE1C4E34560\\\\0\",\n    \"VMId\":  \"6f225311-b793-49cf-98a3-0a32108e49bb\",\n    \"VMName\":  \"project_default_1744059721263_2993\",\n    \"VMSnapshotId\":  \"00000000-0000-0000-0000-000000000000\",\n    \"VMSnapshotName\":  \"\",\n    \"CimSession\":  {\n                       \"ComputerName\":  null,\n                       \"InstanceId\":  \"899e8c1f-5c4f-4ba4-86a4-f72dc887885f\"\n                   },\n    \"ComputerName\":  \"DESKTOP-GICAJ17\",\n    \"IsDeleted\":  false,\n    \"VMCheckpointId\":  \"00000000-0000-0000-0000-000000000000\",\n    \"VMCheckpointName\":  \"\"\n}\nEOF\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/config\")\n\ndescribe VagrantPlugins::HyperV::Config do\n\n  let(:machine){ double(\"machine\", ui: ui) }\n  let(:ui){ Vagrant::UI::Silent.new }\n\n  describe \"#ip_address_timeout\" do\n    it \"can be set\" do\n      subject.ip_address_timeout = 180\n      subject.finalize!\n      expect(subject.ip_address_timeout).to eq(180)\n    end\n    it \"defaults to a number\" do\n      subject.finalize!\n      expect(subject.ip_address_timeout).to eq(120)\n    end\n  end\n\n  describe \"#vlan_id\" do\n    it \"can be set\" do\n      subject.vlan_id = 100\n      subject.finalize!\n      expect(subject.vlan_id).to eq(100)\n    end\n  end\n\n  describe \"#mac\" do\n    it \"can be set\" do\n      subject.mac = \"001122334455\"\n      subject.finalize!\n      expect(subject.mac).to eq(\"001122334455\")\n    end\n  end\n\n  describe \"#vmname\" do\n    it \"can be set\" do\n      subject.vmname = \"test\"\n      subject.finalize!\n      expect(subject.vmname).to eq(\"test\")\n    end\n  end\n\n  describe \"#memory\" do\n    it \"can be set\" do\n      subject.memory = 512\n      subject.finalize!\n      expect(subject.memory).to eq(512)\n    end\n  end\n\n  describe \"#maxmemory\" do\n    it \"can be set\" do\n      subject.maxmemory = 1024\n      subject.finalize!\n      expect(subject.maxmemory).to eq(1024)\n    end\n  end\n\n  describe \"#cpus\" do\n    it \"can be set\" do\n      subject.cpus = 2\n      subject.finalize!\n      expect(subject.cpus).to eq(2)\n    end\n  end\n\n  describe \"#vmname\" do\n    it \"can be set\" do\n      subject.vmname = \"custom\"\n      subject.finalize!\n      expect(subject.vmname).to eq(\"custom\")\n    end\n  end\n\n  describe \"#differencing_disk\" do\n    it \"is false by default\" do\n      subject.finalize!\n      expect(subject.differencing_disk).to eq(false)\n    end\n\n    it \"can be set\" do\n      subject.differencing_disk = true\n      subject.finalize!\n      expect(subject.differencing_disk).to eq(true)\n    end\n\n    it \"should set linked_clone\" do\n      subject.differencing_disk = true\n      subject.finalize!\n      expect(subject.differencing_disk).to eq(true)\n      expect(subject.linked_clone).to eq(true)\n    end\n\n    it \"should provide a deprecation warning when set\" do\n      expect(ui).to receive(:warn)\n      subject.differencing_disk = true\n      subject.finalize!\n      subject.validate(machine)\n    end\n  end\n\n  describe \"#linked_clone\" do\n    it \"is false by default\" do\n      subject.finalize!\n      expect(subject.linked_clone).to eq(false)\n    end\n\n    it \"can be set\" do\n      subject.linked_clone = true\n      subject.finalize!\n      expect(subject.linked_clone).to eq(true)\n    end\n\n    it \"should set differencing_disk\" do\n      subject.linked_clone = true\n      subject.finalize!\n      expect(subject.linked_clone).to eq(true)\n      expect(subject.differencing_disk).to eq(true)\n    end\n  end\n\n  describe \"#auto_start_action\" do\n    it \"should be Nothing by default\" do\n      subject.finalize!\n      expect(subject.auto_start_action).to eq(\"Nothing\")\n    end\n\n    it \"can be set\" do\n      subject.auto_start_action = \"Start\"\n      subject.finalize!\n      expect(subject.auto_start_action).to eq(\"Start\")\n    end\n\n    it \"does not accept invalid values\" do\n      subject.auto_start_action = \"Invalid\"\n      subject.finalize!\n      result = subject.validate(machine)\n      expect(result[\"Hyper-V\"]).not_to be_empty\n    end\n  end\n\n  describe \"#auto_stop_action\" do\n    it \"should be ShutDown by default\" do\n      subject.finalize!\n      expect(subject.auto_stop_action).to eq(\"ShutDown\")\n    end\n\n    it \"can be set\" do\n      subject.auto_stop_action = \"Save\"\n      subject.finalize!\n      expect(subject.auto_stop_action).to eq(\"Save\")\n    end\n\n    it \"does not accept invalid values\" do\n      subject.auto_stop_action = \"Invalid\"\n      subject.finalize!\n      result = subject.validate(machine)\n      expect(result[\"Hyper-V\"]).not_to be_empty\n    end\n  end\n\n  describe \"#enable_checkpoints\" do\n    it \"is true by default\" do\n      subject.finalize!\n      expect(subject.enable_checkpoints).to eq(true)\n    end\n\n    it \"can be set\" do\n      subject.enable_checkpoints = false\n      subject.finalize!\n      expect(subject.enable_checkpoints).to eq(false)\n    end\n\n    it \"is enabled automatically when enable_automatic_checkpoints is enabled\" do\n      subject.enable_checkpoints = false\n      subject.enable_automatic_checkpoints = true\n      subject.finalize!\n      expect(subject.enable_checkpoints).to eq(true)\n    end\n  end\n\n  describe \"#enable_automatic_checkpoints\" do\n    it \"is false by default\" do\n      subject.finalize!\n      expect(subject.enable_automatic_checkpoints).to eq(false)\n    end\n\n    it \"can be set\" do\n      subject.enable_checkpoints = true\n      subject.finalize!\n      expect(subject.enable_checkpoints).to eq(true)\n    end\n  end\n\n  describe \"#enable_virtualization_extensions\" do\n    it \"is false by default\" do\n      subject.finalize!\n      expect(subject.enable_virtualization_extensions).to eq(false)\n    end\n\n    it \"can be set\" do\n      subject.enable_virtualization_extensions = true\n      subject.finalize!\n      expect(subject.enable_virtualization_extensions).to eq(true)\n    end\n  end\n\n  describe \"#vm_integration_services\" do\n    it \"is empty by default\" do\n      subject.finalize!\n      expect(subject.vm_integration_services).to be_empty\n    end\n\n    it \"accepts new entries\" do\n      subject.vm_integration_services[\"entry\"] = \"value\"\n      subject.finalize!\n      expect(subject.vm_integration_services[\"entry\"]).to eq(\"value\")\n    end\n\n    it \"does not accept non-Hash types\" do\n      subject.vm_integration_services = \"value\"\n      subject.finalize!\n      result = subject.validate(machine)\n      expect(result[\"Hyper-V\"]).not_to be_empty\n    end\n\n    it \"accepts boolean values within Hash\" do\n      subject.vm_integration_services[\"custom\"] = true\n      subject.finalize!\n      result = subject.validate(machine)\n      expect(result[\"Hyper-V\"]).to be_empty\n    end\n\n    it \"does not accept non-boolean values within Hash\" do\n      subject.vm_integration_services[\"custom\"] = \"value\"\n      subject.finalize!\n      result = subject.validate(machine)\n      expect(result[\"Hyper-V\"]).not_to be_empty\n    end\n  end\n\n\n  describe \"#enable_enhanced_session_mode\" do\n    it \"is false by default\" do\n      subject.finalize!\n      expect(subject.enable_enhanced_session_mode).to eq(false)\n    end\n\n    it \"can be set\" do\n      subject.enable_enhanced_session_mode = true\n      subject.finalize!\n      expect(subject.enable_enhanced_session_mode).to eq(true)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/driver_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/driver\")\n\ndescribe VagrantPlugins::HyperV::Driver do\n  def generate_result(obj)\n    \"===Begin-Output===\\n\" +\n      JSON.dump(obj) +\n      \"\\n===End-Output===\"\n  end\n\n  def generate_error(msg)\n    \"===Begin-Error===\\n#{JSON.dump(error: msg)}\\n===End-Error===\\n\"\n  end\n\n  let(:result){\n    Vagrant::Util::Subprocess::Result.new(\n      result_exit, result_stdout, result_stderr) }\n  let(:subject){ described_class.new(vm_id) }\n  let(:vm_id){ 1 }\n  let(:result_stdout){ \"\" }\n  let(:result_stderr){ \"\" }\n  let(:result_exit){ 0 }\n\n  context \"public methods\" do\n    before{ allow(subject).to receive(:execute_powershell).and_return(result) }\n\n    describe \"#execute\" do\n      it \"should convert symbol into path string\" do\n        expect(subject).to receive(:execute_powershell).with(kind_of(String), any_args)\n          .and_return(result)\n        subject.execute(:thing)\n      end\n\n      it \"should append extension when converting symbol\" do\n        expect(subject).to receive(:execute_powershell).with(\"thing.ps1\", any_args)\n          .and_return(result)\n        subject.execute(:thing)\n      end\n\n      context \"when command returns non-zero exit code\" do\n        let(:result_exit){ 1 }\n\n        it \"should raise an error\" do\n          expect{ subject.execute(:thing) }.to raise_error(VagrantPlugins::HyperV::Errors::PowerShellError)\n        end\n      end\n\n      context \"when command stdout matches error pattern\" do\n        let(:result_stdout){ generate_error(\"Error Message\") }\n\n        it \"should raise an error\" do\n          expect{ subject.execute(:thing) }.to raise_error(VagrantPlugins::HyperV::Errors::PowerShellError)\n        end\n      end\n\n      context \"with valid JSON output\" do\n        let(:result_stdout){ generate_result(:custom => \"value\") }\n\n        it \"should return parsed JSON data\" do\n          expect(subject.execute(:thing)).to eq(\"custom\" => \"value\")\n        end\n      end\n\n      context \"with invalid JSON output\" do\n        let(:result_stdout){ \"value\" }\n        it \"should return nil\" do\n          expect(subject.execute(:thing)).to be_nil\n        end\n      end\n    end\n\n    describe \"#has_vmcx_support?\" do\n      context \"when support is available\" do\n        let(:result_stdout){ generate_result(:result => true) }\n\n        it \"should be true\" do\n          expect(subject.has_vmcx_support?).to eq(true)\n        end\n      end\n\n      context \"when support is not available\" do\n        let(:result_stdout){ generate_result(:result => false) }\n\n        it \"should be false\" do\n          expect(subject.has_vmcx_support?).to eq(false)\n        end\n      end\n    end\n\n    describe \"#set_vm_integration_services\" do\n      it \"should map known integration services names automatically\" do\n        expect(subject).to receive(:execute) do |name, args|\n          expect(args[:Id]).to eq(VagrantPlugins::HyperV::Driver::INTEGRATION_SERVICES_MAP[:shutdown])\n        end\n        subject.set_vm_integration_services(shutdown: true)\n      end\n\n      it \"should set enable when value is true\" do\n        expect(subject).to receive(:execute) do |name, args|\n          expect(args[:Enable]).to eq(true)\n        end\n        subject.set_vm_integration_services(shutdown: true)\n      end\n\n      it \"should not set enable when value is false\" do\n        expect(subject).to receive(:execute) do |name, args|\n          expect(args[:Enable]).to be_nil\n        end\n        subject.set_vm_integration_services(shutdown: false)\n      end\n\n      it \"should pass unknown key names directly through\" do\n        expect(subject).to receive(:execute) do |name, args|\n          expect(args[:Id]).to eq(\"CustomKey\")\n        end\n        subject.set_vm_integration_services(CustomKey: true)\n      end\n    end\n  end\n\n  describe \"#execute_powershell\" do\n    before{ allow(Vagrant::Util::PowerShell).to receive(:execute) }\n\n    it \"should call the PowerShell module to execute\" do\n      expect(Vagrant::Util::PowerShell).to receive(:execute)\n      subject.send(:execute_powershell, \"path\", {})\n    end\n\n    it \"should modify the path separators\" do\n      expect(Vagrant::Util::PowerShell).to receive(:execute)\n        .with(\"\\\\path\\\\to\\\\script.ps1\", any_args)\n      subject.send(:execute_powershell, \"/path/to/script.ps1\", {})\n    end\n\n    it \"should include ErrorAction option as Stop\" do\n      expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args|\n        expect(args).to include(\"-ErrorAction\")\n        expect(args).to include(\"Stop\")\n      end\n      subject.send(:execute_powershell, \"path\", {})\n    end\n\n    it \"should automatically include module path\" do\n      expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args|\n        opts = args.detect{|i| i.is_a?(Hash)}\n        expect(opts[:module_path]).not_to be_nil\n      end\n      subject.send(:execute_powershell, \"path\", {})\n    end\n\n    it \"should covert hash options into arguments\" do\n      expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args|\n        expect(args).to include(\"-Custom\")\n        expect(args).to include(\"'Value'\")\n      end\n      subject.send(:execute_powershell, \"path\", \"Custom\" => \"Value\")\n    end\n\n    it \"should treat keys with `true` value as switches\" do\n      expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args|\n        expect(args).to include(\"-Custom\")\n        expect(args).not_to include(\"'true'\")\n      end\n      subject.send(:execute_powershell, \"path\", \"Custom\" => true)\n    end\n\n    it \"should not include keys with `false` value\" do\n      expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args|\n        expect(args).not_to include(\"-Custom\")\n      end\n      subject.send(:execute_powershell, \"path\", \"Custom\" => false)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/hyperv/provider_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/hyperv/provider\")\n\ndescribe VagrantPlugins::HyperV::Provider do\n  let(:driver){ double(\"driver\") }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:provider_config){ double(\"provider_config\", ip_address_timeout: ip_address_timeout) }\n  let(:ip_address_timeout){ 1 }\n  let(:machine){ double(\"machine\", provider: provider, provider_config: provider_config) }\n\n  let(:platform)   { double(\"platform\") }\n  let(:powershell) { double(\"powershell\") }\n\n  subject { described_class.new(machine) }\n\n  before do\n    stub_const(\"Vagrant::Util::Platform\", platform)\n    stub_const(\"Vagrant::Util::PowerShell\", powershell)\n    allow(machine).to receive(:id).and_return(\"foo\")\n    allow(platform).to receive(:windows?).and_return(true)\n    allow(platform).to receive(:wsl?).and_return(false)\n    allow(platform).to receive(:windows_admin?).and_return(true)\n    allow(platform).to receive(:windows_hyperv_admin?).and_return(true)\n    allow(powershell).to receive(:available?).and_return(true)\n  end\n\n  describe \".usable?\" do\n    subject { described_class }\n\n    it \"returns false if not windows\" do\n      allow(platform).to receive(:windows?).and_return(false)\n      expect(subject).to_not be_usable\n    end\n\n    it \"returns true if within WSL\" do\n      expect(platform).to receive(:windows?).and_return(false)\n      expect(platform).to receive(:wsl?).and_return(true)\n      expect(subject).to be_usable\n    end\n\n    it \"returns false if neither an admin nor a hyper-v admin\" do\n      allow(platform).to receive(:windows_admin?).and_return(false)\n      allow(platform).to receive(:windows_hyperv_admin?).and_return(false)\n      expect(subject).to_not be_usable\n    end\n\n    it \"returns true if not an admin but is a hyper-v admin\" do\n      allow(platform).to receive(:windows_admin?).and_return(false)\n      allow(platform).to receive(:windows_hyperv_admin?).and_return(true)\n      expect(subject).to be_usable\n    end\n\n    it \"returns false if powershell is not available\" do\n      allow(powershell).to receive(:available?).and_return(false)\n      expect(subject).to_not be_usable\n    end\n\n    it \"raises an exception if not windows\" do\n      allow(platform).to receive(:windows?).and_return(false)\n\n      expect { subject.usable?(true) }.\n        to raise_error(VagrantPlugins::HyperV::Errors::WindowsRequired)\n    end\n\n    it \"raises an exception if neither an admin nor a hyper-v admin\" do\n      allow(platform).to receive(:windows_admin?).and_return(false)\n      allow(platform).to receive(:windows_hyperv_admin?).and_return(false)\n\n      expect { subject.usable?(true) }.\n        to raise_error(VagrantPlugins::HyperV::Errors::AdminRequired)\n    end\n\n    it \"raises an exception if neither an admin nor a hyper-v admin\" do\n      allow(platform).to receive(:windows_admin?).and_return(false)\n      allow(platform).to receive(:windows_hyperv_admin?).and_return(false)\n\n      expect { subject.usable?(true) }.\n        to raise_error(VagrantPlugins::HyperV::Errors::AdminRequired)\n    end\n\n    it \"raises an exception if powershell is not available\" do\n      allow(powershell).to receive(:available?).and_return(false)\n\n      expect { subject.usable?(true) }.\n        to raise_error(VagrantPlugins::HyperV::Errors::PowerShellRequired)\n    end\n  end\n\n  describe \"#driver\" do\n    it \"is initialized\" do\n      expect(subject.driver).to be_kind_of(VagrantPlugins::HyperV::Driver)\n    end\n  end\n\n  describe \"#state\" do\n    it \"returns not_created if no ID\" do\n      allow(machine).to receive(:id).and_return(nil)\n\n      expect(subject.state.id).to eq(:not_created)\n    end\n\n    it \"calls an action to determine the ID\" do\n      allow(machine).to receive(:id).and_return(\"foo\")\n      expect(machine).to receive(:action).with(:read_state).\n        and_return({ machine_state_id: :bar })\n\n      expect(subject.state.id).to eq(:bar)\n    end\n  end\n\n  describe \"#ssh_info\" do\n    let(:result) { \"127.0.0.1\" }\n    let(:exit_code) { 0 }\n    let(:ssh_info) {{:host=>result,:port=>22}}\n\n    before do\n      allow(VagrantPlugins::HyperV::Driver).to receive(:new).and_return(driver)\n      allow(machine).to receive(:action).with(:read_state).and_return(machine_state_id: :running)\n    end\n\n    it \"returns nil if a PowerShellError is returned from the driver\" do\n      allow(driver).to receive(:read_guest_ip)\n        .and_raise(VagrantPlugins::HyperV::Errors::PowerShellError, script: anything, stderr: anything)\n      expect(subject.ssh_info).to eq(nil)\n    end\n\n    it \"should receive a valid address\" do\n      allow(driver).to receive(:execute).with(:get_network_config).and_return(result)\n\n      allow(driver).to receive(:read_guest_ip).and_return({\"ip\" => \"127.0.0.1\"})\n      expect(subject.ssh_info).to eq(ssh_info)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/action/clean_machine_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../base'\n\ndescribe VagrantPlugins::ProviderVirtualBox::Action::CleanMachineFolder do\n  let(:app) { double(\"app\") }\n  let(:driver) { double(\"driver\") }\n  let(:machine) { double(\"machine\", provider: double(\"provider\", driver: driver), name: \"\") }\n  let(:env) {\n    { machine: machine }\n  }\n  let(:subject) { described_class.new(app, env) }\n\n  before do\n    allow(driver).to receive(:read_machine_folder)\n  end\n\n  context \"machine folder is not accessible\" do\n    before do\n      allow(subject).to receive(:clean_machine_folder).and_raise(Errno::EPERM)\n    end\n\n    it \"raises an error\" do\n      expect { subject.call(env) }.to raise_error(Vagrant::Errors::MachineFolderNotAccessible)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/action/import_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative '../base'\n\ndescribe VagrantPlugins::ProviderVirtualBox::Action::Import do\n  let(:app) { double(\"app\") }\n  let(:state) { :test_state }\n  let(:machine) { double(\"machine\", state: double(\"state\", id: state)) }\n  let(:action_runner) { double(\"action_runner\") }\n  let(:env) {\n    {\n      machine: machine,\n      action_runner: action_runner,\n      destroy_on_error: destroy_on_error,\n    }\n  }\n  subject { described_class.new(app, {}) }\n\n  describe \"#recover\" do\n    context \"when destroy_on_error is false\" do\n      let(:destroy_on_error) { false }\n      it \"does nothing\" do\n        expect(action_runner).to_not receive(:run)\n        subject.recover(env)\n      end\n    end\n\n    context \"when destroy_on_error is true\" do\n      let(:destroy_on_error) { true }\n\n      context \"and machine is not_created\" do\n        let(:state) { Vagrant::MachineState::NOT_CREATED_ID }\n\n        it \"does nothing\" do\n          expect(action_runner).to_not receive(:run)\n          subject.recover(env)\n        end\n      end\n\n      context \"and machine is created\" do\n        let(:state) { :running }\n\n        it \"runs the destroy action with the proper environment\" do\n          destroy_stack = double(\"destroy_stack\")\n          allow(VagrantPlugins::ProviderVirtualBox::Action).to receive(:action_destroy) { destroy_stack }\n          expect(action_runner).to receive(:run).with(destroy_stack, hash_including(\n            config_validate: false,\n            force_confirm_destroy: true,\n            raw_action_name: :destroy,\n            action_name: :machine_action_destroy,\n          ))\n          subject.recover(env)\n        end\n\n\n\n        context \"but a VagrantError was raised\" do\n          before {\n            env[\"vagrant.error\"] = Vagrant::Errors::VagrantError.new\n          }\n\n          it \"does nothing\" do\n            expect(action_runner).to_not receive(:run)\n            subject.recover(env)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/action/match_mac_address_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Action::MatchMACAddress do\n  let(:ui) { Vagrant::UI::Silent.new }\n  let(:machine) { double(\"machine\", config: config, provider: double(\"provider\", driver: driver)) }\n  let(:driver) { double(\"driver\") }\n  let(:env) {\n    {machine: machine, ui: ui}\n  }\n  let(:app) { double(\"app\") }\n  let(:config) { double(\"config\", vm: vm) }\n  let(:vm) { double(\"vm\", clone: clone, base_mac: base_mac) }\n  let(:clone) { false }\n  let(:base_mac) { \"00:00:00:00:00:00\" }\n\n  let(:subject) { described_class.new(app, env) }\n\n  before do\n    allow(app).to receive(:call)\n  end\n\n  after { subject.call(env) }\n\n  it \"should set the mac address\" do\n    expect(driver).to receive(:set_mac_address).with(base_mac)\n  end\n\n  context \"when clone is true\" do\n    let(:clone) { true }\n\n    it \"should not set mac address\" do\n      expect(driver).not_to receive(:set_mac_address)\n    end\n  end\n\n  context \"when base_mac is falsey\" do\n    let(:base_mac) { nil }\n\n    it \"should set mac address\" do\n      expect(driver).to receive(:set_mac_address).with(base_mac)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/action/network_fix_ipv6_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\nrequire 'socket'\n\ndescribe VagrantPlugins::ProviderVirtualBox::Action::NetworkFixIPv6 do\n  include_context \"unit\"\n  include_context \"virtualbox\"\n\n  let(:iso_env) do\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|\n      allow(m.provider).to receive(:driver).and_return(driver)\n    end\n  end\n\n  let(:env) {{ machine: machine }}\n  let(:app) { lambda { |*args| }}\n  let(:driver) { double(\"driver\") }\n\n  subject { described_class.new(app, env) }\n\n  it \"ignores nil IP addresses\" do\n    allow(machine.config.vm).to receive(:networks)\n      .and_return(private_network: { ip: nil })\n    expect { subject.call(env) }.to_not raise_error\n  end\n\n  it \"blank nil IP addresses\" do\n    allow(machine.config.vm).to receive(:networks)\n      .and_return(private_network: { ip: \"\" })\n    expect { subject.call(env) }.to_not raise_error\n  end\n\n  context \"with IPv6 interfaces\" do\n    let(:socket) { double(\"socket\") }\n\n    before do\n      # This address is only used to trigger the fixup code. It doesn't matter\n      # what it is.\n      allow(machine.config.vm).to receive(:networks)\n        .and_return(private_network: { ip: 'fe:80::' })\n      allow(UDPSocket).to receive(:new).with(Socket::AF_INET6)\n        .and_return(socket)\n      allow(socket).to receive(:connect)\n    end\n\n    it \"only checks the interfaces associated with the VM\" do\n      all_networks = [{name: \"vboxnet0\",\n                       ipv6: \"dead:beef::\",\n                       ipv6_prefix: 64,\n                       status: 'Up'\n                      },\n                      {name: \"vboxnet1\",\n                       ipv6: \"badd:badd::\",\n                       ipv6_prefix: 64,\n                       status: 'Up'\n                      }\n                     ]\n      ifaces = { 1 => {type: :hostonly, hostonly: \"vboxnet0\"}\n               }\n      allow(machine.provider.driver).to receive(:read_network_interfaces)\n        .and_return(ifaces)\n      allow(machine.provider.driver).to receive(:read_host_only_interfaces)\n        .and_return(all_networks)\n      subject.call(env)\n      expect(socket).to have_received(:connect)\n        .with(all_networks[0][:ipv6] + (['ffff']*4).join(':'), 80)\n    end\n\n    it \"correctly uses the netmask to figure out the probe address\" do\n      all_networks = [{name: \"vboxnet0\",\n                       ipv6: \"dead:beef::\",\n                       ipv6_prefix: 113,\n                       status: 'Up'\n                      }\n                     ]\n      ifaces = { 1 => {type: :hostonly, hostonly: \"vboxnet0\"}\n               }\n      allow(machine.provider.driver).to receive(:read_network_interfaces)\n        .and_return(ifaces)\n      allow(machine.provider.driver).to receive(:read_host_only_interfaces)\n        .and_return(all_networks)\n      subject.call(env)\n      expect(socket).to have_received(:connect)\n        .with(all_networks[0][:ipv6] + '7fff', 80)\n    end\n\n    it \"should ignore interfaces that are down\" do\n      all_networks = [{name: \"vboxnet0\",\n                       ipv6: \"dead:beef::\",\n                       ipv6_prefix: 64,\n                       status: 'Down'\n                      }\n                     ]\n      ifaces = { 1 => {type: :hostonly, hostonly: \"vboxnet0\"}\n               }\n      allow(machine.provider.driver).to receive(:read_network_interfaces)\n        .and_return(ifaces)\n      allow(machine.provider.driver).to receive(:read_host_only_interfaces)\n        .and_return(all_networks)\n      subject.call(env)\n      expect(socket).to_not have_received(:connect)\n    end\n\n    it \"should ignore interfaces without an IPv6 address\" do\n      all_networks = [{name: \"vboxnet0\",\n                       ipv6: \"\",\n                       ipv6_prefix: 0,\n                       status: 'Up'\n                      }\n                     ]\n      ifaces = { 1 => {type: :hostonly, hostonly: \"vboxnet0\"}\n               }\n      allow(machine.provider.driver).to receive(:read_network_interfaces)\n        .and_return(ifaces)\n      allow(machine.provider.driver).to receive(:read_host_only_interfaces)\n        .and_return(all_networks)\n      subject.call(env)\n      expect(socket).to_not have_received(:connect)\n    end\n\n    it \"should ignore interfaces with link-local IPv6 address\" do\n      all_networks = [{name: \"vboxnet0\",\n        ipv6: \"fe80::ffff:ffff:ffff:ffff\",\n        ipv6_prefix: 64,\n        status: 'Up'\n      }\n      ]\n      ifaces = { 1 => {type: :hostonly, hostonly: \"vboxnet0\"}\n      }\n      allow(machine.provider.driver).to receive(:read_network_interfaces)\n        .and_return(ifaces)\n      allow(machine.provider.driver).to receive(:read_host_only_interfaces)\n        .and_return(all_networks)\n      subject.call(env)\n      expect(socket).to_not have_received(:connect)\n    end\n\n    it \"should ignore nat interfaces\" do\n      all_networks = [{name: \"vboxnet0\",\n                       ipv6: \"\",\n                       ipv6_prefix: 0,\n                       status: 'Up'\n                      }\n                     ]\n      ifaces = { 1 => {type: :nat}\n               }\n      allow(machine.provider.driver).to receive(:read_network_interfaces)\n        .and_return(ifaces)\n      allow(machine.provider.driver).to receive(:read_host_only_interfaces)\n        .and_return(all_networks)\n      subject.call(env)\n      expect(socket).to_not have_received(:connect)\n    end\n\n    it \"should reconfigure an interface if unreachable\" do\n      all_networks = [{name: \"vboxnet0\",\n                       ipv6: \"dead:beef::\",\n                       ipv6_prefix: 64,\n                       status: 'Up'\n                      }\n                     ]\n      ifaces = { 1 => {type: :hostonly, hostonly: \"vboxnet0\"}\n               }\n      allow(machine.provider.driver).to receive(:read_network_interfaces)\n        .and_return(ifaces)\n      allow(machine.provider.driver).to receive(:read_host_only_interfaces)\n        .and_return(all_networks)\n      allow(socket).to receive(:connect)\n        .with(all_networks[0][:ipv6] + (['ffff']*4).join(':'), 80)\n        .and_raise Errno::EHOSTUNREACH\n      allow(machine.provider.driver).to receive(:reconfig_host_only)\n      subject.call(env)\n      expect(machine.provider.driver).to have_received(:reconfig_host_only)\n        .with(all_networks[0])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/action/network_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\nrequire \"vagrant/util/platform\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Action::Network do\n  include_context \"unit\"\n  include_context \"virtualbox\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :virtualbox).tap do |m|\n      allow(m.provider).to receive(:driver).and_return(driver)\n    end\n  end\n\n  let(:env)    {{ machine: machine, ui: machine.ui }}\n  let(:app)    { lambda { |*args| }}\n  let(:driver) { double(\"driver\", version: vbox_version) }\n  let(:vbox_version) { \"6.1.0\" }\n\n  let(:nics)         { {} }\n\n  subject { described_class.new(app, env) }\n\n  before do\n    allow(driver).to receive(:enable_adapters)\n    allow(driver).to receive(:read_network_interfaces)   { nics }\n  end\n\n  describe \"#hostonly_config\" do\n    before do\n      allow(subject).to receive(:hostonly_find_matching_network)\n      allow(driver).to receive(:read_bridged_interfaces).and_return([])\n      subject.instance_eval do\n        def env=(e)\n          @env = e\n        end\n      end\n\n      subject.env = env\n    end\n\n    let(:options) {\n      {\n        type: type,\n        ip: address,\n      }\n    }\n    let(:type) { :dhcp }\n    let(:address) { nil }\n\n    it \"should validate the IP\" do\n      expect(subject).to receive(:validate_hostonly_ip!)\n      subject.hostonly_config(options)\n    end\n\n    context \"when address is ipv6\" do\n      let(:address) { \"::1\" }\n\n      context \"when type is static6\" do\n        let(:type) { :static6 }\n\n        it \"should have a static6 type\" do\n          result = subject.hostonly_config(options)\n          expect(result[:type]).to eq(:static6)\n        end\n      end\n\n      context \"when type is static\" do\n        let(:type) { :static }\n\n        it \"should have static6 type\" do\n          result = subject.hostonly_config(options)\n          expect(result[:type]).to eq(:static6)\n        end\n      end\n    end\n\n    context \"when name is provided as interface name\" do\n      let(:options) {\n        {\n          type: type,\n          ip: address,\n          name: name\n        }\n      }\n      let(:name) { \"hostonly_ifname\" }\n      let(:display_name) { \"HostInterfaceNetworking-hostonly_ifname\" }\n      let(:hostonly_networks) do\n        [\n          {\n            name: name,\n            display_name: display_name\n          }\n        ]\n      end\n\n      before { allow(driver).to receive(:read_host_only_interfaces).and_return(hostonly_networks) }\n\n      it \"should lookup host only networks\" do\n        expect(driver).to receive(:read_host_only_interfaces).and_return(hostonly_networks)\n\n        subject.hostonly_config(options)\n      end\n\n      it \"should not change the name\" do\n        expect(subject.hostonly_config(options)[:name]) == name\n      end\n\n      context \"when display name is provided in options\" do\n        let(:options) {\n          {\n            type: type,\n            ip: address,\n            name: display_name\n          }\n        }\n\n        it \"should change the name to the interface name\" do\n          expect(subject.hostonly_config(options)[:name]) == name\n        end\n      end\n    end\n  end\n\n  describe \"#validate_hostonly_ip!\" do\n    let(:address) { \"192.168.1.2\" }\n    let(:net_conf) { [IPAddr.new(address + \"/24\")]}\n    let(:vbox_version) { \"6.1.28\" }\n\n    before do\n      expect(subject).to receive(:validate_hostonly_ip!).and_call_original\n    end\n\n    context \"when configuration file exists\" do\n      before do\n        allow(subject).to receive(:load_net_conf).and_return(net_conf)\n      end\n\n      it \"should load net configuration\" do\n        expect(subject).to receive(:load_net_conf).and_return(net_conf)\n        subject.validate_hostonly_ip!(address, driver)\n      end\n\n      context \"when address is within ranges\" do\n        it \"should not error\" do\n          subject.validate_hostonly_ip!(address, driver)\n        end\n      end\n\n      context \"when address is not found within ranges\" do\n        let(:net_conf) { [IPAddr.new(\"127.0.0.1/20\")] }\n\n        it \"should raise an error\" do\n          expect {\n            subject.validate_hostonly_ip!(address, driver)\n          }.to raise_error(Vagrant::Errors::VirtualBoxInvalidHostSubnet)\n        end\n      end\n\n      context \"when virtualbox version does not restrict range\" do\n        let(:vbox_version) { \"6.1.20\" }\n\n        it \"should not error\" do\n          subject.validate_hostonly_ip!(address, driver)\n        end\n\n        it \"should not attempt to load network configuration\" do\n          expect(subject).not_to receive(:load_net_conf)\n          subject.validate_hostonly_ip!(address, driver)\n        end\n      end\n\n      context \"when platform is windows\" do\n        before do\n          allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n        end\n\n        it \"should not error\" do\n          subject.validate_hostonly_ip!(address, driver)\n        end\n\n        it \"should not attempt to load network configuration\" do\n          expect(subject).not_to receive(:load_net_conf)\n          subject.validate_hostonly_ip!(address, driver)\n        end\n      end\n    end\n\n    context \"when configuration file does not exist\" do\n      before do\n        allow(File).to receive(:exist?).with(described_class.const_get(:VBOX_NET_CONF)).and_return(false)\n      end\n\n      context \"when ipv4 address is within range\" do\n        let(:address) { \"192.168.59.120\" }\n\n        it \"should not error\" do\n          subject.validate_hostonly_ip!(address, driver)\n        end\n      end\n\n      context \"when ipv4 address is not within range\" do\n        let(:address) { \"192.168.33.22\" }\n\n        it \"should raise an error\" do\n          expect {\n            subject.validate_hostonly_ip!(address, driver)\n          }.to raise_error(Vagrant::Errors::VirtualBoxInvalidHostSubnet)\n        end\n      end\n\n      context \"when ipv6 address is within range\" do\n        let(:address) { \"fe80:77:43:99:974:222:115:20\" }\n\n        it \"should not error\" do\n          subject.validate_hostonly_ip!(address, driver)\n        end\n      end\n\n      context \"when ipv6 address not within range\" do\n        let(:address) { \"33:77:43:99:974:222:115:20\" }\n\n        it \"should raise an error\" do\n          expect {\n            subject.validate_hostonly_ip!(address, driver)\n          }.to raise_error(Vagrant::Errors::VirtualBoxInvalidHostSubnet)\n        end\n      end\n    end\n  end\n\n  describe \"#load_net_conf\" do\n    let(:file_contents) { [\"\"] }\n\n    before do\n      allow(File).to receive(:exist?).and_call_original\n      allow(File).to receive(:exist?).\n        with(described_class.const_get(:VBOX_NET_CONF)).\n        and_return(true)\n      allow(File).to receive(:readlines).\n        with(described_class.const_get(:VBOX_NET_CONF)).\n        and_return(file_contents)\n    end\n\n    it \"should read the configuration file\" do\n      expect(File).to receive(:readlines).\n        with(described_class.const_get(:VBOX_NET_CONF)).\n        and_return(file_contents)\n\n      subject.load_net_conf\n    end\n\n    context \"when file has comments only\" do\n      let(:file_contents) {\n        [\n          \"# A comment\",\n          \"# Another comment\",\n        ]\n      }\n\n      it \"should return an empty array\" do\n        expect(subject.load_net_conf).to eq([])\n      end\n    end\n\n    context \"when file has valid range entries\" do\n      let(:file_contents) {\n        [\n          \"* 127.0.0.1/24\",\n          \"* 192.168.1.1/24\",\n        ]\n      }\n\n      it \"should return an array with content\" do\n        expect(subject.load_net_conf).not_to be_empty\n      end\n\n      it \"should include IPAddr instances\" do\n        subject.load_net_conf.each do |entry|\n          expect(entry).to be_a(IPAddr)\n        end\n      end\n    end\n\n    context \"when file has valid range entries and comments\" do\n      let(:file_contents) {\n        [\n          \"# Comment in file\",\n          \"* 127.0.0.0/8\",\n          \"random text\",\n          \" * 192.168.2.0/28\",\n        ]\n      }\n\n      it \"should contain two entries\" do\n        expect(subject.load_net_conf.size).to eq(2)\n      end\n    end\n\n    context \"when file has multiple entries on single line\" do\n      let(:file_contents) {\n        [\n          \"* 0.0.0.0/0 ::/0\"\n        ]\n      }\n\n      it \"should contain two entries\" do\n        expect(subject.load_net_conf.size).to eq(2)\n      end\n\n      it \"should contain an ipv4 and ipv6 range\" do\n        result = subject.load_net_conf\n        expect(result.first).to be_ipv4\n        expect(result.last).to be_ipv6\n      end\n    end\n  end\n\n  it \"calls the next action in the chain\" do\n    called = false\n    app = lambda { |*args| called = true }\n\n    action = described_class.new(app, env)\n    action.call(env)\n\n    expect(called).to eq(true)\n  end\n\n  it \"creates a host-only interface with an IPv6 address <prefix>:1\" do\n    guest = double(\"guest\")\n    machine.config.vm.network 'private_network', type: :static, ip: 'dead:beef::100'\n    #allow(driver).to receive(:read_bridged_interfaces) { [] }\n    allow(driver).to receive(:read_host_only_interfaces) { [] }\n    #allow(driver).to receive(:read_dhcp_servers) { [] }\n    allow(machine).to receive(:guest) { guest }\n    allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }}\n    allow(guest).to receive(:capability)\n    interface_ip = 'dead:beef::1'\n\n    subject.call(env)\n\n    expect(driver).to have_received(:create_host_only_network).with(hash_including({\n      adapter_ip: interface_ip,\n      netmask: 64,\n    }))\n\n    expect(guest).to have_received(:capability).with(:configure_networks, [{\n      type: :static6,\n      adapter_ip: 'dead:beef::1',\n      ip: 'dead:beef::100',\n      netmask: 64,\n      auto_config: true,\n      interface: nil\n    }])\n  end\n\n  it \"raises the appropriate error when provided with an invalid IP address\" do\n    machine.config.vm.network 'private_network', ip: '192.168.33.06'\n\n    expect{ subject.call(env) }.to raise_error(Vagrant::Errors::NetworkAddressInvalid)\n  end\n\n  context \"with a dhcp private network\" do\n    let(:bridgedifs)  { [] }\n    let(:hostonlyifs) { [] }\n    let(:dhcpservers) { [] }\n    let(:guest)       { double(\"guest\") }\n    let(:network_args) {{ type: :dhcp }}\n\n    before do\n      machine.config.vm.network 'private_network', **network_args\n      allow(driver).to receive(:read_bridged_interfaces) { bridgedifs }\n      allow(driver).to receive(:read_host_only_interfaces) { hostonlyifs }\n      allow(driver).to receive(:read_dhcp_servers) { dhcpservers }\n      allow(machine).to receive(:guest) { guest }\n    end\n\n    it \"tries to setup dhpc server using the ip for the specified network\" do\n      allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }}\n      allow(driver).to receive(:create_dhcp_server)\n      allow(guest).to receive(:capability)\n      allow(subject).to receive(:hostonly_find_matching_network).and_return({name: \"vboxnet1\", ip: \"192.168.55.1\"})\n\n      subject.call(env)\n\n      expect(driver).to have_received(:create_dhcp_server).with('vboxnet1', {\n        adapter_ip: \"192.168.55.1\",\n        auto_config: true,\n        ip: \"192.168.55.1\",\n        mac: nil,\n        name: nil,\n        netmask: \"255.255.255.0\",\n        nic_type: nil,\n        type: :dhcp,\n        dhcp_ip: \"192.168.55.2\",\n        dhcp_lower: \"192.168.55.3\",\n        dhcp_upper: \"192.168.55.254\",\n        adapter: 2\n      })\n\n      expect(guest).to have_received(:capability).with(:configure_networks, [{\n        type: :dhcp,\n        adapter_ip: \"192.168.55.1\",\n        ip: \"192.168.55.1\",\n        netmask: \"255.255.255.0\",\n        auto_config: true,\n        interface: nil\n      }])\n    end\n\n    it \"creates a host only interface and a dhcp server using default ips, then tells the guest to configure the network after boot\" do\n      allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }}\n      allow(driver).to receive(:create_dhcp_server)\n      allow(guest).to receive(:capability)\n      allow(subject).to receive(:hostonly_find_matching_network).and_return(nil)\n\n      subject.call(env)\n\n      expect(driver).to have_received(:create_host_only_network).with(hash_including({\n        adapter_ip: '192.168.56.1',\n        netmask: '255.255.255.0',\n      }))\n\n      expect(driver).to have_received(:create_dhcp_server).with('vboxnet0', {\n        adapter_ip: \"192.168.56.1\",\n        auto_config: true,\n        ip: \"192.168.56.1\",\n        mac: nil,\n        name: nil,\n        netmask: \"255.255.255.0\",\n        nic_type: nil,\n        type: :dhcp,\n        dhcp_ip: \"192.168.56.2\",\n        dhcp_lower: \"192.168.56.3\",\n        dhcp_upper: \"192.168.56.254\",\n        adapter: 2\n      })\n\n      expect(guest).to have_received(:capability).with(:configure_networks, [{\n        type: :dhcp,\n        adapter_ip: \"192.168.56.1\",\n        ip: \"192.168.56.1\",\n        netmask: \"255.255.255.0\",\n        auto_config: true,\n        interface: nil\n      }])\n    end\n\n    context \"when the default vbox dhcpserver is present from a fresh vbox install (see issue #3803)\" do\n      let(:dhcpservers) {[\n        {\n          network_name: 'HostInterfaceNetworking-vboxnet0',\n          network: 'vboxnet0',\n          ip: '192.168.56.100',\n          netmask: '255.255.255.0',\n          lower: '192.168.56.101',\n          upper: '192.168.56.254'\n        }\n      ]}\n\n      it \"removes the invalid dhcpserver so it won't collide with any host only interface\" do\n        allow(driver).to receive(:remove_dhcp_server)\n        allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }}\n        allow(driver).to receive(:create_dhcp_server)\n        allow(guest).to receive(:capability)\n\n        subject.call(env)\n\n        expect(driver).to have_received(:remove_dhcp_server).with('HostInterfaceNetworking-vboxnet0')\n      end\n\n      context \"but the user has intentionally configured their network just that way\" do\n        let (:network_args) {{\n          type: :dhcp,\n          adapter_ip: '192.168.56.1',\n          dhcp_ip: '192.168.56.100',\n          dhcp_lower: '192.168.56.101',\n          dhcp_upper: '192.168.56.254'\n        }}\n\n        it \"does not attempt to remove the dhcpserver\" do\n          allow(driver).to receive(:remove_dhcp_server)\n          allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }}\n          allow(driver).to receive(:create_dhcp_server)\n          allow(guest).to receive(:capability)\n\n          subject.call(env)\n\n          expect(driver).not_to have_received(:remove_dhcp_server).with('HostInterfaceNetworking-vboxnet0')\n        end\n      end\n    end\n  end\n\n  context 'with invalid settings' do\n    [\n      { ip: 'foo'},\n      { ip: '1.2.3'},\n      { ip: 'dead::beef::'},\n      { ip: '192.168.56.3', netmask: 64},\n      { ip: '192.168.56.3', netmask: 'ffff:ffff::'},\n      { ip: 'dead:beef::', netmask: 'foo:bar::'},\n      { ip: 'dead:beef::', netmask: '255.255.255.0'}\n    ].each do |args|\n      it 'raises an exception' do\n        machine.config.vm.network 'private_network', **args\n        expect { subject.call(env) }.\n          to raise_error(Vagrant::Errors::NetworkAddressInvalid)\n      end\n    end\n  end\n\n  context \"without type set\" do\n    before { allow(subject).to receive(:hostonly_adapter).and_return({}) }\n\n    [\n      { ip: \"192.168.63.5\" },\n      { ip: \"192.168.63.5\", netmask: \"255.255.255.0\" },\n      { ip: \"dead:beef::100\" },\n      { ip: \"dead:beef::100\", netmask: 96 },\n    ].each do |args|\n      it \"sets the type automatically\" do\n        machine.config.vm.network \"private_network\", **args\n        expect(subject).to receive(:hostonly_config) do |config|\n          expect(config).to have_key(:type)\n          addr = IPAddr.new(args[:ip])\n          if addr.ipv4?\n            expect(config[:type]).to eq(:static)\n          else\n            expect(config[:type]).to eq(:static6)\n          end\n          config\n        end\n        subject.call(env)\n\n      end\n    end\n  end\n\n  describe \"#hostonly_find_matching_network\" do\n    let(:ip){ \"192.168.55.2\" }\n    let(:config){ {ip: ip, netmask: \"255.255.255.0\"} }\n    let(:interfaces){ [] }\n\n    before do\n      allow(driver).to receive(:read_host_only_interfaces).and_return(interfaces)\n      subject.instance_variable_set(:@env, env)\n    end\n\n    context \"with no defined host interfaces\" do\n      it \"should return nil\" do\n        expect(subject.hostonly_find_matching_network(config)).to be_nil\n      end\n    end\n\n    context \"with matching host interface\" do\n      let(:interfaces){ [{ip: \"192.168.55.1\", netmask: \"255.255.255.0\", name: \"vnet\"}] }\n\n      it \"should return matching interface\" do\n        expect(subject.hostonly_find_matching_network(config)).to eq(interfaces.first)\n      end\n\n      context \"with matching name\" do\n        let(:config){ {ip: ip, netmask: \"255.255.255.0\", name: \"vnet\"} }\n\n        it \"should return matching interface\" do\n          expect(subject.hostonly_find_matching_network(config)).to eq(interfaces.first)\n        end\n      end\n\n      context \"with non-matching name\" do\n        let(:config){ {ip: ip, netmask: \"255.255.255.0\", name: \"unknown\"} }\n\n        it \"should return nil\" do\n          expect(subject.hostonly_find_matching_network(config)).to be_nil\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/action/prepare_nfs_settings_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\nrequire \"vagrant/util/platform\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Action::PrepareNFSSettings do\n  include_context \"unit\"\n  include_context \"virtualbox\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|\n      allow(m.provider).to receive(:driver).and_return(driver)\n    end\n  end\n\n  let(:env)    {{ machine: machine }}\n  let(:app)    { lambda { |*args| }}\n  let(:driver) { double(\"driver\") }\n  let(:host)   { double(\"host\") }\n\n  subject { described_class.new(app, env) }\n\n  before do\n    env[:test] = true\n    allow(machine.env).to receive(:host) { host }\n    allow(host).to receive(:capability?).with(:nfs_installed) { true }\n    allow(host).to receive(:capability).with(:nfs_installed) { true }\n    # We don't care about smb support so return not installed\n    allow(host).to receive(:capability?).with(:smb_installed).and_return(false)\n  end\n\n  it \"calls the next action in the chain\" do\n    allow(driver).to receive(:read_network_interfaces).and_return({2 => {type: :hostonly, hostonly: \"vmnet2\"}})\n    allow(driver).to receive(:read_host_only_interfaces).and_return([{name: \"vmnet2\", ip: \"1.2.3.4\"}])\n    allow(driver).to receive(:read_guest_ip).with(1).and_return(\"2.3.4.5\")\n\n    called = false\n    app = lambda { |*args| called = true }\n\n    action = described_class.new(app, env)\n    action.call(env)\n\n    expect(called).to eq(true)\n  end\n\n  context \"with an nfs synced folder\" do\n    let(:host_only_interfaces) {\n      [{name: \"vmnet2\", ip: \"1.2.3.4\"}]\n    }\n\n    before do\n      # We can't be on Windows, because NFS gets disabled on Windows\n      allow(Vagrant::Util::Platform).to receive(:windows?).and_return(false)\n\n      env[:machine].config.vm.synced_folder(\"/host/path\", \"/guest/path\", type: \"nfs\")\n      env[:machine].config.finalize!\n\n      # Stub out the stuff so it just works by default\n      allow(driver).to receive(:read_network_interfaces).and_return({\n        2 => {type: :hostonly, hostonly: \"vmnet2\"},\n      })\n      allow(driver).to receive(:read_host_only_interfaces).and_return(host_only_interfaces)\n      allow(driver).to receive(:read_guest_ip).with(1).and_return(\"2.3.4.5\")\n\n      # override sleep to 0 so test does not take seconds\n      retry_options = subject.retry_options\n      allow(subject).to receive(:retry_options).and_return(retry_options.merge(sleep: 0))\n    end\n\n    context \"with host interface netmask defined\" do\n      context \"with machine IP included within host interface range\" do\n        let(:host_only_interfaces) {\n          [{name: \"vmnet2\", ip: \"2.3.4.1\", netmask: \"255.255.255.0\"}]\n        }\n\n        it \"sets nfs_host_ip and nfs_machine_ip properly\" do\n          subject.call(env)\n\n          expect(env[:nfs_host_ip]).to eq(\"2.3.4.1\")\n          expect(env[:nfs_machine_ip]).to eq(\"2.3.4.5\")\n        end\n      end\n      context \"with machine IP included within host interface range\" do\n        let(:host_only_interfaces) {\n          [{name: \"vmnet2\", ip: \"1.2.3.4\", netmask: \"255.255.255.0\"}]\n        }\n\n        it \"raises an error when the machine IP is not within host interface range\" do\n          expect{ subject.call(env) }.to raise_error(Vagrant::Errors::NFSNoHostonlyNetwork)\n        end\n      end\n    end\n\n    it \"sets nfs_host_ip and nfs_machine_ip properly\" do\n      subject.call(env)\n\n      expect(env[:nfs_host_ip]).to    eq(\"1.2.3.4\")\n      expect(env[:nfs_machine_ip]).to eq(\"2.3.4.5\")\n    end\n\n    it \"raises an error when no host only adapter is configured\" do\n      allow(driver).to receive(:read_network_interfaces) {{}}\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::NFSNoHostonlyNetwork)\n    end\n\n    it \"retries through guest property not found errors\" do\n      raise_then_return = [\n        lambda { raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: 'stub' },\n        lambda { \"2.3.4.5\" }\n      ]\n      allow(driver).to receive(:read_guest_ip) { raise_then_return.shift.call }\n\n      subject.call(env)\n\n      expect(env[:nfs_host_ip]).to    eq(\"1.2.3.4\")\n      expect(env[:nfs_machine_ip]).to eq(\"2.3.4.5\")\n    end\n\n    it \"raises an error informing the user of a bug when the guest IP cannot be found\" do\n      allow(driver).to receive(:read_guest_ip) {\n        raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: 'stub'\n      }\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::NFSNoGuestIP)\n    end\n\n    it \"allows statically configured guest IPs to work for NFS, even when guest property would fail\" do\n      env[:machine].config.vm.network :private_network, ip: \"11.12.13.14\"\n\n      allow(driver).to receive(:read_guest_ip) {\n        raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound, guest_property: \"stub\"\n      }\n\n      subject.call(env)\n\n      expect(env[:nfs_host_ip]).to    eq(\"1.2.3.4\")\n      expect(env[:nfs_machine_ip]).to eq([\"11.12.13.14\"])\n    end\n\n    it \"allows statically configured guest IPs to co-exist with dynamic host only IPs for NFS\" do\n      env[:machine].config.vm.network :private_network, ip: \"11.12.13.14\"\n\n      subject.call(env)\n\n      expect(env[:nfs_host_ip]).to    eq(\"1.2.3.4\")\n      expect(env[:nfs_machine_ip]).to eq([\"11.12.13.14\", \"2.3.4.5\"])\n    end\n\n    it \"allows the use of scoped hash overrides as options\" do\n      env[:machine].config.vm.network :private_network, virtualbox__ip: \"11.12.13.14\"\n\n      subject.call(env)\n\n      expect(env[:nfs_host_ip]).to    eq(\"1.2.3.4\")\n      expect(env[:nfs_machine_ip]).to eq([\"11.12.13.14\", \"2.3.4.5\"])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/action/prepare_nfs_valid_ids_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Action::PrepareNFSValidIds do\n  include_context \"unit\"\n  include_context \"virtualbox\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|\n      allow(m.provider).to receive(:driver).and_return(driver)\n    end\n  end\n\n  let(:env)    {{ machine: machine }}\n  let(:app)    { lambda { |*args| }}\n  let(:driver) { double(\"driver\") }\n\n  subject { described_class.new(app, env) }\n\n  before do\n    allow(driver).to receive(:read_vms).and_return({})\n  end\n\n  it \"calls the next action in the chain\" do\n    called = false\n    app = lambda { |*args| called = true }\n\n    action = described_class.new(app, env)\n    action.call(env)\n\n    expect(called).to eq(true)\n  end\n\n  it \"sets nfs_valid_ids\" do\n    hash = {\"foo\" => \"1\", \"bar\" => \"4\"}\n    allow(driver).to receive(:read_vms).and_return(hash)\n\n    subject.call(env)\n\n    expect(env[:nfs_valid_ids]).to eql(hash.values)\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/action/set_default_nic_type_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Action::SetDefaultNICType do\n  include_context \"unit\"\n  include_context \"virtualbox\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :virtualbox).tap do |m|\n      allow(m.provider).to receive(:driver).and_return(driver)\n    end\n  end\n\n  let(:env)    {{ machine: machine, ui: machine.ui }}\n  let(:app)    { lambda { |*args| }}\n  let(:driver) { double(\"driver\") }\n\n  subject { described_class.new(app, env) }\n\n  describe \"#call\" do\n    let(:provider_config) {\n      double(\"provider_config\",\n        default_nic_type: default_nic_type,\n        network_adapters: network_adapters)\n    }\n    let(:default_nic_type) { nil }\n    let(:network_adapters) { {} }\n    let(:virtualbox_version) { \"5.2.23\" }\n\n    before do\n      allow(driver).to receive(:version).and_return(virtualbox_version)\n      allow(machine).to receive(:provider_config).and_return(provider_config)\n    end\n\n    it \"should call the next action\" do\n      expect(app).to receive(:call)\n      subject.call(env)\n    end\n\n    context \"when default_nic_type is set\" do\n      let(:default_nic_type) { \"CUSTOM_NIC_TYPE\" }\n\n      context \"when network adapters are defined\" do\n        let(:network_adapters) { {\"1\" => [:nat, {}], \"2\" => [:intnet, {nic_type: nil}]} }\n\n        it \"should set nic type if not defined\" do\n          subject.call(env)\n          expect(network_adapters[\"1\"].last[:nic_type]).to eq(default_nic_type)\n        end\n\n        it \"should not set nic type if already defined\" do\n          subject.call(env)\n          expect(network_adapters[\"2\"].last[:nic_type]).to be_nil\n        end\n      end\n\n      context \"when vm networks are defined\" do\n        before do\n          machine.config.vm.network :private_network\n          machine.config.vm.network :public_network, nic_type: nil\n          machine.config.vm.network :private_network, virtualbox__nic_type: \"STANDARD\"\n        end\n\n        it \"should add namespaced nic type when not defined\" do\n          subject.call(env)\n          networks = machine.config.vm.networks.map { |type, opts|\n            opts if type.to_s.end_with?(\"_network\") }.compact\n          expect(networks.first[:virtualbox__nic_type]).to eq(default_nic_type)\n        end\n\n        it \"should not add namespaced nic type when nic type defined\" do\n          subject.call(env)\n          networks = machine.config.vm.networks.map { |type, opts|\n            opts if type.to_s.end_with?(\"_network\") }.compact\n          expect(networks[1][:virtualbox__nic_type]).to be_nil\n        end\n\n        it \"should not modify existing namespaced nic type\" do\n          subject.call(env)\n          networks = machine.config.vm.networks.map { |type, opts|\n            opts if type.to_s.end_with?(\"_network\") }.compact\n          expect(networks.last[:virtualbox__nic_type]).to eq(\"STANDARD\")\n        end\n      end\n    end\n\n    context \"when virtualbox version is has susceptible E1000\" do\n      let(:virtualbox_version) { \"5.2.21\" }\n\n      it \"should output a warning\" do\n        expect(machine.ui).to receive(:warn)\n        subject.call(env)\n      end\n\n      context \"when default_nic_type is set to E1000 type\" do\n        let(:default_nic_type) { \"82540EM\" }\n\n        it \"should output a warning\" do\n          expect(machine.ui).to receive(:warn)\n          subject.call(env)\n        end\n      end\n\n      context \"when default_nic_type is set to non-E1000 type\" do\n        let(:default_nic_type) { \"virtio\" }\n\n        it \"should not output a warning\" do\n          expect(machine.ui).not_to receive(:warn)\n          subject.call(env)\n        end\n\n        context \"when network adapter is configured with E1000 type\" do\n          let(:network_adapters) { {\"1\" => [:nat, {nic_type: \"82540EM\" }]} }\n\n          it \"should output a warning\" do\n            expect(machine.ui).to receive(:warn)\n            subject.call(env)\n          end\n        end\n\n        context \"when vm network is configured with E1000 type\" do\n          before { machine.config.vm.network :private_network, nic_type: \"82540EM\" }\n\n          it \"should output a warning\" do\n            expect(machine.ui).to receive(:warn)\n            subject.call(env)\n          end\n        end\n\n        context \"when vm network is configured with E1000 type in namespaced argument\" do\n          before { machine.config.vm.network :private_network, virtualbox__nic_type: \"82540EM\" }\n\n          it \"should output a warning\" do\n            expect(machine.ui).to receive(:warn)\n            subject.call(env)\n          end\n        end\n      end\n    end\n\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/base.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# base test helper for virtualbox unit tests\n\nrequire_relative \"../../../base\"\nrequire_relative \"support/shared/virtualbox_driver_version_4_x_examples\"\nrequire_relative \"support/shared/virtualbox_driver_version_5_x_examples\"\nrequire_relative \"support/shared/virtualbox_driver_version_6_x_examples\"\nrequire_relative \"support/shared/virtualbox_driver_version_7_x_examples\"\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/virtualbox/cap/cleanup_disks\")\n\ndescribe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:driver) { double(\"driver\") }\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|\n      allow(m.provider).to receive(:driver).and_return(driver)\n      allow(m).to receive(:state).and_return(state)\n    end\n  end\n\n  let(:state) do\n    double(:state)\n  end\n\n  let(:subject) { described_class }\n\n  let(:disk_meta_file) { {\"disk\" => [], \"floppy\" => [], \"dvd\" => []} }\n  let(:defined_disks) { {} }\n\n  let(:attachments) { [{port: \"0\", device: \"0\", uuid: \"12345\"},\n                       {port: \"1\", device: \"0\", uuid: \"67890\"}]}\n\n  let(:controller) { double(\"controller\", name: \"controller\", limit: 30, maxportcount: 30) }\n\n  let(:storage_controllers) { double(\"storage controllers\") }\n\n  before do\n    allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true)\n    allow(controller).to receive(:get_attachment).with(port: \"0\", device: \"0\").and_return(attachments[0])\n    allow(controller).to receive(:get_attachment).with(uuid: \"12345\").and_return(attachments[0])\n    allow(controller).to receive(:get_attachment).with(uuid: \"67890\").and_return(attachments[1])\n    allow(storage_controllers).to receive(:get_controller).and_return(controller)\n    allow(storage_controllers).to receive(:get_primary_controller).and_return(controller)\n    allow(storage_controllers).to receive(:get_primary_attachment).and_return(attachments[0])\n    allow(driver).to receive(:read_storage_controllers).and_return(storage_controllers)\n  end\n\n  describe \"#cleanup_disks\" do\n    it \"returns if there's no data in meta file\" do\n      subject.cleanup_disks(machine, defined_disks, disk_meta_file)\n      expect(subject).not_to receive(:handle_cleanup_disk)\n    end\n\n    context \"with disks to clean up\" do\n      let(:disk_meta_file) { {\"disk\" => [{\"uuid\" => \"1234\", \"name\" => \"storage\"}], \"floppy\" => [], \"dvd\" => []} }\n\n      it \"calls the cleanup method if a disk_meta file is defined\" do\n        expect(subject).to receive(:handle_cleanup_disk).\n          with(machine, defined_disks, disk_meta_file[\"disk\"]).\n          and_return(true)\n\n        subject.cleanup_disks(machine, defined_disks, disk_meta_file)\n      end\n\n      it \"raises an error if primary disk can't be found\" do\n        allow(storage_controllers).to receive(:get_primary_attachment).and_raise(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound)\n\n        expect { subject.cleanup_disks(machine, defined_disks, disk_meta_file) }.\n          to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound)\n      end\n    end\n\n    context \"with dvd attached\" do\n      let(:disk_meta_file) { {\"disk\" => [], \"floppy\" => [], \"dvd\" => [{\"uuid\" => \"12345\", \"name\" => \"iso\"}] } }\n\n      it \"calls the cleanup method if a disk_meta file is defined\" do\n        expect(subject).to receive(:handle_cleanup_dvd).\n          with(machine, defined_disks, disk_meta_file[\"dvd\"]).\n          and_return(true)\n\n        subject.cleanup_disks(machine, defined_disks, disk_meta_file)\n      end\n    end\n  end\n\n  describe \"#handle_cleanup_disk\" do\n    let(:disk_meta_file) { { disk: [{ \"uuid\" => \"67890\", \"name\" => \"storage\", \"controller\" => \"controller\", \"port\" => \"1\", \"device\" => \"0\" }], floppy: [], dvd: [] } }\n\n    let(:defined_disks) { [] }\n\n    it \"removes and closes medium from guest\" do\n      expect(driver).to receive(:remove_disk).with(\"controller\", \"1\", \"0\").and_return(true)\n      expect(driver).to receive(:close_medium).with(\"67890\").and_return(true)\n\n      subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk])\n    end\n\n    context \"when the disk isn't attached to a guest\" do\n      it \"only closes the medium\" do\n        allow(controller).to receive(:get_attachment).with(uuid: \"67890\").and_return(nil)\n        expect(driver).to receive(:close_medium).with(\"67890\").and_return(true)\n\n        subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk])\n      end\n    end\n\n    context \"when attachment is not found at the expected device\" do\n      it \"removes the disk from the correct device\" do\n        allow(controller).to receive(:get_attachment).with(uuid: \"67890\").and_return(port: \"2\", device: \"0\")\n        expect(driver).to receive(:remove_disk).with(\"controller\", \"2\", \"0\").and_return(true)\n        expect(driver).to receive(:close_medium).with(\"67890\").and_return(true)\n\n        subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk])\n      end\n    end\n  end\n\n  describe \"#handle_cleanup_dvd\" do\n    let(:disk_meta_file) { {dvd: [{\"uuid\" => \"1234\", \"name\" => \"iso\", \"port\" => \"0\", \"device\" => \"0\", \"controller\" => \"controller\" }]} }\n\n    let(:defined_disks) { [] }\n\n    it \"removes the medium from guest\" do\n      allow(controller).to receive(:get_attachment).with(uuid: \"1234\").and_return(port: \"0\", device: \"0\")\n      expect(driver).to receive(:remove_disk).with(\"controller\", \"0\", \"0\").and_return(true)\n\n      subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[:dvd])\n    end\n\n    context \"when attachment is not found at the expected device\" do\n      it \"removes the disk from the correct device\" do\n        allow(controller).to receive(:get_attachment).with(uuid: \"1234\").and_return(port: \"0\", device: \"1\")\n        expect(driver).to receive(:remove_disk).with(\"controller\", \"0\", \"1\").and_return(true)\n\n        subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[:dvd])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/virtualbox/cap/configure_disks\")\n\ndescribe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:driver) { double(\"driver\") }\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|\n      allow(m.provider).to receive(:driver).and_return(driver)\n      allow(m).to receive(:state).and_return(state)\n    end\n  end\n\n  let(:state) do\n    double(:state)\n  end\n\n  let(:storage_controllers) { double(\"storage controllers\") }\n\n  let(:controller) { double(\"controller\", name: \"controller\", maxportcount: 30, devices_per_port: 1, limit: 30) }\n\n  let(:attachments) { [{:port=>\"0\", :device=>\"0\",\n                      :uuid=>\"12345\",\n                      :storage_format=>\"VMDK\",\n                      :capacity=>\"65536 MBytes\",\n                      :disk_name=>\"ubuntu-18.04-amd64-disk001\",\n                      :location=>\"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk\"},\n                     {:port=>\"1\", :device=>\"0\",\n                      :uuid=>\"67890\",\n                      :storage_format=>\"VDI\",\n                      :capacity=>\"10240 MBytes\",\n                      :disk_name=>\"disk-0\",\n                      :location=>\"/home/vagrant/VirtualBox VMs/disk-0.vdi\"},\n                     {:port=>\"2\", :device=>\"0\",\n                      :uuid=>\"10111\",\n                      :storage_format=>\"VDI\",\n                      :capacity=>\"10240 MBytes\",\n                      :disk_name=>\"disk-1\",\n                      :location=>\"/home/vagrant/VirtualBox VMs/disk-1.vdi\"}] }\n\n  let(:defined_disks) { [double(\"disk\", name: \"vagrant_primary\", size: Vagrant::Util::Numeric::string_to_bytes(\"65GB\"), primary: true, type: :disk),\n                         double(\"disk\", name: \"disk-0\", size: Vagrant::Util::Numeric::string_to_bytes(\"10GB\"), primary: false, type: :disk),\n                         double(\"disk\", name: \"disk-1\", size: \"10GB\", primary: false, type: :disk),\n                         double(\"disk\", name: \"disk-2\", size: \"5GB\", primary: false, type: :disk)] }\n\n\n  let(:all_disks) { [{:port=>\"0\", :device=>\"0\",\n                      :uuid=>\"12345\",\n                      :storage_format=>\"VMDK\",\n                      :capacity=>\"65536 MBytes\",\n                      :disk_name=>\"ubuntu-18.04-amd64-disk001\",\n                      :location=>\"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk\"},\n                     {:port=>\"1\", :device=>\"0\",\n                      :uuid=>\"67890\",\n                      :storage_format=>\"VDI\",\n                      :capacity=>\"10240 MBytes\",\n                      :disk_name=>\"disk-0\",\n                      :location=>\"/home/vagrant/VirtualBox VMs/disk-0.vdi\"},\n                     {:port=>\"2\", :device=>\"0\",\n                      :uuid=>\"10111\",\n                      :storage_format=>\"VDI\",\n                      :capacity=>\"10240 MBytes\",\n                      :disk_name=>\"disk-1\",\n                      :location=>\"/home/vagrant/VirtualBox VMs/disk-1.vdi\"}] }\n\n  let(:list_hdds_result) { [{\"UUID\"=>\"12345\",\n                            \"Parent UUID\"=>\"base\",\n                            \"State\"=>\"created\",\n                            \"Type\"=>\"normal (base)\",\n                            \"Location\"=>\"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk\",\n                            \"Disk Name\"=>\"ubuntu-18.04-amd64-disk001\",\n                            \"Storage format\"=>\"VMDK\",\n                            \"Capacity\"=>\"65536 MBytes\",\n                            \"Encryption\"=>\"disabled\"},\n                           {\"UUID\"=>\"67890\",\n                            \"Parent UUID\"=>\"base\",\n                            \"State\"=>\"created\",\n                            \"Type\"=>\"normal (base)\",\n                            \"Location\"=>\"/home/vagrant/VirtualBox VMs/disk-0.vdi\",\n                            \"Disk Name\"=>\"disk-0\",\n                            \"Storage format\"=>\"VDI\",\n                            \"Capacity\"=>\"10240 MBytes\",\n                            \"Encryption\"=>\"disabled\"},\n                           {\"UUID\"=>\"324bbb53-d5ad-45f8-9bfa-1f2468b199a8\",\n                            \"Parent UUID\"=>\"base\",\n                            \"State\"=>\"created\",\n                            \"Type\"=>\"normal (base)\",\n                            \"Location\"=>\"/home/vagrant/VirtualBox VMs/disk-1.vdi\",\n                            \"Disk Name\"=>\"disk-1\",\n                            \"Storage format\"=>\"VDI\",\n                            \"Capacity\"=>\"5120 MBytes\",\n                            \"Encryption\"=>\"disabled\"}] }\n\n  let(:subject) { described_class }\n\n  before do\n    allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true)\n    allow(controller).to receive(:attachments).and_return(attachments)\n    allow(storage_controllers).to receive(:get_controller).with(controller.name).and_return(controller)\n    allow(storage_controllers).to receive(:first).and_return(controller)\n    allow(storage_controllers).to receive(:size).and_return(1)\n    allow(driver).to receive(:read_storage_controllers).and_return(storage_controllers)\n    allow(driver).to receive(:list_hdds).and_return(list_hdds_result)\n  end\n\n  describe \"#configure_disks\" do\n    let(:dsk_data) { {uuid: \"1234\", name: \"disk\"} }\n    let(:dvd) { double(\"dvd\", type: :dvd, name: \"dvd\", primary: false) }\n\n    before do\n      allow(driver).to receive(:list_hdds).and_return([])\n    end\n\n    it \"configures disks and returns the disks defined\" do\n      expect(subject).to receive(:handle_configure_disk).with(machine, anything, controller.name).\n        exactly(4).and_return(dsk_data)\n      subject.configure_disks(machine, defined_disks)\n    end\n\n    it \"configures dvd and returns the disks defined\" do\n      defined_disks = [ dvd ]\n\n      expect(subject).to receive(:handle_configure_dvd).with(machine, dvd, controller.name).\n        and_return({})\n      subject.configure_disks(machine, defined_disks)\n    end\n\n    context \"with no disks to configure\" do\n      let(:defined_disks) { {} }\n\n      it \"returns empty hash if no disks to configure\" do\n        expect(subject.configure_disks(machine, defined_disks)).to eq({})\n      end\n    end\n\n    # NOTE: In this scenario, one slot must be reserved for the primary\n    # disk, so the controller limit goes down by 1 when there is no primary\n    # disk defined in the config.\n    context \"with over the disk limit for a given device\" do\n      let(:defined_disks) { (1..controller.limit).map { |i| double(\"disk-#{i}\", type: :disk, primary: false) }.to_a }\n\n      it \"raises an exception if the disks defined exceed the limit\" do\n        expect{subject.configure_disks(machine, defined_disks)}.\n          to raise_error(Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit)\n      end\n    end\n\n    # hashicorp/bionic64\n    context \"with more than one storage controller\" do\n      let(:controller1) { double(\"controller1\", name: \"IDE Controller\", maxportcount: 2, devices_per_port: 2, limit: 4) }\n      let(:controller2) { double(\"controller2\", name: \"SATA Controller\", maxportcount: 30, devices_per_port: 1, limit: 30) }\n\n      before do\n        allow(storage_controllers).to receive(:size).and_return(2)\n        allow(storage_controllers).to receive(:get_controller).with(controller1.name).\n          and_return(controller1)\n        allow(storage_controllers).to receive(:get_controller).with(controller2.name).\n          and_return(controller2)\n        allow(storage_controllers).to receive(:get_dvd_controller).and_return(controller1)\n        allow(storage_controllers).to receive(:get_primary_controller).and_return(controller2)\n      end\n\n      it \"attaches disks to the primary controller\" do\n        expect(subject).to receive(:handle_configure_disk).with(machine, anything, controller2.name).\n          exactly(4).and_return(dsk_data)\n        subject.configure_disks(machine, defined_disks)\n      end\n\n      it \"attaches dvds to the secondary controller\" do\n        defined_disks = [ dvd ]\n\n        expect(subject).to receive(:handle_configure_dvd).with(machine, dvd, controller1.name).\n          and_return({})\n        subject.configure_disks(machine, defined_disks)\n      end\n\n      it \"raises an error if there are more than 4 dvds configured\" do\n        defined_disks = [\n          double(\"dvd\", name: \"installer1\", type: :dvd, file: \"installer.iso\", primary: false),\n          double(\"dvd\", name: \"installer2\", type: :dvd, file: \"installer.iso\", primary: false),\n          double(\"dvd\", name: \"installer3\", type: :dvd, file: \"installer.iso\", primary: false),\n          double(\"dvd\", name: \"installer4\", type: :dvd, file: \"installer.iso\", primary: false),\n          double(\"dvd\", name: \"installer5\", type: :dvd, file: \"installer.iso\", primary: false)\n        ]\n\n        expect { subject.configure_disks(machine, defined_disks) }.\n          to raise_error(Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit)\n      end\n\n      it \"attaches multiple dvds\" do\n        defined_disks = [\n          double(\"dvd\", name: \"installer1\", type: :dvd, file: \"installer.iso\", primary: false),\n          double(\"dvd\", name: \"installer2\", type: :dvd, file: \"installer.iso\", primary: false),\n          double(\"dvd\", name: \"installer3\", type: :dvd, file: \"installer.iso\", primary: false),\n          double(\"dvd\", name: \"installer4\", type: :dvd, file: \"installer.iso\", primary: false),\n        ]\n\n        expect(subject).to receive(:handle_configure_dvd).exactly(4).times.and_return({})\n\n        subject.configure_disks(machine, defined_disks)\n      end\n    end\n  end\n\n  describe \"#get_current_disk\" do\n    it \"gets primary disk uuid if disk to configure is primary\" do\n      allow(storage_controllers).to receive(:get_primary_attachment).and_return(attachments[0])\n      primary_disk = subject.get_current_disk(machine, defined_disks.first, all_disks)\n      expect(primary_disk).to eq(all_disks.first)\n    end\n\n    it \"raises an error if primary disk can't be found\" do\n      allow(storage_controllers).to receive(:get_primary_attachment).and_raise(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound)\n      expect { subject.get_current_disk(machine, defined_disks.first, all_disks) }.\n        to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound)\n    end\n\n    it \"finds the disk to configure\" do\n      disk = subject.get_current_disk(machine, defined_disks[1], all_disks)\n      expect(disk).to eq(all_disks[1])\n    end\n\n    it \"returns nil if disk is not found\" do\n      disk = subject.get_current_disk(machine, defined_disks[3], all_disks)\n      expect(disk).to be_nil\n    end\n  end\n\n  describe \"#handle_configure_disk\" do\n    context \"when creating a new disk\" do\n      let(:all_disks) { [{:port=>\"0\", :device=>\"0\",\n                          :uuid=>\"12345\",\n                          :storage_format=>\"VMDK\",\n                          :capacity=>\"65536 MBytes\",\n                          :disk_name=>\"ubuntu-18.04-amd64-disk001\",\n                          :location=>\"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk\"}] }\n\n      let(:list_hdds_result) { [{\"UUID\"=>\"12345\",\n                            \"Parent UUID\"=>\"base\",\n                            \"State\"=>\"created\",\n                            \"Type\"=>\"normal (base)\",\n                            \"Location\"=>\"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk\",\n                            \"Disk Name\"=>\"ubuntu-18.04-amd64-disk001\",\n                            \"Storage format\"=>\"VMDK\",\n                            \"Capacity\"=>\"65536 MBytes\",\n                            \"Encryption\"=>\"disabled\"}] }\n\n      let(:disk_meta) { {uuid: \"67890\", name: \"disk-0\", controller: \"controller\", port: \"1\", device: \"1\"} }\n\n      it \"creates a new disk if it doesn't yet exist\" do\n        expect(subject).to receive(:create_disk).with(machine, defined_disks[1], controller)\n          .and_return(disk_meta)\n        expect(controller).to receive(:attachments).and_return(all_disks)\n\n        expect(storage_controllers).to receive(:get_primary_attachment)\n          .and_return(all_disks[0])\n\n        subject.handle_configure_disk(machine, defined_disks[1], controller.name)\n      end\n\n      it \"includes disk attachment info in metadata\" do\n        expect(subject).to receive(:create_disk).with(machine, defined_disks[1], controller)\n          .and_return(disk_meta)\n        expect(controller).to receive(:attachments).and_return(all_disks)\n        expect(storage_controllers).to receive(:get_primary_attachment)\n          .and_return(all_disks[0])\n\n        disk_metadata = subject.handle_configure_disk(machine, defined_disks[1], controller.name)\n        expect(disk_metadata).to have_key(:controller)\n        expect(disk_metadata).to have_key(:port)\n        expect(disk_metadata).to have_key(:device)\n        expect(disk_metadata).to have_key(:name)\n      end\n    end\n\n    context \"when a disk needs to be resized\" do\n      let(:all_disks) { [{:port=>\"0\", :device=>\"0\",\n                          :uuid=>\"12345\",\n                          :storage_format=>\"VMDK\",\n                          :capacity=>\"65536 MBytes\",\n                          :disk_name=>\"ubuntu-18.04-amd64-disk001\",\n                          :location=>\"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk\"},\n                         {:port=>\"1\", :device=>\"0\",\n                          :uuid=>\"67890\",\n                          :storage_format=>\"VDI\",\n                          :capacity=>\"10240 MBytes\",\n                          :disk_name=>\"disk-0\",\n                          :location=>\"/home/vagrant/VirtualBox VMs/disk-0.vdi\"}] }\n\n      it \"resizes a disk\" do\n        expect(controller).to receive(:attachments).and_return(all_disks)\n\n        expect(subject).to receive(:get_current_disk).\n          with(machine, defined_disks[1], all_disks).and_return(all_disks[1])\n\n        expect(subject).to receive(:compare_disk_size).\n          with(machine, defined_disks[1], all_disks[1]).and_return(true)\n\n        expect(subject).to receive(:resize_disk).\n          with(machine, defined_disks[1], all_disks[1], controller).and_return({})\n\n        subject.handle_configure_disk(machine, defined_disks[1], controller.name)\n      end\n    end\n\n    context \"if no additional disk configuration is required\" do\n      let(:all_disks) { [{:port=>\"0\", :device=>\"0\",\n                          :uuid=>\"12345\",\n                          :storage_format=>\"VMDK\",\n                          :capacity=>\"65536 MBytes\",\n                          :disk_name=>\"ubuntu-18.04-amd64-disk001\",\n                          :location=>\"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk\"},\n                         {:port=>\"1\", :device=>\"0\",\n                          :uuid=>\"67890\",\n                          :storage_format=>\"VDI\",\n                          :capacity=>\"10240 MBytes\",\n                          :disk_name=>\"disk-0\",\n                          :location=>\"/home/vagrant/VirtualBox VMs/disk-0.vdi\"}] }\n\n      let(:disk_info) { {port: \"1\", device: \"0\"} }\n\n      let(:attachments) { [{:port=>\"0\", :device=>\"0\",\n                          :uuid=>\"12345\",\n                          :disk_name=>\"ubuntu-18.04-amd64-disk001\",\n                          :location=>\"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk\"},\n                         {:port=>\"1\", :device=>\"0\",\n                          :uuid=>\"67890\",\n                          :disk_name=>\"disk-0\",\n                          :location=>\"/home/vagrant/VirtualBox VMs/disk-0.vdi\"}] }\n\n      it \"reattaches disk if vagrant defined disk exists but is not attached to guest\" do\n        expect(controller).to receive(:attachments).and_return(all_disks)\n\n        expect(subject).to receive(:get_current_disk).\n          with(machine, defined_disks[1], all_disks).and_return(nil)\n\n        expect(storage_controllers).to receive(:get_primary_attachment)\n          .and_return(all_disks[0])\n\n        expect(driver).to receive(:attach_disk).with(controller.name,\n                                                     (disk_info[:port].to_i + 1).to_s,\n                                                     disk_info[:device],\n                                                     \"hdd\",\n                                                     all_disks[1][:location])\n\n        subject.handle_configure_disk(machine, defined_disks[1], controller.name)\n      end\n\n      it \"does nothing if all disks are properly configured\" do\n        expect(controller).to receive(:attachments).and_return(all_disks)\n\n        expect(subject).to receive(:get_current_disk).\n          with(machine, defined_disks[1], all_disks).and_return(all_disks[1])\n\n        expect(subject).to receive(:compare_disk_size).\n          with(machine, defined_disks[1], all_disks[1]).and_return(false)\n\n        subject.handle_configure_disk(machine, defined_disks[1], controller.name)\n      end\n    end\n  end\n\n  describe \"#compare_disk_size\" do\n    let(:disk_config_small) { double(\"disk\", name: \"disk-0\", size: 1073741824.0, primary: false, type: :disk) }\n    let(:disk_config_large) { double(\"disk\", name: \"disk-0\", size: 68719476736.0, primary: false, type: :disk) }\n\n    it \"shows a warning if user attempts to shrink size\" do\n      expect(machine.ui).to receive(:warn)\n      expect(subject.compare_disk_size(machine, disk_config_small, all_disks[1])).to be_falsey\n    end\n\n    it \"returns true if requested size is bigger than current size\" do\n      expect(subject.compare_disk_size(machine, disk_config_large, all_disks[1])).to be_truthy\n    end\n  end\n\n  describe \"#create_disk\" do\n    let(:disk_config) { double(\"disk\", name: \"disk-0\", size: 1073741824.0,\n                               primary: false, type: :disk, disk_ext: \"vdi\",\n                               provider_config: nil) }\n    let(:vm_info) { {\"CfgFile\"=>\"/home/vagrant/VirtualBox VMs/disks/\"} }\n    let(:disk_file) { \"/home/vagrant/VirtualBox VMs/disk-0.vdi\" }\n    let(:disk_data) { \"Medium created. UUID: 67890\\n\" }\n\n    let(:port_and_device) { {port: \"1\", device: \"0\"} }\n\n    it \"creates a disk and attaches it to a guest\" do\n      expect(driver).to receive(:show_vm_info).and_return(vm_info)\n\n      expect(driver).to receive(:create_disk).\n        with(disk_file, disk_config.size, \"VDI\").and_return(disk_data)\n\n      expect(subject).to receive(:get_next_port).with(machine, controller).\n        and_return(port_and_device)\n\n      expect(driver).to receive(:attach_disk).with(controller.name,\n                                                   port_and_device[:port],\n                                                   port_and_device[:device],\n                                                   \"hdd\",\n                                                   disk_file)\n\n      subject.create_disk(machine, disk_config, controller)\n    end\n  end\n\n  describe \".get_next_port\" do\n    let(:attachments) { [{:port=>\"0\", :device=>\"0\",\n                        :uuid=>\"12345\",\n                        :disk_name=>\"ubuntu-18.04-amd64-disk001\",\n                        :location=>\"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk\"},\n                       {:port=>\"1\", :device=>\"0\",\n                        :uuid=>\"67890\",\n                        :disk_name=>\"disk-0\",\n                        :location=>\"/home/vagrant/VirtualBox VMs/disk-0.vdi\"}] }\n    it \"determines the next available port and device to use\" do\n      dsk_info = subject.get_next_port(machine, controller)\n      expect(dsk_info[:port]).to eq(\"2\")\n      expect(dsk_info[:device]).to eq(\"0\")\n    end\n\n    context \"with IDE controller\" do\n      let(:controller) {\n        double(\"controller\", name: \"IDE\", maxportcount: 2, devices_per_port: 2, limit: 4)\n      }\n\n      let(:attachments) { [] }\n\n      it \"attaches to port 0, device 0\" do\n        dsk_info = subject.get_next_port(machine, controller)\n        expect(dsk_info[:port]).to eq(\"0\")\n        expect(dsk_info[:device]).to eq(\"0\")\n      end\n\n      context \"with 1 device\" do\n        let(:attachments) { [{port:\"0\", device: \"0\"}] }\n\n        it \"attaches to the next device on that port\" do\n          dsk_info = subject.get_next_port(machine, controller)\n          expect(dsk_info[:port]).to eq(\"0\")\n          expect(dsk_info[:device]).to eq(\"1\")\n        end\n      end\n    end\n\n    context \"with SCSI controller\" do\n      let(:controller) {\n        double(\"controller\", name: \"SCSI\", maxportcount: 16, devices_per_port: 1, limit: 16)\n      }\n\n      let(:attachments) { [] }\n\n      let(:vm_info) { {\"SATA Controller-ImageUUID-0-0\" => \"12345\",\n                       \"SATA Controller-ImageUUID-1-0\" => \"67890\"} }\n\n      it \"determines the next available port and device to use\" do\n        allow(driver).to receive(:show_vm_info).and_return(vm_info)\n        dsk_info = subject.get_next_port(machine, controller)\n        expect(dsk_info[:port]).to eq(\"0\")\n        expect(dsk_info[:device]).to eq(\"0\")\n      end\n    end\n  end\n\n  describe \"#resize_disk\" do\n    context \"when a disk is vmdk format\" do\n      let(:disk_config) { double(\"disk\", name: \"vagrant_primary\", size: 1073741824.0,\n                                 primary: false, type: :disk, disk_ext: \"vmdk\",\n                                 provider_config: nil) }\n      let(:attach_info) { {port: \"0\", device: \"0\"} }\n      let(:vdi_disk_file) { \"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vdi\" }\n      let(:vmdk_disk_file) { \"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk\" }\n\n      it \"converts the disk to vdi, resizes it, and converts back to vmdk\" do\n        expect(FileUtils).to receive(:mv).with(vmdk_disk_file, \"#{vmdk_disk_file}.backup\").\n          and_return(true)\n\n        expect(driver).to receive(:vmdk_to_vdi).with(all_disks[0][:location]).\n          and_return(vdi_disk_file)\n\n        expect(driver).to receive(:resize_disk).with(vdi_disk_file, disk_config.size.to_i).and_return(true)\n\n        expect(driver).to receive(:remove_disk).with(controller.name, attach_info[:port], attach_info[:device]).\n          and_return(true)\n        expect(driver).to receive(:close_medium).with(\"12345\")\n\n        expect(driver).to receive(:vdi_to_vmdk).with(vdi_disk_file).\n          and_return(vmdk_disk_file)\n\n        expect(driver).to receive(:attach_disk).\n          with(controller.name, attach_info[:port], attach_info[:device], \"hdd\", vmdk_disk_file).and_return(true)\n        expect(driver).to receive(:close_medium).with(vdi_disk_file).and_return(true)\n\n        expect(driver).to receive(:read_storage_controllers)\n        expect(storage_controllers).to receive(:get_controller)\n\n        expect(FileUtils).to receive(:remove).with(\"#{vmdk_disk_file}.backup\", force: true).\n          and_return(true)\n\n        subject.resize_disk(machine, disk_config, all_disks[0], controller)\n      end\n\n      it \"reattaches original disk if something goes wrong\" do\n        expect(FileUtils).to receive(:mv).with(vmdk_disk_file, \"#{vmdk_disk_file}.backup\").\n          and_return(true)\n\n        expect(driver).to receive(:vmdk_to_vdi).with(all_disks[0][:location]).\n          and_return(vdi_disk_file)\n\n        expect(driver).to receive(:resize_disk).with(vdi_disk_file, disk_config.size.to_i).and_return(true)\n\n        expect(driver).to receive(:remove_disk).with(controller.name, attach_info[:port], attach_info[:device]).\n          and_return(true)\n        expect(driver).to receive(:close_medium).with(\"12345\")\n\n        allow(driver).to receive(:vdi_to_vmdk).and_raise(StandardError)\n\n        expect(subject).to receive(:recover_from_resize).with(machine, all_disks[0], \"#{vmdk_disk_file}.backup\", all_disks[0], vdi_disk_file, controller)\n\n        expect{subject.resize_disk(machine, disk_config, all_disks[0], controller)}.to raise_error(Exception)\n      end\n    end\n\n    context \"when a disk is vdi format\" do\n      let(:disk_config) { double(\"disk\", name: \"disk-0\", size: 1073741824.0,\n                                 primary: false, type: :disk, disk_ext: \"vdi\",\n                                 provider_config: nil) }\n      it \"resizes the disk\" do\n        expect(driver).to receive(:resize_disk).with(all_disks[1][:location], disk_config.size.to_i)\n\n        subject.resize_disk(machine, disk_config, all_disks[1], controller)\n      end\n    end\n  end\n\n  describe \"#vmdk_to_vdi\" do\n    it \"converts a disk from vmdk to vdi\" do\n    end\n  end\n\n  describe \"#vdi_to_vmdk\" do\n    it \"converts a disk from vdi to vmdk\" do\n    end\n  end\n\n  describe \".recover_from_resize\" do\n    let(:disk_config) { double(\"disk\", name: \"vagrant_primary\", size: 1073741824.0,\n                               primary: false, type: :disk, disk_ext: \"vmdk\",\n                               provider_config: nil) }\n    let(:attach_info) { {port: \"0\", device: \"0\"} }\n    let(:vdi_disk_file) { \"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vdi\" }\n    let(:vmdk_disk_file) { \"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk\" }\n    let(:vmdk_backup_file) { \"/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk.backup\" }\n\n    it \"reattaches the original disk file and closes the cloned medium\" do\n      expect(FileUtils).to receive(:mv).with(vmdk_backup_file, vmdk_disk_file, force: true).\n        and_return(true)\n\n      expect(driver).to receive(:attach_disk).\n        with(controller.name, attach_info[:port], attach_info[:device], \"hdd\", vmdk_disk_file).and_return(true)\n\n      expect(driver).to receive(:close_medium).with(vdi_disk_file).and_return(true)\n\n      subject.recover_from_resize(machine, attach_info, vmdk_backup_file, all_disks[0], vdi_disk_file, controller)\n    end\n  end\n\n  describe \".handle_configure_dvd\" do\n    let(:dvd_config) { double(\"dvd\", file: \"/tmp/untitled.iso\", name: \"dvd1\", primary: false) }\n\n    before do\n      allow(subject).to receive(:get_next_port).with(machine, controller).\n        and_return({device: \"0\", port: \"0\"})\n      allow(controller).to receive(:attachments).and_return(\n        [port: \"0\", device: \"0\", uuid: \"12345\"]\n      )\n    end\n\n    it \"includes disk attachment info in metadata\" do\n      expect(driver).to receive(:attach_disk).with(controller.name, \"0\", \"0\", \"dvddrive\", \"/tmp/untitled.iso\")\n\n      dvd_metadata = subject.handle_configure_dvd(machine, dvd_config, controller.name)\n      expect(dvd_metadata[:uuid]).to eq(\"12345\")\n      expect(dvd_metadata).to have_key(:controller)\n      expect(dvd_metadata).to have_key(:port)\n      expect(dvd_metadata).to have_key(:device)\n      expect(dvd_metadata).to have_key(:name)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/cap/mount_options_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\ndescribe \"VagrantPlugins::ProviderVirtualBox::Cap::MountOptions\" do\n  let(:caps) do\n    VagrantPlugins::ProviderVirtualBox::Plugin\n      .components\n      .synced_folder_capabilities[:virtualbox]\n  end\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:mount_owner){ \"vagrant\" }\n  let(:mount_group){ \"vagrant\" }\n  let(:mount_uid){ \"1000\" }\n  let(:mount_gid){ \"1000\" }\n  let(:mount_name){ \"vagrant\" }\n  let(:mount_guest_path){ \"/vagrant\" }\n  let(:folder_options) do\n    {\n      owner: mount_owner,\n      group: mount_group,\n      hostpath: \"/host/directory/path\"\n    }\n  end\n  let(:cap){ caps.get(:mount_options) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  describe \".mount_options\" do\n\n    before do\n      allow(comm).to receive(:sudo).with(any_args)\n      allow(comm).to receive(:execute).with(any_args)\n    end\n\n    context \"with owner user ID explicitly defined\" do\n\n      before do\n        expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_yield(:stdout, \"vagrant:x:#{mount_gid}:\")\n      end\n\n      context \"with user ID provided as Integer\" do\n        let(:mount_owner){ 2000 }\n        it \"generates the expected mount command using mount_owner directly\" do\n          out_mount_options, out_mount_uid, out_mount_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)\n          expect(out_mount_options).to eq(\"uid=#{mount_owner},gid=#{mount_gid},_netdev\")\n          expect(out_mount_uid).to eq(mount_owner)\n          expect(out_mount_gid).to eq(mount_gid)\n        end\n      end\n\n      context \"with user ID provided as String\" do\n        let(:mount_owner){ \"2000\" }\n        it \"generates the expected mount command using mount_owner directly\" do\n          out_mount_options, out_mount_uid, out_mount_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)\n          expect(out_mount_options).to eq(\"uid=#{mount_owner},gid=#{mount_gid},_netdev\")\n          expect(out_mount_uid).to eq(mount_owner)\n          expect(out_mount_gid).to eq(mount_gid)\n        end\n      end\n\n    end\n\n    context \"with owner group ID explicitly defined\" do\n\n      before do\n        expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n      end\n\n      context \"with owner group ID provided as Integer\" do\n        let(:mount_group){ 2000 }\n\n        it \"generates the expected mount command using mount_group directly\" do\n          out_mount_options, out_mount_uid, out_mount_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)\n          expect(out_mount_options).to eq(\"uid=#{mount_uid},gid=#{mount_group},_netdev\")\n          expect(out_mount_uid).to eq(mount_uid)\n          expect(out_mount_gid).to eq(mount_group)\n        end\n      end\n\n      context \"with owner group ID provided as String\" do\n        let(:mount_group){ \"2000\" }\n\n        it \"generates the expected mount command using mount_group directly\" do\n          out_mount_options, out_mount_uid, out_mount_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)\n          expect(out_mount_options).to eq(\"uid=#{mount_uid},gid=#{mount_group},_netdev\")\n          expect(out_mount_uid).to eq(mount_uid)\n          expect(out_mount_gid).to eq(mount_group)\n        end\n      end\n\n    end\n\n    context \"with non-existent default owner group\" do\n      it \"fetches the effective group ID of the user\" do\n        expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n        expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_raise(Vagrant::Errors::VirtualBoxMountFailed, {command: '', output: ''})\n        expect(comm).to receive(:execute).with(\"id -g #{mount_owner}\", anything).and_yield(:stdout, \"1\").and_return(0)\n        out_mount_options, out_mount_uid, out_mount_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)\n        expect(out_mount_options).to eq(\"uid=#{mount_uid},gid=1,_netdev\")\n      end\n    end\n\n    context \"with non-existent owner group\" do\n      it \"raises an error\" do\n        expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n        expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_raise(Vagrant::Errors::VirtualBoxMountFailed, {command: '', output: ''})\n        expect do\n          cap.mount_options(machine, mount_name, mount_guest_path, folder_options)\n        end.to raise_error Vagrant::Errors::VirtualBoxMountFailed\n      end\n    end\n\n    context \"with read-only option defined\" do\n      it \"does not chown mounted guest directory\" do\n        expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n        expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_yield(:stdout, \"vagrant:x:#{mount_gid}:\")\n        out_mount_options, out_mount_uid, out_mount_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options.merge(mount_options: [\"ro\"]))\n        expect(out_mount_options).to eq(\"ro,uid=#{mount_uid},gid=#{mount_gid},_netdev\")\n        expect(out_mount_uid).to eq(mount_uid)\n        expect(out_mount_gid).to eq(mount_gid)\n      end\n    end\n\n    context \"with custom mount options\" do\n      let(:ui){ Vagrant::UI::Silent.new }\n      before do\n        allow(machine).to receive(:ui).and_return(ui)\n      end\n\n      context \"with uid defined\" do\n        let(:options_uid){ '1234' }\n\n        it \"should only include uid defined within mount options\" do\n          expect(comm).not_to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n          expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_yield(:stdout, \"vagrant:x:#{mount_gid}:\")\n          out_mount_options, out_mount_uid, out_mount_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options.merge(mount_options: [\"uid=#{options_uid}\"]) )\n          expect(out_mount_options).to eq(\"uid=#{options_uid},gid=#{mount_gid},_netdev\")\n          expect(out_mount_uid).to eq(options_uid)\n          expect(out_mount_gid).to eq(mount_gid)\n        end\n      end\n\n      context \"with gid defined\" do\n        let(:options_gid){ '1234' }\n\n        it \"should only include gid defined within mount options\" do\n          expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n          expect(comm).not_to receive(:execute).with(\"getent group #{mount_group}\", anything).and_yield(:stdout, \"vagrant:x:#{mount_gid}:\")\n          out_mount_options, out_mount_uid, out_mount_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options.merge(mount_options: [\"gid=#{options_gid}\"]) )\n          expect(out_mount_options).to eq(\"uid=#{mount_uid},gid=#{options_gid},_netdev\")\n          expect(out_mount_uid).to eq(mount_uid)\n          expect(out_mount_gid).to eq(options_gid)\n        end\n      end\n\n      context \"with uid and gid defined\" do\n        let(:options_gid){ '1234' }\n        let(:options_uid){ '1234' }\n\n        it \"should only include uid and gid defined within mount options\" do\n          expect(comm).not_to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n          expect(comm).not_to receive(:execute).with(\"getent group #{mount_group}\", anything).and_yield(:stdout, \"vagrant:x:#{options_gid}:\")\n          out_mount_options, out_mount_uid, out_mount_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options.merge(mount_options: [\"uid=#{options_uid}\", \"gid=#{options_gid}\"]) )\n          expect(out_mount_options).to eq(\"uid=#{options_uid},gid=#{options_gid},_netdev\")\n          expect(out_mount_uid).to eq(options_uid)\n          expect(out_mount_gid).to eq(options_gid)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/cap/public_address_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/virtualbox/cap/public_address\")\n\ndescribe VagrantPlugins::ProviderVirtualBox::Cap::PublicAddress do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|\n      allow(m).to receive(:state).and_return(state)\n    end\n  end\n\n  let(:state) do\n    double(:state)\n  end\n\n  describe \"#public_address\" do\n    it \"returns nil when the machine is not running\" do\n      allow(state).to receive(:id).and_return(:not_created)\n      expect(described_class.public_address(machine)).to be(nil)\n    end\n\n    it \"returns nil when there is no ssh info\" do\n      allow(state).to receive(:id).and_return(:not_created)\n      allow(machine).to receive(:ssh_info).and_return(nil)\n      expect(described_class.public_address(machine)).to be(nil)\n    end\n\n    it \"returns the host\" do\n      allow(state).to receive(:id).and_return(:running)\n      allow(machine).to receive(:ssh_info).and_return(host: \"test\")\n      expect(described_class.public_address(machine)).to eq(\"test\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/cap_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/virtualbox/cap\")\n\ndescribe VagrantPlugins::ProviderVirtualBox::Cap do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) do\n    iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|\n      allow(m.provider).to receive(:driver).and_return(driver)\n      allow(m).to receive(:state).and_return(state)\n    end\n  end\n\n  let(:driver) { double(\"driver\") }\n  let(:state)  { double(\"state\", id: :running) }\n\n  describe \"#forwarded_ports\" do\n    it \"returns all the forwarded ports\" do\n      allow(driver).to receive(:read_forwarded_ports).and_return([\n        [nil, nil, 123, 456],\n        [nil, nil, 245, 245],\n      ])\n\n      expect(described_class.forwarded_ports(machine)).to eq({\n        123 => 456,\n        245 => 245,\n      })\n    end\n\n    it \"returns nil when the machine is not running\" do\n      allow(machine).to receive(:state).and_return(double(:state, id: :stopped))\n      expect(described_class.forwarded_ports(machine)).to be(nil)\n    end\n  end\n\n  describe \"#snapshot_list\" do\n    it \"returns all the snapshots\" do\n      allow(machine).to receive(:id).and_return(\"1234\")\n      allow(driver).to receive(:list_snapshots).with(machine.id).\n        and_return([\"backup\", \"old\"])\n\n      expect(described_class.snapshot_list(machine)).to eq([\"backup\", \"old\"])\n    end\n\n    it \"returns empty array when the machine is does not exist\" do\n      allow(machine).to receive(:id).and_return(nil)\n      expect(described_class.snapshot_list(machine)).to eq([])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/virtualbox/config\")\n\ndescribe VagrantPlugins::ProviderVirtualBox::Config do\n  let(:machine) { double(\"machine\") }\n\n  def assert_invalid\n    errors = subject.validate(machine)\n    if !errors.values.any? { |v| !v.empty? }\n      raise \"No errors: #{errors.inspect}\"\n    end\n  end\n\n  def assert_valid\n    errors = subject.validate(machine)\n    if !errors.values.all? { |v| v.empty? }\n      raise \"Errors: #{errors.inspect}\"\n    end\n  end\n\n  def valid_defaults\n    subject.image = \"foo\"\n  end\n\n  before do\n    vm_config = double(\"vm_config\")\n    allow(vm_config).to receive(:networks).and_return([])\n    config = double(\"config\")\n    allow(config).to receive(:vm).and_return(vm_config)\n    allow(machine).to receive(:config).and_return(config)\n  end\n\n  its \"valid by default\" do\n    subject.finalize!\n    assert_valid\n  end\n\n  context \"defaults\" do\n    before { subject.finalize! }\n\n    it { expect(subject.check_guest_additions).to be(true) }\n    it { expect(subject.gui).to be(false) }\n    it { expect(subject.name).to be_nil }\n    it { expect(subject.functional_vboxsf).to be(true) }\n    it { expect(subject.default_nic_type).to be_nil }\n\n    it \"should have one NAT adapter\" do\n      expect(subject.network_adapters).to eql({\n        1 => [:nat, {}],\n      })\n    end\n  end\n\n  describe \"#default_nic_type\" do\n    let(:nic_type) { \"custom\" }\n\n    before do\n      subject.default_nic_type = nic_type\n      subject.finalize!\n    end\n\n    it { expect(subject.default_nic_type).to eq(nic_type) }\n  end\n\n  describe \"#merge\" do\n    let(:one) { described_class.new }\n    let(:two) { described_class.new }\n\n    subject { one.merge(two) }\n\n    it \"merges the customizations\" do\n      one.customize [\"foo\"]\n      two.customize [\"bar\"]\n\n      expect(subject.customizations).to eq([\n        [\"pre-boot\", [\"foo\"]],\n        [\"pre-boot\", [\"bar\"]]])\n    end\n  end\n\n  describe \"#network_adapter\" do\n    it \"configures additional adapters\" do\n      subject.network_adapter(2, :bridged, auto_config: true)\n      expect(subject.network_adapters[2]).to eql(\n        [:bridged, auto_config: true])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/driver/base.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\nrequire Vagrant.source_root.join(\"plugins/providers/virtualbox/driver/base\")\n\ndescribe VagrantPlugins::ProviderVirtualBox::Driver::Base do\n  describe \"#env_lang\" do\n    context \"when locale command is not available\" do\n      before do\n        allow(Vagrant::Util::Which).to receive(:which).with(\"locale\").and_return(false)\n      end\n\n      it \"should return default value\" do\n        expect(subject.send(:env_lang)).to eq({LANG: \"C\"})\n      end\n    end\n\n    context \"when the locale command is available\" do\n      let(:result) { Vagrant::Util::Subprocess::Result.new(exit_code, stdout, stderr) }\n      let(:stderr) { \"\" }\n      let(:stdout) { \"C.default\" }\n      let(:exit_code) { 0 }\n\n      before do\n        allow(Vagrant::Util::Which).to receive(:which).with(\"locale\").and_return(true)\n        allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"locale\", \"-a\").and_return(result)\n      end\n\n      context \"when locale command errors\" do\n        let(:exit_code) { 1 }\n\n        it \"should return default value\" do\n          expect(subject.send(:env_lang)).to eq({LANG: \"C\"})\n        end\n      end\n\n      context \"when locale command does not error\" do\n        let(:exit_code) { 0 }\n        let(:base) { \"de_AT.utf8\\nde_BE.utf8\\nde_CH.utf8\\nde_DE.utf8\\nde_IT.utf8\\nde_LI.utf8\\nde_LU.utf8\\nen_AG\\nen_AG.utf8\\nen_AU.utf8\\nen_BW.utf8\\nen_CA.utf8\\nen_DK.utf8\\nen_GB.utf8\\nen_HK.utf8\\nen_IE.utf8\\nen_IL\\nen_IL.utf8\\nen_IN\\nen_IN.utf8\\nen_NG\\n\" }\n\n        context \"when stdout includes C\" do\n          let(:stdout) { \"#{base}C\\n\" }\n\n          it \"should use C for the lang\" do\n            expect(subject.send(:env_lang)).to eq({LANG: \"C\"})\n          end\n\n          context \"when stdout includes UTF-8 variants of C\" do\n            let(:stdout) { \"#{base}C\\nC.UTF-8\" }\n\n            it \"should use the UTF-8 variant\" do\n              expect(subject.send(:env_lang)).to eq({LANG: \"C.UTF-8\"})\n            end\n          end\n\n          context \"when stdout includes utf8 variants of C\" do\n            let(:stdout) { \"#{base}C\\nC.utf8\" }\n\n            it \"should use the utf8 variant\" do\n              expect(subject.send(:env_lang)).to eq({LANG: \"C.utf8\"})\n            end\n          end\n        end\n\n        context \"when stdout does not include C\" do\n          context \"when stdout includes C.UTF-8\" do\n            let(:stdout) { \"#{base}C.UTF-8\\n\"}\n\n            it \"should use C.UTF-8 for the lang\" do\n              expect(subject.send(:env_lang)).to eq({LANG: \"C.UTF-8\"})\n            end\n          end\n\n          context \"when stdout includes C.utf8\" do\n            let(:stdout) { \"#{base}C.utf8\\n\"}\n\n            it \"should use C.utf8 for the lang\" do\n              expect(subject.send(:env_lang)).to eq({LANG: \"C.utf8\"})\n            end\n          end\n\n          context \"when stdout includes POSIX\" do\n            let(:stdout) { \"#{base}POSIX\\n\"}\n\n            it \"should use POSIX for the lang\" do\n              expect(subject.send(:env_lang)).to eq({LANG: \"POSIX\"})\n            end\n          end\n\n          context \"when stdout includes en_US.UTF-8\" do\n            let(:stdout) { \"#{base}en_US.UTF-8\\n\"}\n\n            it \"should use en_US.UTF-8 for the lang\" do\n              expect(subject.send(:env_lang)).to eq({LANG: \"en_US.UTF-8\"})\n            end\n          end\n\n          context \"when stdout includes en_US.utf8\" do\n            let(:stdout) { \"#{base}en_US.utf8\\n\"}\n\n            it \"should use en_US.utf8 for the lang\" do\n              expect(subject.send(:env_lang)).to eq({LANG: \"en_US.utf8\"})\n            end\n          end\n        end\n\n        context \"when stdout does not include any variations\" do\n          let(:stdout) { base }\n\n          it \"should default to C\" do\n            expect(subject.send(:env_lang)).to eq({LANG: \"C\"})\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/driver/version_4_0_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Driver::Version_4_0 do\n  include_context \"virtualbox\"\n  let(:vbox_version) { \"4.0.0\" }\n  subject { VagrantPlugins::ProviderVirtualBox::Driver::Meta.new(uuid) }\n\n  it_behaves_like \"a version 4.x virtualbox driver\"\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/driver/version_4_1_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Driver::Version_4_1 do\n  include_context \"virtualbox\"\n  let(:vbox_version) { \"4.1.0\" }\n  subject { VagrantPlugins::ProviderVirtualBox::Driver::Meta.new(uuid) }\n\n  it_behaves_like \"a version 4.x virtualbox driver\"\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/driver/version_4_2_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Driver::Version_4_2 do\n  include_context \"virtualbox\"\n  let(:vbox_version) { \"4.2.0\" }\n  subject { VagrantPlugins::ProviderVirtualBox::Driver::Meta.new(uuid) }\n\n  it_behaves_like \"a version 4.x virtualbox driver\"\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/driver/version_4_3_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Driver::Version_4_3 do\n  include_context \"virtualbox\"\n\n  let(:vbox_version) { \"4.3.0\" }\n\n  subject { VagrantPlugins::ProviderVirtualBox::Driver::Meta.new(uuid) }\n\n  it_behaves_like \"a version 4.x virtualbox driver\"\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire_relative \"../base\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Driver::Version_5_0 do\n  include_context \"virtualbox\"\n\n  let(:vbox_version) { \"5.0.0\" }\n  let(:controller_name) { \"controller\" }\n\n  subject { VagrantPlugins::ProviderVirtualBox::Driver::Version_5_0.new(uuid) }\n\n  it_behaves_like \"a version 4.x virtualbox driver\"\n  it_behaves_like \"a version 5.x virtualbox driver\"\n\n  describe \"#import\" do\n    let(:ovf) { double(\"ovf\") }\n    let(:machine_id) { double(\"machine_id\") }\n    let(:output) {<<-OUTPUT\n0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%\nInterpreting /home/user/.vagrant.d/boxes/hashicorp-VAGRANTSLASH-precise64/1.1.0/virtualbox/box.ovf...\nOK.\nDisks:\n   vmdisk1       85899345920     -1      http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized       box-disk1.vmdk  -1      -1\n\nVirtual system 0:\n 0: Suggested OS type: \"Ubuntu_64\"\n    (change with \"--vsys 0 --ostype <type>\"; use \"list ostypes\" to list all possible values)\n 1: Suggested VM name \"precise64\"\n    (change with \"--vsys 0 --vmname <name>\")\n 2: Number of CPUs: 2\n    (change with \"--vsys 0 --cpus <n>\")\n 3: Guest memory: 384 MB\n    (change with \"--vsys 0 --memory <MB>\")\n 4: Network adapter: orig NAT, config 3, extra slot=0;type=NAT\n 5: CD-ROM\n    (disable with \"--vsys 0 --unit 5 --ignore\")\n 6: IDE controller, type PIIX4\n    (disable with \"--vsys 0 --unit 6 --ignore\")\n 7: IDE controller, type PIIX4\n   (disable with \"--vsys 0 --unit 7 --ignore\")\n 8: SATA controller, type AHCI\n    (disable with \"--vsys 0 --unit 8 --ignore\")\n 9: Hard disk image: source image=box-disk1.vmdk, target path=/home/user/VirtualBox VMs/precise64/box-disk1.vmdk, controller=8;channel=0\n    (change target path with \"--vsys 0 --unit 9 --disk path\";\n    disable with \"--vsys 0 --unit 9 --ignore\")\nOUTPUT\n    }\n\n    before do\n      allow(Vagrant::Util::Platform).to receive(:windows_path).\n        with(ovf).and_return(ovf)\n      allow(subject).to receive(:execute).with(\"import\", \"-n\", ovf).\n        and_return(output)\n      allow(subject).to receive(:execute).with(\"import\", ovf, any_args)\n      allow(subject).to receive(:get_machine_id).and_return(machine_id)\n    end\n\n    it \"should return the machine id\" do\n      expect(subject).to receive(:get_machine_id).and_return(machine_id)\n      expect(subject.import(ovf)).to eq(machine_id)\n    end\n\n    it \"should return machine id using custom name\" do\n      expect(subject).to receive(:get_machine_id).with(/.*precise64_.+/).and_return(machine_id)\n      expect(subject.import(ovf)).to eq(machine_id)\n    end\n\n    it \"should include disk image on import\" do\n      expect(subject).to receive(:execute).with(\"import\", \"-n\", ovf).and_return(output)\n      expect(subject).to receive(:execute) do |*args|\n        match = args[3, args.size].detect { |a| a.include?(\"disk1.vmdk\") }\n        expect(match).to include(\"disk1.vmdk\")\n      end\n      expect(subject.import(ovf)).to eq(machine_id)\n    end\n\n    it \"should include full path for disk image on import\" do\n      expect(subject).to receive(:execute).with(\"import\", \"-n\", ovf).and_return(output)\n      expect(subject).to receive(:execute) do |*args|\n        dpath = args[3, args.size].detect { |a| a.include?(\"disk1.vmdk\") }\n        expect(Pathname.new(dpath).absolute?).to be_truthy\n      end\n      expect(subject.import(ovf)).to eq(machine_id)\n    end\n\n    context \"suggested name is not provided\" do\n      before { output.sub!(/Suggested VM name/, \"\") }\n\n      it \"should raise an error\" do\n        expect { subject.import(ovf) }.to raise_error(Vagrant::Errors::VirtualBoxNoName)\n      end\n    end\n  end\n\n  describe \"#attach_disk\" do\n    it \"attaches a device to the specified controller\" do\n      expect(subject).to receive(:execute) do |*args|\n        storagectl = args[args.index(\"--storagectl\") + 1]\n        expect(storagectl).to eq(controller_name)\n      end\n      subject.attach_disk(controller_name, anything, anything, anything, anything)\n    end\n  end\n\n  describe \"#remove_disk\" do\n    it \"removes a disk from the specified controller\" do\n      expect(subject).to receive(:execute) do |*args|\n        storagectl = args[args.index(\"--storagectl\") + 1]\n        expect(storagectl).to eq(controller_name)\n      end\n      subject.remove_disk(controller_name, anything, anything)\n    end\n  end\n\n  describe \"#read_storage_controllers\" do\n    before do\n      allow(subject).to receive(:show_vm_info).and_return(\n        { \"storagecontrollername0\" => \"SATA Controller\",\n          \"storagecontrollertype0\" => \"IntelAhci\",\n          \"storagecontrollermaxportcount0\" => \"30\",\n          \"SATA Controller-0-0\" => \"/tmp/primary.vdi\",\n          \"SATA Controller-ImageUUID-0-0\" => \"12345\",\n          \"SATA Controller-1-0\" => \"/tmp/secondary.vdi\",\n          \"SATA Controller-ImageUUID-1-0\" => \"67890\" }\n      )\n\n      allow(subject).to receive(:list_hdds).and_return(\n        [\n         {\"UUID\"=>\"12345\",\n         \"Parent UUID\"=>\"base\",\n         \"State\"=>\"created\",\n         \"Type\"=>\"normal (base)\",\n         \"Location\"=>\"/tmp/primary.vdi\",\n         \"Disk Name\"=>\"primary\",\n         \"Storage format\"=>\"VDI\",\n         \"Capacity\"=>\"65536 MBytes\",\n         \"Encryption\"=>\"disabled\"},\n         {\"UUID\"=>\"67890\",\n         \"Parent UUID\"=>\"base\",\n         \"State\"=>\"created\",\n         \"Type\"=>\"normal (base)\",\n         \"Location\"=>\"/tmp/secondary.vdi\",\n         \"Disk Name\"=>\"primary\",\n         \"Storage format\"=>\"VDI\",\n         \"Capacity\"=>\"65536 MBytes\",\n         \"Encryption\"=>\"disabled\"}\n        ]\n      )\n    end\n\n    let(:attachments_result) { [{:port=>\"0\",\n                                 :device=>\"0\",\n                                 :uuid=>\"12345\",\n                                 :location=>\"/tmp/primary.vdi\",\n                                 :parent_uuid=>\"base\",\n                                 :state=>\"created\",\n                                 :type=>\"normal (base)\",\n                                 :disk_name=>\"primary\",\n                                 :storage_format=>\"VDI\",\n                                 :capacity=>\"65536 MBytes\",\n                                 :encryption=>\"disabled\"},\n                                {:port=>\"1\",\n                                 :device=>\"0\",\n                                 :uuid=>\"67890\",\n                                 :location=>\"/tmp/secondary.vdi\",\n                                 :parent_uuid=>\"base\",\n                                 :state=>\"created\",\n                                 :type=>\"normal (base)\",\n                                 :disk_name=>\"primary\",\n                                 :storage_format=>\"VDI\",\n                                 :capacity=>\"65536 MBytes\",\n                                 :encryption=>\"disabled\"}] }\n\n\n    it \"returns a list of storage controllers\" do\n      storage_controllers = subject.read_storage_controllers\n\n      expect(storage_controllers.first.name).to eq(\"SATA Controller\")\n      expect(storage_controllers.first.type).to eq(\"IntelAhci\")\n      expect(storage_controllers.first.maxportcount).to eq(30)\n    end\n\n    it \"includes attachments for each storage controller\" do\n      storage_controllers = subject.read_storage_controllers\n\n      expect(storage_controllers.first.attachments).to eq(attachments_result)\n    end\n  end\nend\n\nVBOX_SYSTEM_PROPERTIES=%(\nAPI version:                     7_0\nMinimum guest RAM size:          4 Megabytes\nMaximum guest RAM size:          2097152 Megabytes\nMinimum video RAM size:          0 Megabytes\nMaximum video RAM size:          256 Megabytes\nMaximum guest monitor count:     64\nMinimum guest CPU count:         1\nMaximum guest CPU count:         64\nVirtual disk limit (info):       2199022206976 Bytes\nMaximum Serial Port count:       4\nMaximum Parallel Port count:     2\nMaximum Boot Position:           4\nMaximum PIIX3 Network Adapter count:   8\nMaximum ICH9 Network Adapter count:   36\nMaximum PIIX3 IDE Controllers:   1\nMaximum ICH9 IDE Controllers:    1\nMaximum IDE Port count:          2\nMaximum Devices per IDE Port:    2\nMaximum PIIX3 SATA Controllers:  1\nMaximum ICH9 SATA Controllers:   8\nMaximum SATA Port count:         30\nMaximum Devices per SATA Port:   1\nMaximum PIIX3 SCSI Controllers:  1\nMaximum ICH9 SCSI Controllers:   8\nMaximum SCSI Port count:         16\nMaximum Devices per SCSI Port:   1\nMaximum SAS PIIX3 Controllers:   1\nMaximum SAS ICH9 Controllers:    8\nMaximum SAS Port count:          255\nMaximum Devices per SAS Port:    1\nMaximum NVMe PIIX3 Controllers:  1\nMaximum NVMe ICH9 Controllers:   8\nMaximum NVMe Port count:         255\nMaximum Devices per NVMe Port:   1\nMaximum virtio-scsi PIIX3 Controllers:  1\nMaximum virtio-scsi ICH9 Controllers:   8\nMaximum virtio-scsi Port count:         256\nMaximum Devices per virtio-scsi Port:   1\nMaximum PIIX3 Floppy Controllers:1\nMaximum ICH9 Floppy Controllers: 1\nMaximum Floppy Port count:       1\nMaximum Devices per Floppy Port: 2\nDefault machine folder:          /home/username/VirtualBox VMs\nRaw-mode Supported:              no\nExclusive HW virtualization use: on\nDefault hard disk format:        VDI\nVRDE auth library:               VBoxAuth\nWebservice auth. library:        VBoxAuth\nRemote desktop ExtPack:\nVM encryption ExtPack:\nLog history count:               3\nDefault frontend:\nDefault audio driver:            ALSA\nAutostart database path:\nDefault Guest Additions ISO:     /usr/share/virtualbox/VBoxGuestAdditions.iso\nLogging Level:                   all\nProxy Mode:                      System\nProxy URL:\nUser language:                   en_US\n)\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/driver/version_6_0_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire_relative \"../base\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Driver::Version_6_0 do\n  include_context \"virtualbox\"\n\n  let(:vbox_version) { \"6.0.0\" }\n\n  subject { VagrantPlugins::ProviderVirtualBox::Driver::Version_6_0.new(uuid) }\n\n  it_behaves_like \"a version 4.x virtualbox driver\"\n  it_behaves_like \"a version 5.x virtualbox driver\"\n  it_behaves_like \"a version 6.x virtualbox driver\"\n\n  describe \"#import\" do\n    let(:ovf) { double(\"ovf\") }\n    let(:machine_id) { double(\"machine_id\") }\n    let(:output) {<<-OUTPUT\n0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%\nInterpreting /home/user/.vagrant.d/boxes/hashicorp-VAGRANTSLASH-precise64/1.1.0/virtualbox/box.ovf...\nOK.\nDisks:\n  vmdisk1       85899345920     -1      http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized       box-disk1.vmdk  -1      -1\n\nVirtual system 0:\n 0: Suggested OS type: \"Ubuntu_64\"\n    (change with \"--vsys 0 --ostype <type>\"; use \"list ostypes\" to list all possible values)\n 1: Suggested VM name \"precise64\"\n    (change with \"--vsys 0 --vmname <name>\")\n 2: Suggested VM group \"/\"\n    (change with \"--vsys 0 --group <group>\")\n 3: Suggested VM settings file name \"/home/user/VirtualBox VMs/precise64/precise64.vbox\"\n    (change with \"--vsys 0 --settingsfile <filename>\")\n 4: Suggested VM base folder \"/home/vagrant/VirtualBox VMs\"\n    (change with \"--vsys 0 --basefolder <path>\")\n 5: Number of CPUs: 2\n    (change with \"--vsys 0 --cpus <n>\")\n 6: Guest memory: 384 MB\n    (change with \"--vsys 0 --memory <MB>\")\n 7: Network adapter: orig NAT, config 3, extra slot=0;type=NAT\n 8: CD-ROM\n    (disable with \"--vsys 0 --unit 8 --ignore\")\n 9: IDE controller, type PIIX4\n    (disable with \"--vsys 0 --unit 9 --ignore\")\n10: IDE controller, type PIIX4\n    (disable with \"--vsys 0 --unit 10 --ignore\")\n11: SATA controller, type AHCI\n    (disable with \"--vsys 0 --unit 11 --ignore\")\n12: Hard disk image: source image=box-disk1.vmdk, target path=box-disk1.vmdk, controller=11;channel=0\n    (change target path with \"--vsys 0 --unit 12 --disk path\";\n    disable with \"--vsys 0 --unit 12 --ignore\")\nOUTPUT\n    }\n\n    before do\n      allow(Vagrant::Util::Platform).to receive(:windows_path).\n        with(ovf).and_return(ovf)\n      allow(subject).to receive(:execute).with(\"import\", \"-n\", ovf).\n        and_return(output)\n      allow(subject).to receive(:execute).with(\"import\", ovf, any_args)\n      allow(subject).to receive(:get_machine_id).and_return(machine_id)\n    end\n\n    it \"should return the machine id\" do\n      expect(subject).to receive(:get_machine_id).and_return(machine_id)\n      expect(subject.import(ovf)).to eq(machine_id)\n    end\n\n    it \"should return machine id using custom name\" do\n      expect(subject).to receive(:get_machine_id).with(/.*precise64_.+/).and_return(machine_id)\n      expect(subject.import(ovf)).to eq(machine_id)\n    end\n\n    it \"should include disk image on import\" do\n      expect(subject).to receive(:execute).with(\"import\", \"-n\", ovf).and_return(output)\n      expect(subject).to receive(:execute) do |*args|\n        match = args[3, args.size].detect { |a| a.include?(\"disk1.vmdk\") }\n        expect(match).to include(\"disk1.vmdk\")\n      end\n      expect(subject.import(ovf)).to eq(machine_id)\n    end\n\n    it \"should include full path for disk image on import\" do\n      expect(subject).to receive(:execute).with(\"import\", \"-n\", ovf).and_return(output)\n      expect(subject).to receive(:execute) do |*args|\n        dpath = args[3, args.size].detect { |a| a.include?(\"disk1.vmdk\") }\n        expect(Pathname.new(dpath).absolute?).to be_truthy\n      end\n      expect(subject.import(ovf)).to eq(machine_id)\n    end\n\n    context \"suggested name is not provided\" do\n      before { output.sub!(/Suggested VM name/, \"\") }\n\n      it \"should raise an error\" do\n        expect { subject.import(ovf) }.to raise_error(Vagrant::Errors::VirtualBoxNoName)\n      end\n    end\n\n    context \"when within windows\" do\n      before do\n        allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n      end\n\n      let(:output) {<<-OUTPUT\n0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%\nInterpreting C:\\\\home\\\\user\\\\.vagrant.d\\\\boxes\\\\hashicorp-VAGRANTSLASH-precise64\\\\1.1.0\\\\virtualbox\\\\box.ovf...\nOK.\nDisks:\n  vmdisk1       85899345920     -1      http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized       box-disk1.vmdk  -1      -1\n\nVirtual system 0:\n 0: Suggested OS type: \"Ubuntu_64\"\n    (change with \"--vsys 0 --ostype <type>\"; use \"list ostypes\" to list all possible values)\n 1: Suggested VM name \"precise64\"\n    (change with \"--vsys 0 --vmname <name>\")\n 2: Suggested VM group \"/\"\n    (change with \"--vsys 0 --group <group>\")\n 3: Suggested VM settings file name \"C:\\\\home\\\\user\\\\VirtualBox VMs\\\\precise64\\\\precise64.vbox\"\n    (change with \"--vsys 0 --settingsfile <filename>\")\n 4: Suggested VM base folder \"C:\\\\home\\\\vagrant\\\\VirtualBox VMs\"\n    (change with \"--vsys 0 --basefolder <path>\")\n 5: Number of CPUs: 2\n    (change with \"--vsys 0 --cpus <n>\")\n 6: Guest memory: 384 MB\n    (change with \"--vsys 0 --memory <MB>\")\n 7: Network adapter: orig NAT, config 3, extra slot=0;type=NAT\n 8: CD-ROM\n    (disable with \"--vsys 0 --unit 8 --ignore\")\n 9: IDE controller, type PIIX4\n    (disable with \"--vsys 0 --unit 9 --ignore\")\n10: IDE controller, type PIIX4\n    (disable with \"--vsys 0 --unit 10 --ignore\")\n11: SATA controller, type AHCI\n    (disable with \"--vsys 0 --unit 11 --ignore\")\n12: Hard disk image: source image=box-disk1.vmdk, target path=box-disk1.vmdk, controller=11;channel=0\n    (change target path with \"--vsys 0 --unit 12 --disk path\";\n    disable with \"--vsys 0 --unit 12 --ignore\")\nOUTPUT\n      }\n\n      it \"should include disk image on import\" do\n        expect(subject).to receive(:execute).with(\"import\", \"-n\", ovf).and_return(output)\n        expect(subject).to receive(:execute) do |*args|\n          match = args[3, args.size].detect { |a| a.include?(\"disk1.vmdk\") }\n          expect(match).to include(\"disk1.vmdk\")\n        end\n        expect(subject.import(ovf)).to eq(machine_id)\n      end\n\n      it \"should update the suggested VM path from default box name\" do\n        expect(subject).to receive(:execute).with(\"import\", \"-n\", ovf).and_return(output)\n        expect(subject).to receive(:execute) do |*args|\n          match = args[3, args.size].detect { |a| a.include?(\"box-disk1.vmdk\") }\n          expect(match).not_to include(\"/precise64/box-disk1.vmdk\")\n          expect(match).to match(/.+precise64_.+?\\/box-disk1.vmdk/)\n        end\n        expect(subject.import(ovf)).to eq(machine_id)\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/driver/version_6_1_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Driver::Version_6_1 do\n  include_context \"virtualbox\"\n\n  let(:vbox_version) { \"6.1.0\" }\n\n  subject { VagrantPlugins::ProviderVirtualBox::Driver::Version_6_1.new(uuid) }\n\n  it_behaves_like \"a version 5.x virtualbox driver\"\n  it_behaves_like \"a version 6.x virtualbox driver\"\n\n  describe \"#read_dhcp_servers\" do\n    before {\n      expect(subprocess).to receive(:execute).\n        with(\"VBoxManage\", \"list\", \"dhcpservers\", an_instance_of(Hash)).\n        and_return(subprocess_result(stdout: output))\n    }\n\n    context \"with empty output\" do\n      let(:output) { \"\" }\n\n      it \"returns an empty list\" do\n        expect(subject.read_dhcp_servers).to eq([])\n      end\n    end\n\n    context \"with a single dhcp server\" do\n      let(:output) {\n        <<-OUTPUT.gsub(/^ */, '')\n          NetworkName:    HostInterfaceNetworking-vboxnet0\n          Dhcpd IP:       192.168.56.100\n          LowerIPAddress: 192.168.56.101\n          UpperIPAddress: 192.168.56.254\n          NetworkMask:    255.255.255.0\n          Enabled:        Yes\n          Global Configuration:\n              minLeaseTime:     default\n              defaultLeaseTime: default\n              maxLeaseTime:     default\n              Forced options:   None\n              Suppressed opts.: None\n                  1/legacy: 255.255.255.0\n          Groups:               None\n          Individual Configs:   None\n\n        OUTPUT\n      }\n\n\n      it \"returns a list with one entry describing that server\" do\n        expect(subject.read_dhcp_servers).to eq([{\n          network_name: 'HostInterfaceNetworking-vboxnet0',\n          network:      'vboxnet0',\n          ip:           '192.168.56.100',\n          netmask:      '255.255.255.0',\n          lower:        '192.168.56.101',\n          upper:        '192.168.56.254',\n        }])\n      end\n    end\n\n    context \"with a multiple dhcp servers\" do\n      let(:output) {\n        <<-OUTPUT.gsub(/^ */, '')\n          NetworkName:    HostInterfaceNetworking-vboxnet0\n          Dhcpd IP:       192.168.56.100\n          LowerIPAddress: 192.168.56.101\n          UpperIPAddress: 192.168.56.254\n          NetworkMask:    255.255.255.0\n          Enabled:        Yes\n          Global Configuration:\n              minLeaseTime:     default\n              defaultLeaseTime: default\n              maxLeaseTime:     default\n              Forced options:   None\n              Suppressed opts.: None\n                  1/legacy: 255.255.255.0\n          Groups:               None\n          Individual Configs:   None\n\n          NetworkName:    HostInterfaceNetworking-vboxnet5\n          Dhcpd IP:       172.28.128.2\n          LowerIPAddress: 172.28.128.3\n          UpperIPAddress: 172.28.128.254\n          NetworkMask:    255.255.255.0\n          Enabled:        Yes\n          Global Configuration:\n              minLeaseTime:     default\n              defaultLeaseTime: default\n              maxLeaseTime:     default\n              Forced options:   None\n              Suppressed opts.: None\n                  1/legacy: 255.255.255.0\n          Groups:               None\n          Individual Configs:   None\n        OUTPUT\n      }\n\n\n      it \"returns a list with one entry for each server\" do\n        expect(subject.read_dhcp_servers).to eq([{\n          network_name: 'HostInterfaceNetworking-vboxnet0',\n          network:      'vboxnet0',\n          ip:           '192.168.56.100',\n          netmask:      '255.255.255.0',\n          lower:        '192.168.56.101',\n          upper:        '192.168.56.254',\n        },{\n          network_name: 'HostInterfaceNetworking-vboxnet5',\n          network:      'vboxnet5',\n          ip:           '172.28.128.2',\n          netmask:      '255.255.255.0',\n          lower:        '172.28.128.3',\n          upper:        '172.28.128.254',\n        }])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/driver/version_7_0_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"stringio\"\nrequire_relative \"../base\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Driver::Version_7_0 do\n  include_context \"virtualbox\"\n\n  let(:vbox_version) { \"7.0.0\" }\n\n  subject { VagrantPlugins::ProviderVirtualBox::Driver::Version_7_0.new(uuid) }\n\n  it_behaves_like \"a version 5.x virtualbox driver\"\n  it_behaves_like \"a version 6.x virtualbox driver\"\n  it_behaves_like \"a version 7.x virtualbox driver\"\n\n  describe \"#read_forwarded_ports\" do\n    let(:uuid) { \"MACHINE-UUID\" }\n    let(:cfg_path) { \"MACHINE_CONFIG_PATH\" }\n    let(:vm_info) {\n%(name=\"vagrant-test_default_1665781960041_56631\"\nEncryption:     disabled\ngroups=\"/\"\nostype=\"Ubuntu (64-bit)\"\nUUID=\"#{uuid}\"\nCfgFile=\"#{cfg_path}\"\nSnapFldr=\"/VirtualBox VMs/vagrant-test_default_1665781960041_56631/Snapshots\"\nLogFldr=\"/VirtualBox VMs/vagrant-test_default_1665781960041_56631/Logs\"\nmemory=1024)\n    }\n    let(:config_file) {\n      StringIO.new(VBOX_VMCONFIG_FILE)\n    }\n\n    before do\n      allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).to receive(:version).and_return(vbox_version)\n    end\n\n    describe \"VirtualBox version 7.0.0\" do\n      let(:vbox_version) { \"7.0.0\" }\n\n      before do\n        allow(subject).to receive(:execute).with(\"showvminfo\", uuid, any_args).and_return(vm_info)\n        allow(File).to receive(:open).with(cfg_path, \"r\").and_yield(config_file)\n      end\n\n      it \"should return two port forward values\" do\n        expect(subject.read_forwarded_ports.size).to eq(2)\n      end\n\n      it \"should have port forwards on slot one\" do\n        subject.read_forwarded_ports.each do |fwd|\n          expect(fwd.first).to eq(1)\n        end\n      end\n\n      it \"should include host ip for ssh forward\" do\n        fwd = subject.read_forwarded_ports.detect { |f| f[1] == \"ssh\" }\n        expect(fwd).not_to be_nil\n        expect(fwd.last).to eq(\"127.0.0.1\")\n      end\n\n      describe \"when config file cannot be determine\" do\n        let(:vm_info) { %(name=\"vagrant-test_default_1665781960041_56631\") }\n\n        it \"should raise a custom error\" do\n          expect(File).not_to receive(:open).with(cfg_path, \"r\")\n\n          expect { subject.read_forwarded_ports }.to raise_error(Vagrant::Errors::VirtualBoxConfigNotFound)\n        end\n      end\n    end\n\n    describe \"VirtualBox version greater than 7.0.0\" do\n      let(:vbox_version) { \"7.0.1\" }\n\n      before do\n        allow(subject).to receive(:execute).with(\"showvminfo\", uuid, any_args).and_return(vm_info)\n      end\n\n      it \"should not read configuration file\" do\n        expect(File).not_to receive(:open).with(cfg_path, \"r\")\n        subject.read_forwarded_ports\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/driver/version_7_1_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"stringio\"\nrequire_relative \"../base\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Driver::Version_7_1 do\n  include_context \"virtualbox\"\n\n  subject { VagrantPlugins::ProviderVirtualBox::Driver::Version_7_1.new(uuid) }\n\n  it_behaves_like \"a version 5.x virtualbox driver\"\n  it_behaves_like \"a version 6.x virtualbox driver\"\n  it_behaves_like \"a version 7.x virtualbox driver\"\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/driver/version_7_2_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"stringio\"\nrequire_relative \"../base\"\n\ndescribe VagrantPlugins::ProviderVirtualBox::Driver::Version_7_2 do\n  include_context \"virtualbox\"\n\n  subject { VagrantPlugins::ProviderVirtualBox::Driver::Version_7_2.new(uuid) }\n\n  it_behaves_like \"a version 5.x virtualbox driver\"\n  it_behaves_like \"a version 6.x virtualbox driver\"\n  it_behaves_like \"a version 7.x virtualbox driver\"\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\ndescribe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do\n  include_context \"unit\"\n\n  let(:controller1) { double(\"controller1\", name: \"IDE Controller\", supported?: true, boot_priority: 1) }\n  let(:controller2) { double(\"controller2\", name: \"SATA Controller\", supported?: true, boot_priority: 2) }\n\n  let(:primary_disk) { {location: \"/tmp/primary.vdi\"} }\n\n  before do\n    subject.replace([controller1, controller2])\n  end\n\n  describe \"#get_controller\" do\n    it \"gets a controller by name\" do\n      expect(subject.get_controller(\"IDE Controller\")).to eq(controller1)\n    end\n\n    it \"raises an exception if a matching storage controller can't be found\" do\n      expect { subject.get_controller(name: \"Foo Controller\") }.\n        to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound)\n    end\n  end\n\n  describe \"#get_primary_controller\" do\n    context \"with a single supported controller\" do\n      before do\n        subject.replace([controller1])\n        allow(controller1).to receive(:attachments).and_return([primary_disk])\n      end\n\n      it \"returns the controller\" do\n        expect(subject.get_primary_controller).to eq(controller1)\n      end\n    end\n\n    context \"with multiple controllers\" do\n      before do\n        allow(controller1).to receive(:attachments).and_return([])\n        allow(controller2).to receive(:attachments).and_return([primary_disk])\n      end\n\n      it \"returns the first supported controller with a disk attached\" do\n        expect(subject.get_primary_controller).to eq(controller2)\n      end\n\n      it \"raises an error if the primary disk is attached to an unsupported controller\" do\n        allow(controller2).to receive(:supported?).and_return(false)\n\n        expect { subject.get_primary_controller }.\n          to raise_error(Vagrant::Errors::VirtualBoxDisksNoSupportedControllers)\n      end\n    end\n  end\n\n  describe \"#hdd?\" do\n    let(:attachment) { {} }\n    it \"determines whether the given attachment represents a hard disk\" do\n      expect(subject.send(:hdd?, attachment)).to be(false)\n    end\n\n    it \"returns true for disk files ending in compatible extensions\" do\n      attachment[:location] = \"/tmp/primary.vdi\"\n      expect(subject.send(:hdd?, attachment)).to be(true)\n    end\n\n    it \"is case insensitive\" do\n      attachment[:location] = \"/tmp/PRIMARY.VDI\"\n      expect(subject.send(:hdd?, attachment)).to be(true)\n    end\n  end\n\n  describe \"#get_primary_attachment\" do\n    let(:attachment) { {location: \"/tmp/primary.vdi\"} }\n\n    before do\n      allow(subject).to receive(:get_primary_controller).and_return(controller2)\n    end\n\n    it \"returns the first attachment on the primary controller\" do\n      allow(controller2).to receive(:get_attachment).with(port: \"0\", device: \"0\").and_return(attachment)\n      expect(subject.get_primary_attachment).to be(attachment)\n    end\n\n    it \"raises an exception if no attachment exists at port 0, device 0\" do\n      allow(controller2).to receive(:get_attachment).with(port: \"0\", device: \"0\").and_return(nil)\n      expect { subject.get_primary_attachment }.to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound)\n    end\n  end\n\n  describe \"#get_dvd_controller\" do\n    context \"with one controller\" do\n      let(:controller) { double(\"controller\", supported?: true, boot_priority: 1) }\n\n      before do\n        subject.replace([controller])\n      end\n\n      it \"returns the controller\" do\n        expect(subject.get_dvd_controller).to be(controller)\n      end\n\n      it \"raises an exception if the controller is unsupported\" do\n        allow(controller).to receive(:supported?).and_return(false)\n\n        expect { subject.get_dvd_controller }.to raise_error(Vagrant::Errors::VirtualBoxDisksNoSupportedControllers)\n      end\n    end\n\n    context \"with multiple controllers\" do\n      let(:controller1) { double(\"controller\", supported?: true, boot_priority: 2) }\n      let(:controller2) { double(\"controller\", supported?: true, boot_priority: 1) }\n      let(:controller3) { double(\"controller\", supported?: false, boot_priority: nil) }\n\n      before do\n        subject.replace([controller1, controller2, controller3])\n      end\n\n      it \"returns the first supported controller\" do\n        expect(subject.get_dvd_controller).to be(controller2)\n      end\n\n      it \"raises an exception if no controllers are supported\" do\n        allow(controller1).to receive(:supported?).and_return(false)\n        allow(controller2).to receive(:supported?).and_return(false)\n\n        expect { subject.get_dvd_controller }.to raise_error(Vagrant::Errors::VirtualBoxDisksNoSupportedControllers)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/model/storage_controller_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\ndescribe VagrantPlugins::ProviderVirtualBox::Model::StorageController do\n  include_context \"unit\"\n\n  let(:name) {}\n  let(:type) { \"IntelAhci\" }\n  let(:maxportcount) { 30 }\n  let(:attachments) {}\n\n  subject { described_class.new(name, type, maxportcount, attachments) }\n\n  describe \"#initialize\" do\n    context \"with SATA controller type\" do\n      it \"recognizes a SATA controller\" do\n        expect(subject.sata?).to be(true)\n      end\n\n      it \"calculates the maximum number of attachments\" do\n        expect(subject.limit).to eq(30)\n      end\n\n      it \"sets the boot priority\" do\n        expect(subject.boot_priority).to eq(2)\n      end\n    end\n\n    context \"with IDE controller type\" do\n      let(:type) { \"PIIX4\" }\n      let(:maxportcount) { 2 }\n\n      it \"recognizes an IDE controller\" do\n        expect(subject.ide?).to be(true)\n      end\n\n      it \"calculates the maximum number of attachments\" do\n        expect(subject.limit).to eq(4)\n      end\n\n      it \"sets the boot priority\" do\n        expect(subject.boot_priority).to eq(1)\n      end\n    end\n\n    context \"with SCSI controller type\" do\n      let(:type) { \"LsiLogic\" }\n      let(:maxportcount) { 16 }\n\n      it \"recognizes an SCSI controller\" do\n        expect(subject.scsi?).to be(true)\n      end\n\n      it \"calculates the maximum number of attachments\" do\n        expect(subject.limit).to eq(16)\n      end\n\n      it \"sets the boot priority\" do\n        expect(subject.boot_priority).to eq(3)\n      end\n    end\n\n    context \"with some other type\" do\n      let(:type) { \"foo\" }\n\n      it \"is unknown\" do\n        expect(subject.ide?).to be(false)\n        expect(subject.sata?).to be(false)\n      end\n    end\n  end\n\n  describe \"#supported?\" do\n    it \"returns true if the controller type is supported\" do\n      expect(subject.supported?).to be(true)\n    end\n\n    context \"with unsupported type\" do\n      let(:type) { \"foo\" }\n\n      it \"returns false\" do\n        expect(subject.supported?).to be(false)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/provider_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/providers/virtualbox/provider\")\n\ndescribe VagrantPlugins::ProviderVirtualBox::Provider do\n  let(:driver){ double(\"driver\") }\n  let(:provider){ double(\"provider\", driver: driver) }\n  let(:provider_config){ double(\"provider_config\") }\n  let(:uid) { \"1000\" }\n  let(:machine){ double(\"machine\", uid: uid, provider: provider, provider_config: provider_config) }\n\n  let(:platform)   { double(\"platform\") }\n\n  subject { described_class.new(machine) }\n\n  before do\n    stub_const(\"Vagrant::Util::Platform\", platform)\n    allow(platform).to receive(:windows?).and_return(false)\n    allow(platform).to receive(:cygwin?).and_return(false)\n    allow(platform).to receive(:wsl?).and_return(false)\n    allow(platform).to receive(:wsl_windows_access_bypass?).and_return(false)\n    allow(machine).to receive(:id).and_return(\"foo\")\n\n    allow(Process).to receive(:uid).and_return(uid)\n  end\n\n  describe \".usable?\" do\n    subject { described_class }\n\n    it \"returns true if usable\" do\n      allow(VagrantPlugins::ProviderVirtualBox::Driver::Meta).to receive(:new).and_return(driver)\n      expect(subject).to be_usable\n    end\n\n    it \"raises an exception if virtualbox is not available\" do\n      allow(VagrantPlugins::ProviderVirtualBox::Driver::Meta).to receive(:new).\n        and_raise(Vagrant::Errors::VirtualBoxNotDetected)\n\n      expect { subject.usable?(true) }.\n        to raise_error(Vagrant::Errors::VirtualBoxNotDetected)\n    end\n\n    it \"raises an exception if virtualbox is the wrong version\" do\n      allow(VagrantPlugins::ProviderVirtualBox::Driver::Meta).to receive(:new).\n        and_raise(Vagrant::Errors::VirtualBoxInvalidVersion, supported_versions: \"1,2,3\")\n\n      expect { subject.usable?(true) }.\n        to raise_error(Vagrant::Errors::VirtualBoxInvalidVersion)\n    end\n\n    it \"raises an exception if virtualbox kernel module is not loaded\" do\n      allow(VagrantPlugins::ProviderVirtualBox::Driver::Meta).to receive(:new).\n        and_raise(Vagrant::Errors::VirtualBoxKernelModuleNotLoaded)\n\n      expect { subject.usable?(true) }.\n        to raise_error(Vagrant::Errors::VirtualBoxKernelModuleNotLoaded)\n    end\n\n    it \"raises an exception if virtualbox installation is incomplete\" do\n      allow(VagrantPlugins::ProviderVirtualBox::Driver::Meta).to receive(:new).\n        and_raise(Vagrant::Errors::VirtualBoxInstallIncomplete)\n\n      expect { subject.usable?(true) }.\n        to raise_error(Vagrant::Errors::VirtualBoxInstallIncomplete)\n    end\n\n    it \"raises an exception if VBoxManage is not found\" do\n      allow(VagrantPlugins::ProviderVirtualBox::Driver::Meta).to receive(:new).\n        and_raise(Vagrant::Errors::VBoxManageNotFoundError)\n\n      expect { subject.usable?(true) }.\n        to raise_error(Vagrant::Errors::VBoxManageNotFoundError)\n    end\n  end\n\n  describe \"#driver\" do\n    it \"is initialized\" do\n      allow(VagrantPlugins::ProviderVirtualBox::Driver::Meta).to receive(:new).and_return(driver)\n      expect(subject.driver).to be(driver)\n    end\n  end\n\n  describe \"#state\" do\n    it \"returns not_created if no ID\" do\n      allow(machine).to receive(:id).and_return(nil)\n      allow(machine).to receive(:data_dir).and_return(\".vagrant\")\n\n      expect(subject.state.id).to eq(:not_created)\n    end\n  end\n\n  describe \"#ssh_info\" do\n    let(:result) { \"127.0.0.1\" }\n    let(:exit_code) { 0 }\n    let(:ssh_info) {{:host=>result,:port=>22}}\n    let(:ssh) { double(\"ssh\", guest_port: 22) }\n    let(:config) { double(\"config\", ssh: ssh) }\n\n    before do\n      allow(VagrantPlugins::ProviderVirtualBox::Driver::Meta).to receive(:new).and_return(driver)\n      allow(machine).to receive(:action).with(:read_state).and_return(machine_state_id: :running)\n      allow(machine).to receive(:data_dir).and_return(\".vagrant\")\n      allow(driver).to receive(:uuid).and_return(\"1234\")\n      allow(driver).to receive(:read_state).and_return(:running)\n      allow(driver).to receive(:ssh_port).and_return(22)\n      allow(machine).to receive(:config).and_return(config)\n    end\n\n    it \"returns nil if machine state is not running\" do\n      allow(driver).to receive(:read_state).and_return(:not_created)\n      expect(subject.ssh_info).to eq(nil)\n    end\n\n    it \"should receive a valid address\" do\n      allow(driver).to receive(:execute).with(:get_network_config).and_return(result)\n\n      allow(driver).to receive(:read_guest_ip).and_return({\"ip\" => \"127.0.0.1\"})\n      expect(subject.ssh_info).to eq(ssh_info)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/support/shared/virtualbox_driver_version_4_x_examples.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nshared_examples \"a version 4.x virtualbox driver\" do |options|\n  before do\n    raise ArgumentError, \"Need virtualbox context to use these shared examples.\" if !(defined? vbox_context)\n  end\n\n  describe \"read_dhcp_servers\" do\n    before {\n      expect(subprocess).to receive(:execute).\n        with(\"VBoxManage\", \"list\", \"dhcpservers\", an_instance_of(Hash)).\n        and_return(subprocess_result(stdout: output))\n    }\n\n    context \"with empty output\" do\n      let(:output) { \"\" }\n\n      it \"returns an empty list\" do\n        expect(subject.read_dhcp_servers).to eq([])\n      end\n    end\n\n    context \"with a single dhcp server\" do\n      let(:output) {\n        <<-OUTPUT.gsub(/^ */, '')\n          NetworkName:    HostInterfaceNetworking-vboxnet0\n          IP:             172.28.128.2\n          NetworkMask:    255.255.255.0\n          lowerIPAddress: 172.28.128.3\n          upperIPAddress: 172.28.128.254\n          Enabled:        Yes\n\n        OUTPUT\n      }\n\n\n      it \"returns a list with one entry describing that server\" do\n        expect(subject.read_dhcp_servers).to eq([{\n          network_name: 'HostInterfaceNetworking-vboxnet0',\n          network:      'vboxnet0',\n          ip:           '172.28.128.2',\n          netmask:      '255.255.255.0',\n          lower:        '172.28.128.3',\n          upper:        '172.28.128.254',\n        }])\n      end\n    end\n\n    context \"with a multiple dhcp servers\" do\n      let(:output) {\n        <<-OUTPUT.gsub(/^ */, '')\n          NetworkName:    HostInterfaceNetworking-vboxnet0\n          IP:             172.28.128.2\n          NetworkMask:    255.255.255.0\n          lowerIPAddress: 172.28.128.3\n          upperIPAddress: 172.28.128.254\n          Enabled:        Yes\n\n          NetworkName:    HostInterfaceNetworking-vboxnet1\n          IP:             10.0.0.2\n          NetworkMask:    255.255.255.0\n          lowerIPAddress: 10.0.0.3\n          upperIPAddress: 10.0.0.254\n          Enabled:        Yes\n        OUTPUT\n      }\n\n\n      it \"returns a list with one entry for each server\" do\n        expect(subject.read_dhcp_servers).to eq([\n          {network_name: 'HostInterfaceNetworking-vboxnet0', network: 'vboxnet0', ip: '172.28.128.2', netmask: '255.255.255.0', lower: '172.28.128.3', upper: '172.28.128.254'},\n          {network_name: 'HostInterfaceNetworking-vboxnet1', network: 'vboxnet1', ip: '10.0.0.2', netmask: '255.255.255.0', lower: '10.0.0.3', upper: '10.0.0.254'},\n        ])\n      end\n    end\n  end\n\n  describe \"read_guest_property\" do\n    it \"reads the guest property of the machine referenced by the UUID\" do\n      key  = \"/Foo/Bar\"\n\n      expect(subprocess).to receive(:execute).\n        with(\"VBoxManage\", \"guestproperty\", \"get\", uuid, key, an_instance_of(Hash)).\n        and_return(subprocess_result(stdout: \"Value: Baz\\n\"))\n\n      expect(subject.read_guest_property(key)).to eq(\"Baz\")\n    end\n\n    it \"raises a virtualBoxGuestPropertyNotFound exception when the value is not set\" do\n      key  = \"/Not/There\"\n\n      expect(subprocess).to receive(:execute).\n        with(\"VBoxManage\", \"guestproperty\", \"get\", uuid, key, an_instance_of(Hash)).\n        and_return(subprocess_result(stdout: \"No value set!\"))\n\n      expect { subject.read_guest_property(key) }.\n        to raise_error Vagrant::Errors::VirtualBoxGuestPropertyNotFound\n    end\n  end\n\n  describe \"read_guest_ip\" do\n    it \"reads the guest property for the provided adapter number\" do\n      key = \"/VirtualBox/GuestInfo/Net/1/V4/IP\"\n\n      expect(subprocess).to receive(:execute).\n        with(\"VBoxManage\", \"guestproperty\", \"get\", uuid, key, an_instance_of(Hash)).\n        and_return(subprocess_result(stdout: \"Value: 127.1.2.3\"))\n\n      value = subject.read_guest_ip(1)\n\n      expect(value).to eq(\"127.1.2.3\")\n    end\n\n    it \"does not accept 0.0.0.0 as a valid IP address\" do\n      key = \"/VirtualBox/GuestInfo/Net/1/V4/IP\"\n\n      expect(subprocess).to receive(:execute).\n        with(\"VBoxManage\", \"guestproperty\", \"get\", uuid, key, an_instance_of(Hash)).\n        and_return(subprocess_result(stdout: \"Value: 0.0.0.0\"))\n\n      expect { subject.read_guest_ip(1) }.\n        to raise_error Vagrant::Errors::VirtualBoxGuestPropertyNotFound\n    end\n  end\n\n  describe \"read_host_only_interfaces\" do\n    before {\n      expect(subprocess).to receive(:execute).\n        with(\"VBoxManage\", \"list\", \"hostonlyifs\", an_instance_of(Hash)).\n        and_return(subprocess_result(stdout: output))\n    }\n\n    context \"with empty output\" do\n      let(:output) { \"\" }\n\n      it \"returns an empty list\" do\n        expect(subject.read_host_only_interfaces).to eq([])\n      end\n    end\n\n    context \"with a single host only interface\" do\n      let(:output) {\n        <<-OUTPUT.gsub(/^ */, '')\n          Name:            vboxnet0\n          GUID:            786f6276-656e-4074-8000-0a0027000000\n          DHCP:            Disabled\n          IPAddress:       172.28.128.1\n          NetworkMask:     255.255.255.0\n          IPV6Address:\n          IPV6NetworkMaskPrefixLength: 0\n          HardwareAddress: 0a:00:27:00:00:00\n          MediumType:      Ethernet\n          Status:          Up\n          VBoxNetworkName: HostInterfaceNetworking-vboxnet0\n\n        OUTPUT\n      }\n\n      it \"returns a list with one entry describing that interface\" do\n        expect(subject.read_host_only_interfaces).to eq([{\n          name:    'vboxnet0',\n          ip:      '172.28.128.1',\n          netmask: '255.255.255.0',\n          ipv6_prefix: '0',\n          status:  'Up',\n          display_name: 'HostInterfaceNetworking-vboxnet0',\n        }])\n      end\n    end\n\n    context \"with multiple host only interfaces\" do\n      let(:output) {\n        <<-OUTPUT.gsub(/^ */, '')\n          Name:            vboxnet0\n          GUID:            786f6276-656e-4074-8000-0a0027000000\n          DHCP:            Disabled\n          IPAddress:       172.28.128.1\n          NetworkMask:     255.255.255.0\n          IPV6Address:\n          IPV6NetworkMaskPrefixLength: 0\n          HardwareAddress: 0a:00:27:00:00:00\n          MediumType:      Ethernet\n          Status:          Up\n          VBoxNetworkName: HostInterfaceNetworking-vboxnet0\n\n          Name:            vboxnet1\n          GUID:            5764a976-8479-8388-1245-8a0048080840\n          DHCP:            Disabled\n          IPAddress:       10.0.0.1\n          NetworkMask:     255.255.255.0\n          IPV6Address:\n          IPV6NetworkMaskPrefixLength: 0\n          HardwareAddress: 0a:00:27:00:00:01\n          MediumType:      Ethernet\n          Status:          Up\n          VBoxNetworkName: HostInterfaceNetworking-vboxnet1\n\n        OUTPUT\n      }\n\n      it \"returns a list with one entry for each interface\" do\n        expect(subject.read_host_only_interfaces).to eq([\n          {name: 'vboxnet0', ip: '172.28.128.1', netmask: '255.255.255.0', ipv6_prefix: \"0\", status: 'Up', display_name: 'HostInterfaceNetworking-vboxnet0'},\n          {name: 'vboxnet1', ip: '10.0.0.1', netmask: '255.255.255.0', ipv6_prefix: \"0\", status: 'Up', display_name: 'HostInterfaceNetworking-vboxnet1'},\n        ])\n      end\n    end\n\n    context \"with an IPv6 host-only interface\" do\n      let(:output) {\n        <<-OUTPUT.gsub(/^ */, '')\n          Name:            vboxnet1\n          GUID:            786f6276-656e-4174-8000-0a0027000001\n          DHCP:            Disabled\n          IPAddress:       192.168.57.1\n          NetworkMask:     255.255.255.0\n          IPV6Address:     fde4:8dba:82e1::\n          IPV6NetworkMaskPrefixLength: 64\n          HardwareAddress: 0a:00:27:00:00:01\n          MediumType:      Ethernet\n          Status:          Up\n          VBoxNetworkName: HostInterfaceNetworking-vboxnet1\n        OUTPUT\n      }\n\n      it \"returns a list with one entry describing that interface\" do\n        expect(subject.read_host_only_interfaces).to eq([{\n          name:    'vboxnet1',\n          ip:      '192.168.57.1',\n          netmask: '255.255.255.0',\n          ipv6: 'fde4:8dba:82e1::',\n          ipv6_prefix: '64',\n          status:  'Up',\n          display_name: 'HostInterfaceNetworking-vboxnet1'\n        }])\n      end\n    end\n  end\n\n  describe \"remove_dhcp_server\" do\n    it \"removes the dhcp server with the specified network name\" do\n      expect(subprocess).to receive(:execute).\n        with(\"VBoxManage\", \"dhcpserver\", \"remove\", \"--netname\", \"HostInterfaceNetworking-vboxnet0\", an_instance_of(Hash)).\n        and_return(subprocess_result(stdout: ''))\n\n      subject.remove_dhcp_server(\"HostInterfaceNetworking-vboxnet0\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/support/shared/virtualbox_driver_version_5_x_examples.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nshared_examples \"a version 5.x virtualbox driver\" do |options|\n  before do\n    raise ArgumentError, \"Need virtualbox context to use these shared examples.\" if !(defined? vbox_context)\n  end\n\n  describe \"#shared_folders\" do\n    let(:folders) { [{:name=>\"folder\",\n                     :hostpath=>\"/Users/brian/vagrant-folder\",\n                     :transient=>false,\n                     :SharedFoldersEnableSymlinksCreate=>true}]}\n\n    let(:folders_automount) { [{:name=>\"folder\",\n                     :hostpath=>\"/Users/brian/vagrant-folder\",\n                     :transient=>false,\n                     :automount=>true,\n                     :SharedFoldersEnableSymlinksCreate=>true}]}\n\n    let(:folders_disabled) { [{:name=>\"folder\",\n                     :hostpath=>\"/Users/brian/vagrant-folder\",\n                     :transient=>false,\n                     :SharedFoldersEnableSymlinksCreate=>false}]}\n\n    it \"enables SharedFoldersEnableSymlinksCreate if true\" do\n      expect(subprocess).to receive(:execute).\n        with(\"VBoxManage\", \"setextradata\", anything, \"VBoxInternal2/SharedFoldersEnableSymlinksCreate/folder\", \"1\", {:env => {:LANG => \"C\"}, :notify=>[:stdout, :stderr]}).\n        and_return(subprocess_result(exit_code: 0))\n\n      expect(subprocess).to receive(:execute).\n        with(\"VBoxManage\", \"sharedfolder\", \"add\", anything, \"--name\", \"folder\", \"--hostpath\", \"/Users/brian/vagrant-folder\", {:env => {:LANG => \"C\"}, :notify=>[:stdout, :stderr]}).\n        and_return(subprocess_result(exit_code: 0))\n      subject.share_folders(folders)\n\n    end\n\n    it \"enables automount if option is true\" do\n      expect(subprocess).to receive(:execute).\n        with(\"VBoxManage\", \"setextradata\", anything, \"VBoxInternal2/SharedFoldersEnableSymlinksCreate/folder\", \"1\", {:env => {:LANG => \"C\"}, :notify=>[:stdout, :stderr]}).\n        and_return(subprocess_result(exit_code: 0))\n\n      expect(subprocess).to receive(:execute).\n        with(\"VBoxManage\", \"sharedfolder\", \"add\", anything, \"--name\", \"folder\", \"--hostpath\", \"/Users/brian/vagrant-folder\", \"--automount\", {:env => {:LANG => \"C\"}, :notify=>[:stdout, :stderr]}).\n        and_return(subprocess_result(exit_code: 0))\n      subject.share_folders(folders_automount)\n\n    end\n\n    it \"disables SharedFoldersEnableSymlinksCreate if false\" do\n      expect(subprocess).to receive(:execute).\n        with(\"VBoxManage\", \"sharedfolder\", \"add\", anything, \"--name\", \"folder\", \"--hostpath\", \"/Users/brian/vagrant-folder\", {:env => {:LANG => \"C\"}, :notify=>[:stdout, :stderr]}).\n        and_return(subprocess_result(exit_code: 0))\n      subject.share_folders(folders_disabled)\n\n    end\n  end\n\n  describe \"#set_mac_address\" do\n    let(:mac) { \"00:00:00:00:00:00\" }\n\n    after { subject.set_mac_address(mac) }\n\n    it \"should modify vm and set mac address\" do\n      expect(subprocess).to receive(:execute).with(\"VBoxManage\", \"modifyvm\", anything, \"--macaddress1\", mac, anything).\n        and_return(subprocess_result(exit_code: 0))\n    end\n\n    context \"when mac address is falsey\" do\n      let(:mac) { nil }\n\n      it \"should modify vm and set mac address to automatic value\" do\n        expect(subprocess).to receive(:execute).with(\"VBoxManage\", \"modifyvm\", anything, \"--macaddress1\", \"auto\", anything).\n          and_return(subprocess_result(exit_code: 0))\n      end\n    end\n  end\n\n  describe \"#ssh_port\" do\n    let(:forwards) {\n      [[1, \"ssh\", 2222, 22, \"127.0.0.1\"],\n        [1, \"ssh\", 8080, 80, \"\"]]\n    }\n\n    before { allow(subject).to receive(:read_forwarded_ports).and_return(forwards) }\n\n    it \"should return the host port\" do\n      expect(subject.ssh_port(22)).to eq(2222)\n    end\n\n    context \"when multiple matches are available\" do\n      let(:forwards) {\n        [[1, \"ssh\", 2222, 22, \"127.0.0.1\"],\n          [1, \"\", 2221, 22, \"\"]]\n      }\n\n      it \"should choose localhost port forward\" do\n        expect(subject.ssh_port(22)).to eq(2222)\n      end\n\n      context \"when multiple named matches are available\" do\n        let(:forwards) {\n          [[1, \"ssh\", 2222, 22, \"127.0.0.1\"],\n            [1, \"SSH\", 2221, 22, \"127.0.0.1\"]]\n        }\n\n        it \"should choose lowercased name forward\" do\n          expect(subject.ssh_port(22)).to eq(2222)\n        end\n      end\n    end\n\n    context \"when only ports are defined\" do\n      let(:forwards) {\n        [[1, \"\", 2222, 22, \"\"]]\n      }\n\n      it \"should return the host port\" do\n        expect(subject.ssh_port(22)).to eq(2222)\n      end\n    end\n\n    context \"when no matches are available\" do\n      let(:forwards) { [] }\n\n      it \"should return nil\" do\n        expect(subject.ssh_port(22)).to be_nil\n      end\n    end\n  end\n\n  describe \"#read_guest_ip\" do\n    context \"when guest ip ends in .1\" do\n      before do\n        key = \"/VirtualBox/GuestInfo/Net/1/V4/IP\"\n\n        expect(subprocess).to receive(:execute).\n          with(\"VBoxManage\", \"guestproperty\", \"get\", uuid, key, an_instance_of(Hash)).\n          and_return(subprocess_result(stdout: \"Value: 172.28.128.1\"))\n      end\n\n      it \"should raise an error\" do\n        expect { subject.read_guest_ip(1) }.to raise_error(Vagrant::Errors::VirtualBoxGuestPropertyNotFound)\n      end\n    end\n  end\n\n  describe \"#valid_ip_address?\" do\n    context \"when ip is 0.0.0.0\" do\n      let(:ip) { \"0.0.0.0\" }\n\n      it \"should be false\" do\n        result = subject.send(:valid_ip_address?, ip)\n        expect(result).to be(false)\n      end\n    end\n\n    context \"when ip address is nil\" do\n      let(:ip) { nil }\n\n      it \"should be false\" do\n        result = subject.send(:valid_ip_address?, ip)\n        expect(result).to be(false)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/support/shared/virtualbox_driver_version_6_x_examples.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nshared_examples \"a version 6.x virtualbox driver\" do |options|\n  before do\n    raise ArgumentError, \"Need virtualbox context to use these shared examples.\" if !(defined? vbox_context)\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/support/shared/virtualbox_driver_version_7_x_examples.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nshared_examples \"a version 7.x virtualbox driver\" do |opts|\n  before do\n    raise ArgumentError, \"Need virtualbox context to use these shared examples.\" if !(defined? vbox_context)\n  end\n\n  describe \"#use_host_only_nets?\" do\n    context \"when platform is darwin\" do\n      before do\n        allow(Vagrant::Util::Platform).to receive(:darwin?).and_return(true)\n      end\n\n      context \"when virtualbox version is less than 7\" do\n        before do\n          allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).\n            to receive(:version).and_return(\"6.0.28\")\n        end\n\n        it \"should return false\" do\n          expect(subject.send(:use_host_only_nets?)).to be(false)\n        end\n      end\n\n      context \"when virtualbox version is greater than 7\" do\n        before do\n          allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).\n            to receive(:version).and_return(\"7.0.2\")\n        end\n\n        it \"should return true\" do\n          expect(subject.send(:use_host_only_nets?)).to be(true)\n        end\n      end\n\n      context \"when virtualbox version is equal to 7\" do\n        before do\n          allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).\n            to receive(:version).and_return(\"7.0.0\")\n        end\n\n        it \"should return true\" do\n          expect(subject.send(:use_host_only_nets?)).to be(true)\n        end\n      end\n    end\n\n    context \"when platform is not darwin\" do\n      before do\n        allow(Vagrant::Util::Platform).to receive(:darwin?).and_return(false)\n      end\n\n      context \"when virtualbox version is less than 7\" do\n        before do\n          allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).\n            to receive(:version).and_return(\"6.0.28\")\n        end\n\n        it \"should return false\" do\n          expect(subject.send(:use_host_only_nets?)).to be(false)\n        end\n      end\n\n      context \"when virtualbox version is greater than 7\" do\n        before do\n          allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).\n            to receive(:version).and_return(\"7.0.2\")\n        end\n\n        it \"should return false\" do\n          expect(subject.send(:use_host_only_nets?)).to be(false)\n        end\n      end\n\n      context \"when virtualbox version is equal to 7\" do\n        before do\n          allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).\n            to receive(:version).and_return(\"7.0.0\")\n        end\n\n        it \"should return false\" do\n          expect(subject.send(:use_host_only_nets?)).to be(false)\n        end\n      end\n    end\n  end\n\n  describe \"#read_bridged_interfaces\" do\n    let(:bridgedifs) { VBOX_BRIDGEDIFS }\n\n    before do\n      allow(subject).to receive(:execute).and_call_original\n      expect(subject).\n        to receive(:execute).\n             with(\"list\", \"bridgedifs\").\n             and_return(bridgedifs)\n    end\n\n    context \"when hostonlynets are not enabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(false)\n      end\n\n      it \"should return all interfaces in list\" do\n        expect(subject.read_bridged_interfaces.size).to eq(5)\n      end\n\n      it \"should not read host only networks\" do\n        expect(subject).not_to receive(:read_host_only_networks)\n        subject.read_bridged_interfaces\n      end\n    end\n\n    context \"when hostonlynets are enabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(true)\n      end\n\n      it \"should return all interfaces in list\" do\n        expect(subject).to receive(:read_host_only_networks).and_return([])\n        expect(subject.read_bridged_interfaces.size).to eq(5)\n      end\n\n      context \"when hostonly networks are defined\" do\n        before do\n          expect(subject).\n            to receive(:execute).\n                 with(\"list\", \"hostonlynets\", any_args).\n                 and_return(VBOX_HOSTONLYNETS)\n        end\n\n        it \"should not return all interfaces in list\" do\n          expect(subject.read_bridged_interfaces.size).to_not eq(5)\n        end\n\n        it \"should not include hostonly network devices\" do\n          expect(\n            subject.read_bridged_interfaces.any? { |int|\n              int[:name].start_with?(\"bridge\")\n            }\n          ).to be(false)\n        end\n      end\n    end\n\n    context \"when address is empty\" do\n      let(:bridgedifs) { VBOX_BRIDGEDIFS.sub(\"0.0.0.0\", \"\") }\n\n      it \"should not raise an error\" do\n        expect { subject.read_bridged_interfaces }.to_not raise_error\n      end\n    end\n  end\n\n  describe \"#delete_unused_host_only_networks\" do\n    context \"when hostonlynets are not enabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(false)\n      end\n\n      it \"should remove host only interfaces\" do\n        expect(subject).to receive(:execute).with(\"list\", \"hostonlyifs\", any_args).and_return(\"\")\n        expect(subject).to receive(:execute).with(\"list\", \"vms\", any_args).and_return(\"\")\n        subject.delete_unused_host_only_networks\n      end\n\n      it \"should not read host only networks\" do\n        expect(subject).to receive(:execute).with(\"list\", \"hostonlyifs\", any_args).and_return(\"\")\n        expect(subject).to receive(:execute).with(\"list\", \"vms\", any_args).and_return(\"\")\n        expect(subject).not_to receive(:read_host_only_networks)\n        subject.delete_unused_host_only_networks\n      end\n    end\n\n    context \"when hostonlynets are enabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(true)\n        allow(subject).to receive(:read_host_only_networks).and_return([])\n        allow(subject).to receive(:execute).with(\"list\", \"vms\", any_args).and_return(\"\")\n      end\n\n      it \"should not read host only interfaces\" do\n        expect(subject).not_to receive(:execute).with(\"list\", \"hostonlyifs\", any_args)\n        subject.delete_unused_host_only_networks\n      end\n\n      context \"when no host only networks are defined\" do\n        before do\n          expect(subject).to receive(:read_host_only_networks).and_return([])\n        end\n\n        it \"should not list vms\" do\n          expect(subject).not_to receive(:execute).with(\"list\", \"vms\", any_args)\n          subject.delete_unused_host_only_networks\n        end\n      end\n\n      context \"when host only networks are defined\" do\n        before do\n          expect(subject).\n            to receive(:read_host_only_networks).\n                 and_return([{name: \"vagrantnet-vbox-1\"}])\n\n        end\n\n        context \"when no vms are using the network\" do\n          before do\n            expect(subject).to receive(:execute).with(\"list\", \"vms\", any_args).and_return(\"\")\n          end\n\n          it \"should delete the network\" do\n            expect(subject).\n              to receive(:execute).\n                   with(\"hostonlynet\", \"remove\", \"--name\", \"vagrantnet-vbox-1\", any_args)\n            subject.delete_unused_host_only_networks\n          end\n        end\n\n        context \"when vms are using the network\" do\n          before do\n            expect(subject).\n              to receive(:execute).\n                   with(\"list\", \"vms\", any_args).\n                   and_return(%(\"VM_NAME\" {VM_ID}))\n            expect(subject).\n              to receive(:execute).\n                   with(\"showvminfo\", \"VM_ID\", any_args).\n                   and_return(%(hostonly-network=\"vagrantnet-vbox-1\"))\n          end\n\n          it \"should not delete the network\" do\n            expect(subject).not_to receive(:execute).with(\"hostonlynet\", \"remove\", any_args)\n            subject.delete_unused_host_only_networks\n          end\n        end\n      end\n    end\n  end\n\n  describe \"#enable_adapters\" do\n    let(:adapters) {\n      [{hostonly: \"hostonlynetwork\", adapter: 1},\n       {bridge: \"eth0\", adapter: 2}]\n    }\n\n    before do\n      allow(subject).to receive(:execute).with(\"modifyvm\", any_args)\n    end\n\n    context \"when hostonlynets are not enabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(false)\n      end\n\n      it \"should only call modifyvm once\" do\n        expect(subject).to receive(:execute).with(\"modifyvm\", any_args).once\n        subject.enable_adapters(adapters)\n      end\n\n      it \"should configure host only network using hostonlyadapter\" do\n        expect(subject).to receive(:execute) { |*args|\n          expect(args.first).to eq(\"modifyvm\")\n          expect(args).to include(\"--hostonlyadapter1\")\n          true\n        }\n        subject.enable_adapters(adapters)\n      end\n    end\n\n    context \"when hostonlynets are enabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(true)\n      end\n\n      it \"should call modifyvm twice\" do\n        expect(subject).to receive(:execute).with(\"modifyvm\", any_args).twice\n        subject.enable_adapters(adapters)\n      end\n\n      it \"should configure host only network using hostonlynet\" do\n        expect(subject).to receive(:execute).once\n        expect(subject).to receive(:execute) { |*args|\n          expect(args.first).to eq(\"modifyvm\")\n          expect(args).to include(\"--host-only-net1\")\n          true\n        }\n        subject.enable_adapters(adapters)\n      end\n    end\n  end\n\n  describe \"#create_host_only_network\" do\n      let(:options) {\n        {\n          adapter_ip: \"127.0.0.1\",\n          netmask: \"255.255.255.0\"\n        }\n      }\n\n    context \"when hostonlynets are disabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(false)\n      end\n\n      it \"should create using hostonlyif\" do\n        expect(subject).\n          to receive(:execute).\n               with(\"hostonlyif\", \"create\", any_args).\n               and_return(\"Interface 'host_only' was successfully created\")\n        expect(subject).\n          to receive(:execute).\n               with(\"hostonlyif\", \"ipconfig\", \"host_only\", any_args)\n        subject.create_host_only_network(options)\n      end\n    end\n\n    context \"when hostonlynets are enabled\" do\n      let(:prefix) { described_class.const_get(:HOSTONLY_NAME_PREFIX) }\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(true)\n        allow(subject).to receive(:read_host_only_networks).and_return([])\n      end\n\n      it \"should create using hostonlynet\" do\n        expect(subject).\n          to receive(:execute).\n               with(\"hostonlynet\", \"add\", \"--name\", prefix + \"1\",\n                    \"--netmask\", options[:netmask], \"--lower-ip\",\n                    \"127.0.0.0\", \"--upper-ip\", \"127.0.0.0\", any_args)\n        subject.create_host_only_network(options)\n      end\n\n      context \"when other host only networks exist\" do\n        before do\n          expect(subject).\n            to receive(:read_host_only_networks).\n                 and_return([\"custom\", prefix + \"1\", prefix + \"20\"].map { |n| {name: n} })\n        end\n\n        it \"should create network with incremented name\" do\n          expect(subject).\n            to receive(:execute).\n                 with(\"hostonlynet\", \"add\", \"--name\", prefix + \"21\", any_args)\n          subject.create_host_only_network(options)\n        end\n      end\n\n      context \"when dhcp information is included\" do\n        let(:options) {\n          {\n            type: :dhcp,\n            dhcp_lower: \"127.0.0.1\",\n            dhcp_upper: \"127.0.1.200\",\n            netmask: \"255.255.240.0\"\n          }\n        }\n\n        it \"should set DHCP range\" do\n          expect(subject).\n            to receive(:execute).\n                 with(\"hostonlynet\", \"add\", \"--name\", anything, \"--netmask\", options[:netmask],\n                     \"--lower-ip\", options[:dhcp_lower], \"--upper-ip\", options[:dhcp_upper],\n                     any_args)\n          subject.create_host_only_network(options)\n        end\n      end\n    end\n  end\n\n  describe \"#reconfig_host_only\" do\n    let(:interface) { {name: \"iname\", ipv6: \"VALUE\"} }\n\n    context \"when hostonlynets are disabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(false)\n      end\n\n      it \"should apply ipv6 update\" do\n        expect(subject).to receive(:execute).with(\"hostonlyif\", \"ipconfig\", interface[:name],\n                                                 \"--ipv6\", interface[:ipv6], any_args)\n        subject.reconfig_host_only(interface)\n      end\n    end\n\n    context \"when hostonlynets are enabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(true)\n      end\n\n      it \"should do nothing\" do\n        expect(subject).not_to receive(:execute)\n        subject.reconfig_host_only(interface)\n      end\n    end\n  end\n\n  describe \"#remove_dhcp_server\" do\n    let(:dhcp_name) { double(:dhcp_name) }\n\n    context \"when hostonlynets are disabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(false)\n      end\n\n      it \"should remove the dhcp server\" do\n        expect(subject).to receive(:execute).with(\"dhcpserver\", \"remove\", \"--netname\",\n                                                  dhcp_name, any_args)\n        subject.remove_dhcp_server(dhcp_name)\n      end\n    end\n\n    context \"when hostonlynets are enabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(true)\n      end\n\n      it \"should do nothing\" do\n        expect(subject).not_to receive(:execute)\n        subject.remove_dhcp_server(dhcp_name)\n      end\n    end\n  end\n\n  describe \"#create_dhcp_server\" do\n    let(:network) { double(\"network\") }\n    let(:options) {\n      {\n        dhcp_ip: \"127.0.0.1\",\n        netmask: \"255.255.255.0\",\n        dhcp_lower: \"127.0.0.2\",\n        dhcp_upper: \"127.0.0.200\"\n      }\n    }\n\n    context \"when hostonlynets is diabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(false)\n      end\n\n      it \"should create a dhcp server\" do\n        expect(subject).to receive(:execute).with(\"dhcpserver\", \"add\", \"--ifname\", network,\n                                                 \"--ip\", options[:dhcp_ip], any_args)\n\n        subject.create_dhcp_server(network, options)\n      end\n    end\n\n    context \"when hostonlynets is enabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(true)\n      end\n\n      it \"should do nothing\" do\n        expect(subject).not_to receive(:execute)\n        subject.create_dhcp_server(network, options)\n      end\n    end\n  end\n\n  describe \"#read_host_only_interfaces\" do\n    context \"when hostonlynets is diabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(false)\n        allow(subject).to receive(:execute).and_return(\"\")\n      end\n\n      it \"should list hostonlyifs\" do\n        expect(subject).to receive(:execute).with(\"list\", \"hostonlyifs\", any_args).and_return(\"\")\n        subject.read_host_only_interfaces\n      end\n\n      it \"should not call read_host_only_networks\" do\n        expect(subject).not_to receive(:read_host_only_networks)\n        subject.read_host_only_interfaces\n      end\n    end\n\n    context \"when hostonlynets is enabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(true)\n        allow(subject).to receive(:execute).with(\"list\", \"hostonlynets\", any_args).\n                            and_return(VBOX_HOSTONLYNETS)\n      end\n\n      it \"should call read_host_only_networks\" do\n        expect(subject).to receive(:read_host_only_networks).and_return([])\n        subject.read_host_only_interfaces\n      end\n\n      it \"should return defined networks\" do\n        expect(subject.read_host_only_interfaces.size).to eq(2)\n      end\n\n      it \"should add compat information to network entries\" do\n        result = subject.read_host_only_interfaces\n        expect(result.first[:netmask]).to eq(result.first[:networkmask])\n        expect(result.first[:status]).to eq(\"Up\")\n      end\n\n      it \"should assign the address as the first in the subnet\" do\n        result = subject.read_host_only_interfaces\n        expect(result.first[:ip]).to eq(IPAddr.new(result.first[:lowerip]).succ.to_s)\n      end\n\n      context \"when dhcp range is set\" do\n        before do\n          allow(subject).to receive(:execute).with(\"list\", \"hostonlynets\", any_args).\n                              and_return(VBOX_RANGE_HOSTONLYNETS)\n        end\n\n        it \"should assign the address as the first in the dhcp range\" do\n          result = subject.read_host_only_interfaces\n          expect(result.first[:ip]).to eq(result.first[:lowerip])\n        end\n      end\n    end\n  end\n\n  describe \"#read_host_only_networks\" do\n    before do\n      allow(subject).to receive(:execute).with(\"list\", \"hostonlynets\", any_args).\n                          and_return(VBOX_HOSTONLYNETS)\n    end\n\n    it \"should return defined networks\" do\n      expect(subject.send(:read_host_only_networks).size).to eq(2)\n    end\n\n    it \"should return expected network information\" do\n      result = subject.send(:read_host_only_networks)\n      expect(result.first[:name]).to eq(\"vagrantnet-vbox1\")\n      expect(result.first[:lowerip]).to eq(\"192.168.61.0\")\n      expect(result.first[:networkmask]).to eq(\"255.255.255.0\")\n      expect(result.last[:name]).to eq(\"vagrantnet-vbox2\")\n      expect(result.last[:lowerip]).to eq(\"192.168.22.0\")\n      expect(result.last[:networkmask]).to eq(\"255.255.255.0\")\n    end\n  end\n\n  describe \"#read_network_interfaces\" do\n    before do\n      allow(subject)\n        .to receive(:execute).\n              with(\"showvminfo\", any_args).\n              and_return(VBOX_GUEST_HOSTONLYVNETS_INFO)\n    end\n\n    context \"when hostonlynets is disabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(false)\n      end\n\n      it \"should return two interfaces\" do\n        valid_interfaces = subject.read_network_interfaces.find_all { |k, v|\n          v[:type] != :none\n        }\n        expect(valid_interfaces.size).to eq(2)\n      end\n\n      it \"should include a nat type\" do\n        expect(subject.read_network_interfaces.detect { |_, v| v[:type] == :nat }).to be\n      end\n\n      it \"should include a hostonlynetwork type with no information\" do\n        expect(subject.read_network_interfaces[2]).to eq({type: :hostonlynetwork})\n      end\n    end\n\n    context \"when hostonlynets is enabled\" do\n      before do\n        allow(subject).to receive(:use_host_only_nets?).and_return(true)\n      end\n\n      it \"should return two interfaces\" do\n        valid_interfaces = subject.read_network_interfaces.find_all { |k, v|\n          v[:type] != :none\n        }\n        expect(valid_interfaces.size).to eq(2)\n      end\n\n      it \"should include a nat type\" do\n        expect(subject.read_network_interfaces.detect { |_, v| v[:type] == :nat }).to be\n      end\n\n      it \"should include a hostonly type\" do\n        expect(subject.read_network_interfaces.detect { |_, v| v[:type] == :hostonly }).to be\n      end\n\n      it \"should not include a hostonlynetwork type\" do\n        expect(subject.read_network_interfaces.detect { |_, v|\n                 v[:type] == :hostonlynetwork\n               }).to_not be\n      end\n\n      it \"should include the hostonly network name\" do\n        hostonly = subject.read_network_interfaces.values.detect { |v|\n          v[:type] == :hostonly\n        }\n        expect(hostonly).to be\n        expect(hostonly[:hostonly]).to eq(\"vagrantnet-vbox1\")\n      end\n    end\n  end\nend\n\nVBOX_VMCONFIG_FILE=%(<?xml version=\"1.0\"?>\n<VirtualBox xmlns=\"http://www.virtualbox.org/\" version=\"1.19-linux\">\n  <Machine uuid=\"{623842dc-0947-4143-aa4e-7d180c5eb348}\" name=\"vagrant-test_default_1665781960041_56631\" OSType=\"Ubuntu_64\" snapshotFolder=\"Snapshots\">\n    <Snapshot uuid=\"{467622d6-f25b-4aaa-94dd-e3e949efca0f}\" name=\"Snapshot 1\" timeStamp=\"2023-01-12T18:28:25Z\">\n      <Hardware>\n        <Network>\n          <Adapter slot=\"0\" enabled=\"true\" MACAddress=\"080027BB1475\" type=\"82540EM\">\n            <NAT localhost-reachable=\"true\">\n              <DNS use-proxy=\"true\"/>\n              <Forwarding name=\"ssh\" proto=\"1\" hostip=\"127.0.0.1\" hostport=\"2222\" guestport=\"22\"/>\n            </NAT>\n          </Adapter>\n          <Adapter slot=\"1\" enabled=\"true\" MACAddress=\"080027DD5ADF\" type=\"82540EM\">\n            <DisabledModes>\n              <InternalNetwork name=\"intnet\"/>\n              <NATNetwork name=\"NatNetwork\"/>\n            </DisabledModes>\n            <HostOnlyInterface name=\"vboxnet0\"/>\n          </Adapter>\n        </Network>\n      </Hardware>\n    </Snapshot>\n    <Hardware>\n      <Network>\n        <Adapter slot=\"0\" enabled=\"true\" MACAddress=\"080027BB1475\" type=\"82540EM\">\n          <NAT localhost-reachable=\"true\">\n            <DNS use-proxy=\"true\"/>\n            <Forwarding name=\"ssh\" proto=\"1\" hostip=\"127.0.0.1\" hostport=\"2222\" guestport=\"22\"/>\n            <Forwarding name=\"tcp7700\" proto=\"1\" hostport=\"7700\" guestport=\"80\"/>\n          </NAT>\n        </Adapter>\n        <Adapter slot=\"1\" enabled=\"true\" MACAddress=\"080027DD5ADF\" type=\"82540EM\">\n          <DisabledModes>\n            <InternalNetwork name=\"intnet\"/>\n            <NATNetwork name=\"NatNetwork\"/>\n          </DisabledModes>\n          <HostOnlyInterface name=\"vboxnet0\"/>\n        </Adapter>\n      </Network>\n    </Hardware>\n  </Machine>\n</VirtualBox>)\n\n\nVBOX_BRIDGEDIFS=%(Name:            en1: Wi-Fi (AirPort)\nGUID:            00000000-0000-0000-0000-000000000001\nDHCP:            Disabled\nIPAddress:       10.0.0.49\nNetworkMask:     255.255.255.0\nIPV6Address:\nIPV6NetworkMaskPrefixLength: 0\nHardwareAddress: xx:xx:xx:xx:xx:01\nMediumType:      Ethernet\nWireless:        Yes\nStatus:          Up\nVBoxNetworkName: HostInterfaceNetworking-en1\n\nName:            en0: Ethernet\nGUID:            00000000-0000-0000-0000-000000000002\nDHCP:            Disabled\nIPAddress:       0.0.0.0\nNetworkMask:     0.0.0.0\nIPV6Address:\nIPV6NetworkMaskPrefixLength: 0\nHardwareAddress: xx:xx:xx:xx:xx:02\nMediumType:      Ethernet\nWireless:        No\nStatus:          Up\nVBoxNetworkName: HostInterfaceNetworking-en0\n\nName:            bridge100\nGUID:            00000000-0000-0000-0000-000000000003\nDHCP:            Disabled\nIPAddress:       192.168.61.1\nNetworkMask:     255.255.255.0\nIPV6Address:\nIPV6NetworkMaskPrefixLength: 0\nHardwareAddress: xx:xx:xx:xx:xx:03\nMediumType:      Ethernet\nWireless:        No\nStatus:          Up\nVBoxNetworkName: HostInterfaceNetworking-bridge100\n\nName:            en2: Thunderbolt 1\nGUID:            00000000-0000-0000-0000-000000000004\nDHCP:            Disabled\nIPAddress:       0.0.0.0\nNetworkMask:     0.0.0.0\nIPV6Address:\nIPV6NetworkMaskPrefixLength: 0\nHardwareAddress: xx:xx:xx:xx:xx:04\nMediumType:      Ethernet\nWireless:        No\nStatus:          Up\nVBoxNetworkName: HostInterfaceNetworking-en2\n\nName:            bridge101\nGUID:            00000000-0000-0000-0000-000000000005\nDHCP:            Disabled\nIPAddress:       192.168.22.1\nNetworkMask:     255.255.255.0\nIPV6Address:\nIPV6NetworkMaskPrefixLength: 0\nHardwareAddress: xx:xx:xx:xx:xx:05\nMediumType:      Ethernet\nWireless:        No\nStatus:          Up\nVBoxNetworkName: HostInterfaceNetworking-bridge101)\n\nVBOX_HOSTONLYNETS=%(Name:            vagrantnet-vbox1\nGUID:            10000000-0000-0000-0000-000000000000\n\nState:           Enabled\nNetworkMask:     255.255.255.0\nLowerIP:         192.168.61.0\nUpperIP:         192.168.61.0\nVBoxNetworkName: hostonly-vagrantnet-vbox1\n\nName:            vagrantnet-vbox2\nGUID:            20000000-0000-0000-0000-000000000000\n\nState:           Enabled\nNetworkMask:     255.255.255.0\nLowerIP:         192.168.22.0\nUpperIP:         192.168.22.0\nVBoxNetworkName: hostonly-vagrantnet-vbox2)\n\nVBOX_RANGE_HOSTONLYNETS=%(Name:            vagrantnet-vbox1\nGUID:            10000000-0000-0000-0000-000000000000\n\nState:           Enabled\nNetworkMask:     255.255.255.0\nLowerIP:         192.168.61.10\nUpperIP:         192.168.61.100\nVBoxNetworkName: hostonly-vagrantnet-vbox1\n\nName:            vagrantnet-vbox2\nGUID:            20000000-0000-0000-0000-000000000000\n\nState:           Enabled\nNetworkMask:     255.255.255.0\nLowerIP:         192.168.22.0\nUpperIP:         192.168.22.0\nVBoxNetworkName: hostonly-vagrantnet-vbox2)\n\nVBOX_GUEST_HOSTONLYVNETS_INFO=%(\nnatnet1=\"nat\"\nmacaddress1=\"080027BB1475\"\ncableconnected1=\"on\"\nnic1=\"nat\"\nnictype1=\"82540EM\"\nnicspeed1=\"0\"\nmtu=\"0\"\nsockSnd=\"64\"\nsockRcv=\"64\"\ntcpWndSnd=\"64\"\ntcpWndRcv=\"64\"\nForwarding(0)=\"ssh,tcp,127.0.0.1,2222,,22\"\nhostonly-network2=\"vagrantnet-vbox1\"\nmacaddress2=\"080027FBC15B\"\ncableconnected2=\"on\"\nnic2=\"hostonlynetwork\"\nnictype2=\"82540EM\"\nnicspeed2=\"0\"\nnic3=\"none\"\nnic4=\"none\"\nnic5=\"none\"\nnic6=\"none\"\nnic7=\"none\"\nnic8=\"none\"\n)\n"
  },
  {
    "path": "test/unit/plugins/providers/virtualbox/synced_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"vagrant\"\nrequire Vagrant.source_root.join(\"test/unit/base\")\n\nrequire Vagrant.source_root.join(\"plugins/providers/virtualbox/config\")\nrequire Vagrant.source_root.join(\"plugins/providers/virtualbox/synced_folder\")\n\ndescribe VagrantPlugins::ProviderVirtualBox::SyncedFolder do\n  include_context \"unit\"\n\n  let(:vm_config) do\n    double(\"vm_config\").tap do |vm_config|\n      allow(vm_config).to receive(:allow_fstab_modification).and_return(true)\n    end\n  end\n\n  let(:machine_config) do\n    double(\"machine_config\").tap do |top_config|\n      allow(top_config).to receive(:vm).and_return(vm_config)\n    end\n  end\n\n  let(:machine) do\n    double(\"machine\").tap do |m|\n      allow(m).to receive(:provider_config).and_return(VagrantPlugins::ProviderVirtualBox::Config.new)\n      allow(m).to receive(:provider_name).and_return(:virtualbox)\n      allow(m).to receive(:config).and_return(machine_config)\n    end\n  end\n\n  let(:folders) { {\"/folder\"=>\n    {:SharedFoldersEnableSymlinksCreate=>true,\n     :guestpath=>\"/folder\",\n     :hostpath=>\"/Users/brian/vagrant-folder\",\n     :automount=>false,\n     :disabled=>false,\n     :__vagrantfile=>true}} }\n\n  subject { described_class.new }\n\n  before do\n    machine.provider_config.finalize!\n  end\n\n  describe \"#usable?\" do\n    it \"should be with virtualbox provider\" do\n      allow(machine).to receive(:provider_name).and_return(:virtualbox)\n      expect(subject).to be_usable(machine)\n    end\n\n    it \"should not be with another provider\" do\n      allow(machine).to receive(:provider_name).and_return(:vmware_fusion)\n      expect(subject).not_to be_usable(machine)\n    end\n\n    it \"should not be usable if not functional vboxsf\" do\n      machine.provider_config.functional_vboxsf = false\n      expect(subject).to_not be_usable(machine)\n    end\n  end\n\n  describe \"#enable\" do\n    let(:ui){ Vagrant::UI::Silent.new }\n    let(:guest) { double(\"guest\") }\n\n    let(:no_guestpath_folder) { {\"/no_guestpath_folder\"=>\n      {:SharedFoldersEnableSymlinksCreate=>false,\n       :guestpath=>nil,\n       :hostpath=>\"/Users/brian/vagrant-folder\",\n       :automount=>false,\n       :disabled=>true,\n       :__vagrantfile=>true}} }\n\n    before do\n      allow(subject).to receive(:share_folders).and_return(true)\n      allow(machine).to receive(:ui).and_return(ui)\n      allow(machine).to receive(:ssh_info).and_return({:username => \"test\"})\n      allow(machine).to receive(:guest).and_return(guest)\n    end\n  end\n\n  describe \"#prepare\" do\n    let(:driver) { double(\"driver\") }\n    let(:provider) { double(\"driver\", driver: driver) }\n\n    let(:folders_disabled) { {\"/folder\"=>\n                                {:SharedFoldersEnableSymlinksCreate=>false,\n                                 :guestpath=>\"/folder\",\n                                 :hostpath=>\"/Users/brian/vagrant-folder\",\n                                 :automount=>false,\n                                 :disabled=>false,\n                                 :__vagrantfile=>true}} }\n\n\n    let(:folders_automount) { {\"/folder\"=>\n                                {:SharedFoldersEnableSymlinksCreate=>true,\n                                 :guestpath=>\"/folder\",\n                                 :hostpath=>\"/Users/brian/vagrant-folder\",\n                                 :disabled=>false,\n                                 :automount=>true,\n                                 :__vagrantfile=>true}} }\n\n    let(:folders_nosymvar) { {\"/folder\"=>\n                                {:guestpath=>\"/folder\",\n                                 :hostpath=>\"/Users/brian/vagrant-folder\",\n                                 :automount=>false,\n                                 :disabled=>false,\n                                 :__vagrantfile=>true}} }\n\n    before do\n      allow(machine).to receive(:provider).and_return(provider)\n      allow(machine).to receive(:env)\n      allow(subject).to receive(:display_symlink_create_warning)\n    end\n\n    it \"should prepare and share the folders\" do\n      expect(driver).to receive(:share_folders).with([{:name=>\"folder\", :hostpath=>\"/Users/brian/vagrant-folder\", :transient=>false, :automount=>false, :SharedFoldersEnableSymlinksCreate=>true}])\n      subject.prepare(machine, folders, nil)\n    end\n\n    it \"should prepare and share the folders without symlinks enabled\" do\n      expect(driver).to receive(:share_folders).with([{:name=>\"folder\", :hostpath=>\"/Users/brian/vagrant-folder\", :transient=>false, :automount=>false, :SharedFoldersEnableSymlinksCreate=>false}])\n      subject.prepare(machine, folders_disabled, nil)\n    end\n\n    it \"should prepare and share the folders without symlinks enabled with env var set\" do\n      stub_env('VAGRANT_DISABLE_VBOXSYMLINKCREATE'=>'1')\n\n      expect(driver).to receive(:share_folders).with([{:name=>\"folder\", :hostpath=>\"/Users/brian/vagrant-folder\", :transient=>false, :automount=>false, :SharedFoldersEnableSymlinksCreate=>false}])\n      subject.prepare(machine, folders_nosymvar, nil)\n    end\n\n    it \"should prepare and share the folders and override symlink setting\" do\n      stub_env('VAGRANT_DISABLE_VBOXSYMLINKCREATE'=>'1')\n\n      expect(driver).to receive(:share_folders).with([{:name=>\"folder\", :hostpath=>\"/Users/brian/vagrant-folder\", :transient=>false, :automount=>false, :SharedFoldersEnableSymlinksCreate=>true}])\n      subject.prepare(machine, folders, nil)\n    end\n\n    it \"should prepare and share the folders with automount enabled\" do\n      expect(driver).to receive(:share_folders).with([{:name=>\"folder\", :hostpath=>\"/Users/brian/vagrant-folder\", :transient=>false, :SharedFoldersEnableSymlinksCreate=>true, :automount=>true}])\n      subject.prepare(machine, folders_automount, nil)\n    end\n  end\n\n  describe \"#os_friendly_id\" do\n    it \"should not replace normal chars\" do\n      expect(subject.send(:os_friendly_id, 'perfectly_valid0_name')).to eq('perfectly_valid0_name')\n    end\n\n    it \"should replace spaces\" do\n      expect(subject.send(:os_friendly_id, 'Program Files')).to eq('Program_Files')\n    end\n\n    it \"should replace leading underscore\" do\n      expect(subject.send(:os_friendly_id, '_vagrant')).to eq('vagrant')\n    end\n\n    it \"should replace slash\" do\n      expect(subject.send(:os_friendly_id, 'va/grant')).to eq('va_grant')\n    end\n\n    it \"should replace leading underscore and slash\" do\n      expect(subject.send(:os_friendly_id, '/vagrant')).to eq('vagrant')\n    end\n\n    it \"should replace backslash\" do\n      expect(subject.send(:os_friendly_id, 'foo\\\\bar')).to eq('foo_bar')\n    end\n  end\n\n  describe \"#share_folders\" do\n    let(:folders){ {'folder1' => {hostpath: '/vagrant', transient: true},\n      'folder2' => {hostpath: '/vagrant2', transient: false}} }\n    let(:symlink_create_disable){ nil }\n    let(:driver){ double(\"driver\") }\n\n    before do\n      allow(subject).to receive(:display_symlink_create_warning)\n      allow(machine).to receive(:env)\n      allow(subject).to receive(:driver).and_return(driver)\n      allow(driver).to receive(:share_folders)\n      allow(ENV).to receive(:[]).and_call_original\n      allow(ENV).to receive(:[]).with(\"VAGRANT_DISABLE_VBOXSYMLINKCREATE\").and_return(symlink_create_disable)\n    end\n\n    it \"should only add transient folder\" do\n      expect(driver).to receive(:share_folders).with(any_args) do |defs|\n        expect(defs.size).to eq(1)\n      end\n      subject.send(:share_folders, machine, folders, true)\n    end\n\n    it \"should display symlink create warning\" do\n      expect(subject).to receive(:display_symlink_create_warning)\n      subject.send(:share_folders, machine, folders, true)\n    end\n\n    context \"with create symlink globally disabled\" do\n      let(:symlink_create_disable){ \"1\" }\n\n      it \"should not enable option within definitions\" do\n        expect(driver).to receive(:share_folders).with(any_args) do |defs|\n          expect(defs.first[:SharedFoldersEnableSymlinksCreate]).to be(false)\n        end\n        subject.send(:share_folders, machine, folders, true)\n      end\n\n      it \"should not display symlink warning\" do\n        expect(subject).not_to receive(:display_symlink_create_warning)\n        subject.send(:share_folders, machine, folders, true)\n      end\n    end\n  end\n\n  describe \"#display_symlink_create_warning\" do\n    let(:env){ double(\"env\", ui: Vagrant::UI::Silent.new, data_dir: double(\"data_dir\")) }\n    let(:gate_file){ double(\"gate\") }\n\n    before{ allow(gate_file).to receive(:to_path).and_return(\"PATH\") }\n    after{ subject.send(:display_symlink_create_warning, env) }\n\n    context \"gate file does not exist\" do\n      before do\n        allow(env.data_dir).to receive(:join).and_return(gate_file)\n        allow(gate_file).to receive(:exist?).and_return(false)\n        allow(FileUtils).to receive(:touch)\n      end\n\n      it \"should create file\" do\n        expect(FileUtils).to receive(:touch).with(\"PATH\")\n      end\n\n      it \"should output warning to user\" do\n        expect(env.ui).to receive(:warn).and_call_original\n      end\n    end\n\n    context \"gate file does exist\" do\n      before do\n        allow(env.data_dir).to receive(:join).and_return(gate_file)\n        allow(gate_file).to receive(:exist?).and_return(true)\n        allow(FileUtils).to receive(:touch)\n      end\n\n      it \"should not create/update file\" do\n        expect(FileUtils).not_to receive(:touch).with(\"PATH\")\n      end\n\n      it \"should not output warning to user\" do\n        expect(env.ui).not_to receive(:warn).and_call_original\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/ansible/cap/guest/alpine/ansible_install_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../../../base\"\nrequire_relative \"../shared/pip_ansible_install_examples\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/ansible/cap/guest/alpine/ansible_install\")\n\ndescribe VagrantPlugins::Ansible::Cap::Guest::Alpine::AnsibleInstall do\n  include_context \"unit\"\n\n  subject { VagrantPlugins::Ansible::Cap::Guest::Alpine::AnsibleInstall }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { double(\"comm\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(communicator).to receive(:execute).and_return(true)\n  end\n\n  describe \"#pip_setup\" do\n    it \"install required alpine packages for pip\" do\n      expect(communicator).to receive(:sudo).once.ordered.\n        with(\"apk add --update --no-cache python3\")\n      expect(communicator).to receive(:sudo).once.ordered.\n        with(\"if [ ! -e /usr/bin/python ]; then ln -sf python3 /usr/bin/python ; fi\")\n      expect(communicator).to receive(:sudo).once.ordered.\n        with(\"apk add --update --no-cache --virtual .build-deps python3-dev libffi-dev openssl-dev build-base\")\n\n      subject.pip_setup(machine)\n    end\n  end\n\n  describe \"#ansible_install\" do\n\n    it_behaves_like \"Ansible setup via pip\"\n\n    describe \"when install_mode is :default (or unknown)\" do\n      it \"installs ansible with 'apk' package manager\" do\n        expect(communicator).to receive(:sudo).once.ordered.\n            with(\"apk add --update --no-cache python3 ansible\")\n        expect(communicator).to receive(:sudo).once.ordered.\n            with(\"if [ ! -e /usr/bin/python ]; then ln -sf python3 /usr/bin/python ; fi\")\n        expect(communicator).to receive(:sudo).once.ordered.\n            with(\"if [ ! -e /usr/bin/pip ]; then ln -sf pip3 /usr/bin/pip ; fi\")\n\n        subject.ansible_install(machine, :default, \"\", \"\", \"\")\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/ansible/cap/guest/arch/ansible_install_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../../../base\"\nrequire_relative \"../shared/pip_ansible_install_examples\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/ansible/cap/guest/arch/ansible_install\")\n\ndescribe VagrantPlugins::Ansible::Cap::Guest::Arch::AnsibleInstall do\n  include_context \"unit\"\n\n  subject { VagrantPlugins::Ansible::Cap::Guest::Arch::AnsibleInstall }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { double(\"comm\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(communicator).to receive(:execute).and_return(true)\n  end\n\n  describe \"#pip_setup\" do\n    it \"install required Arch packages and call Cap::Guest::Pip::get_pip\" do\n      pip_install_cmd = \"foo\"\n\n      expect(communicator).to receive(:sudo).once.ordered.\n        with(\"pacman -Syy --noconfirm\")\n      expect(communicator).to receive(:sudo).once.ordered.\n        with(\"pacman -S --noconfirm base-devel curl git python\")\n      expect(VagrantPlugins::Ansible::Cap::Guest::Pip).to receive(:get_pip).once.ordered.\n        with(machine, pip_install_cmd)\n\n      subject.pip_setup(machine, pip_install_cmd)\n    end\n  end\n\n  describe \"#ansible_install\" do\n\n    it_behaves_like \"Ansible setup via pip\"\n\n    describe \"when install_mode is :default (or unknown)\" do\n      it \"installs ansible with 'pacman' package manager\" do\n        expect(communicator).to receive(:sudo).once.ordered.\n          with(\"pacman -Syy --noconfirm\")\n        expect(communicator).to receive(:sudo).once.ordered.\n          with(\"pacman -S --noconfirm ansible\")\n\n        subject.ansible_install(machine, :default, \"\", \"\", \"\")\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/ansible/cap/guest/debian/ansible_install_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../../../base\"\nrequire_relative \"../shared/pip_ansible_install_examples\"\n\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/ansible/cap/guest/debian/ansible_install\")\n\n\ndescribe VagrantPlugins::Ansible::Cap::Guest::Debian::AnsibleInstall do\n  include_context \"unit\"\n\n  subject { VagrantPlugins::Ansible::Cap::Guest::Debian::AnsibleInstall }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { double(\"comm\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(communicator).to receive(:execute).and_return(true)\n    allow(communicator).to receive(:test).and_return(false)\n  end\n\n  describe \"#ansible_install\" do\n\n    it_behaves_like \"Ansible setup via pip on Debian-based systems\"\n\n    describe \"when install_mode is :default (or unknown)\" do\n      it \"installs ansible with apt package manager\" do\n        install_backports_if_wheezy_release = <<INLINE_CRIPT\nCODENAME=`lsb_release -cs`\nif [ x$CODENAME == 'xwheezy' ]; then\n  echo 'deb http://http.debian.net/debian wheezy-backports main' > /etc/apt/sources.list.d/wheezy-backports.list\nfi\nINLINE_CRIPT\n\n        expect(communicator).to receive(:sudo).once.ordered.with(install_backports_if_wheezy_release)\n        expect(communicator).to receive(:sudo).once.ordered.with(\"apt-get update -y -qq\")\n        expect(communicator).to receive(:sudo).once.ordered.with(\"DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --option \\\"Dpkg::Options::=--force-confold\\\" ansible\")\n\n        subject.ansible_install(machine, :default, \"\", \"\", \"\")\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/ansible/cap/guest/freebsd/ansible_install_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../../../base\"\nrequire_relative \"../shared/pip_ansible_install_examples\"\n\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/ansible/cap/guest/freebsd/ansible_install\")\n\n\ndescribe VagrantPlugins::Ansible::Cap::Guest::FreeBSD::AnsibleInstall do\n  include_context \"unit\"\n\n  subject { VagrantPlugins::Ansible::Cap::Guest::FreeBSD::AnsibleInstall }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { double(\"comm\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(communicator).to receive(:execute).and_return(true)\n  end\n\n  describe \"#ansible_install\" do\n\n    it_behaves_like \"Ansible setup via pip is not implemented\"\n\n    describe \"when install_mode is :default (or unknown)\" do\n      it \"installs ansible with 'pkg' package manager\" do\n        expect(communicator).to receive(:sudo).with(\"pkg install -qy py37-ansible\")\n\n        subject.ansible_install(machine, :default, \"\", \"\", \"\")\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/ansible/cap/guest/pip/pip_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/ansible/cap/guest/pip/pip\")\n\ndescribe VagrantPlugins::Ansible::Cap::Guest::Pip do\n  include_context \"unit\"\n\n  subject { VagrantPlugins::Ansible::Cap::Guest::Pip }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { double(\"comm\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(communicator).to receive(:execute).and_return(true)\n  end\n\n  describe \"#get_pip\" do\n    describe \"when no pip_install_cmd argument is provided\" do\n      it \"installs pip using the default command\" do\n        expect(communicator).to receive(:execute).\n          with(\"curl https://bootstrap.pypa.io/get-pip.py | sudo python\")\n\n        subject.get_pip(machine)\n      end\n    end\n\n    describe \"when pip_install_cmd argument is provided\" do\n      it \"runs the supplied argument instead of default\" do\n        pip_install_cmd = \"foo\"\n\n        expect(communicator).to receive(:execute).with(pip_install_cmd)\n\n        subject.get_pip(machine, pip_install_cmd)\n      end\n\n      it \"installs pip using the default command if the argument is empty\" do\n        pip_install_cmd = \"\"\n\n        expect(communicator).to receive(:execute).\n          with(\"curl https://bootstrap.pypa.io/get-pip.py | sudo python\")\n\n        subject.get_pip(machine, pip_install_cmd)\n      end\n\n      it \"installs pip using the default command if the argument is nil\" do\n        expect(communicator).to receive(:execute).\n          with(\"curl https://bootstrap.pypa.io/get-pip.py | sudo python\")\n\n        subject.get_pip(machine, nil)\n      end\n    end\n  end\nend"
  },
  {
    "path": "test/unit/plugins/provisioners/ansible/cap/guest/redhat/ansible_install_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/ansible/cap/guest/redhat/ansible_install\")\n\ndescribe VagrantPlugins::Ansible::Cap::Guest::RedHat::AnsibleInstall do\n  include_context \"unit\"\n\n  subject { VagrantPlugins::Ansible::Cap::Guest::RedHat::AnsibleInstall }\n\n  let(:iso_env) do\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { double(\"comm\") }\n  let(:dist) { \".el8\" }\n  let(:epel) { 1 }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(communicator).to receive(:execute).with(\"rpm -E %dist\").and_yield(:stdout, dist)\n  end\n\n  describe \"#ansible_epel_download_url\" do\n    it \"returns the correct EPEL download URL for RHEL-like versions below 10\" do\n      expect(subject.ansible_epel_download_url(machine)).to eq(\"https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm\")\n    end\n\n    context \"for RHEL-like versions 10 and above\" do\n      let(:dist) { \".el10\" }\n      it \"returns the correct EPEL download URL\" do\n        out = subject.ansible_epel_download_url(machine)\n        expect(out).to eq(\"https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm\")\n      end\n    end\n  end\n\n  describe \"#ansible_rpm_install\" do\n    before do\n      expect(communicator).to receive(:test).with(\"/usr/bin/which -s dnf\").and_return(false)\n      expect(communicator).to receive(:execute).with(\"yum repolist epel | grep -q epel\", error_check: false).and_return(epel)\n      expect(communicator).to receive(:sudo).with(\"yum -y --enablerepo=epel install ansible\")\n    end\n\n    it \"installs ansible package, when epel is not installed\" do\n      expect(communicator).to receive(:sudo).with(\"sudo rpm -i https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm\")\n      subject.ansible_rpm_install(machine)\n    end\n\n    context \"when the EPEL repository is already installed\" do\n      let(:epel) { 0 }\n      it \"installs ansible package\" do\n        expect(communicator).to_not receive(:sudo).with(\"sudo rpm -i https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm\")\n        subject.ansible_rpm_install(machine)\n      end\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/ansible/cap/guest/shared/pip_ansible_install_examples.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\nshared_examples_for \"Ansible setup via pip\" do\n\n  describe \"when install_mode is :pip\" do\n    before { allow(communicator).to receive(:test) }\n\n    it \"installs pip and calls Cap::Guest::Pip::pip_install\" do\n      expect(communicator).to receive(:sudo).at_least(1).times.ordered\n      expect(VagrantPlugins::Ansible::Cap::Guest::Pip).to receive(:pip_install).once.ordered.\n        with(machine, \"ansible\", anything, anything, true)\n\n      subject.ansible_install(machine, :pip, \"\", \"\", \"\")\n    end\n  end\n\n  describe \"when install_mode is :pip_args_only\" do\n    before { allow(communicator).to receive(:test) }\n\n    it \"installs pip and calls Cap::Guest::Pip::pip_install with 'pip_args' parameter\" do\n      pip_args = \"-r /vagrant/requirements.txt\"\n\n      expect(communicator).to receive(:sudo).at_least(1).times.ordered\n      expect(VagrantPlugins::Ansible::Cap::Guest::Pip).to receive(:pip_install).with(machine, \"\", \"\", pip_args, false).ordered\n\n      subject.ansible_install(machine, :pip_args_only, \"\", pip_args, \"\")\n    end\n  end\n\nend\n\nshared_examples_for \"Ansible setup via pip on Debian-based systems\" do\n\n  describe \"installs required Debian packages and...\" do\n    before { allow(communicator).to receive(:test) }\n    pip_install_cmd = \"foo\"\n\n    it \"calls Cap::Guest::Pip::get_pip with 'pip' install_mode\" do\n      expect(communicator).to receive(:sudo).\n        with(\"apt-get update -y -qq\")\n      expect(communicator).to receive(:sudo).\n        with(\"DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --option \\\"Dpkg::Options::=--force-confold\\\" build-essential curl git libssl-dev libffi-dev python-dev\")\n      expect(communicator).to receive(:sudo).\n        with(\"pip install --upgrade ansible\")\n\n      subject.ansible_install(machine, :pip, \"\", \"\", pip_install_cmd)\n    end\n\n    it \"calls Cap::Guest::Pip::get_pip with 'pip_args_only' install_mode\" do\n      expect(communicator).to receive(:sudo).\n        with(\"apt-get update -y -qq\")\n      expect(communicator).to receive(:sudo).\n        with(\"DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --option \\\"Dpkg::Options::=--force-confold\\\" build-essential curl git libssl-dev libffi-dev python-dev\")\n      expect(communicator).to receive(:sudo).\n        with(\"pip install\")\n\n      subject.ansible_install(machine, :pip_args_only, \"\", \"\", pip_install_cmd)\n    end\n\n    context \"when python-dev-is-python3 package is available\" do\n      before { allow(communicator).to receive(:test).with(\"apt-cache show python-dev-is-python3\").and_return(true) }\n\n      it \"calls Cap::Guest::Pip::get_pip with 'pip' install_mode\" do\n        expect(communicator).to receive(:sudo).\n          with(\"apt-get update -y -qq\")\n        expect(communicator).to receive(:sudo).\n          with(\"DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --option \\\"Dpkg::Options::=--force-confold\\\" build-essential curl git libssl-dev libffi-dev python-dev-is-python3\")\n        expect(communicator).to receive(:sudo).\n          with(\"pip install --upgrade ansible\")\n\n        subject.ansible_install(machine, :pip, \"\", \"\", pip_install_cmd)\n      end\n\n      it \"calls Cap::Guest::Pip::get_pip with 'pip_args_only' install_mode\" do\n        expect(communicator).to receive(:sudo).\n          with(\"apt-get update -y -qq\")\n        expect(communicator).to receive(:sudo).\n          with(\"DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --option \\\"Dpkg::Options::=--force-confold\\\" build-essential curl git libssl-dev libffi-dev python-dev-is-python3\")\n        expect(communicator).to receive(:sudo).\n          with(\"pip install\")\n\n        subject.ansible_install(machine, :pip_args_only, \"\", \"\", pip_install_cmd)\n      end\n    end\n\n  end\n\n  it_behaves_like \"Ansible setup via pip\"\n\nend\n\nshared_examples_for \"Ansible setup via pip is not implemented\" do\n\n  describe \"when install_mode is different from :default\" do\n    it \"raises an AnsiblePipInstallIsNotSupported error\" do\n      expect { subject.ansible_install(machine, :ansible_the_hardway, \"\", \"\", \"\") }.to raise_error(VagrantPlugins::Ansible::Errors::AnsiblePipInstallIsNotSupported)\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/ansible/cap/guest/suse/ansible_install_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../../../base\"\nrequire_relative \"../shared/pip_ansible_install_examples\"\n\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/ansible/cap/guest/suse/ansible_install\")\n\n\ndescribe VagrantPlugins::Ansible::Cap::Guest::SUSE::AnsibleInstall do\n  include_context \"unit\"\n\n  subject { VagrantPlugins::Ansible::Cap::Guest::SUSE::AnsibleInstall }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { double(\"comm\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(communicator).to receive(:execute).and_return(true)\n  end\n\n  describe \"#ansible_install\" do\n\n    it_behaves_like \"Ansible setup via pip is not implemented\"\n\n    describe \"when install_mode is :default (or unknown)\" do\n      it \"installs ansible with 'zypper' package manager\" do\n        expect(communicator).to receive(:sudo).with(\"zypper --non-interactive --quiet install ansible\")\n\n        subject.ansible_install(machine, :default, \"\", \"\", \"\")\n      end\n    end\n  end\n\nend"
  },
  {
    "path": "test/unit/plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../../../base\"\nrequire_relative \"../shared/pip_ansible_install_examples\"\n\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install\")\n\n\ndescribe VagrantPlugins::Ansible::Cap::Guest::Ubuntu::AnsibleInstall do\n  include_context \"unit\"\n\n  subject { VagrantPlugins::Ansible::Cap::Guest::Ubuntu::AnsibleInstall }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { double(\"comm\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(communicator).to receive(:execute).and_return(true)\n  end\n\n  describe \"#ansible_install\" do\n\n    it_behaves_like \"Ansible setup via pip on Debian-based systems\"\n\n    describe \"when install_mode is :default (or unknown)\" do\n      describe \"#ansible_apt_install\" do\n        describe \"installs ansible from ansible/ansible PPA repository\" do\n\n          check_if_add_apt_repository_is_present=\"test -x \\\"$(which add-apt-repository)\\\"\"\n\n          it \"first installs 'software-properties-common' package if add-apt-repository is not already present\" do\n            allow(communicator).to receive(:test).\n              with(check_if_add_apt_repository_is_present).and_return(false)\n\n            expect(communicator).to receive(:sudo).once.ordered.\n              with(\"\"\"\n                  apt-get update -y -qq && \\\n                  DEBIAN_FRONTEND=noninteractive apt-get install -y -qq software-properties-common --option \\\"Dpkg::Options::=--force-confold\\\"\n                \"\"\")\n            expect(communicator).to receive(:sudo).once.ordered.\n              with(\"\"\"\n                add-apt-repository ppa:ansible/ansible -y && \\\n                apt-get update -y -qq && \\\n                DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ansible --option \\\"Dpkg::Options::=--force-confold\\\"\n              \"\"\")\n\n            subject.ansible_install(machine, :default, \"\", \"\", \"\")\n          end\n\n          it \"adds 'ppa:ansible/ansible' and install 'ansible' package\" do\n            allow(communicator).to receive(:test).\n              with(check_if_add_apt_repository_is_present).and_return(true)\n\n            expect(communicator).to receive(:sudo).\n              with(\"\"\"\n                add-apt-repository ppa:ansible/ansible -y && \\\n                apt-get update -y -qq && \\\n                DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ansible --option \\\"Dpkg::Options::=--force-confold\\\"\n              \"\"\")\n\n            subject.ansible_install(machine, :default, \"\", \"\", \"\")\n          end\n\n        end\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/ansible/config/guest_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../support/shared/config\"\nrequire_relative \"shared\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/ansible/config/guest\")\n\ndescribe VagrantPlugins::Ansible::Config::Guest do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  # FIXME: machine.ui.warn stub is not working as expected...\n  let(:machine) { double(\"machine\", env: Vagrant::Environment.new) }\n\n  let(:communicator) { double(\"communicator\") }\n  let(:existing_file) { \"this/path/is/a/stub\" }\n\n  it \"supports a list of options\" do\n    supported_options = %w(\n                            become\n                            become_user\n                            compatibility_mode\n                            config_file\n                            extra_vars\n                            galaxy_command\n                            galaxy_role_file\n                            galaxy_roles_path\n                            groups\n                            host_vars\n                            install\n                            install_mode\n                            inventory_path\n                            limit\n                            pip_args\n                            pip_install_cmd\n                            playbook\n                            playbook_command\n                            provisioning_path\n                            raw_arguments\n                            skip_tags\n                            start_at_task\n                            sudo\n                            sudo_user\n                            tags\n                            tmp_path\n                            vault_password_file\n                            verbose\n                            version\n                          )\n\n    expect(get_provisioner_option_names(described_class)).to eql(supported_options)\n  end\n\n  describe \"default options handling\" do\n    it_behaves_like \"options shared by both Ansible provisioners\"\n\n    it \"assigns default values to unset guest-specific options\" do\n      subject.finalize!\n\n      expect(subject.install).to be(true)\n      expect(subject.install_mode).to eql(:default)\n      expect(subject.provisioning_path).to eql(\"/vagrant\")\n      expect(subject.tmp_path).to eql(\"/tmp/vagrant-ansible\")\n      expect(subject.pip_install_cmd).to eql(\"\")\n    end\n  end\n\n  describe \"#validate\" do\n    before do\n      subject.playbook = existing_file\n    end\n\n    it_behaves_like \"an Ansible provisioner\", \"/vagrant\", \"local\"\n\n    it \"falls back to :default install_mode for any invalid setting\" do\n      subject.install_mode = \"from_source\"\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(subject.install_mode).to eql(:default)\n    end\n\n    it \"supports :pip install_mode\" do\n      subject.install_mode = \"pip\"\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(subject.install_mode).to eql(:pip)\n    end\n\n    it \"supports :pip_args_only install_mode\" do\n      subject.install_mode = \"pip_args_only\"\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(subject.install_mode).to eql(:pip_args_only)\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/ansible/config/host_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\nrequire_relative \"../../support/shared/config\"\nrequire_relative \"shared\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/ansible/config/host\")\n\ndescribe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  let(:machine) { double(\"machine\", env: Vagrant::Environment.new) }\n  let(:existing_file) { File.expand_path(__FILE__) }\n\n  it \"supports a list of options\" do\n    supported_options = %w(\n                            ask_become_pass\n                            ask_sudo_pass\n                            ask_vault_pass\n                            become\n                            become_user\n                            compatibility_mode\n                            config_file\n                            extra_vars\n                            force_remote_user\n                            galaxy_command\n                            galaxy_role_file\n                            galaxy_roles_path\n                            groups\n                            host_key_checking\n                            host_vars\n                            inventory_path\n                            limit\n                            playbook\n                            playbook_command\n                            raw_arguments\n                            raw_ssh_args\n                            skip_tags\n                            start_at_task\n                            sudo\n                            sudo_user\n                            tags\n                            vault_password_file\n                            verbose\n                            version\n                          )\n\n    expect(get_provisioner_option_names(described_class)).to eql(supported_options)\n  end\n\n  describe \"default options handling\" do\n    it_behaves_like \"options shared by both Ansible provisioners\"\n\n    it \"assigns default values to unset host-specific options\" do\n      subject.finalize!\n\n      expect(subject.ask_become_pass).to be(false)\n      expect(subject.ask_sudo_pass).to be(false)      # deprecated\n      expect(subject.ask_vault_pass).to be(false)\n      expect(subject.force_remote_user).to be(true)\n      expect(subject.host_key_checking).to be(false)\n      expect(subject.raw_ssh_args).to be_nil\n    end\n  end\n\n  describe \"force_remote_user option\" do\n    it_behaves_like \"any VagrantConfigProvisioner strict boolean attribute\", :force_remote_user, true\n  end\n  describe \"host_key_checking option\" do\n    it_behaves_like \"any VagrantConfigProvisioner strict boolean attribute\", :host_key_checking, false\n  end\n  describe \"ask_become_pass option\" do\n    it_behaves_like \"any VagrantConfigProvisioner strict boolean attribute\", :ask_become_pass, false\n  end\n  describe \"ask_sudo_pass option\" do\n    before do\n      # Filter the deprecation notice\n      allow($stdout).to receive(:puts)\n    end\n    it_behaves_like \"any VagrantConfigProvisioner strict boolean attribute\", :ask_sudo_pass, false\n    it_behaves_like \"any deprecated option\", :ask_sudo_pass, :ask_become_pass, true\n  end\n  describe \"ask_vault_pass option\" do\n    it_behaves_like \"any VagrantConfigProvisioner strict boolean attribute\", :ask_vault_pass, false\n  end\n\n  describe \"#validate\" do\n    before do\n      subject.playbook = existing_file\n    end\n\n    it_behaves_like \"an Ansible provisioner\", \"\", \"remote\"\n\n    it \"returns an error if the raw_ssh_args is of the wrong data type\" do\n      subject.raw_ssh_args = { arg1: 1, arg2: \"foo\" }\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(result[\"ansible remote provisioner\"]).to eql([\n        I18n.t(\"vagrant.provisioners.ansible.errors.raw_ssh_args_invalid\",\n               type:  subject.raw_ssh_args.class.to_s,\n               value: subject.raw_ssh_args.to_s)\n      ])\n    end\n\n    it \"converts a raw_ssh_args option defined as a String into an Array\" do\n      subject.raw_arguments = \"-o ControlMaster=no\"\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(subject.raw_arguments).to eql([\"-o ControlMaster=no\"])\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/ansible/config/shared.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nshared_examples_for 'options shared by both Ansible provisioners' do\n\n  it \"assigns default values to unset common options\" do\n    subject.finalize!\n\n    expect(subject.become).to be(false)\n    expect(subject.become_user).to be_nil\n    expect(subject.compatibility_mode).to eql(VagrantPlugins::Ansible::COMPATIBILITY_MODE_AUTO)\n    expect(subject.config_file).to be_nil\n    expect(subject.extra_vars).to be_nil\n    expect(subject.galaxy_command).to eql(\"ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force\")\n    expect(subject.galaxy_role_file).to be_nil\n    expect(subject.galaxy_roles_path).to be_nil\n    expect(subject.groups).to eq({})\n    expect(subject.host_vars).to eq({})\n    expect(subject.inventory_path).to be_nil\n    expect(subject.limit).to be_nil\n    expect(subject.playbook).to be_nil\n    expect(subject.playbook_command).to eql(\"ansible-playbook\")\n    expect(subject.raw_arguments).to be_nil\n    expect(subject.skip_tags).to be_nil\n    expect(subject.start_at_task).to be_nil\n    expect(subject.sudo).to be(false)              # deprecated\n    expect(subject.sudo_user).to be_nil            # deprecated\n    expect(subject.tags).to be_nil\n    expect(subject.vault_password_file).to be_nil\n    expect(subject.verbose).to be(false)\n    expect(subject.version).to be_empty\n  end\n\nend\n\nshared_examples_for 'any deprecated option' do |deprecated_option, new_option, option_value|\n  it \"shows the deprecation message\" do\n    expect($stdout).to receive(:puts).with(\"DEPRECATION: The '#{deprecated_option}' option for the Ansible provisioner is deprecated.\").and_return(nil)\n    expect($stdout).to receive(:puts).with(\"Please use the '#{new_option}' option instead.\").and_return(nil)\n    expect($stdout).to receive(:puts).with(\"The '#{deprecated_option}' option will be removed in a future release of Vagrant.\\n\\n\").and_return(nil)\n\n    subject.send(\"#{deprecated_option}=\", option_value)\n    subject.finalize!\n  end\nend\n\nshared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup |\n\n  provisioner_label  = \"ansible #{ansible_setup} provisioner\"\n  provisioner_system = ansible_setup == \"local\" ? \"guest\" : \"host\"\n\n  it \"returns an error if the playbook option is undefined\" do\n    subject.playbook = nil\n    subject.finalize!\n\n    result = subject.validate(machine)\n    expect(result[provisioner_label]).to eql([\n      I18n.t(\"vagrant.provisioners.ansible.errors.no_playbook\")\n    ])\n  end\n\n  describe \"compatibility_mode option\" do\n\n    VagrantPlugins::Ansible::COMPATIBILITY_MODES.each do |valid_mode|\n      it \"supports compatibility mode '#{valid_mode}'\" do\n        subject.compatibility_mode = valid_mode\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(subject.compatibility_mode).to eql(valid_mode)\n      end\n    end\n\n    it \"returns an error if the compatibility mode is not set\" do\n      subject.compatibility_mode = nil\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(result[provisioner_label]).to eql([\n        I18n.t(\"vagrant.provisioners.ansible.errors.no_compatibility_mode\",\n               valid_modes: \"'auto', '1.8', '2.0'\")\n      ])\n    end\n\n    %w(invalid 1.9 2.3).each do |invalid_mode|\n      it \"returns an error if the compatibility mode is invalid (e.g. '#{invalid_mode}')\" do\n        subject.compatibility_mode = invalid_mode\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[provisioner_label]).to eql([\n          I18n.t(\"vagrant.provisioners.ansible.errors.no_compatibility_mode\",\n                 valid_modes: \"'auto', '1.8', '2.0'\")\n        ])\n      end\n    end\n\n  end\n\n  it \"passes if the extra_vars option is a hash\" do\n    subject.extra_vars = { var1: 1, var2: \"foo\" }\n    subject.finalize!\n\n    result = subject.validate(machine)\n    expect(result[provisioner_label]).to eql([])\n  end\n\n  it \"returns an error if the extra_vars option is of wrong data type\" do\n    subject.extra_vars = [\"var1\", 3, \"var2\", 5]\n    subject.finalize!\n\n    result = subject.validate(machine)\n    expect(result[provisioner_label]).to eql([\n      I18n.t(\"vagrant.provisioners.ansible.errors.extra_vars_invalid\",\n             type:  subject.extra_vars.class.to_s,\n             value: subject.extra_vars.to_s)\n    ])\n  end\n\n  it \"converts a raw_arguments option defined as a String into an Array\" do\n    subject.raw_arguments = \"--foo=bar\"\n    subject.finalize!\n\n    result = subject.validate(machine)\n    expect(subject.raw_arguments).to eql(%w(--foo=bar))\n  end\n\n  it \"returns an error if the raw_arguments is of the wrong data type\" do\n    subject.raw_arguments = { arg1: 1, arg2: \"foo\" }\n    subject.finalize!\n\n    result = subject.validate(machine)\n    expect(result[provisioner_label]).to eql([\n      I18n.t(\"vagrant.provisioners.ansible.errors.raw_arguments_invalid\",\n             type:  subject.raw_arguments.class.to_s,\n             value: subject.raw_arguments.to_s)\n    ])\n  end\n\n  it \"it collects and returns all detected errors\" do\n    subject.compatibility_mode = nil\n    subject.playbook = nil\n    subject.extra_vars = [\"var1\", 3, \"var2\", 5]\n    subject.raw_arguments = { arg1: 1, arg2: \"foo\" }\n    subject.finalize!\n\n    result = subject.validate(machine)\n\n    expect(result[provisioner_label].size).to eql(4)\n    expect(result[provisioner_label]).to include(\n      I18n.t(\"vagrant.provisioners.ansible.errors.no_compatibility_mode\",\n             valid_modes: \"'auto', '1.8', '2.0'\"))\n    expect(result[provisioner_label]).to include(\n      I18n.t(\"vagrant.provisioners.ansible.errors.no_playbook\"))\n    expect(result[provisioner_label]).to include(\n      I18n.t(\"vagrant.provisioners.ansible.errors.extra_vars_invalid\",\n             type:  subject.extra_vars.class.to_s,\n             value: subject.extra_vars.to_s))\n    expect(result[provisioner_label]).to include(\n      I18n.t(\"vagrant.provisioners.ansible.errors.raw_arguments_invalid\",\n             type:  subject.raw_arguments.class.to_s,\n             value: subject.raw_arguments.to_s))\n  end\n\n  describe \"become option\" do\n    it_behaves_like \"any VagrantConfigProvisioner strict boolean attribute\", :become, false\n  end\n\n  describe \"sudo option\" do\n    before do\n      # Filter the deprecation notice\n      allow($stdout).to receive(:puts)\n    end\n    it_behaves_like \"any VagrantConfigProvisioner strict boolean attribute\", :sudo, false\n    it_behaves_like \"any deprecated option\", :sudo, :become, true\n  end\n\n  describe \"sudo_user option\" do\n    before do\n      # Filter the deprecation notice\n      allow($stdout).to receive(:puts)\n    end\n    it_behaves_like \"any deprecated option\", :sudo_user, :become_user, \"foo\"\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/ansible/provisioner_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/ansible/config/host\")\nrequire Vagrant.source_root.join(\"plugins/provisioners/ansible/provisioner/host\")\n\n#\n# Helper Functions\n#\n\ndef find_last_argument_after(ref_index, ansible_playbook_args, arg_pattern)\n  subset = ansible_playbook_args[(ref_index + 1)..(ansible_playbook_args.length-2)].reverse\n  subset.each do |i|\n    return true if i =~ arg_pattern\n  end\n  return false\nend\n\ndescribe VagrantPlugins::Ansible::Provisioner::Host do\n  include_context \"unit\"\n\n  subject { described_class.new(machine, config) }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a Vagrant Environment to provide:\n    # - a location for the generated inventory\n    # - multi-machines configuration\n\n    env = isolated_environment\n    env.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"base\"\n  config.vm.define :machine1\n  config.vm.define :machine2\nend\nVF\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:config)  { VagrantPlugins::Ansible::Config::Host.new }\n  let(:ssh_info) {{\n    private_key_path: ['/path/to/my/key'],\n    keys_only: true,\n    username: 'testuser',\n    host: '127.0.0.1',\n    port: 2223\n  }}\n  let(:default_execute_result) { Vagrant::Util::Subprocess::Result.new(0, \"\", \"\") }\n\n  let(:existing_file) { File.expand_path(__FILE__) }\n  let(:generated_inventory_dir) { File.join(machine.env.local_data_path, %w(provisioners ansible inventory)) }\n  let(:generated_inventory_file) { File.join(generated_inventory_dir, 'vagrant_ansible_inventory') }\n\n  before do\n    allow(Vagrant::Util::Platform).to receive(:solaris?).and_return(false)\n\n    allow(machine).to receive(:ssh_info).and_return(ssh_info)\n    allow(machine.env).to receive(:active_machines)\n      .and_return([[iso_env.machine_names[0], :dummy], [iso_env.machine_names[1], :dummy]])\n\n    stubbed_ui = Vagrant::UI::Colored.new\n    allow(stubbed_ui).to receive(:detail).and_return(\"\")\n    allow(stubbed_ui).to receive(:warn).and_return(\"\")\n\n    allow(machine.env).to receive(:ui).and_return(stubbed_ui)\n\n    config.playbook = 'playbook.yml'\n  end\n\n  #\n  # Class methods for code reuse across examples\n  #\n\n  def self.it_should_check_ansible_version\n    it \"execute 'Python ansible version check before executing 'ansible-playbook'\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute)\n        .once.with('python3', '-c', \"import importlib.metadata; print('ansible ' + importlib.metadata.version('ansible'))\", { notify: %i[\n                     stdout stderr\n                   ] })\n      expect(Vagrant::Util::Subprocess).to receive(:execute)\n        .once.with('ansible-playbook', any_args)\n    end\n  end\n\n  def self.it_should_check_ansible_core_version\n    it \"executes 'Python ansible-core version check before executing 'ansible-playbook'\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute)\n        .once.with('python3', '-c', \"import importlib.metadata; print('ansible-core ' + importlib.metadata.version('ansible-core'))\", { notify: %i[\n                     stdout stderr\n                   ] })\n      expect(Vagrant::Util::Subprocess).to receive(:execute)\n        .once.with('ansible-playbook', any_args)\n    end\n  end\n\n  def self.it_should_set_arguments_and_environment_variables(\n    expected_args_count = 5,\n    expected_vars_count = 4,\n    expected_host_key_checking = false,\n    expected_transport_mode = \"ssh\")\n\n    it \"sets implicit arguments in a specific order\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n        expect(args[1]).to eq(\"--connection=ssh\")\n        expect(args[2]).to eq(\"--timeout=30\")\n\n        inventory_count = args.count { |x| x.match(/^--inventory-file=.+$/) if x.is_a?(String) }\n        expect(inventory_count).to be > 0\n\n        expect(args[args.length-2]).to eq(\"playbook.yml\")\n      }.and_return(default_execute_result)\n    end\n\n    it \"sets --limit argument\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n        all_limits = args.select { |x| x.match(/^(--limit=|-l)/) if x.is_a?(String) }\n        if config.raw_arguments\n          raw_limits = config.raw_arguments.select { |x| x.match(/^(--limit=|-l)/) if x.is_a?(String) }\n          expect(all_limits.length - raw_limits.length).to eq(1)\n          expect(all_limits.last).to eq(raw_limits.last)\n        else\n          if config.limit\n            limit = config.limit.kind_of?(Array) ? config.limit.join(',') : config.limit\n            expect(all_limits.last).to eq(\"--limit=#{limit}\")\n          else\n            expect(all_limits.first).to eq(\"--limit=#{machine.name}\")\n          end\n        end\n      }.and_return(default_execute_result)\n    end\n\n    it \"exports environment variables\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n        cmd_opts = args.last\n\n        if expected_host_key_checking\n          expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include(\"-o UserKnownHostsFile=/dev/null\")\n        else\n          expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include(\"-o UserKnownHostsFile=/dev/null\")\n        end\n\n        expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include(\"-o IdentitiesOnly=yes\")\n        expect(cmd_opts[:env]['ANSIBLE_FORCE_COLOR']).to eql(\"true\")\n        expect(cmd_opts[:env]).to_not include(\"ANSIBLE_NOCOLOR\")\n        expect(cmd_opts[:env]['ANSIBLE_HOST_KEY_CHECKING']).to eql(expected_host_key_checking.to_s)\n        expect(cmd_opts[:env]['PYTHONUNBUFFERED']).to eql(1)\n      }.and_return(default_execute_result)\n    end\n\n    # \"roughly\" verify that only expected args/vars have been defined by the provisioner\n    it \"sets the expected number of arguments and environment variables\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n        expect(args.length - 2).to eq(expected_args_count)\n        expect(args.last[:env].length).to eq(expected_vars_count)\n      }.and_return(default_execute_result)\n    end\n\n    it \"enables '#{expected_transport_mode}' as default transport mode\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n        index = args.rindex(\"--connection=#{expected_transport_mode}\")\n        expect(index).to be > 0\n        expect(find_last_argument_after(index, args, /--connection=\\w+/)).to be(false)\n      }.and_return(default_execute_result)\n    end\n\n  end\n\n  def self.it_should_set_optional_arguments(arg_map)\n    it \"sets optional arguments\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n        arg_map.each_pair do |vagrant_option, ansible_argument|\n          index = args.index(ansible_argument)\n          if config.send(vagrant_option)\n            expect(index).to be > 0\n          else\n            expect(index).to be_nil\n          end\n        end\n      }.and_return(default_execute_result)\n    end\n  end\n\n  def self.it_should_explicitly_enable_ansible_ssh_control_persist_defaults\n    it \"configures ControlPersist (like Ansible defaults) via ANSIBLE_SSH_ARGS\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n        cmd_opts = args.last\n        expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include(\"-o ControlMaster=auto\")\n        expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include(\"-o ControlPersist=60s\")\n      }.and_return(default_execute_result)\n    end\n  end\n\n  def self.it_should_create_and_use_generated_inventory(with_user = true)\n    it \"generates an inventory with all active machines\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n        expect(config.inventory_path).to be_nil\n        expect(File.exist?(generated_inventory_file)).to be(true)\n        inventory_content = File.read(generated_inventory_file)\n        _ssh = config.compatibility_mode == VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0 ? \"\" : \"_ssh\"\n        if with_user\n          expect(inventory_content).to include(\"#{machine.name} ansible#{_ssh}_host=#{machine.ssh_info[:host]} ansible#{_ssh}_port=#{machine.ssh_info[:port]} ansible#{_ssh}_user='#{machine.ssh_info[:username]}' ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\\n\")\n        else\n          expect(inventory_content).to include(\"#{machine.name} ansible#{_ssh}_host=#{machine.ssh_info[:host]} ansible#{_ssh}_port=#{machine.ssh_info[:port]} ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\\n\")\n        end\n        expect(inventory_content).to include(\"# MISSING: '#{iso_env.machine_names[1]}' machine was probably removed without using Vagrant. This machine should be recreated.\\n\")\n      }.and_return(default_execute_result)\n    end\n\n    it \"sets as ansible inventory the directory containing the auto-generated inventory file\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n        inventory_index = args.rindex(\"--inventory-file=#{generated_inventory_dir}\")\n        expect(inventory_index).to be > 0\n        expect(find_last_argument_after(inventory_index, args, /--inventory-file=\\w+/)).to be(false)\n      }.and_return(default_execute_result)\n    end\n  end\n\n  def ensure_that_config_is_valid\n    # Abort the test when an invalid configuration is detected\n    config.validate(machine)\n    if config._detected_errors.length > 0\n      raise \"Invalid Provisioner Configuration! Detected Errors:\\n#{config._detected_errors.to_s}\"\n    end\n  end\n\n  describe \"#provision\" do\n\n    before do\n      unless RSpec.current_example.metadata[:skip_before]\n        config.finalize!\n\n        allow(Vagrant::Util::Subprocess).to receive(:execute)\n          .and_return(Vagrant::Util::Subprocess::Result.new(0, \"\", \"\"))\n        allow(subject).to receive(:check_path)\n      end\n    end\n\n    after do\n      unless RSpec.current_example.metadata[:skip_after]\n        ensure_that_config_is_valid\n\n        subject.provision\n      end\n    end\n\n    describe 'checking existence of Ansible configuration files' do\n\n      STUBBED_INVALID_PATH = \"/test/239nfmd/invalid_path\".freeze\n\n      it 'raises an error when the `playbook` file does not exist', skip_before: true, skip_after: true do\n        allow(subject).to receive(:check_path).and_raise(VagrantPlugins::Ansible::Errors::AnsibleError,\n          _key: :config_file_not_found,\n          config_option: \"playbook\",\n          path: STUBBED_INVALID_PATH,\n          system: \"host\")\n\n        config.playbook = STUBBED_INVALID_PATH\n        config.finalize!\n        ensure_that_config_is_valid\n\n        expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleError,\n          \"`playbook` does not exist on the host: #{STUBBED_INVALID_PATH}\")\n      end\n\n      %w(config_file extra_vars inventory_path galaxy_role_file vault_password_file).each do |option_name|\n        it \"raises an error when the '#{option_name}' does not exist\", skip_before: true, skip_after: true do\n          allow(Vagrant::Util::Subprocess).to receive(:execute)\n            .and_return( Vagrant::Util::Subprocess::Result.new(0, \"\", \"\"))\n\n            config.playbook = existing_file\n            config.send(option_name + '=', STUBBED_INVALID_PATH)\n            config.finalize!\n            ensure_that_config_is_valid\n\n            expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleError,\n              \"`#{option_name}` does not exist on the host: #{STUBBED_INVALID_PATH}\")\n        end\n      end\n\n    end\n\n    describe 'when ansible-playbook fails' do\n      it \"raises an error\", skip_before: true, skip_after: true do\n        config.finalize!\n        ensure_that_config_is_valid\n\n        allow(subject).to receive(:check_path)\n        allow(Vagrant::Util::Subprocess).to receive(:execute)\n          .and_return(Vagrant::Util::Subprocess::Result.new(1, \"\", \"\"))\n\n        expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCommandFailed)\n      end\n    end\n\n    describe \"with default options\" do\n      it_should_check_ansible_version\n      it_should_check_ansible_core_version\n      it_should_set_arguments_and_environment_variables\n      it_should_create_and_use_generated_inventory\n\n      it \"does not add any group section to the generated inventory\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {\n          inventory_content = File.read(generated_inventory_file)\n          expect(inventory_content).to_not match(/^\\s*\\[^\\\\+\\]\\s*$/)\n        }.and_return(default_execute_result)\n      end\n\n      it \"doesn't show the ansible-playbook command\" do\n        expect(machine.env.ui).to_not receive(:detail).with(/ansible-playbook/)\n      end\n    end\n\n    describe \"deprecated 'sudo' options are aliases for equivalent 'become' options\" do\n      before do\n        # Filter the deprecation notices\n        allow($stdout).to receive(:puts)\n\n        config.sudo = true\n        config.sudo_user = 'deployer'\n        config.ask_sudo_pass = true\n      end\n\n      it_should_set_optional_arguments({\"sudo\"            => \"--sudo\",\n                                        \"sudo_user\"       => \"--sudo-user=deployer\",\n                                        \"ask_sudo_pass\"   => \"--ask-sudo-pass\",\n                                        \"become\"          => \"--sudo\",\n                                        \"become_user\"     => \"--sudo-user=deployer\",\n                                        \"ask_become_pass\" => \"--ask-sudo-pass\"})\n    end\n\n    context \"with compatibility_mode 'auto'\" do\n      before do\n        config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_AUTO\n      end\n\n      valid_versions = {\n        \"0.6\": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8,\n        \"1.9.4\": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8,\n        \"2.5.0.0-rc1\": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0,\n        \"2.x.y.z\": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0,\n        \"4.3.2.1\": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0,\n        \"[core 2.11.0]\": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0,\n        \"7.1.0\": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0,\n        \"10.1.0\": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0\n      }\n      valid_versions.each_pair do |ansible_version, mode|\n        describe \"and ansible version #{ansible_version}\" do\n          before do\n            allow(subject).to receive(:gather_ansible_version).and_return(\"ansible #{ansible_version}\\n...\\n\")\n          end\n\n          it \"detects the compatibility mode #{mode}\" do\n            expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n              expect(config.compatibility_mode).to eq(mode)\n            }.and_return(default_execute_result)\n          end\n        end\n      end\n\n      invalid_versions = [\n        \"ansible devel\",\n        \"anything 1.2\",\n        \"2.9.2.1\",\n      ]\n      invalid_versions.each do |unknown_ansible_version|\n        describe \"and `ansible version check returning '#{unknown_ansible_version}'\" do\n          before do\n            allow(subject).to receive(:gather_ansible_version).and_return(unknown_ansible_version)\n          end\n\n          it \"applies the safest compatibility mode ('#{VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE}')\" do\n            expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n              expect(config.compatibility_mode).to eq(VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE)\n            }.and_return(default_execute_result)\n          end\n\n          it \"warns about not being able to detect the best compatibility mode\" do\n            expect(machine.env.ui).to receive(:warn).with(\n              I18n.t(\"vagrant.provisioners.ansible.compatibility_mode_not_detected\",\n                compatibility_mode: VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE,\n                gathered_version: unknown_ansible_version) +\n              \"\\n\")\n          end\n        end\n      end\n\n    end\n\n    context \"with compatibility_mode '#{VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8}'\" do\n      before do\n        config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8\n      end\n\n      it_should_check_ansible_version\n      it_should_check_ansible_core_version\n      it_should_create_and_use_generated_inventory\n\n      it \"doesn't warn about compatibility mode auto-detection\" do\n        expect(machine.env.ui).to_not receive(:warn)\n      end\n    end\n\n    context \"with compatibility_mode '#{VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0}'\" do\n      before do\n        config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0\n        allow(subject).to receive(:gather_ansible_version).and_return(\"ansible 2.3.0.0\\n...\\n\")\n      end\n\n      it_should_create_and_use_generated_inventory\n\n      it \"doesn't warn about compatibility mode auto-detection\" do\n        expect(machine.env.ui).to_not receive(:warn)\n      end\n\n      describe \"and an incompatible ansible version\" do\n        before do\n          allow(subject).to receive(:gather_ansible_version).and_return(\"ansible 1.9.3\\n...\\n\")\n        end\n\n        it \"raises a compatibility conflict error\", skip_before: false, skip_after: true do\n          ensure_that_config_is_valid\n          expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCompatibilityModeConflict)\n        end\n      end\n\n      describe \"deprecated 'sudo' options are aliases for equivalent 'become' options\" do\n        before do\n          # Filter the deprecation notices\n          allow($stdout).to receive(:puts)\n\n          config.sudo = true\n          config.sudo_user = 'deployer'\n          config.ask_sudo_pass = true\n        end\n\n        it_should_set_optional_arguments({\"sudo\"            => \"--become\",\n                                          \"sudo_user\"       => \"--become-user=deployer\",\n                                          \"ask_sudo_pass\"   => \"--ask-become-pass\",\n                                          \"become\"          => \"--become\",\n                                          \"become_user\"     => \"--become-user=deployer\",\n                                          \"ask_become_pass\" => \"--ask-become-pass\"})\n      end\n    end\n\n    describe \"with playbook_command option\" do\n      before do\n        config.playbook_command = \"custom-ansible-playbook\"\n\n        # set the compatibility mode to ensure that only ansible-playbook is executed\n        config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8\n      end\n\n      it \"uses custom playbook_command to run playbooks\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute)\n          .with(\"custom-ansible-playbook\", any_args)\n          .and_return(default_execute_result)\n      end\n    end\n\n    describe \"with host_vars option\" do\n      it_should_create_and_use_generated_inventory\n\n      it \"adds host variables (given in Hash format) to the generated inventory\" do\n        config.host_vars = {\n          machine1: {\n            \"http_port\" => 80,\n            \"comments\" => \"'some text with spaces and quotes'\",\n            \"description\" => \"text with spaces but no quotes\",\n          }\n        }\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {\n          inventory_content = File.read(generated_inventory_file)\n          expect(inventory_content).to match(\"^\" + Regexp.quote(machine.name) + \".+http_port=80 comments='some text with spaces and quotes' description='text with spaces but no quotes'\")\n        }.and_return(default_execute_result)\n      end\n\n      it \"adds host variables (given in Array format) to the generated inventory\" do\n        config.host_vars = {\n          machine1: [\"http_port=80\", \"maxRequestsPerChild=808\"]\n        }\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {\n          inventory_content = File.read(generated_inventory_file)\n          expect(inventory_content).to match(\"^\" + Regexp.quote(machine.name) + \".+http_port=80 maxRequestsPerChild=808\")\n        }.and_return(default_execute_result)\n      end\n\n      it \"adds host variables (given in String format) to the generated inventory \" do\n        config.host_vars = {\n          :machine1 => \"http_port=80 maxRequestsPerChild=808\"\n        }\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {\n          inventory_content = File.read(generated_inventory_file)\n          expect(inventory_content).to match(\"^\" + Regexp.quote(machine.name) + \".+http_port=80 maxRequestsPerChild=808\")\n        }.and_return(default_execute_result)\n      end\n\n      it \"retrieves the host variables by machine name, also in String format\" do\n        config.host_vars = {\n          \"machine1\" => \"http_port=80 maxRequestsPerChild=808\"\n        }\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {\n          inventory_content = File.read(generated_inventory_file)\n          expect(inventory_content).to match(\"^\" + Regexp.quote(machine.name) + \".+http_port=80 maxRequestsPerChild=808\")\n        }.and_return(default_execute_result)\n      end\n    end\n\n    describe \"with groups option\" do\n      it_should_create_and_use_generated_inventory\n\n      it \"adds group sections to the generated inventory\" do\n        config.groups = {\n          \"group1\" => \"machine1\",\n          \"group1:children\" => 'bar group3',\n          \"group2\" => [iso_env.machine_names[1]],\n          \"group3\" => [\"unknown\", \"#{machine.name}\"],\n          \"group4\" => [\"machine[1:2]\", \"machine[a:f]\"],\n          \"group6\" => [machine.name],\n          \"bar\" => [\"#{machine.name}\", \"group3\"],\n          \"bar:children\" => [\"group1\", \"group2\", \"group3\", \"group5\"],\n        }\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          inventory_content = File.read(generated_inventory_file)\n\n          # Accept String instead of Array for group member list\n          expect(inventory_content).to include(\"[group1]\\nmachine1\\n\\n\")\n          expect(inventory_content).to include(\"[group1:children]\\nbar\\ngroup3\\n\\n\")\n\n          # Skip \"lost\" machines\n          expect(inventory_content).to include(\"[group2]\\n\\n\")\n\n          # Skip \"unknown\" machines\n          expect(inventory_content).to include(\"[group3]\\n#{machine.name}\\n\\n\")\n\n          # Accept Symbol datatype for group names\n          expect(inventory_content).to include(\"[group6]\\n#{machine.name}\\n\\n\")\n\n          # Accept host range patterns\n          expect(inventory_content).to include(\"[group4]\\nmachine[1:2]\\nmachine[a:f]\\n\")\n\n          # Don't mix group names and host names\n          expect(inventory_content).to include(\"[bar]\\n#{machine.name}\\n\\n\")\n\n          # A group of groups only includes declared groups\n          expect(inventory_content).not_to include(\"group5\")\n          expect(inventory_content).to match(Regexp.quote(\"[bar:children]\\ngroup1\\ngroup2\\ngroup3\\n\") + \"$\")\n        }.and_return(default_execute_result)\n      end\n\n      it \"adds group vars to the generated inventory\" do\n        config.groups = {\n          \"group1\" => [machine.name],\n          \"group2\" => [machine.name],\n          \"group3\" => [machine.name],\n          \"group1:vars\" => {\"hashvar1\" => \"hashvalue1\", \"hashvar2\" => \"hashvalue2\"},\n          \"group2:vars\" => [\"arrayvar1=arrayvalue1\", \"arrayvar2=arrayvalue2\"],\n          \"group3:vars\" => \"stringvar1=stringvalue1 stringvar2=stringvalue2\",\n        }\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          inventory_content = File.read(generated_inventory_file)\n\n          # Hash syntax\n          expect(inventory_content).to include(\"[group1:vars]\\nhashvar1=hashvalue1\\nhashvar2=hashvalue2\\n\")\n\n          # Array syntax\n          expect(inventory_content).to include(\"[group2:vars]\\narrayvar1=arrayvalue1\\narrayvar2=arrayvalue2\\n\")\n\n          # Single string syntax\n          expect(inventory_content).to include(\"[group3:vars]\\nstringvar1=stringvalue1\\nstringvar2=stringvalue2\\n\")\n        }.and_return(default_execute_result)\n      end\n\n      it \"adds 'all:vars' section to the generated inventory\" do\n        config.groups = {\n          \"all:vars\" => { \"var1\" => \"value1\", \"var2\" => \"value2\" }\n        }\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          inventory_content = File.read(generated_inventory_file)\n\n          expect(inventory_content).to include(\"[all:vars]\\nvar1=value1\\nvar2=value2\\n\")\n\n        }.and_return(default_execute_result)\n      end\n    end\n\n    describe \"with host_key_checking option enabled\" do\n      before do\n        config.host_key_checking = true\n      end\n\n      it_should_set_arguments_and_environment_variables 5, 4, true\n    end\n\n    describe \"with boolean (flag) options disabled\" do\n      before do\n        config.become = false\n        config.ask_become_pass = false\n        config.ask_vault_pass = false\n\n        config.become_user = 'root'\n      end\n\n      it_should_set_arguments_and_environment_variables 6\n      it_should_set_optional_arguments({ \"become_user\" => \"--sudo-user=root\" })\n\n      it \"it does not set boolean flag when corresponding option is set to false\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          expect(args.index(\"--sudo\")).to be_nil\n          expect(args.index(\"--ask-sudo-pass\")).to be_nil\n          expect(args.index(\"--ask-vault-pass\")).to be_nil\n        }.and_return(default_execute_result)\n      end\n    end\n\n    describe \"with raw_arguments option\" do\n      before do\n        config.become = false\n        config.force_remote_user = false\n        config.skip_tags = %w(foo bar)\n        config.limit = \"all\"\n        config.raw_arguments = [\"--connection=paramiko\",\n                                \"--skip-tags=ignored\",\n                                \"--module-path=/other/modules\",\n                                \"--sudo\",\n                                \"-l localhost\",\n                                \"--limit=foo\",\n                                \"--limit=bar\",\n                                \"--inventory-file=/forget/it/my/friend\",\n                                \"--user=lion\",\n                                \"--new-arg=yeah\"]\n      end\n\n      it_should_set_arguments_and_environment_variables 17, 4, false, \"paramiko\"\n\n      it \"sets all raw arguments\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          config.raw_arguments.each do |raw_arg|\n            expect(args).to include(raw_arg)\n          end\n        }.and_return(default_execute_result)\n      end\n\n      it \"sets raw arguments after arguments related to supported options\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          expect(args.index(\"--user=lion\")).to be > args.index(\"--user=testuser\")\n          expect(args.index(\"--inventory-file=/forget/it/my/friend\")).to be > args.index(\"--inventory-file=#{generated_inventory_dir}\")\n          expect(args.index(\"--limit=bar\")).to be > args.index(\"--limit=all\")\n          expect(args.index(\"--skip-tags=ignored\")).to be > args.index(\"--skip-tags=foo,bar\")\n        }.and_return(default_execute_result)\n      end\n\n      it \"sets boolean flag (e.g. --sudo) defined in raw_arguments, even if corresponding option is set to false\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          expect(args).to include('--sudo')\n        }.and_return(default_execute_result)\n      end\n\n    end\n\n    describe \"with limit option\" do\n      before do\n        config.limit = %w(foo !bar)\n      end\n\n      it_should_set_arguments_and_environment_variables\n    end\n\n    context \"with force_remote_user option disabled\" do\n      before do\n        config.force_remote_user = false\n      end\n\n      it_should_create_and_use_generated_inventory false # i.e. without setting ansible_ssh_user in inventory\n\n      it_should_set_arguments_and_environment_variables 6\n\n      it \"uses a --user argument to set a default remote user\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          expect(args).not_to include(\"--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'\")\n          expect(args).to include(\"--user=#{machine.ssh_info[:username]}\")\n        }.and_return(default_execute_result)\n      end\n    end\n\n    context \"with winrm communicator\" do\n\n      let(:iso_winrm_env) do\n        env = isolated_environment\n        env.vagrantfile <<-VF\nVagrant.configure(\"2\") do |config|\n  config.winrm.username = 'winner'\n  config.winrm.password = 'winword'\n  config.winrm.transport = :ssl\n\n  config.vm.define :machine1 do |machine|\n    machine.vm.box = \"winbox\"\n    machine.vm.communicator = :winrm\n  end\nend\nVF\n        env.create_vagrant_env\n      end\n\n      let(:machine) { iso_winrm_env.machine(iso_winrm_env.machine_names[0], :dummy) }\n\n      it_should_set_arguments_and_environment_variables\n\n      it \"generates an inventory with winrm connection settings\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          expect(config.inventory_path).to be_nil\n          expect(File.exist?(generated_inventory_file)).to be(true)\n          inventory_content = File.read(generated_inventory_file)\n\n          expect(inventory_content).to include(\"machine1 ansible_connection=winrm ansible_ssh_host=127.0.0.1 ansible_ssh_port=55986 ansible_ssh_user='winner' ansible_ssh_pass='winword'\\n\")\n        }.and_return(default_execute_result)\n      end\n\n      describe \"with force_remote_user option disabled\" do\n        before do\n          config.force_remote_user = false\n        end\n\n        it \"doesn't set the ansible remote user in inventory and use '--user' argument with the vagrant ssh username\" do\n          expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n            inventory_content = File.read(generated_inventory_file)\n\n            expect(inventory_content).to include(\"machine1 ansible_connection=winrm ansible_ssh_host=127.0.0.1 ansible_ssh_port=55986 ansible_ssh_pass='winword'\\n\")\n            expect(args).to include(\"--user=testuser\")\n          }.and_return(default_execute_result)\n        end\n      end\n    end\n\n    describe \"with inventory_path option\" do\n      before do\n        config.inventory_path = existing_file\n      end\n\n      it_should_set_arguments_and_environment_variables 6\n\n      it \"does not generate the inventory and uses given inventory path instead\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          expect(args).to include(\"--inventory-file=#{existing_file}\")\n          expect(args).not_to include(\"--inventory-file=#{generated_inventory_file}\")\n          expect(File.exist?(generated_inventory_file)).to be(false)\n        }.and_return(default_execute_result)\n      end\n\n      it \"uses an --extra-vars argument to force ansible_ssh_user parameter\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          expect(args).not_to include(\"--user=#{machine.ssh_info[:username]}\")\n          expect(args).to include(\"--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'\")\n        }.and_return(default_execute_result)\n      end\n\n      describe \"with force_remote_user option disabled\" do\n        before do\n          config.force_remote_user = false\n        end\n\n        it \"uses a --user argument to set a default remote user\" do\n          expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n            expect(args).not_to include(\"--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'\")\n            expect(args).to include(\"--user=#{machine.ssh_info[:username]}\")\n          }.and_return(default_execute_result)\n        end\n      end\n    end\n\n    context \"with config_file option defined\" do\n      before do\n        config.config_file = existing_file\n      end\n\n      it \"sets ANSIBLE_CONFIG environment variable\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          cmd_opts = args.last\n          expect(cmd_opts[:env]).to include(\"ANSIBLE_CONFIG\")\n          expect(cmd_opts[:env]['ANSIBLE_CONFIG']).to eql(existing_file)\n        }.and_return(default_execute_result)\n      end\n    end\n\n    describe \"with ask_vault_pass option\" do\n      before do\n        config.ask_vault_pass = true\n      end\n\n      it_should_set_arguments_and_environment_variables 6\n\n      it \"should ask the vault password\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          expect(args).to include(\"--ask-vault-pass\")\n        }.and_return(default_execute_result)\n      end\n    end\n\n    describe \"with vault_password_file option\" do\n      before do\n        config.vault_password_file = existing_file\n      end\n\n      it_should_set_arguments_and_environment_variables 6\n\n      it \"uses the given vault password file\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          expect(args).to include(\"--vault-password-file=#{existing_file}\")\n        }.and_return(default_execute_result)\n      end\n    end\n\n    describe \"with raw_ssh_args\" do\n      before do\n        config.raw_ssh_args = ['-o ControlMaster=no', '-o ForwardAgent=no']\n      end\n\n      it_should_set_arguments_and_environment_variables\n      it_should_explicitly_enable_ansible_ssh_control_persist_defaults\n\n      it \"passes custom SSH options via ANSIBLE_SSH_ARGS with the highest priority\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          cmd_opts = args.last\n          raw_opt_index = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index(\"-o ControlMaster=no\")\n          default_opt_index = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index(\"-o ControlMaster=auto\")\n          expect(raw_opt_index).to be < default_opt_index\n        }.and_return(default_execute_result)\n      end\n\n      describe \"and with ssh forwarding enabled\" do\n        before do\n          ssh_info[:forward_agent] = true\n        end\n\n        it \"sets '-o ForwardAgent=yes' via ANSIBLE_SSH_ARGS with higher priority than raw_ssh_args values\" do\n          expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n            cmd_opts = args.last\n            forwardAgentYes = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index(\"-o ForwardAgent=yes\")\n            forwardAgentNo = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index(\"-o ForwardAgent=no\")\n            expect(forwardAgentYes).to be < forwardAgentNo\n          }.and_return(default_execute_result)\n        end\n      end\n\n    end\n\n    describe \"with multiple SSH identities\" do\n      before do\n        ssh_info[:private_key_path] = ['/path/to/my/key', '/an/other/identity', '/yet/an/other/key']\n      end\n\n      it_should_set_arguments_and_environment_variables\n      it_should_explicitly_enable_ansible_ssh_control_persist_defaults\n\n      it \"passes additional Identity Files via ANSIBLE_SSH_ARGS\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          cmd_opts = args.last\n          expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include(\"-o IdentityFile=/an/other/identity\")\n          expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include(\"-o IdentityFile=/yet/an/other/key\")\n        }.and_return(default_execute_result)\n      end\n    end\n\n    describe \"with an identity file containing `%`\" do\n      before do\n        ssh_info[:private_key_path] = ['/foo%bar/key', '/bar%%buz/key']\n      end\n\n      it \"replaces `%` with `%%`\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          cmd_opts = args.last\n          expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include(\"-o IdentityFile=/foo%%bar/key\")\n          expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include(\"-o IdentityFile=/bar%%%%buz/key\")\n        }.and_return(default_execute_result)\n      end\n    end\n\n    describe \"with ssh forwarding enabled\" do\n      before do\n        ssh_info[:forward_agent] = true\n      end\n\n      it_should_set_arguments_and_environment_variables\n      it_should_explicitly_enable_ansible_ssh_control_persist_defaults\n\n      it \"enables SSH-Forwarding via ANSIBLE_SSH_ARGS\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          cmd_opts = args.last\n          expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include(\"-o ForwardAgent=yes\")\n        }.and_return(default_execute_result)\n      end\n    end\n\n    describe \"with an ssh proxy command configured\" do\n      before do\n        ssh_info[:proxy_command] = \"ssh -W %h:%p -q user@remote_libvirt_host\"\n      end\n\n      it \"sets '-o ProxyCommand' via ANSIBLE_SSH_ARGS\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          cmd_opts = args.last\n          expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include(\"-o ProxyCommand='ssh -W %h:%p -q user@remote_libvirt_host'\")\n        }.and_return(default_execute_result)\n      end\n    end\n\n    context \"with verbose option defined\" do\n      %w(vv vvvv).each do |verbose_option|\n\n        describe \"with a value of '#{verbose_option}'\" do\n          before do\n            config.verbose = verbose_option\n          end\n\n          it_should_set_arguments_and_environment_variables 6\n          it_should_set_optional_arguments({ \"verbose\" => \"-#{verbose_option}\" })\n\n          it \"shows the ansible-playbook command and set verbosity to '-#{verbose_option}' level\" do\n            expect(machine.env.ui).to receive(:detail)\n              .with(\"PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit=\\\"machine1\\\" --inventory-file=#{generated_inventory_dir} -#{verbose_option} playbook.yml\")\n          end\n        end\n\n        describe \"with a value of '-#{verbose_option}'\" do\n          before do\n            config.verbose = \"-#{verbose_option}\"\n          end\n\n          it_should_set_arguments_and_environment_variables 6\n          it_should_set_optional_arguments({ \"verbose\" => \"-#{verbose_option}\" })\n\n          it \"shows the ansible-playbook command and set verbosity to '-#{verbose_option}' level\" do\n            expect(machine.env.ui).to receive(:detail)\n              .with(\"PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit=\\\"machine1\\\" --inventory-file=#{generated_inventory_dir} -#{verbose_option} playbook.yml\")\n          end\n        end\n      end\n\n      describe \"with an invalid string\" do\n        before do\n          config.verbose = \"wrong\"\n        end\n\n        it_should_set_arguments_and_environment_variables 6\n        it_should_set_optional_arguments({ \"verbose\" => \"-v\" })\n\n        it \"shows the ansible-playbook command and set verbosity to '-v' level\" do\n          expect(machine.env.ui).to receive(:detail)\n            .with(\"PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit=\\\"machine1\\\" --inventory-file=#{generated_inventory_dir} -v playbook.yml\")\n        end\n      end\n\n      describe \"with an empty string\" do\n        before do\n          config.verbose = \"\"\n        end\n\n        it_should_set_arguments_and_environment_variables\n\n        it \"doesn't show the ansible-playbook command\" do\n          expect(machine.env.ui).to_not receive(:detail).with(/ansible-playbook/)\n        end\n      end\n\n    end\n\n    describe \"without colorized output\" do\n      before do\n        allow(machine.env).to receive(:ui).and_return(Vagrant::UI::Basic.new)\n\n        allow(machine.env.ui).to receive(:warn).and_return(\"\") # hide the breaking change warning\n      end\n\n      it \"disables ansible-playbook colored output\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          cmd_opts = args.last\n          expect(cmd_opts[:env]).to_not include(\"ANSIBLE_FORCE_COLOR\")\n          expect(cmd_opts[:env]['ANSIBLE_NOCOLOR']).to eql(\"true\")\n        }.and_return(default_execute_result)\n      end\n    end\n\n\n    context \"with version option set\" do\n      before do\n        config.version = \"2.3.4.5\"\n      end\n\n      describe \"and the installed ansible version is correct\" do\n        before do\n          allow(subject).to receive(:gather_ansible_version).and_return(\"ansible #{config.version}\\n...\\n\")\n        end\n\n        it \"executes ansible-playbook command\" do\n          expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result)\n        end\n      end\n\n      describe \"whitespace in version string\" do\n        before do\n          allow(subject).to receive(:gather_ansible_version).and_return(\"ansible #{config.version}  \\n...\\n\")\n        end\n\n        it \"sets the correct gathered_version\" do\n          expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result)\n        end\n      end\n\n      describe \"and there is an ansible version mismatch\" do\n        before do\n          allow(subject).to receive(:gather_ansible_version).and_return(\"ansible 1.9.6\\n...\\n\")\n        end\n\n        it \"raises an error about the ansible version mismatch\", skip_before: false, skip_after: true do\n          ensure_that_config_is_valid\n          expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleVersionMismatch)\n        end\n      end\n\n      describe \"and the installed ansible version cannot be detected\" do\n        before do\n          allow(subject).to receive(:gather_ansible_version).and_return(\"\")\n        end\n\n        it \"skips the ansible version check and executes ansible-playbook command\" do\n          expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result)\n        end\n      end\n\n      describe \"with special value: 'latest'\" do\n        before do\n          config.version = :latest\n          allow(subject).to receive(:gather_ansible_version).and_return(\"ansible 2.2.0.1\\n...\\n\")\n        end\n\n        it \"skips the ansible version check and executes ansible-playbook command\" do\n          expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result)\n        end\n      end\n    end\n\n    describe \"with galaxy support\" do\n\n      before do\n        config.galaxy_role_file = existing_file\n      end\n\n      it \"raises an error when ansible-galaxy command fails\", skip_before: true, skip_after: true do\n        config.finalize!\n        ensure_that_config_is_valid\n\n        allow(subject).to receive(:check_path)\n        allow(Vagrant::Util::Subprocess).to receive(:execute)\n          .and_return(Vagrant::Util::Subprocess::Result.new(1, \"\", \"\"))\n\n        expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCommandFailed)\n      end\n\n      it 'execute three commands: Python ansible version check, ansible-galaxy, and ansible-playbook' do\n        expect(Vagrant::Util::Subprocess).to receive(:execute)\n          .once\n          .with('python3', '-c',\n                \"import importlib.metadata; print('ansible ' + importlib.metadata.version('ansible'))\", { notify: %i[stdout stderr] })\n          .and_return(default_execute_result)\n        expect(Vagrant::Util::Subprocess).to receive(:execute)\n          .once\n          .with('ansible-galaxy', any_args)\n          .and_return(default_execute_result)\n        expect(Vagrant::Util::Subprocess).to receive(:execute)\n          .once\n          .with('ansible-playbook', any_args)\n          .and_return(default_execute_result)\n      end\n\n      it \"doesn't show the ansible-galaxy command\" do\n        expect(machine.env.ui).to_not receive(:detail).with(/ansible-galaxy/)\n      end\n\n      describe \"with verbose option enabled\" do\n        before do\n          config.galaxy_roles_path = \"/tmp/roles\"\n          config.verbose = true\n        end\n\n        it \"shows the ansible-galaxy command in use\" do\n          expect(machine.env.ui).to receive(:detail)\n            .with(%Q(ansible-galaxy install --role-file='#{existing_file}' --roles-path='/tmp/roles' --force))\n        end\n      end\n    end\n\n    context \"with galaxy_roles_path option defined\" do\n      before do\n        config.galaxy_roles_path = \"my-roles\"\n      end\n\n      it \"sets ANSIBLE_ROLES_PATH with corresponding absolute path\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          cmd_opts = args.last\n          expect(cmd_opts[:env]).to include(\"ANSIBLE_ROLES_PATH\")\n          expect(cmd_opts[:env]['ANSIBLE_ROLES_PATH']).to eql(File.join(machine.env.root_path, \"my-roles\"))\n        }.and_return(default_execute_result)\n      end\n    end\n\n    context \"with extra_vars option defined\" do\n      describe \"with a hash value\" do\n        before do\n          config.extra_vars = { var1: %Q(string with 'apo$trophe$', \\\\, \" and =), var2: { x: 42 } }\n        end\n\n        it_should_set_optional_arguments({ \"extra_vars\" => \"--extra-vars={\\\"var1\\\":\\\"string with 'apo$trophe$', \\\\\\\\, \\\\\\\" and =\\\",\\\"var2\\\":{\\\"x\\\":42}}\" })\n      end\n\n      describe \"with a string value referring to file path (with the '@' prefix)\" do\n        before do\n          config.extra_vars = \"@#{existing_file}\"\n        end\n\n        it_should_set_optional_arguments({ \"extra_vars\" => \"--extra-vars=@#{File.expand_path(__FILE__)}\" })\n      end\n    end\n\n    # The Vagrant Ansible provisioner does not validate the coherency of\n    # argument combinations, and lets ansible-playbook complain.\n    describe \"with a maximum of options\" do\n      before do\n        # vagrant general options\n        ssh_info[:forward_agent] = true\n        ssh_info[:private_key_path] = ['/my/key1', '/my/key2']\n\n        # command line arguments\n        config.galaxy_roles_path = \"/up/to the stars\"\n        config.extra_vars = { var1: %Q(string with 'apo$trophe$', \\\\, \" and =), var2: { x: 42 } }\n        config.become = true\n        config.become_user = 'deployer'\n        config.verbose = \"vvv\"\n        config.ask_become_pass = true\n        config.ask_vault_pass = true\n        config.vault_password_file = existing_file\n        config.tags = %w(db www)\n        config.skip_tags = %w(foo bar)\n        config.limit = 'machine*:&vagrant:!that_one'\n        config.start_at_task = \"joe's awesome task\"\n        config.raw_arguments = [\"--why-not\", \"--su-user=foot\", \"--ask-su-pass\", \"--limit=all\", \"--private-key=./myself.key\", \"--extra-vars='{\\\"var3\\\":\\\"foo\\\"}'\"]\n\n        # environment variables\n        config.config_file = existing_file\n        config.host_key_checking = true\n        config.raw_ssh_args = ['-o ControlMaster=no']\n      end\n\n      it_should_set_arguments_and_environment_variables 21, 6, true\n      it_should_explicitly_enable_ansible_ssh_control_persist_defaults\n      it_should_set_optional_arguments({  \"extra_vars\"          => \"--extra-vars={\\\"var1\\\":\\\"string with 'apo$trophe$', \\\\\\\\, \\\\\\\" and =\\\",\\\"var2\\\":{\\\"x\\\":42}}\",\n                                          \"become\"              => \"--sudo\",\n                                          \"become_user\"         => \"--sudo-user=deployer\",\n                                          \"verbose\"             => \"-vvv\",\n                                          \"ask_become_pass\"     => \"--ask-sudo-pass\",\n                                          \"ask_vault_pass\"      => \"--ask-vault-pass\",\n                                          \"vault_password_file\" => \"--vault-password-file=#{File.expand_path(__FILE__)}\",\n                                          \"tags\"                => \"--tags=db,www\",\n                                          \"skip_tags\"           => \"--skip-tags=foo,bar\",\n                                          \"limit\"               => \"--limit=machine*:&vagrant:!that_one\",\n                                          \"start_at_task\"       => \"--start-at-task=joe's awesome task\",\n                                        })\n\n      it \"also includes given raw arguments\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          expect(args).to include(\"--why-not\")\n          expect(args).to include(\"--su-user=foot\")\n          expect(args).to include(\"--ask-su-pass\")\n          expect(args).to include(\"--limit=all\")\n          expect(args).to include(\"--private-key=./myself.key\")\n        }.and_return(default_execute_result)\n      end\n\n      it \"shows the ansible-playbook command, with additional quotes when required\" do\n        expect(machine.env.ui).to receive(:detail)\n          .with(%Q(PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_ROLES_PATH='/up/to the stars' ANSIBLE_CONFIG='#{existing_file}' ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -o IdentityFile=/my/key1 -o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --ask-sudo-pass --ask-vault-pass --limit=\"machine*:&vagrant:!that_one\" --inventory-file=#{generated_inventory_dir} --extra-vars=\\\\{\\\\\"var1\\\\\":\\\\\"string\\\\ with\\\\ \\\\'apo\\\\$trophe\\\\$\\\\',\\\\ \\\\\\\\\\\\\\\\,\\\\ \\\\\\\\\\\\\"\\\\ and\\\\ \\\\=\\\\\",\\\\\"var2\\\\\":\\\\{\\\\\"x\\\\\":42\\\\}\\\\} --sudo --sudo-user=deployer -vvv --vault-password-file=#{existing_file} --tags=db,www --skip-tags=foo,bar --start-at-task=\"joe's awesome task\" --why-not --su-user=foot --ask-su-pass --limit=all --private-key=./myself.key --extra-vars='{\\\"var3\\\":\\\"foo\\\"}' playbook.yml))\n      end\n    end\n\n    #\n    # Special cases related to the VM provider context\n    #\n\n    context \"with Docker provider on a non-Linux host\" do\n\n      let(:fake_host_ssh_info) {{\n        private_key_path: ['/path/to/docker/host/key'],\n        username: 'boot9docker',\n        host: '127.0.0.1',\n        port: 2299\n      }}\n      let(:fake_host_vm) {\n        double(\"host_vm\").tap do |h|\n          allow(h).to receive(:ssh_info).and_return(fake_host_ssh_info)\n        end\n      }\n\n      before do\n        allow(machine).to receive(:provider_name).and_return(:docker)\n        allow(machine.provider).to receive(:host_vm?).and_return(true)\n        allow(machine.provider).to receive(:host_vm).and_return(fake_host_vm)\n      end\n\n      it \"uses an SSH ProxyCommand to reach the VM\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          cmd_opts = args.last\n          expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include(\"-o ProxyCommand='ssh boot9docker@127.0.0.1 -p 2299 -i /path/to/docker/host/key -o Compression=yes -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no exec nc %h %p 2>/dev/null'\")\n        }.and_return(default_execute_result)\n      end\n    end\n\n    #\n    # Special cases related to the Vagrant Host operating system in use\n    #\n\n    context \"on a Windows host\" do\n      before do\n        allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n        allow(machine.ui).to receive(:warn)\n\n        # Set the compatibility mode to only get the Windows warning\n        config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8\n      end\n\n      it \"warns that Windows is not officially supported for the Ansible control machine\" do\n        expect(machine.env.ui).to receive(:warn)\n          .with(I18n.t(\"vagrant.provisioners.ansible.windows_not_supported_for_control_machine\") + \"\\n\")\n      end\n    end\n\n    context \"on a Solaris-like host\" do\n      before do\n        allow(Vagrant::Util::Platform).to receive(:solaris?).and_return(true)\n      end\n\n      it \"does not set IdentitiesOnly=yes in ANSIBLE_SSH_ARGS\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          cmd_opts = args.last\n          expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include(\"-o IdentitiesOnly=yes\")\n        }.and_return(default_execute_result)\n      end\n\n      describe \"and with host_key_checking option enabled\" do\n        it \"does not set ANSIBLE_SSH_ARGS environment variable\" do\n          config.host_key_checking = true\n\n          expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n            cmd_opts = args.last\n            expect(cmd_opts[:env]).to_not include('ANSIBLE_SSH_ARGS')\n          }.and_return(Vagrant::Util::Subprocess::Result.new(0, \"\", \"\"))\n        end\n      end\n\n    end\n\n    describe 'with config.ssh.keys_only = false' do\n      it 'does not set IdentitiesOnly=yes in ANSIBLE_SSH_ARGS' do\n        ssh_info[:keys_only] = false\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n          cmd_opts = args.last\n          expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include(\"-o IdentitiesOnly=yes\")\n        }.and_return(default_execute_result)\n      end\n    end\n\n    describe '#get_inventory_argument' do\n      context 'when ansible version is not detected' do\n        before do\n          config.version = nil\n          allow(subject).to receive(:gather_ansible_version).and_return(\"ansible #{config.version}\\n...\\n\")\n          allow(subject).to receive(:gather_ansible_version).with(\"ansible-core\").and_return(\"ansible #{config.version}\\n...\\n\")\n        end\n\n        it 'returns the default inventory command' do\n          expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n            expect(args).to include(\"--inventory-file=#{generated_inventory_dir}\")\n          }.and_return(default_execute_result)\n        end\n      end\n\n      context 'when ansible version is detected' do\n        describe 'version >= 2.19' do\n          before do\n            config.version = \"2.19.0\"\n            allow(subject).to receive(:gather_ansible_version).with(\"ansible\").and_return(\"ansible #{config.version}\\n...\\n\")\n            allow(subject).to receive(:gather_ansible_version).with(\"ansible-core\").and_return(\"ansible-core #{config.version}\\n...\\n\")\n          end\n          \n          it 'returns --inventory as the inventory command' do\n            expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n              expect(args).to include(\"--inventory=#{generated_inventory_dir}\")\n            }.and_return(default_execute_result)\n          end\n        end\n\n        describe 'version < 2.19' do\n          before do\n            config.version = \"2.18.5\"\n            allow(subject).to receive(:gather_ansible_version).with(\"ansible\").and_return(\"ansible #{config.version}\\n...\\n\")\n            allow(subject).to receive(:gather_ansible_version).with(\"ansible-core\").and_return(\"ansible-core #{config.version}\\n...\\n\")\n          end\n\n          it 'returns --inventory-file as the inventory command' do\n            expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|\n              expect(args).to include(\"--inventory-file=#{generated_inventory_dir}\")\n            }.and_return(default_execute_result)\n          end\n        end\n      end\n    end\n\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/cap/freebsd/chef_installed_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/cap/freebsd/chef_installed\")\n\ndescribe VagrantPlugins::Chef::Cap::FreeBSD::ChefInstalled do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:config)  { double(\"config\") }\n\n  subject { described_class }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n  end\n\n  describe \"#chef_installed\" do\n    describe \"when chef-workstation\" do\n      let(:version) { \"15.0.0\" }\n      let(:command) { \"test -x /opt/chef-workstation/bin/chef&& /opt/chef-workstation/bin/chef --version | grep '15.0.0'\" }\n\n      it \"returns true if installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(true)\n        subject.chef_installed(machine, \"chef-workstation\", version)\n      end\n\n      it \"returns false if not installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(false)\n        expect(subject.chef_installed(machine, \"chef-workstation\", version)).to be_falsey\n      end\n    end\n\n    describe \"when not chef-workstation\" do\n      let(:version) { \"15.0.0\" }\n      let(:command) { \"test -x /opt/chef/bin/chef-client&& /opt/chef/bin/chef-client --version | grep '15.0.0'\" }\n\n      it \"returns true if installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(true)\n        subject.chef_installed(machine, \"chef_solo\", version)\n      end\n\n      it \"returns false if not installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(false)\n        expect(subject.chef_installed(machine, \"chef_solo\", version)).to be_falsey\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/cap/linux/chef_installed_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/cap/linux/chef_installed\")\n\ndescribe VagrantPlugins::Chef::Cap::Linux::ChefInstalled do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:config)  { double(\"config\") }\n\n  subject { described_class }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n  end\n\n  describe \"#chef_installed\" do\n    describe \"when chef-workstation\" do\n      let(:version) { \"15.0.0\" }\n      let(:command) { \"test -x /opt/chef-workstation/bin/chef&& /opt/chef-workstation/bin/chef --version | grep '15.0.0'\" }\n\n      it \"returns true if installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(true)\n        subject.chef_installed(machine, \"chef-workstation\", version)\n      end\n\n      it \"returns false if not installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(false)\n        expect(subject.chef_installed(machine, \"chef-workstation\", version)).to be_falsey\n      end\n    end\n\n    describe \"when not chef-workstation\" do\n      let(:version) { \"15.0.0\" }\n      let(:command) { \"test -x /opt/chef/bin/chef-client&& /opt/chef/bin/chef-client --version | grep '15.0.0'\" }\n\n      it \"returns true if installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(true)\n        subject.chef_installed(machine, \"chef_solo\", version)\n      end\n\n      it \"returns false if not installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(false)\n        expect(subject.chef_installed(machine, \"chef_solo\", version)).to be_falsey\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/cap/omnios/chef_installed_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/cap/omnios/chef_installed\")\n\ndescribe VagrantPlugins::Chef::Cap::OmniOS::ChefInstalled do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:config)  { double(\"config\") }\n\n  subject { described_class }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n  end\n\n  describe \"#chef_installed\" do\n    describe \"when chef-workstation\" do\n      let(:version) { \"15.0.0\" }\n      let(:command) { \"test -x /opt/chef-workstation/bin/chef&& /opt/chef-workstation/bin/chef --version | grep '15.0.0'\" }\n\n      it \"returns true if installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(true)\n        subject.chef_installed(machine, \"chef-workstation\", version)\n      end\n\n      it \"returns false if not installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(false)\n        expect(subject.chef_installed(machine, \"chef-workstation\", version)).to be_falsey\n      end\n    end\n\n    describe \"when not chef-workstation\" do\n      let(:version) { \"15.0.0\" }\n      let(:command) { \"test -x /opt/chef/bin/chef-client&& /opt/chef/bin/chef-client --version | grep '15.0.0'\" }\n\n      it \"returns true if installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(true)\n        subject.chef_installed(machine, \"chef_solo\", version)\n      end\n\n      it \"returns false if not installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(false)\n        expect(subject.chef_installed(machine, \"chef_solo\", version)).to be_falsey\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/cap/windows/chef_installed_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/cap/windows/chef_installed\")\n\ndescribe VagrantPlugins::Chef::Cap::Windows::ChefInstalled do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:config)  { double(\"config\") }\n\n  subject { described_class }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n  end\n\n  describe \"#chef_installed\" do\n    describe \"when chef-workstation\" do\n      let(:version) { \"15.0.0\" }\n      let(:command) { \"if ((&chef --version) -Match \\\"15.0.0\\\"){ exit 0 } else { exit 1 }\" }\n\n      it \"returns true if installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(true)\n        subject.chef_installed(machine, \"chef-workstation\", version)\n      end\n\n      it \"returns false if not installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(false)\n        expect(subject.chef_installed(machine, \"chef-workstation\", version)).to be_falsey\n      end\n    end\n\n    describe \"when chef-workstation\" do\n      let(:version) { \"15.0.0\" }\n      let(:command) { \"if ((&chef-client --version) -Match \\\"15.0.0\\\"){ exit 0 } else { exit 1 }\" }\n\n      it \"returns true if installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(true)\n        subject.chef_installed(machine, \"chef_solo\", version)\n      end\n\n      it \"returns false if not installed\" do\n        expect(machine.communicate).to receive(:test).\n          with(command, sudo: true).and_return(false)\n        expect(subject.chef_installed(machine, \"chef_solo\", version)).to be_falsey\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/command_builder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/command_builder\")\n\ndescribe VagrantPlugins::Chef::CommandBuilder do\n\n  let(:machine) { double(\"machine\") }\n  let(:chef_config) { double(\"chef_config\") }\n\n  before(:each) do\n    allow(chef_config).to receive(:install).and_return(true)\n    allow(chef_config).to receive(:version).and_return(\"12.0.0\")\n    allow(chef_config).to receive(:provisioning_path).and_return(\"/tmp/vagrant-chef-1\")\n    allow(chef_config).to receive(:arguments).and_return(nil)\n    allow(chef_config).to receive(:binary_env).and_return(nil)\n    allow(chef_config).to receive(:binary_path).and_return(nil)\n    allow(chef_config).to receive(:binary_env).and_return(nil)\n    allow(chef_config).to receive(:log_level).and_return(:info)\n  end\n\n  describe \".initialize\" do\n    it \"raises an error when chef type is not client or solo\" do\n      expect { VagrantPlugins::Chef::CommandBuilder.new(:client_bad, chef_config) }.\n        to raise_error(RuntimeError)\n    end\n\n    it \"does not raise an error for :client\" do\n      expect {\n        VagrantPlugins::Chef::CommandBuilder.new(:client, chef_config)\n      }.to_not raise_error\n    end\n\n    it \"does not raise an error for :solo\" do\n      expect {\n        VagrantPlugins::Chef::CommandBuilder.new(:solo, chef_config)\n      }.to_not raise_error\n    end\n  end\n\n  describe \"#command\" do\n    describe \"windows\" do\n      subject do\n        VagrantPlugins::Chef::CommandBuilder.new(:client, chef_config, windows: true)\n      end\n\n      it \"executes the chef-client in PATH by default\" do\n        expect(subject.command).to match(/^chef-client/)\n      end\n\n      it \"executes the chef-client using full path if binary_path is specified\" do\n        allow(chef_config).to receive(:binary_path).and_return(\n          \"c:\\\\opscode\\\\chef\\\\bin\\\\chef-client\")\n        expect(subject.command).to match(/^c:\\\\opscode\\\\chef\\\\bin\\\\chef-client\\\\chef-client/)\n      end\n\n      it \"builds a guest friendly client.rb path\" do\n        expect(subject.command).to include(\n          '--config c:\\\\tmp\\\\vagrant-chef-1\\\\client.rb')\n      end\n\n      it \"builds a guest friendly solo.json path\" do\n        expect(subject.command).to include(\n          '--json-attributes c:\\\\tmp\\\\vagrant-chef-1\\\\dna.json')\n      end\n\n      it \"includes Chef arguments if specified\" do\n        allow(chef_config).to receive(:arguments).and_return(\"bacon pants\")\n        expect(subject.command).to include(\n          \" bacon pants\")\n      end\n\n      it \"includes --no-color if UI is not colored\" do\n        expect(subject.command).to include(\n          \" --no-color\")\n      end\n\n      it \"includes --force-formatter if Chef > 10\" do\n        expect(subject.command).to include(\n          \" --force-formatter\")\n      end\n\n      it \"does not include --force-formatter if Chef < 11\" do\n        allow(chef_config).to receive(:version).and_return(\"10.0\")\n        expect(subject.command).to_not include(\n          \" --force-formatter\")\n      end\n\n      it \"does not include --force-formatter if we did not install Chef\" do\n        allow(chef_config).to receive(:install).and_return(false)\n        expect(subject.command).to_not include(\n          \" --force-formatter\")\n      end\n    end\n\n    describe \"linux\" do\n      subject do\n        VagrantPlugins::Chef::CommandBuilder.new(:client, chef_config, windows: false)\n      end\n\n      it \"executes the chef-client in PATH by default\" do\n        expect(subject.command).to match(/^chef-client/)\n      end\n\n      it \"executes the chef-client using full path if binary_path is specified\" do\n        allow(chef_config).to receive(:binary_path).and_return(\n          \"/opt/chef/chef-client\")\n        expect(subject.command).to match(/^\\/opt\\/chef\\/chef-client/)\n      end\n\n      it \"builds a guest friendly client.rb path\" do\n        expect(subject.command).to include(\n          \"--config /tmp/vagrant-chef-1/client.rb\")\n      end\n\n      it \"builds a guest friendly solo.json path\" do\n        expect(subject.command).to include(\n          \"--json-attributes /tmp/vagrant-chef-1/dna.json\")\n      end\n\n      it \"includes Chef arguments if specified\" do\n        allow(chef_config).to receive(:arguments).and_return(\"bacon\")\n        expect(subject.command).to include(\n          \" bacon\")\n      end\n\n      it \"includes --no-color if UI is not colored\" do\n        expect(subject.command).to include(\n          \" --no-color\")\n      end\n\n      it \"includes environment variables if specified\" do\n        allow(chef_config).to receive(:binary_env).and_return(\"ENVVAR=VAL\")\n        expect(subject.command).to match(/^ENVVAR=VAL /)\n      end\n\n      it \"does not include --force-formatter if Chef < 11\" do\n        allow(chef_config).to receive(:version).and_return(\"10.0\")\n        expect(subject.command).to_not include(\n          \" --force-formatter\")\n      end\n\n      it \"does not include --force-formatter if we did not install Chef\" do\n        allow(chef_config).to receive(:install).and_return(false)\n        expect(subject.command).to_not include(\n          \" --force-formatter\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/config/base_runner_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/config/base_runner\")\n\ndescribe VagrantPlugins::Chef::Config::BaseRunner do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  let(:machine) { double(\"machine\") }\n\n  describe \"#arguments\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.arguments).to be(nil)\n    end\n  end\n\n  describe \"#attempts\" do\n    it \"defaults to 1\" do\n      subject.finalize!\n      expect(subject.attempts).to eq(1)\n    end\n  end\n\n  describe \"#custom_config_path\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.custom_config_path).to be(nil)\n    end\n  end\n\n  describe \"#environment\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.environment).to be(nil)\n    end\n  end\n\n  describe \"#encrypted_data_bag_secret_key_path\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.encrypted_data_bag_secret_key_path).to be(nil)\n    end\n  end\n\n  describe \"#formatter\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.formatter).to be(nil)\n    end\n  end\n\n  describe \"#http_proxy\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.http_proxy).to be(nil)\n    end\n  end\n\n  describe \"#http_proxy_user\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.http_proxy_user).to be(nil)\n    end\n  end\n\n  describe \"#http_proxy_pass\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.http_proxy_pass).to be(nil)\n    end\n  end\n\n  describe \"#https_proxy\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.https_proxy).to be(nil)\n    end\n  end\n\n  describe \"#https_proxy_user\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.https_proxy_user).to be(nil)\n    end\n  end\n\n  describe \"#https_proxy_pass\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.https_proxy_pass).to be(nil)\n    end\n  end\n\n  describe \"#log_level\" do\n    it \"defaults to :info\" do\n      subject.finalize!\n      expect(subject.log_level).to be(:info)\n    end\n\n    it \"is converted to a symbol\" do\n      subject.log_level = \"foo\"\n      subject.finalize!\n      expect(subject.log_level).to eq(:foo)\n    end\n  end\n\n  describe \"#no_proxy\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.no_proxy).to be(nil)\n    end\n  end\n\n  describe \"#node_name\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.node_name).to be(nil)\n    end\n  end\n\n  describe \"#provisioning_path\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.provisioning_path).to be(nil)\n    end\n  end\n\n  describe \"#file_backup_path\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.file_backup_path).to be(nil)\n    end\n  end\n\n  describe \"#file_cache_path\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.file_cache_path).to be(nil)\n    end\n  end\n\n  describe \"#verbose_logging\" do\n    it \"defaults to false\" do\n      subject.finalize!\n      expect(subject.verbose_logging).to be(false)\n    end\n  end\n\n  describe \"#enable_reporting\" do\n    it \"defaults to true\" do\n      subject.finalize!\n      expect(subject.enable_reporting).to be(true)\n    end\n  end\n\n  describe \"#run_list\" do\n    it \"defaults to an empty array\" do\n      subject.finalize!\n      expect(subject.run_list).to be_a(Array)\n      expect(subject.run_list).to be_empty\n    end\n  end\n\n  describe \"#json\" do\n    it \"defaults to an empty hash\" do\n      subject.finalize!\n      expect(subject.json).to be_a(Hash)\n      expect(subject.json).to be_empty\n    end\n  end\n\n  describe \"#add_recipe\" do\n    context \"when the prefix is given\" do\n      it \"adds the value to the run_list\" do\n        subject.add_recipe(\"recipe[foo::bar]\")\n        expect(subject.run_list).to eq %w(recipe[foo::bar])\n      end\n    end\n\n    context \"when the prefix is not given\" do\n      it \"adds the prefixed value to the run_list\" do\n        subject.add_recipe(\"foo::bar\")\n        expect(subject.run_list).to eq %w(recipe[foo::bar])\n      end\n    end\n  end\n\n  describe \"#add_role\" do\n    context \"when the prefix is given\" do\n      it \"adds the value to the run_list\" do\n        subject.add_role(\"role[foo]\")\n        expect(subject.run_list).to eq %w(role[foo])\n      end\n    end\n\n    context \"when the prefix is not given\" do\n      it \"adds the prefixed value to the run_list\" do\n        subject.add_role(\"foo\")\n        expect(subject.run_list).to eq %w(role[foo])\n      end\n    end\n  end\n\n   describe \"#validate_base\" do\n    context \"when #custom_config_path does not exist\" do\n      let(:path) do\n        next \"/path/to/file\" if !Vagrant::Util::Platform.windows?\n        \"C:/path/to/file\"\n      end\n\n      before do\n        allow(File).to receive(:file?)\n          .with(path)\n          .and_return(false)\n\n        allow(machine).to receive(:env)\n          .and_return(double(\"env\",\n            root_path: \"\",\n          ))\n      end\n\n      it \"returns an error\" do\n        subject.custom_config_path = path\n        subject.finalize!\n\n        expect(subject.validate_base(machine))\n          .to eq ['Path specified for \"custom_config_path\" does not exist.']\n      end\n    end\n  end\n\n  describe \"#merge\" do\n    it \"merges the json hash\" do\n      a = described_class.new.tap do |i|\n        i.json = { \"foo\" => \"bar\" }\n      end\n      b = described_class.new.tap do |i|\n        i.json = { \"zip\" => \"zap\" }\n      end\n\n      result = a.merge(b)\n      expect(result.json).to eq(\n        \"foo\" => \"bar\",\n        \"zip\" => \"zap\",\n      )\n    end\n\n    it \"appends the run_list array\" do\n      a = described_class.new.tap do |i|\n        i.run_list = [\"recipe[foo::bar]\"]\n      end\n      b = described_class.new.tap do |i|\n        i.run_list = [\"recipe[zip::zap]\"]\n      end\n\n      result = a.merge(b)\n      expect(result.run_list).to eq %w(\n        recipe[foo::bar]\n        recipe[zip::zap]\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/config/base_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/config/base\")\n\ndescribe VagrantPlugins::Chef::Config::Base do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  let(:machine) { double(\"machine\") }\n\n  describe \"#binary_path\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.binary_path).to be(nil)\n    end\n  end\n\n  describe \"#binary_env\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.binary_env).to be(nil)\n    end\n  end\n\n  describe \"#product\" do\n    it \"defaults to \\\"chef\\\"\" do\n      subject.finalize!\n      expect(subject.product).to eq(\"chef\")\n    end\n  end\n\n  describe \"#install\" do\n    it \"defaults to true\" do\n      subject.finalize!\n      expect(subject.install).to be(true)\n    end\n\n    it \"is converted to a symbol\" do\n      subject.install = \"force\"\n      subject.finalize!\n      expect(subject.install).to eq(:force)\n    end\n  end\n\n  describe \"#log_level\" do\n    it \"defaults to :info\" do\n      subject.finalize!\n      expect(subject.log_level).to be(:info)\n    end\n\n    it \"is converted to a symbol\" do\n      subject.log_level = \"foo\"\n      subject.finalize!\n      expect(subject.log_level).to eq(:foo)\n    end\n  end\n\n  describe \"#channel\" do\n    it \"defaults to \\\"stable\\\"\" do\n      subject.finalize!\n      expect(subject.channel).to eq(\"stable\")\n    end\n  end\n\n  describe \"#version\" do\n    it \"defaults to :latest\" do\n      subject.finalize!\n      expect(subject.version).to eq(:latest)\n    end\n\n    it \"converts the string 'latest' to a symbol\" do\n      subject.version = \"latest\"\n      subject.finalize!\n      expect(subject.version).to eq(:latest)\n    end\n  end\n\n  describe \"#installer_download_path\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.installer_download_path).to be(nil)\n    end\n  end\n\n  describe \"#omnibus_url\" do\n    it \"defaults to https://omnitruck.chef.io\" do\n      subject.finalize!\n      expect(subject.omnibus_url).to eq(\"https://omnitruck.chef.io\")\n    end\n\n    it \"makes use of the configured url\" do\n      subject.omnibus_url = \"https://omnitruck.example.com\"\n      subject.finalize!\n      expect(subject.omnibus_url).to eq(\"https://omnitruck.example.com\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/config/chef_apply_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/config/chef_apply\")\n\ndescribe VagrantPlugins::Chef::Config::ChefApply do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  let(:machine) { double(\"machine\") }\n\n  def chef_error(key, options = {})\n    I18n.t(\"vagrant.provisioners.chef.#{key}\", **options)\n  end\n\n  describe \"#recipe\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.recipe).to be(nil)\n    end\n  end\n\n  describe \"#upload_path\" do\n    it \"defaults to /tmp/vagrant-chef-apply.rb\" do\n      subject.finalize!\n      expect(subject.upload_path).to eq(\"/tmp/vagrant-chef-apply\")\n    end\n  end\n\n  describe \"#validate\" do\n    before do\n      allow(machine).to receive(:env)\n        .and_return(double(\"env\",\n          root_path: \"\",\n        ))\n\n      subject.recipe = <<-EOH\n        package \"foo\"\n      EOH\n    end\n\n    let(:result) { subject.validate(machine) }\n    let(:errors) { result[\"chef apply provisioner\"] }\n\n    context \"when the recipe is nil\" do\n      it \"returns an error\" do\n        subject.recipe = nil\n        subject.finalize!\n        expect(errors).to include chef_error(\"recipe_empty\")\n      end\n    end\n\n    context \"when the recipe is empty\" do\n      it \"returns an error\" do\n        subject.recipe = \"  \"\n        subject.finalize!\n        expect(errors).to include chef_error(\"recipe_empty\")\n      end\n    end\n\n    context \"when the log_level is an empty array\" do\n      it \"returns an error\" do\n        subject.log_level = \"  \"\n        subject.finalize!\n        expect(errors).to include chef_error(\"log_level_empty\")\n      end\n    end\n\n    context \"when the upload_path is nil\" do\n      it \"returns an error\" do\n        subject.upload_path = nil\n        subject.finalize!\n        expect(errors).to include chef_error(\"upload_path_empty\")\n      end\n    end\n\n    context \"when the upload_path is an empty array\" do\n      it \"returns an error\" do\n        subject.upload_path = \"  \"\n        subject.finalize!\n        expect(errors).to include chef_error(\"upload_path_empty\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/config/chef_client_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/config/chef_client\")\n\ndescribe VagrantPlugins::Chef::Config::ChefClient do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  let(:machine) { double(\"machine\") }\n\n  describe \"#chef_server_url\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.chef_server_url).to be(nil)\n    end\n  end\n\n  describe \"#client_key_path\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.client_key_path).to be(nil)\n    end\n  end\n\n  describe \"#delete_client\" do\n    it \"defaults to false\" do\n      subject.finalize!\n      expect(subject.delete_client).to be(false)\n    end\n  end\n\n  describe \"#delete_node\" do\n    it \"defaults to false\" do\n      subject.finalize!\n      expect(subject.delete_node).to be(false)\n    end\n  end\n\n  describe \"#validation_key_path\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.validation_key_path).to be(nil)\n    end\n  end\n\n  describe \"#validation_client_name\" do\n    it \"defaults to chef-validator\" do\n      subject.finalize!\n      expect(subject.validation_client_name).to eq(\"chef-validator\")\n    end\n  end\n\n  describe \"#validate\" do\n    before do\n      allow(machine).to receive(:env)\n        .and_return(double(\"env\",\n          root_path: \"\",\n        ))\n\n      subject.chef_server_url = \"https://example.com\"\n      subject.validation_key_path = \"/path/to/key.pem\"\n    end\n\n    let(:result) { subject.validate(machine) }\n    let(:errors) { result[\"chef client provisioner\"] }\n\n    context \"when the chef_server_url is nil\" do\n      it \"returns an error\" do\n        subject.chef_server_url = nil\n        subject.finalize!\n        expect(errors).to eq([I18n.t(\"vagrant.config.chef.server_url_empty\")])\n      end\n    end\n\n    context \"when the chef_server_url is blank\" do\n      it \"returns an error\" do\n        subject.chef_server_url = \"  \"\n        subject.finalize!\n        expect(errors).to eq([I18n.t(\"vagrant.config.chef.server_url_empty\")])\n      end\n    end\n\n    context \"when the validation_key_path is nil\" do\n      it \"returns an error\" do\n        subject.validation_key_path = nil\n        subject.finalize!\n        expect(errors).to eq([I18n.t(\"vagrant.config.chef.validation_key_path\")])\n      end\n    end\n\n    context \"when the validation_key_path is blank\" do\n      it \"returns an error\" do\n        subject.validation_key_path = \"  \"\n        subject.finalize!\n        expect(errors).to eq([I18n.t(\"vagrant.config.chef.validation_key_path\")])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/config/chef_solo_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/config/chef_solo\")\n\ndescribe VagrantPlugins::Chef::Config::ChefSolo do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  let(:machine) { double(\"machine\") }\n\n  describe \"#cookbooks_path\" do\n    it \"defaults to something\" do\n      subject.finalize!\n      expect(subject.cookbooks_path).to eq([\n        [:host, \"cookbooks\"],\n        [:vm, \"cookbooks\"],\n      ])\n    end\n  end\n\n  describe \"#data_bags_path\" do\n    it \"defaults to an empty array\" do\n      subject.finalize!\n      expect(subject.data_bags_path).to be_a(Array)\n      expect(subject.data_bags_path).to be_empty\n    end\n  end\n\n  describe \"#environments_path\" do\n    it \"defaults to an empty array\" do\n      subject.finalize!\n      expect(subject.environments_path).to be_a(Array)\n      expect(subject.environments_path).to be_empty\n    end\n\n    it \"merges deeply nested paths\" do\n      subject.environments_path = [\"/foo\", \"/bar\", [\"/zip\"]]\n      subject.finalize!\n      expect(subject.environments_path)\n        .to eq([:host, :host, :host].zip %w(/foo /bar /zip))\n    end\n  end\n\n  describe \"#recipe_url\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.recipe_url).to be(nil)\n    end\n  end\n\n  describe \"#roles_path\" do\n    it \"defaults to an empty array\" do\n      subject.finalize!\n      expect(subject.roles_path).to be_a(Array)\n      expect(subject.roles_path).to be_empty\n    end\n  end\n\n  describe \"#nodes_path\" do\n    it \"defaults to an empty array\" do\n      subject.finalize!\n      expect(subject.nodes_path).to be_a(Array)\n      expect(subject.nodes_path).to be_empty\n    end\n  end\n\n  describe \"#synced_folder_type\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.synced_folder_type).to be(nil)\n    end\n  end\n\n  describe \"#validate\" do\n    before do\n      allow(machine).to receive(:env)\n        .and_return(double(\"env\",\n          root_path: \"\",\n        ))\n\n      subject.cookbooks_path = [\"/cookbooks\", \"/more/cookbooks\"]\n    end\n\n    let(:result) { subject.validate(machine) }\n    let(:errors) { result[\"chef solo provisioner\"] }\n\n    context \"when the cookbooks_path is nil\" do\n      it \"returns an error\" do\n        subject.cookbooks_path = nil\n        subject.finalize!\n        expect(errors).to eq [I18n.t(\"vagrant.config.chef.cookbooks_path_empty\")]\n      end\n    end\n\n    context \"when the cookbooks_path is an empty array\" do\n      it \"returns an error\" do\n        subject.cookbooks_path = []\n        subject.finalize!\n        expect(errors).to eq [I18n.t(\"vagrant.config.chef.cookbooks_path_empty\")]\n      end\n    end\n\n    context \"when the cookbooks_path is an array with nil\" do\n      it \"returns an error\" do\n        subject.cookbooks_path = [nil, nil]\n        subject.finalize!\n        expect(errors).to eq [I18n.t(\"vagrant.config.chef.cookbooks_path_empty\")]\n      end\n    end\n\n    context \"when environments is given\" do\n      before do\n        subject.environment = \"production\"\n      end\n\n      context \"when the environments_path is nil\" do\n        it \"returns an error\" do\n          subject.environments_path = nil\n          subject.finalize!\n          expect(errors).to eq [I18n.t(\"vagrant.config.chef.environment_path_required\")]\n        end\n      end\n\n      context \"when the environments_path is an empty array\" do\n        it \"returns an error\" do\n          subject.environments_path = []\n          subject.finalize!\n          expect(errors).to eq [I18n.t(\"vagrant.config.chef.environment_path_required\")]\n        end\n      end\n\n      context \"when the environments_path is an array with nil\" do\n        it \"returns an error\" do\n          subject.environments_path = [nil, nil]\n          subject.finalize!\n          expect(errors).to eq [I18n.t(\"vagrant.config.chef.environment_path_required\")]\n        end\n      end\n\n      context \"when the environments_path does not exist\" do\n        it \"returns an error\" do\n          env_path = \"/path/to/environments/that/will/never/exist\"\n          subject.environments_path = env_path\n          subject.finalize!\n          expect(errors).to eq [\n            I18n.t(\"vagrant.config.chef.environment_path_missing\",\n              path: env_path\n            )\n          ]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/config/chef_zero_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/config/chef_zero\")\n\ndescribe VagrantPlugins::Chef::Config::ChefZero do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  let(:machine) { double(\"machine\") }\n\n  describe \"#cookbooks_path\" do\n    it \"defaults to something\" do\n      subject.finalize!\n      expect(subject.cookbooks_path).to eq([\n        [:host, \"cookbooks\"],\n        [:vm, \"cookbooks\"],\n      ])\n    end\n  end\n\n  describe \"#data_bags_path\" do\n    it \"defaults to an empty array\" do\n      subject.finalize!\n      expect(subject.data_bags_path).to be_a(Array)\n      expect(subject.data_bags_path).to be_empty\n    end\n  end\n\n  describe \"#environments_path\" do\n    it \"defaults to an empty array\" do\n      subject.finalize!\n      expect(subject.environments_path).to be_a(Array)\n      expect(subject.environments_path).to be_empty\n    end\n\n    it \"merges deeply nested paths\" do\n      subject.environments_path = [\"/foo\", \"/bar\", [\"/zip\"]]\n      subject.finalize!\n      expect(subject.environments_path)\n        .to eq([:host, :host, :host].zip %w(/foo /bar /zip))\n    end\n  end\n\n  describe \"#roles_path\" do\n    it \"defaults to an empty array\" do\n      subject.finalize!\n      expect(subject.roles_path).to be_a(Array)\n      expect(subject.roles_path).to be_empty\n    end\n  end\n\n  describe \"#nodes_path\" do\n    it \"defaults to an empty array\" do\n      subject.finalize!\n      expect(subject.nodes_path).to be_a(Array)\n      expect(subject.nodes_path).to be_empty\n    end\n  end\n\n  describe \"#synced_folder_type\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.synced_folder_type).to be(nil)\n    end\n  end\n\n  describe \"#validate\" do\n    before do\n      allow(machine).to receive(:env)\n        .and_return(double(\"env\",\n          root_path: \"\",\n        ))\n\n      subject.cookbooks_path = [\"/cookbooks\", \"/more/cookbooks\"]\n    end\n\n    let(:result) { subject.validate(machine) }\n    let(:errors) { result[\"chef zero provisioner\"] }\n\n    context \"when the cookbooks_path is nil\" do\n      it \"returns an error\" do\n        subject.cookbooks_path = nil\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"vagrant.config.chef.cookbooks_path_empty\"))\n      end\n    end\n\n    context \"when the cookbooks_path is an empty array\" do\n      it \"returns an error\" do\n        subject.cookbooks_path = []\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"vagrant.config.chef.cookbooks_path_empty\"))\n      end\n    end\n\n    context \"when the cookbooks_path is an array with nil\" do\n      it \"returns an error\" do\n        subject.cookbooks_path = [nil, nil]\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"vagrant.config.chef.cookbooks_path_empty\"))\n      end\n    end\n\n    context \"when the nodes_path is nil\" do\n      it \"returns an error\" do\n        subject.nodes_path = nil\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"vagrant.config.chef.nodes_path_empty\"))\n      end\n    end\n\n    context \"when an element of nodes_path does not exist on disk\" do\n      it \"returns an error\" do\n        nodes_path = [\"/path/to/nodes/that/will/never/exist\"]\n        subject.nodes_path = nodes_path\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"vagrant.config.chef.nodes_path_missing\",\n          path: nodes_path\n        ))\n      end\n    end\n\n    context \"when the nodes_path is an empty array\" do\n      it \"returns an error\" do\n        subject.nodes_path = []\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"vagrant.config.chef.nodes_path_empty\"))\n      end\n    end\n\n    context \"when the nodes_path is an array with nil\" do\n      it \"returns an error\" do\n        subject.nodes_path = [nil, nil]\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"vagrant.config.chef.nodes_path_empty\"))\n      end\n    end\n\n    context \"when environments is given\" do\n      before do\n        subject.environment = \"production\"\n      end\n\n      context \"when the environments_path is nil\" do\n        it \"returns an error\" do\n          subject.environments_path = nil\n          subject.finalize!\n          expect(errors).to include(I18n.t(\"vagrant.config.chef.environment_path_required\"))\n        end\n      end\n\n      context \"when the environments_path is an empty array\" do\n        it \"returns an error\" do\n          subject.environments_path = []\n          subject.finalize!\n          expect(errors).to include(I18n.t(\"vagrant.config.chef.environment_path_required\"))\n        end\n      end\n\n      context \"when the environments_path is an array with nil\" do\n        it \"returns an error\" do\n          subject.environments_path = [nil, nil]\n          subject.finalize!\n          expect(errors).to include(I18n.t(\"vagrant.config.chef.environment_path_required\"))\n        end\n      end\n\n      context \"when the environments_path does not exist\" do\n        it \"returns an error\" do\n          env_path = \"/path/to/environments/that/will/never/exist\"\n          subject.environments_path = env_path\n          subject.finalize!\n          expect(errors).to include(I18n.t(\"vagrant.config.chef.environment_path_missing\",\n            path: env_path,\n          ))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/omnibus_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/omnibus\")\n\ndescribe VagrantPlugins::Chef::Omnibus do\n  describe \"#sh_command\" do\n    it \"includes the project name\" do\n      command = described_class.sh_command(\"chef\", nil, \"stable\", \"https://omnitruck.chef.io\")\n      expect(command).to include %|-P \"chef\"|\n    end\n\n    it \"includes the channel\" do\n      command = described_class.sh_command(\"chef\", nil, \"stable\", \"https://omnitruck.chef.io\")\n      expect(command).to include %|-c \"stable\"|\n    end\n\n    it \"includes the version\" do\n      command = described_class.sh_command(\"chef\", \"1.2.3\", \"stable\", \"https://omnitruck.chef.io\")\n      expect(command).to include %|-v \"1.2.3\"|\n    end\n\n    it \"includes the Omnibus installation URL\" do\n      command = described_class.sh_command(\"chef\", \"1.2.3\", \"stable\", \"https://omnitruck.chef.io\")\n      expect(command).to include %|https://omnitruck.chef.io/install.sh|\n    end\n\n    it \"includes the download path\" do\n      command = described_class.sh_command(\"chef\", \"1.2.3\", \"stable\", \"https://omnitruck.chef.io\",\n        download_path: \"/some/path\",\n      )\n      expect(command).to include %|-d \"/some/path\"|\n    end\n  end\n\n  describe \"#ps_command\" do\n    it \"includes the project name\" do\n      command = described_class.ps_command(\"chef\", nil, \"stable\", \"https://omnitruck.chef.io\")\n      expect(command).to include %|-project 'chef'|\n    end\n\n    it \"includes the channel\" do\n      command = described_class.ps_command(\"chef\", nil, \"stable\", \"https://omnitruck.chef.io\")\n      expect(command).to include %|-channel 'stable'|\n    end\n\n    it \"includes the version\" do\n      command = described_class.ps_command(\"chef\", \"1.2.3\", \"stable\", \"https://omnitruck.chef.io\")\n      expect(command).to include %|-version '1.2.3'|\n    end\n\n    it \"includes the Omnibus installation URL\" do\n      command = described_class.ps_command(\"chef\", \"1.2.3\", \"stable\", \"https://omnitruck.chef.io\")\n      expect(command).to include %|https://omnitruck.chef.io/install.ps1|\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/provisioner/base_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/provisioner/base\")\n\ndescribe VagrantPlugins::Chef::Provisioner::Base do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:config)  { double(\"config\") }\n\n  subject { described_class.new(machine, config) }\n\n  before do\n    allow(config).to receive(:node_name)\n    allow(config).to receive(:node_name=)\n  end\n\n  describe \"#node_name\" do\n    let(:env) { double(\"env\") }\n    let(:root_path) { \"/my/root\" }\n\n    before do\n      allow(machine).to receive(:env).and_return(env)\n      allow(env).to receive(:root_path).and_return(root_path)\n    end\n\n    it \"defaults to node_name if given\" do\n      config = OpenStruct.new(node_name: \"name\")\n      instance = described_class.new(machine, config)\n      expect(instance.config.node_name).to eq(\"name\")\n    end\n\n    it \"defaults to hostname if given\" do\n      machine.config.vm.hostname = \"by.hostname\"\n      instance = described_class.new(machine, OpenStruct.new(node_name: nil))\n      expect(instance.config.node_name).to eq(\"by.hostname\")\n    end\n\n    it \"generates a random name if no hostname or node_name is given\" do\n      machine.config.vm.hostname = nil\n      instance = described_class.new(machine, OpenStruct.new(node_name: nil))\n      expect(instance.config.node_name).to match(/vagrant\\-.+/)\n    end\n\n    it \"does not set node_name if configuration does not define it\" do\n      expect(config).to receive(:respond_to?).with(:node_name).and_return(false)\n      expect(config).not_to receive(:node_name)\n      described_class.new(machine, config)\n    end\n  end\n\n  describe \"#encrypted_data_bag_secret_key_path\" do\n    let(:env) { double(\"env\") }\n    let(:root_path) { \"/my/root\" }\n\n    before do\n      allow(machine).to receive(:env).and_return(env)\n      allow(env).to receive(:root_path).and_return(root_path)\n    end\n\n    it \"returns absolute path as is\" do\n      expect(config).to receive(:encrypted_data_bag_secret_key_path).\n        and_return(\"/foo/bar\")\n      expect(subject.encrypted_data_bag_secret_key_path).to eq \"/foo/bar\"\n    end\n\n    it \"returns relative path joined to root_path\" do\n      expect(config).to receive(:encrypted_data_bag_secret_key_path).\n        and_return(\"secret\")\n      expect(subject.encrypted_data_bag_secret_key_path).to eq \"/my/root/secret\"\n    end\n  end\n\n  describe \"#guest_encrypted_data_bag_secret_key_path\" do\n    it \"returns nil if host path is not configured\" do\n      allow(config).to receive(:encrypted_data_bag_secret_key_path).and_return(nil)\n      allow(config).to receive(:provisioning_path).and_return(\"/tmp/foo\")\n      expect(subject.guest_encrypted_data_bag_secret_key_path).to be_nil\n    end\n\n    it \"returns path under config.provisioning_path\" do\n      allow(config).to receive(:encrypted_data_bag_secret_key_path).and_return(\"secret\")\n      allow(config).to receive(:provisioning_path).and_return(\"/tmp/foo\")\n      expect(File.dirname(subject.guest_encrypted_data_bag_secret_key_path)).\n        to eq \"/tmp/foo\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/chef/provisioner/chef_solo_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/chef/provisioner/chef_solo\")\n\ndescribe VagrantPlugins::Chef::Provisioner::ChefSolo do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:config)  { double(\"config\") }\n\n  subject { described_class.new(machine, config) }\n\n  before do\n    allow(config).to receive(:node_name)\n    allow(config).to receive(:node_name=)\n  end\n\n  describe \"#expanded_folders\" do\n    before { allow(subject).to receive(:windows?).and_return(true) }\n\n    it \"handles the default Windows provisioning path\" do\n      allow(config).to receive(:provisioning_path).and_return(nil)\n      remote_path = subject.expanded_folders([[:vm, \"cookbooks-1\"]])[0][2]\n      expect(remote_path).to eq(\"/vagrant-chef/cookbooks-1\")\n    end\n\n    it \"removes drive letter prefix from path\" do\n      allow(config).to receive(:provisioning_path).and_return(nil)\n      expect(File).to receive(:expand_path).and_return(\"C:/vagrant-chef/cookbooks-1\")\n      result = subject.expanded_folders([[:vm, \"cookbooks-1\"]])\n      remote_path = result[0][2]\n      expect(remote_path).to eq(\"/vagrant-chef/cookbooks-1\")\n    end\n\n  end\n\n  describe \"#expanded_folders\" do\n    it \"expands Windows absolute provisioning path with relative path\" do\n      provisioning_path = \"C:/vagrant-chef-1\"\n      unexpanded_path = \"cookbooks-1\"\n\n      allow(config).to receive(:provisioning_path).and_return(provisioning_path)\n      remote_path = subject.expanded_folders([[:vm, unexpanded_path]])[0][2]\n\n      expect(remote_path).to eq(\"/vagrant-chef-1/cookbooks-1\")\n    end\n\n    it \"expands Windows absolute provisioning path with absolute path\" do\n      provisioning_path = \"C:/vagrant-chef-1\"\n      unexpanded_path = \"/cookbooks-1\"\n\n      allow(config).to receive(:provisioning_path).and_return(provisioning_path)\n      remote_path = subject.expanded_folders([[:vm, unexpanded_path]])[0][2]\n\n      expect(remote_path).to eq(\"/cookbooks-1\")\n    end\n\n    it \"expands Windows absolute provisioning path with Windows absolute path\" do\n      provisioning_path = \"C:/vagrant-chef-1\"\n      unexpanded_path = \"D:/cookbooks-1\"\n\n      allow(config).to receive(:provisioning_path).and_return(provisioning_path)\n      remote_path = subject.expanded_folders([[:vm, unexpanded_path]])[0][2]\n\n      expect(remote_path).to eq(\"/cookbooks-1\")\n    end\n\n    it \"expands absolute provisioning path with Windows absolute path\" do\n      provisioning_path = \"/vagrant-chef-1\"\n      unexpanded_path = \"D:/cookbooks-1\"\n\n      allow(config).to receive(:provisioning_path).and_return(provisioning_path)\n      remote_path = subject.expanded_folders([[:vm, unexpanded_path]])[0][2]\n\n      expect(remote_path).to eq(\"/cookbooks-1\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/container/client_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/container/client\")\n\ndescribe VagrantPlugins::ContainerProvisioner::Client do\n\n  let(:machine) { double(\"machine\", communicate: communicator, ui: ui) }\n  let(:ui) { Vagrant::UI::Silent.new }\n  let(:communicator) { double(\"communicator\") }\n  let(:container_command) { \"CONTAINER_COMMAND\" }\n  subject { described_class.new(machine, container_command) }\n\n  describe \"#container_name\" do\n    it \"converts a container name to a run appropriate form\" do\n      config = { :name => \"test/test:1.1.1\", :original_name => \"test/test:1.1.1\" }\n      expect(subject.container_name(config)).to eq(\"test-test-1.1.1\")\n    end\n  end\n\n  describe \"#build_images\" do\n    before { allow(communicator).to receive(:sudo) }\n\n    it \"should use sudo to run command\" do\n      expect(communicator).to receive(:sudo).with(/#{Regexp.escape(container_command)}/)\n      subject.build_images([[\"path\", {}]])\n    end\n\n    it \"should output information to use\" do\n      expect(ui).to receive(:info).and_call_original\n      subject.build_images([[\"path\", {}]])\n    end\n\n    it \"should handle communicator output\" do\n      expect(communicator).to receive(:sudo).with(/#{Regexp.escape(container_command)}/).\n        and_yield(:stdout, \"some output\")\n      subject.build_images([[\"path\", {}]])\n    end\n  end\n\n  describe \"#pull_images\" do\n    before do\n      allow(communicator).to receive(:sudo)\n    end\n\n    it \"should use sudo to run command\" do\n      expect(communicator).to receive(:sudo).with(/#{Regexp.escape(container_command)}/)\n      subject.pull_images(:image)\n    end\n\n    it \"should output information to use\" do\n      expect(ui).to receive(:info).and_call_original\n      subject.pull_images(:image)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/container/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/container/config\")\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/vm\")\n\ndescribe VagrantPlugins::ContainerProvisioner::Config do\n  subject { described_class.new }\n\n  describe \"#build_image\" do\n    it \"stores them\" do\n      subject.build_image(\"foo\")\n      subject.build_image(\"bar\", foo: :bar)\n      subject.finalize!\n      expect(subject.build_images.length).to eql(2)\n      expect(subject.build_images[0]).to eql([\"foo\", {}])\n      expect(subject.build_images[1]).to eql([\"bar\", { foo: :bar }])\n    end\n  end\n\n  describe \"#images\" do\n    it \"stores them in a set\" do\n      subject.images = [\"1\", \"1\", \"2\"]\n      subject.finalize!\n      expect(subject.images.to_a.sort).to eql([\"1\", \"2\"])\n    end\n\n    it \"overrides previously set images\" do\n      subject.images = [\"3\"]\n      subject.images = [\"1\", \"1\", \"2\"]\n      subject.finalize!\n      expect(subject.images.to_a.sort).to eql([\"1\", \"2\"])\n    end\n  end\n\n  describe \"#merge\" do\n    it \"has all images to pull\" do\n      subject.pull_images(\"1\")\n\n      other = described_class.new\n      other.pull_images(\"2\", \"3\")\n\n      result = subject.merge(other)\n      expect(result.images.to_a.sort).to eq(\n        [\"1\", \"2\", \"3\"])\n    end\n\n    it \"has all the containers to run\" do\n      subject.run(\"foo\", image: \"bar\", daemonize: false)\n      subject.run(\"bar\")\n\n      other = described_class.new\n      other.run(\"foo\", image: \"foo\")\n\n      result = subject.merge(other)\n      result.finalize!\n\n      cs     = result.containers\n      expect(cs.length).to eq(2)\n      expect(cs[\"foo\"]).to eq({\n        auto_assign_name: true,\n        image: \"foo\",\n        daemonize: false,\n        restart: \"always\",\n      })\n      expect(cs[\"bar\"]).to eq({\n        auto_assign_name: true,\n        image: \"bar\",\n        daemonize: true,\n        restart: \"always\",\n      })\n    end\n\n    it \"has all the containers to build\" do\n      subject.build_image(\"foo\")\n\n      other = described_class.new\n      other.build_image(\"bar\")\n\n      result = subject.merge(other)\n      result.finalize!\n\n      images = result.build_images\n      expect(images.length).to eq(2)\n      expect(images[0]).to eq([\"foo\", {}])\n      expect(images[1]).to eq([\"bar\", {}])\n    end\n  end\n\n  describe \"#pull_images\" do\n    it \"adds images to the list of images to build\" do\n      subject.pull_images(\"1\")\n      subject.pull_images(\"2\", \"3\")\n      subject.finalize!\n      expect(subject.images.to_a.sort).to eql([\"1\", \"2\", \"3\"])\n    end\n  end\n\n  describe \"#run\" do\n    it \"runs the given image\" do\n      subject.run(\"foo\")\n\n      subject.finalize!\n      expect(subject.containers).to eql({\n        \"foo\" => {\n          auto_assign_name: true,\n          daemonize: true,\n          image: \"foo\",\n          restart: \"always\",\n        }\n      })\n    end\n\n    it \"can not auto assign name\" do\n      subject.run(\"foo\", auto_assign_name: false)\n\n      subject.finalize!\n      expect(subject.containers).to eql({\n        \"foo\" => {\n          auto_assign_name: false,\n          daemonize: true,\n          image: \"foo\",\n          restart: \"always\",\n        }\n      })\n    end\n\n    it \"can not daemonize\" do\n      subject.run(\"foo\", daemonize: false)\n\n      subject.finalize!\n      expect(subject.containers).to eql({\n        \"foo\" => {\n          auto_assign_name: true,\n          daemonize: false,\n          image: \"foo\",\n          restart: \"always\",\n        }\n      })\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/docker/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/docker/config\")\nrequire Vagrant.source_root.join(\"plugins/provisioners/docker/provisioner\")\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/vm\")\n\ndescribe VagrantPlugins::DockerProvisioner::Config do\n  subject { described_class.new }\n\n  describe \"#post_install_provision\" do\n    it \"raises an error if 'docker' provisioner was provided\" do\n      expect {subject.post_install_provision(\"myprov\", :type=>\"docker\", :inline=>\"echo 'hello'\")}\n        .to raise_error(VagrantPlugins::DockerProvisioner::DockerError)\n    end\n\n    it \"setups a basic provisioner\" do\n      prov = double()\n      mock_provisioner = \"mock\"\n      mock_provisioners = [mock_provisioner]\n\n      allow(VagrantPlugins::Kernel_V2::VMConfig).to receive(:new).\n        and_return(prov)\n      allow(prov).to receive(:provision).and_return(mock_provisioners)\n      allow(prov).to receive(:provisioners).and_return(mock_provisioners)\n\n      subject.post_install_provision(\"myprov\", :inline=>\"echo 'hello'\")\n      expect(subject.post_install_provisioner).to eq(mock_provisioner)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/docker/installer_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/docker/provisioner\")\n\ndescribe VagrantPlugins::DockerProvisioner::Installer do\n  include_context \"unit\"\n  subject { described_class.new(machine) }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { double(\"comm\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n\n    allow(communicator).to receive(:ready?).and_return(true)\n    allow(communicator).to receive(:test).with(/Linux/).and_return(true)\n  end\n\n  describe \"#ensure_installed\" do\n    it \"returns if docker capability not present\" do\n      allow(machine).to receive_message_chain(:guest, :capability?).with(:docker_installed).and_return(false)\n      expect(subject.ensure_installed()).to eq(false)\n    end\n\n    it \"does not install docker if already present\" do\n      expect(communicator).to receive(:test).with(/docker/, {:sudo=>true}).and_return(true)\n      allow(communicator).to receive(:test).and_return(true)\n      expect(subject.ensure_installed()).to eq(true)\n    end\n\n    it \"installs docker if not present\" do\n      allow(machine).to receive_message_chain(:guest, :capability?).with(:docker_installed).and_return(true)\n      allow(machine).to receive_message_chain(:guest, :capability).with(:docker_install).and_return(false)\n      allow(machine).to receive_message_chain(:guest, :capability).with(:docker_installed).and_return(false)\n\n      # Expect to raise error since we are mocking out the test for docker install to return false\n      expect {subject.ensure_installed()}.to raise_error(VagrantPlugins::DockerProvisioner::DockerError)\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/docker/plugin_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/docker/provisioner\")\n\ndescribe VagrantPlugins::DockerProvisioner::Plugin do\n  subject { described_class }\n\n  it \"has valid guest capabilities\" do\n    subject.components.guest_capabilities.each do |guest, caps|\n      caps.each do |cap|\n        subject.components.guest_capabilities[guest][cap]\n      end\n    end\n  end\n\nend"
  },
  {
    "path": "test/unit/plugins/provisioners/docker/provisioner_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/docker/provisioner\")\n\ndescribe VagrantPlugins::DockerProvisioner::Provisioner do\n  include_context \"unit\"\n  subject { described_class.new(machine, config, installer, client) }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:config)       { double(\"config\") }\n  let(:communicator) { double(\"comm\") }\n  let(:guest)        { double(\"guest\") }\n  let(:client)       { double(\"client\") }\n  let(:installer)    { double(\"installer\") }\n  let(:hook)         { double(\"hook\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(machine).to receive(:guest).and_return(guest)\n\n    allow(communicator).to receive(:execute).and_return(true)\n    allow(communicator).to receive(:upload).and_return(true)\n\n    allow(guest).to receive(:capability?).and_return(false)\n    allow(guest).to receive(:capability).and_return(false)\n\n    allow(client).to receive(:start_service).and_return(true)\n    allow(client).to receive(:daemon_running?).and_return(true)\n\n    allow(config).to receive(:images).and_return(Set.new)\n    allow(config).to receive(:build_images).and_return(Set.new)\n    allow(config).to receive(:containers).and_return(Hash.new)\n  end\n\n  describe \"#provision\" do\n    let(:provisioner) do\n      prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"spec-test\", :shell)\n      prov.config = {}\n      prov\n    end\n\n    it \"invokes a post_install_provisioner if defined and docker is installed\" do\n      allow(installer).to receive(:ensure_installed).and_return(true)\n      allow(config).to receive(:post_install_provisioner).and_return(provisioner)\n      allow(machine).to receive(:env).and_return(iso_env)\n      allow(machine.env).to receive(:hook).and_return(true)\n\n      expect(machine.env).to receive(:hook).with(:run_provisioner, anything)\n      subject.provision()\n    end\n\n    it \"does not invoke post_install_provisioner if not defined\" do\n      allow(installer).to receive(:ensure_installed).and_return(true)\n      allow(config).to receive(:post_install_provisioner).and_return(nil)\n      allow(machine).to receive(:env).and_return(iso_env)\n      allow(machine.env).to receive(:hook).and_return(true)\n\n      expect(machine.env).not_to receive(:hook).with(:run_provisioner, anything)\n      subject.provision()\n    end\n\n    it \"raises an error if docker daemon isn't running\" do\n      allow(installer).to receive(:ensure_installed).and_return(false)\n      allow(client).to receive(:start_service).and_return(false)\n      allow(client).to receive(:daemon_running?).and_return(false)\n\n      expect { subject.provision() }.\n        to raise_error(VagrantPlugins::DockerProvisioner::DockerError)\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/file/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/file/config\")\n\ndescribe VagrantPlugins::FileUpload::Config do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  let(:env) do\n    iso_env = isolated_environment\n    iso_env.vagrantfile(\"\")\n    iso_env.create_vagrant_env\n  end\n\n  let(:machine) { double(\"machine\", env: env) }\n\n  describe \"#validate\" do\n    it \"returns an error if destination is not specified\" do\n      existing_file = File.expand_path(__FILE__)\n\n      subject.source = existing_file\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(result[\"File provisioner\"]).to eql([\n        I18n.t(\"vagrant.provisioners.file.no_dest_file\")\n      ])\n    end\n\n    it \"returns an error if source is not specified\" do\n      subject.destination = \"/tmp/foo\"\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(result[\"File provisioner\"]).to eql([\n        I18n.t(\"vagrant.provisioners.file.no_source_file\")\n      ])\n    end\n\n    it \"returns an error if source file does not exist\" do\n      non_existing_file = \"/this/does/not/exist\"\n\n      subject.source = non_existing_file\n      subject.destination = \"/tmp/foo\"\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(result[\"File provisioner\"]).to eql([\n        I18n.t(\"vagrant.provisioners.file.path_invalid\",\n               path: File.expand_path(non_existing_file))\n      ])\n    end\n\n    it \"passes with absolute source path\" do\n      existing_absolute_path = File.expand_path(__FILE__)\n\n      subject.source = existing_absolute_path\n      subject.destination = \"/tmp/foo\"\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(result[\"File provisioner\"]).to eql([])\n    end\n\n    it \"passes with relative source path\" do\n      path = env.root_path.join(\"foo\")\n      path.open(\"w+\") { |f| f.write(\"hello\") }\n\n      existing_relative_path = \"foo\"\n\n      subject.source = existing_relative_path\n      subject.destination = \"/tmp/foo\"\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(result[\"File provisioner\"]).to eql([])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/file/provisioner_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/file/provisioner\")\n\ndescribe VagrantPlugins::FileUpload::Provisioner do\n  include_context \"unit\"\n\n  subject { described_class.new(machine, config) }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:config)       { double(\"config\") }\n  let(:communicator) { double(\"comm\") }\n  let(:guest)        { double(\"guest\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(machine).to receive(:guest).and_return(guest)\n\n    allow(communicator).to receive(:execute).and_return(true)\n    allow(communicator).to receive(:upload).and_return(true)\n\n    allow(guest).to receive(:capability?).and_return(false)\n  end\n\n  describe \"#provision\" do\n    it \"creates the destination directory\" do\n      allow(config).to receive(:source).and_return(\"/source\")\n      allow(config).to receive(:destination).and_return(\"/foo/bar\")\n\n      subject.provision\n    end\n\n    it \"creates the destination directory with a space\" do\n      allow(config).to receive(:source).and_return(\"/source\")\n      allow(config).to receive(:destination).and_return(\"/foo bar/bar\")\n\n      subject.provision\n    end\n\n    it \"creates the destination directory above file\" do\n      allow(config).to receive(:source).and_return(\"/source/file.sh\")\n      allow(config).to receive(:destination).and_return(\"/foo/bar/file.sh\")\n\n      subject.provision\n    end\n\n    it \"uploads the file\" do\n      allow(config).to receive(:source).and_return(\"/source\")\n      allow(config).to receive(:destination).and_return(\"/foo/bar\")\n\n      expect(communicator).to receive(:upload).with(\"/source\", \"/foo/bar\")\n\n      subject.provision\n    end\n\n    it \"expands the source file path\" do\n      allow(config).to receive(:source).and_return(\"source\")\n      allow(config).to receive(:destination).and_return(\"/foo/bar\")\n\n      expect(communicator).to receive(:upload).with(\n        File.expand_path(\"#{machine.env.cwd}/source\"), \"/foo/bar\")\n\n      subject.provision\n    end\n\n    it \"expands the destination file path if capable\" do\n      allow(config).to receive(:source).and_return(\"/source\")\n      allow(config).to receive(:destination).and_return(\"$HOME/foo\")\n\n      expect(guest).to receive(:capability?).\n        with(:shell_expand_guest_path).and_return(true)\n      expect(guest).to receive(:capability).\n        with(:shell_expand_guest_path, \"$HOME/foo\").and_return(\"/home/foo\")\n\n      expect(communicator).to receive(:upload).with(\"/source\", \"/home/foo\")\n\n      subject.provision\n    end\n\n    it \"appends a '/.' if the destination doesnt end with a file separator\" do\n      allow(config).to receive(:source).and_return(\"/source\")\n      allow(config).to receive(:destination).and_return(\"/foo/bar\")\n      allow(File).to receive(:directory?).with(\"/source\").and_return(true)\n\n      expect(guest).to receive(:capability?).\n        with(:shell_expand_guest_path).and_return(true)\n      expect(guest).to receive(:capability).\n        with(:shell_expand_guest_path, \"/foo/bar\").and_return(\"/foo/bar\")\n\n      expect(communicator).to receive(:upload).with(\"/source/.\", \"/foo/bar\")\n\n      subject.provision\n    end\n\n    it \"appends a '/.' to expanded source if defined in original source\" do\n      allow(config).to receive(:source).and_return(\"/source/.\")\n      allow(File).to receive(:directory?).with(\"/source\").and_return(true)\n      allow(config).to receive(:destination).and_return(\"/foo/bar\")\n\n      expect(communicator).to receive(:upload).with(\"/source/.\", \"/foo/bar\")\n\n      subject.provision\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/podman/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/podman/config\")\nrequire Vagrant.source_root.join(\"plugins/provisioners/podman/provisioner\")\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/vm\")\n\ndescribe VagrantPlugins::PodmanProvisioner::Config do\n  subject { described_class.new }\n\n  describe \"#post_install_provision\" do\n    it \"raises an error if 'podman' provisioner was provided\" do\n      expect {subject.post_install_provision(\"myprov\", :type=>\"podman\", :inline=>\"echo 'hello'\")}\n        .to raise_error(VagrantPlugins::PodmanProvisioner::PodmanError)\n    end\n\n    it \"setups a basic provisioner\" do\n      prov = double()\n      mock_provisioner = \"mock\"\n      mock_provisioners = [mock_provisioner]\n\n      allow(VagrantPlugins::Kernel_V2::VMConfig).to receive(:new).\n        and_return(prov)\n      allow(prov).to receive(:provision).and_return(mock_provisioners)\n      allow(prov).to receive(:provisioners).and_return(mock_provisioners)\n\n      subject.post_install_provision(\"myprov\", :inline=>\"echo 'hello'\")\n      expect(subject.post_install_provisioner).to eq(mock_provisioner)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/podman/installer_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/podman/provisioner\")\n\ndescribe VagrantPlugins::PodmanProvisioner::Installer do\n  include_context \"unit\"\n  subject { described_class.new(machine) }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:communicator) { double(\"comm\") }\n  let(:install_mode) { :kubic }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n\n    allow(communicator).to receive(:ready?).and_return(true)\n    allow(communicator).to receive(:test).with(/Linux/).and_return(true)\n  end\n\n  describe \"#ensure_installed\" do\n    it \"returns if podman capability not present\" do\n      allow(machine).to receive_message_chain(:guest, :capability?).with(:podman_installed).and_return(false)\n      expect(subject.ensure_installed(install_mode)).to eq(false)\n    end\n\n    it \"does not install podman if already present\" do\n      expect(communicator).to receive(:test).with(/podman/).and_return(true)\n      allow(communicator).to receive(:test).and_return(true)\n      expect(subject.ensure_installed(install_mode)).to eq(true)\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/podman/provisioner_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/podman/provisioner\")\n\ndescribe VagrantPlugins::PodmanProvisioner::Provisioner do\n  include_context \"unit\"\n  subject { described_class.new(machine, config, installer, client) }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:config)       { double(\"config\") }\n  let(:communicator) { double(\"comm\") }\n  let(:guest)        { double(\"guest\") }\n  let(:client)       { double(\"client\") }\n  let(:installer)    { double(\"installer\") }\n  let(:hook)         { double(\"hook\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(machine).to receive(:guest).and_return(guest)\n\n    allow(communicator).to receive(:execute).and_return(true)\n    allow(communicator).to receive(:upload).and_return(true)\n\n    allow(guest).to receive(:capability?).and_return(false)\n    allow(guest).to receive(:capability).and_return(false)\n\n    allow(client).to receive(:start_service).and_return(true)\n    allow(client).to receive(:daemon_running?).and_return(true)\n\n    allow(config).to receive(:images).and_return(Set.new)\n    allow(config).to receive(:build_images).and_return(Set.new)\n    allow(config).to receive(:containers).and_return(Hash.new)\n  end\n\n  describe \"#provision\" do\n    let(:provisioner) do\n      prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"spec-test\", :shell)\n      prov.config = {}\n      prov\n    end\n\n    it \"invokes a post_install_provisioner if defined and podman is installed\" do\n      allow(installer).to receive(:ensure_installed).and_return(true)\n      allow(config).to receive(:post_install_provisioner).and_return(provisioner)\n      allow(config).to receive(:kubic).and_return(false)\n      allow(machine).to receive(:env).and_return(iso_env)\n      allow(machine.env).to receive(:hook).and_return(true)\n\n      expect(machine.env).to receive(:hook).with(:run_provisioner, anything)\n      subject.provision()\n    end\n\n    it \"does not invoke post_install_provisioner if not defined\" do\n      allow(installer).to receive(:ensure_installed).and_return(true)\n      allow(config).to receive(:post_install_provisioner).and_return(nil)\n      allow(config).to receive(:kubic).and_return(false)\n      allow(machine).to receive(:env).and_return(iso_env)\n      allow(machine.env).to receive(:hook).and_return(true)\n\n      expect(machine.env).not_to receive(:hook).with(:run_provisioner, anything)\n      subject.provision()\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/puppet/provisioner/puppet_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/puppet/provisioner/puppet\")\n\ndescribe VagrantPlugins::Puppet::Provisioner::Puppet do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:config)       { double(\"config\") }\n  let(:facts)        { [] }\n  let(:communicator) { double(\"comm\") }\n  let(:guest)        { double(\"guest\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:module_paths) { [\"etc/puppet/modules\"] } # make this something real\n\n  subject { described_class.new(machine, config) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  describe \"#run_puppet_apply\" do\n    let(:options) { \"--environment production\" }\n    let(:binary_path) { \"/opt/puppetlabs/bin\" }\n    let(:manifest_file) { \"default.pp\" }\n\n    it \"runs puppet on a manifest\" do\n      allow(config).to receive(:options).and_return(options)\n      allow(config).to receive(:environment_path).and_return(false)\n      allow(config).to receive(:facter).and_return(facts)\n      allow(config).to receive(:binary_path).and_return(binary_path)\n      allow(config).to receive(:environment_variables).and_return({hello: \"there\", test: \"test\"})\n      allow(config).to receive(:working_directory).and_return(false)\n      allow(config).to receive(:manifest_file).and_return(manifest_file)\n      allow(config).to receive(:structured_facts).and_return(double(\"structured_facts\"))\n\n      allow_message_expectations_on_nil\n      allow(@module_paths).to receive(:map) { module_paths }\n      allow(@module_paths).to receive(:empty?).and_return(true)\n\n      expect(machine).to receive(:communicate).and_return(comm)\n      expect(machine.communicate).to receive(:sudo).with(\"hello=\\\"there\\\" test=\\\"test\\\" /opt/puppetlabs/bin/puppet apply --environment production --color=false --detailed-exitcodes \", anything)\n\n      subject.run_puppet_apply()\n    end\n\n    it \"properly sets env variables on windows\" do\n      allow(config).to receive(:options).and_return(options)\n      allow(config).to receive(:environment_path).and_return(false)\n      allow(config).to receive(:facter).and_return(facts)\n      allow(config).to receive(:binary_path).and_return(binary_path)\n      allow(config).to receive(:environment_variables).and_return({hello: \"there\", test: \"test\"})\n      allow(config).to receive(:working_directory).and_return(false)\n      allow(config).to receive(:manifest_file).and_return(manifest_file)\n      allow(config).to receive(:structured_facts).and_return(double(\"structured_facts\"))\n      allow(subject).to receive(:windows?).and_return(true)\n\n      allow_message_expectations_on_nil\n      allow(@module_paths).to receive(:map) { module_paths }\n      allow(@module_paths).to receive(:empty?).and_return(true)\n\n      expect(machine).to receive(:communicate).and_return(comm)\n      expect(machine.communicate).to receive(:sudo).with(\"$env:hello=\\\"there\\\"; $env:test=\\\"test\\\"; /opt/puppetlabs/bin/puppet apply --environment production --color=false --detailed-exitcodes \", anything)\n\n      subject.run_puppet_apply()\n    end\n  end\n\n  describe \"#provision\" do\n    let(:options) { double(\"options\") }\n    let(:binary_path) { \"/opt/puppetlabs/bin\" }\n    let(:manifest_file) { \"default.pp\" }\n    let(:module_paths) { [\"etc/puppet/modules\"] } # make this something real\n    let(:environment_paths) { [\"/etc/puppet/environment\"] }\n\n    it \"builds structured facts if set\" do\n      allow(machine).to receive(:guest).and_return(double(\"guest\"))\n      allow(machine.guest).to receive(:capability?).and_return(false)\n      allow(config).to receive(:environment_path).and_return(environment_paths)\n      allow(config).to receive(:environment).and_return(\"production\")\n      allow(config).to receive(:manifests_path).and_return(manifest_file)\n      allow(config).to receive(:temp_dir).and_return(\"/tmp\")\n      allow(config).to receive(:hiera_config_path).and_return(false)\n      allow(subject).to receive(:parse_environment_metadata).and_return(true)\n      allow(subject).to receive(:verify_binary).and_return(true)\n      allow(subject).to receive(:run_puppet_apply).and_return(true)\n\n      allow_message_expectations_on_nil\n      allow(@module_paths).to receive(:each) { module_paths }\n\n      allow(config).to receive(:facter).and_return({\"coolfacts\"=>\"here they are\"})\n      allow(config).to receive(:structured_facts).and_return(true)\n\n      expect(machine.communicate).to receive(:upload).with(anything, \"/tmp/vagrant_facts.yaml\")\n      expect(machine.communicate).to receive(:sudo).with(\"mkdir -p /tmp; chmod 0777 /tmp\", {})\n      expect(machine.communicate).to receive(:sudo).with(\"cp /tmp/vagrant_facts.yaml /etc/puppetlabs/facter/facts.d/vagrant_facts.yaml\")\n      subject.provision()\n    end\n\n    it \"does not build structured facts if not set\" do\n      allow(machine).to receive(:guest).and_return(double(\"guest\"))\n      allow(machine.guest).to receive(:capability?).and_return(false)\n      allow(config).to receive(:environment_path).and_return(environment_paths)\n      allow(config).to receive(:environment).and_return(\"production\")\n      allow(config).to receive(:manifests_path).and_return(manifest_file)\n      allow(config).to receive(:temp_dir).and_return(\"/tmp\")\n      allow(config).to receive(:hiera_config_path).and_return(false)\n      allow(subject).to receive(:parse_environment_metadata).and_return(true)\n      allow(subject).to receive(:verify_binary).and_return(true)\n      allow(subject).to receive(:run_puppet_apply).and_return(true)\n\n      allow_message_expectations_on_nil\n      allow(@module_paths).to receive(:each) { module_paths }\n\n      allow(config).to receive(:facter).and_return({\"coolfacts\"=>\"here they are\"})\n      allow(config).to receive(:structured_facts).and_return(nil)\n\n      expect(machine.communicate).not_to receive(:upload).with(anything, \"/tmp/vagrant_facts.yaml\")\n      expect(machine.communicate).not_to receive(:sudo).with(\"cp /tmp/vagrant_facts.yaml /etc/puppetlabs/facter/facts.d/vagrant_facts.yaml\")\n      subject.provision()\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/salt/bootstrap_downloader_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/salt/bootstrap_downloader\")\n\ndescribe VagrantPlugins::Salt::BootstrapDownloader do\n  include_context \"unit\"\n\n  subject { described_class.new(:computer) }\n\n  describe \"verify_sha256\" do\n    let(:sha256) { \"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\" }\n    let(:bad_sha256) { \"ffffffffffffffff\" }\n    let(:sha256_file) { StringIO.new(\"#{sha256} test_script_value\") }\n    let(:test_script) { StringIO.new(\"test_script_value\") }\n\n    it \"does not error if both shas match\" do\n      allow(subject).to receive(:download).and_return(sha256_file)\n      allow(Digest::SHA256).to receive(:hexdigest).and_return(sha256)\n\n      expect{subject.verify_sha256(test_script)}.to_not raise_error\n    end\n\n    it \"raises an exception if shas don't match\" do\n      allow(subject).to receive(:download).and_return(sha256_file)\n      allow(Digest::SHA256).to receive(:hexdigest).and_return(bad_sha256)\n\n      expect{subject.verify_sha256(test_script)}.to raise_error(VagrantPlugins::Salt::Errors::InvalidShasumError) { |err|\n        expect(err.message).to include(\"The bootstrap-salt script downloaded from '#{described_class::URL}' couldn't be verified.\") \n        expect(err.message).to include(\"Expected SHA256 '#{sha256}', but computed '#{bad_sha256}'\")\n      }\n    end\n\n    it \"raises the correct error message to a windows guest\" do\n      subject = described_class.new(:windows)\n      allow(subject).to receive(:download).and_return(sha256_file)\n      allow(Digest::SHA256).to receive(:hexdigest).and_return(bad_sha256)\n\n      expect{subject.verify_sha256(test_script)}.to raise_error(VagrantPlugins::Salt::Errors::InvalidShasumError) { |err|\n        expect(err.message).to include(\"The bootstrap-salt script downloaded from '#{described_class::WINDOWS_URL}' couldn't be verified.\") \n      }\n    end\n  end\nend\n\n"
  },
  {
    "path": "test/unit/plugins/provisioners/salt/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/salt/config\")\n\ndescribe VagrantPlugins::Salt::Config do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  describe \"validate\" do\n    let(:error_key) { \"salt provisioner\" }\n\n    it \"passes by default\" do\n      subject.finalize!\n      result = subject.validate(machine)\n      expect(result[error_key]).to be_empty\n    end\n\n    context \"minion_config\" do\n      it \"fails if minion_config is set and missing\" do\n        subject.minion_config = \"/nope/nope/i/dont/exist\"\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to_not be_empty\n      end\n\n      it \"is valid if is set and not missing\" do\n        subject.minion_config = File.expand_path(__FILE__)\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to be_empty\n      end\n    end\n\n    context \"master_config\" do\n      it \"fails if master_config is set and missing\" do\n        subject.master_config = \"/ceci/nest/pas/une/path\"\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to_not be_empty\n      end\n\n      it \"is valid if is set and not missing\" do\n        subject.master_config = File.expand_path(__FILE__)\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to be_empty\n      end\n    end\n\n    context \"grains_config\" do\n      it \"fails if grains_config is set and missing\" do\n        subject.grains_config = \"/nope/still/not/here\"\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to_not be_empty\n      end\n\n      it \"is valid if is set and not missing\" do\n        subject.grains_config = File.expand_path(__FILE__)\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to be_empty\n      end\n    end\n\n    context \"salt_call_args\" do\n      it \"fails if salt_call_args is not an array\" do\n        subject.salt_call_args = \"--flags\"\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to_not be_empty\n      end\n\n      it \"is valid if is set and not missing\" do\n        subject.salt_call_args = [\"--flags\"]\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to be_empty\n      end\n    end\n\n    context \"salt_args\" do\n      it \"fails if not an array\" do\n        subject.salt_args = \"--flags\"\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to_not be_empty\n      end\n\n      it \"is valid if is set and not missing\" do\n        subject.salt_args = [\"--flags\"]\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to be_empty\n      end\n    end\n\n    context \"python_version\" do\n      it \"is valid if is set and not missing\" do\n        subject.python_version = \"2\"\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to be_empty\n      end\n\n      it \"can be a string\" do\n        subject.python_version = \"2\"\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to be_empty\n      end\n\n      it \"can be an integer\" do\n        subject.python_version = 2\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to be_empty\n      end\n\n      it \"is not a number that is not an integer\" do\n        subject.python_version = 2.7\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to_not be_empty\n      end\n\n      it \"is not a string that does not parse to an integer\" do\n        subject.python_version = '2.7'\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to_not be_empty\n      end\n    end\n\n    context \"version\" do\n      it \"is valid if is set without install_type on Windows\" do\n        allow(machine.config.vm).to receive(:communicator).and_return(:winrm)\n\n        subject.version = \"2018.3.3\"\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to be_empty\n      end\n\n      it \"is invalid if is set without install_type on Linux\" do\n        subject.version = \"2018.3.3\"\n        subject.finalize!\n\n        result = subject.validate(machine)\n        expect(result[error_key]).to_not be_empty\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/salt/provisioner_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/provisioners/salt/provisioner\")\n\ndescribe VagrantPlugins::Salt::Provisioner do\n  include_context \"unit\"\n\n  subject { described_class.new(machine, config) }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:config)       { double(\"config\") }\n  let(:communicator) { double(\"comm\") }\n  let(:guest)        { double(\"guest\") }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(communicator)\n    allow(machine).to receive(:guest).and_return(guest)\n\n    allow(communicator).to receive(:execute).and_return(true)\n    allow(communicator).to receive(:upload).and_return(true)\n\n    allow(guest).to receive(:capability?).and_return(false)\n  end\n\n  describe \"#provision\" do\n    context \"minion\" do\n      let(:python_version) { \"2\" }\n\n      before do\n        allow(config).to receive(:seed_master).and_return([])\n        allow(config).to receive(:install_master).and_return(true)\n        allow(config).to receive(:install_syndic).and_return(true)\n        allow(config).to receive(:no_minion).and_return(true)\n        allow(config).to receive(:python_version).and_return(python_version)\n        allow(config).to receive(:install_type).and_return('stable')\n        allow(config).to receive(:install_args).and_return('develop')\n        allow(config).to receive(:verbose).and_return(true)\n        allow(config).to receive(:master_json_config).and_return(true)\n        allow(config).to receive(:minion_json_config).and_return(true)\n        allow(config).to receive(:bootstrap_options).and_return(\"\")\n      end\n\n      it \"does not add linux-only bootstrap flags when on windows\" do\n        additional_windows_options = \"-only -these options -should -remain\"\n        allow(machine.config.vm).to receive(:communicator).and_return(:winrm)\n        expect(config).to receive(:bootstrap_options).twice.and_return(additional_windows_options)\n\n        result = subject.bootstrap_options(true, true, \"C:\\\\salttmp\")\n        expect(result.strip).to eq(additional_windows_options)\n      end\n\n      context \"python version\" do\n        before { allow(communicator).to receive(:sudo) }\n        context \"when not set\" do\n          let(:python_version) { nil }\n\n          it \"should not include python flag\" do\n            result = subject.bootstrap_options(true, true, \"/tmp\")\n            expect(result).not_to include(\"-python\")\n          end\n        end\n\n        context \"when set\" do\n          it \"should include python flag\" do\n            result = subject.bootstrap_options(true, true, \"/tmp\")\n            expect(result).to include(\"python#{python_version}\")\n          end\n        end\n      end\n    end\n  end\n\n  describe \"#get_pillar\" do\n    context \"windows\" do\n      it \"escapes pillar data for powershell and returns as json\" do\n        allow(machine.config.vm).to receive(:communicator).and_return(:winrm)\n        allow(config).to receive(:pillar_data).and_return({\"cat\"=>\"qubit\"})\n\n        expect(subject.get_pillar).to eq(\" --% pillar={\\\"\\\"\\\"cat\\\"\\\"\\\":\\\"\\\"\\\"qubit\\\"\\\"\\\"}\")\n      end\n    end\n\n    context \"linux\" do\n      it \"returns pillar data as json\" do\n        allow(machine.config.vm).to receive(:communicator).and_return(:false)\n        allow(config).to receive(:pillar_data).and_return({\"cat\"=>\"shimi\"})\n        expect(subject.get_pillar).to eq(\" pillar='{\\\"cat\\\":\\\"shimi\\\"}'\")\n      end\n    end\n\n    context \"empty data\" do\n      it \"returns nothing if pillar data is empty\" do\n        allow(config).to receive(:pillar_data).and_return({})\n        expect(subject.get_pillar).to eq(nil)\n      end\n    end\n  end\n\n  describe \"#call_highstate\" do\n    context \"master\" do\n      it \"passes along extra cli flags\" do\n        allow(config).to receive(:run_highstate).and_return(true)\n        allow(config).to receive(:verbose).and_return(true)\n        allow(config).to receive(:masterless).and_return(false)\n        allow(config).to receive(:minion_id).and_return(nil)\n        allow(config).to receive(:log_level).and_return(nil)\n        allow(config).to receive(:colorize).and_return(false)\n        allow(config).to receive(:pillar_data).and_return([])\n        allow(config).to receive(:install_master).and_return(true)\n\n        allow(config).to receive(:salt_args).and_return([\"--async\"])\n        allow(machine.communicate).to receive(:sudo)\n        allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm)\n\n        expect(machine.communicate).to receive(:sudo).with(\"salt '*' state.highstate --verbose --log-level=debug --no-color --async\", {:error_key=>:ssh_bad_exit_status_muted})\n        subject.call_highstate()\n      end\n\n      it \"has no additional cli flags if not included\" do\n        allow(config).to receive(:run_highstate).and_return(true)\n        allow(config).to receive(:verbose).and_return(true)\n        allow(config).to receive(:masterless).and_return(false)\n        allow(config).to receive(:minion_id).and_return(nil)\n        allow(config).to receive(:log_level).and_return(nil)\n        allow(config).to receive(:colorize).and_return(false)\n        allow(config).to receive(:pillar_data).and_return([])\n        allow(config).to receive(:install_master).and_return(true)\n\n        allow(config).to receive(:salt_args).and_return(nil)\n        allow(machine.communicate).to receive(:sudo)\n        allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm)\n\n        expect(machine.communicate).to receive(:sudo).with(\"salt '*' state.highstate --verbose --log-level=debug --no-color \", {:error_key=>:ssh_bad_exit_status_muted})\n        subject.call_highstate()\n      end\n    end\n\n    context \"with masterless\" do\n      it \"passes along extra cli flags\" do\n        allow(config).to receive(:run_highstate).and_return(true)\n        allow(config).to receive(:verbose).and_return(true)\n        allow(config).to receive(:masterless).and_return(true)\n        allow(config).to receive(:minion_id).and_return(nil)\n        allow(config).to receive(:log_level).and_return(nil)\n        allow(config).to receive(:colorize).and_return(false)\n        allow(config).to receive(:pillar_data).and_return([])\n\n        allow(config).to receive(:salt_args).and_return([\"--async\"])\n        allow(config).to receive(:salt_call_args).and_return([\"--output-dif\"])\n        allow(machine.communicate).to receive(:sudo)\n        allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm)\n        allow(config).to receive(:install_master).and_return(false)\n\n        expect(machine.communicate).to receive(:sudo).with(\"salt-call state.highstate --retcode-passthrough --local --log-level=debug --no-color --output-dif\", {:error_key=>:ssh_bad_exit_status_muted})\n        subject.call_highstate()\n      end\n\n      it \"has no additional cli flags if not included\" do\n        allow(config).to receive(:run_highstate).and_return(true)\n        allow(config).to receive(:verbose).and_return(true)\n        allow(config).to receive(:masterless).and_return(true)\n        allow(config).to receive(:minion_id).and_return(nil)\n        allow(config).to receive(:log_level).and_return(nil)\n        allow(config).to receive(:colorize).and_return(false)\n        allow(config).to receive(:pillar_data).and_return([])\n\n        allow(config).to receive(:salt_call_args).and_return(nil)\n        allow(config).to receive(:salt_args).and_return(nil)\n        allow(machine.communicate).to receive(:sudo)\n        allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm)\n        allow(config).to receive(:install_master).and_return(false)\n\n        expect(machine.communicate).to receive(:sudo).with(\"salt-call state.highstate --retcode-passthrough --local --log-level=debug --no-color \", {:error_key=>:ssh_bad_exit_status_muted})\n        subject.call_highstate()\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/shell/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe \"VagrantPlugins::Shell::Config\" do\n  let(:described_class) do\n    VagrantPlugins::Shell::Plugin.components.configs[:provisioner][:shell]\n  end\n\n  let(:machine)          { double('machine', env: Vagrant::Environment.new) }\n  let(:file_that_exists) { File.expand_path(__FILE__)                       }\n\n  subject { described_class.new }\n\n  describe \"validate\" do\n    it \"passes with no args\" do\n      subject.path = file_that_exists\n      subject.finalize!\n\n      result = subject.validate(machine)\n\n      expect(result[\"shell provisioner\"]).to eq([])\n    end\n\n    it \"passes with string args\" do\n      subject.path = file_that_exists\n      subject.args = \"a string\"\n      subject.finalize!\n\n      result = subject.validate(machine)\n\n      expect(result[\"shell provisioner\"]).to eq([])\n    end\n\n    it \"passes with integer args\" do\n      subject.path = file_that_exists\n      subject.args = 1\n      subject.finalize!\n\n      result = subject.validate(machine)\n\n      expect(result[\"shell provisioner\"]).to eq([])\n    end\n\n    it \"passes with array args\" do\n      subject.path = file_that_exists\n      subject.args = [\"an\", \"array\"]\n      subject.finalize!\n\n      result = subject.validate(machine)\n\n      expect(result[\"shell provisioner\"]).to eq([])\n    end\n\n    it \"returns an error if args is neither a string nor an array\" do\n      neither_array_nor_string = Object.new\n\n      subject.path = file_that_exists\n      subject.args = neither_array_nor_string\n      subject.finalize!\n\n      result = subject.validate(machine)\n\n      expect(result[\"shell provisioner\"]).to eq([\n        I18n.t(\"vagrant.provisioners.shell.args_bad_type\")\n      ])\n    end\n\n    it \"handles scalar array args\" do\n      subject.path = file_that_exists\n      subject.args = [\"string\", 1, 2]\n      subject.finalize!\n\n      result = subject.validate(machine)\n\n      expect(result[\"shell provisioner\"]).to eq([])\n    end\n\n    it \"returns an error if args is an array with non-scalar types\" do\n      subject.path = file_that_exists\n      subject.args = [[1]]\n      subject.finalize!\n\n      result = subject.validate(machine)\n\n      expect(result[\"shell provisioner\"]).to eq([\n        I18n.t(\"vagrant.provisioners.shell.args_bad_type\")\n      ])\n    end\n\n    it \"returns an error if elevated_interactive is true but privileged is false\" do\n      subject.path = file_that_exists\n      subject.powershell_elevated_interactive = true\n      subject.privileged = false\n      subject.finalize!\n\n      result = subject.validate(machine)\n\n      expect(result[\"shell provisioner\"]).to eq([\n        I18n.t(\"vagrant.provisioners.shell.interactive_not_elevated\")\n      ])\n    end\n\n    it \"returns an error if the environment is not a hash\" do\n      subject.env = \"foo\"\n      subject.finalize!\n\n      result = subject.validate(machine)\n\n      expect(result[\"shell provisioner\"]).to include(\n        I18n.t(\"vagrant.provisioners.shell.env_must_be_a_hash\")\n      )\n    end\n\n    it \"returns an error if file and script are unset\" do\n      subject.finalize!\n      result = subject.validate(machine)\n      expect(result[\"shell provisioner\"]).to include(\n        I18n.t(\"vagrant.provisioners.shell.no_path_or_inline\")\n      )\n    end\n\n    it \"returns an error if inline and path are both set\" do\n      subject.inline = \"script\"\n      subject.path = \"script\"\n      result = subject.validate(machine)\n      expect(result[\"shell provisioner\"]).to include(\n        I18n.t(\"vagrant.provisioners.shell.path_and_inline_set\")\n      )\n    end\n\n    it \"returns no error when inline and path are unset but reset is true\" do\n      subject.reset = true\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(result[\"shell provisioner\"]).to be_empty\n    end\n\n    it \"returns no error when inline and path are unset but reboot is true\" do\n      subject.reboot = true\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(result[\"shell provisioner\"]).to be_empty\n    end\n\n    it \"returns no error if upload_path is unset\" do\n      subject.inline = \"script\"\n      subject.finalize!\n\n      result = subject.validate(machine)\n      expect(result[\"shell provisioner\"]).to be_empty\n    end\n  end\n\n  describe 'finalize!' do\n    it 'changes integer args into strings' do\n      subject.path = file_that_exists\n      subject.args = 1\n      subject.finalize!\n\n      expect(subject.args).to eq('1')\n    end\n\n    it 'changes integer args in arrays into strings' do\n      subject.path = file_that_exists\n      subject.args = [\"string\", 1, 2]\n      subject.finalize!\n\n      expect(subject.args).to eq([\"string\", '1', '2'])\n    end\n\n    it \"no longer sets a default for upload_path\" do\n      subject.finalize!\n\n      expect(subject.upload_path).to eq(nil)\n    end\n\n    context \"with sensitive option enabled\" do\n      it 'marks environment variable values sensitive' do\n        subject.env = {\"KEY1\" => \"VAL1\", \"KEY2\" => \"VAL2\"}\n        subject.sensitive = true\n\n        expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(\"VAL1\")\n        expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(\"VAL2\")\n        subject.finalize!\n      end\n    end\n\n    context \"with sensitive option disabled\" do\n      it 'does not mark environment variable values sensitive' do\n        subject.env = {\"KEY1\" => \"VAL1\", \"KEY2\" => \"VAL2\"}\n        subject.sensitive = false\n\n        expect(Vagrant::Util::CredentialScrubber).not_to receive(:sensitive)\n        subject.finalize!\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/shell/provisioner_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/provisioners/shell/provisioner\")\n\ndescribe \"Vagrant::Shell::Provisioner\" do\n  include_context \"unit\"\n\n  let(:default_win_path) { \"C:/tmp/vagrant-shell\" }\n  let(:env){ isolated_environment }\n  let(:machine) {\n    double(:machine, env: env, id: \"ID\").tap { |machine|\n      allow(machine).to receive_message_chain(:config, :vm, :communicator).and_return(:not_winrm)\n      allow(machine).to receive_message_chain(:config, :vm, :guest).and_return(:linux)\n      allow(machine).to receive_message_chain(:communicate, :tap) {}\n    }\n  }\n\n  before do\n    allow(env).to receive(:tmp_path).and_return(Pathname.new(\"/dev/null\"))\n  end\n\n  context \"when reset is enabled\" do\n    let(:path) { nil }\n    let(:inline) { \"\" }\n    let(:communicator) { double(\"communicator\") }\n\n    let(:config) {\n      double(\n        :config,\n        :args        => \"doesn't matter\",\n        :env         => {},\n        :upload_path => \"arbitrary\",\n        :remote?     => false,\n        :path        => path,\n        :inline      => inline,\n        :binary      => false,\n        :reset       => true,\n        :reboot      => false,\n      )\n    }\n\n    let(:vsp) {\n      VagrantPlugins::Shell::Provisioner.new(machine, config)\n    }\n\n    before {\n      allow(machine).to receive(:communicate).and_return(communicator)\n      allow(vsp).to receive(:provision_ssh)\n    }\n\n    it \"should provision and then reset the connection\" do\n      expect(vsp).to receive(:provision_ssh)\n      expect(communicator).to receive(:reset!)\n      vsp.provision\n    end\n\n    context \"when path and inline are not set\" do\n      let(:path) { nil }\n      let(:inline) { nil }\n\n      it \"should reset the connection and not provision\" do\n        expect(vsp).not_to receive(:provision_ssh)\n        expect(communicator).to receive(:reset!)\n        vsp.provision\n      end\n    end\n  end\n\n  context \"when reboot is enabled\" do\n    let(:path) { nil }\n    let(:inline) { \"\" }\n    let(:communicator) { double(\"communicator\") }\n    let(:guest) { double(\"guest\") }\n\n    let(:config) {\n      double(\n        :config,\n        :args        => \"doesn't matter\",\n        :env         => {},\n        :upload_path => \"arbitrary\",\n        :remote?     => false,\n        :path        => path,\n        :inline      => inline,\n        :binary      => false,\n        :reset       => false,\n        :reboot      => true\n      )\n    }\n\n    let(:vsp) {\n      VagrantPlugins::Shell::Provisioner.new(machine, config)\n    }\n\n    before {\n      allow(machine).to receive(:communicate).and_return(communicator)\n      allow(machine).to receive(:guest).and_return(guest)\n      allow(vsp).to receive(:provision_ssh)\n    }\n\n    it \"should provision and then reboot the guest\" do\n      expect(vsp).to receive(:provision_ssh)\n      expect(guest).to receive(:capability).with(:reboot)\n      vsp.provision\n    end\n\n    context \"when path and inline are not set\" do\n      let(:path) { nil }\n      let(:inline) { nil }\n\n      it \"should reboot the guest and not provision\" do\n        expect(vsp).not_to receive(:provision_ssh)\n        expect(guest).to receive(:capability).with(:reboot)\n        vsp.provision\n      end\n    end\n  end\n\n  context \"with a script that contains invalid us-ascii byte sequences\" do\n    let(:config) {\n      double(\n        :config,\n        :args        => \"doesn't matter\",\n        :env         => {},\n        :upload_path => \"arbitrary\",\n        :remote?     => false,\n        :path        => nil,\n        :inline      => script_that_is_incorrectly_us_ascii_encoded,\n        :binary      => false,\n        :reset       => false,\n        :reboot      => false\n      )\n    }\n\n    let(:script_that_is_incorrectly_us_ascii_encoded) {\n      [207].pack(\"c*\").force_encoding(\"US-ASCII\")\n    }\n\n    it \"does not raise an exception when normalizing newlines\" do\n      vsp = VagrantPlugins::Shell::Provisioner.new(machine, config)\n\n      expect {\n        vsp.provision\n      }.not_to raise_error\n    end\n  end\n\n  context \"with a script that was set to freeze the string\" do\n    TEST_CONSTANT_VARIABLE = <<-TEST_CONSTANT_VARIABLE.freeze\n      echo test\n    TEST_CONSTANT_VARIABLE\n\n    let(:script) { TEST_CONSTANT_VARIABLE }\n    let(:config) {\n      double(\n        :config,\n        :args        => \"doesn't matter\",\n        :env         => {},\n        :upload_path => \"arbitrary\",\n        :remote?     => false,\n        :path        => nil,\n        :inline      => script,\n        :binary      => false,\n        :reset       => false,\n        :reboot      => false\n      )\n    }\n\n    it \"does not raise an exception\" do\n      vsp = VagrantPlugins::Shell::Provisioner.new(machine, config)\n\n      RSpec::Expectations.configuration.on_potential_false_positives = :nothing\n      # This test should be fine, since we are specifically looking for the\n      # string 'freeze' when RuntimeError is raised\n      expect {\n        vsp.provision\n      }.not_to raise_error(RuntimeError)\n    end\n  end\n\n  context \"with remote script\" do\n    let(:filechecksum) { double(\"filechecksum\", checksum: checksum_value) }\n    let(:checksum_value) { double(\"checksum_value\") }\n\n    before do\n      allow(FileChecksum).to receive(:new).and_return(filechecksum)\n      allow_any_instance_of(Vagrant::Util::Downloader).to receive(:execute_curl).and_return(true)\n    end\n\n    context \"that does not have matching sha1 checksum\" do\n      let(:checksum_value) { \"INVALID_VALUE\" }\n      let(:config) {\n        double(\n          :config,\n          :args        => \"doesn't matter\",\n          :env         => {},\n          :upload_path => \"arbitrary\",\n          :remote?     => true,\n          :path        => \"http://example.com/script.sh\",\n          :binary      => false,\n          :md5         => nil,\n          :sha1        => 'EXPECTED_VALUE',\n          :sha256      => nil,\n          :sha384      => nil,\n          :sha512      => nil,\n          :reset       => false,\n          :reboot      => false\n        )\n      }\n\n      it \"should raise an exception\" do\n        vsp = VagrantPlugins::Shell::Provisioner.new(machine, config)\n\n        expect{ vsp.provision }.to raise_error(Vagrant::Errors::DownloaderChecksumError)\n      end\n    end\n\n    context \"that does not have matching sha256 checksum\" do\n      let(:checksum_value) { \"INVALID_VALUE\" }\n      let(:config) {\n        double(\n          :config,\n          :args        => \"doesn't matter\",\n          :env         => {},\n          :upload_path => \"arbitrary\",\n          :remote?     => true,\n          :path        => \"http://example.com/script.sh\",\n          :binary      => false,\n          :md5         => nil,\n          :sha1        => nil,\n          :sha256      => 'EXPECTED_VALUE',\n          :sha384      => nil,\n          :sha512      => nil,\n          :reset       => false,\n          :reboot      => false\n        )\n      }\n\n      it \"should raise an exception\" do\n        vsp = VagrantPlugins::Shell::Provisioner.new(machine, config)\n\n        expect{ vsp.provision }.to raise_error(Vagrant::Errors::DownloaderChecksumError)\n      end\n    end\n\n    context \"that does not have matching sha384 checksum\" do\n      let(:checksum_value) { \"INVALID_VALUE\" }\n      let(:config) {\n        double(\n          :config,\n          :args        => \"doesn't matter\",\n          :env         => {},\n          :upload_path => \"arbitrary\",\n          :remote?     => true,\n          :path        => \"http://example.com/script.sh\",\n          :binary      => false,\n          :md5         => nil,\n          :sha1        => nil,\n          :sha256      => nil,\n          :sha384      => 'EXPECTED_VALUE',\n          :sha512      => nil,\n          :reset       => false,\n          :reboot      => false\n        )\n      }\n\n      it \"should raise an exception\" do\n        vsp = VagrantPlugins::Shell::Provisioner.new(machine, config)\n\n        expect{ vsp.provision }.to raise_error(Vagrant::Errors::DownloaderChecksumError)\n      end\n    end\n\n    context \"that does not have matching sha512 checksum\" do\n      let(:checksum_value) { \"INVALID_VALUE\" }\n      let(:config) {\n        double(\n          :config,\n          :args        => \"doesn't matter\",\n          :env         => {},\n          :upload_path => \"arbitrary\",\n          :remote?     => true,\n          :path        => \"http://example.com/script.sh\",\n          :binary      => false,\n          :md5         => nil,\n          :sha1        => nil,\n          :sha256      => nil,\n          :sha384      => nil,\n          :sha512      => 'EXPECTED_VALUE',\n          :reset       => false,\n          :reboot      => false\n        )\n      }\n\n      it \"should raise an exception\" do\n        vsp = VagrantPlugins::Shell::Provisioner.new(machine, config)\n\n        expect{ vsp.provision }.to raise_error(Vagrant::Errors::DownloaderChecksumError)\n      end\n    end\n\n    context \"that does not have matching md5 checksum\" do\n      let(:checksum_value) { \"INVALID_VALUE\" }\n      let(:config) {\n        double(\n          :config,\n          :args        => \"doesn't matter\",\n          :env         => {},\n          :upload_path => \"arbitrary\",\n          :remote?     => true,\n          :path        => \"http://example.com/script.sh\",\n          :binary      => false,\n          :md5         => 'EXPECTED_VALUE',\n          :sha1        => nil,\n          :sha256      => nil,\n          :sha384      => nil,\n          :sha512      => nil,\n          :reset       => false,\n          :reboot      => false\n        )\n      }\n\n      it \"should raise an exception\" do\n        vsp = VagrantPlugins::Shell::Provisioner.new(machine, config)\n\n        expect{ vsp.provision }.to raise_error(Vagrant::Errors::DownloaderChecksumError)\n      end\n    end\n  end\n\n  describe \"#upload_path\" do\n    context \"when upload path is not set\" do\n      let(:vsp) {\n        VagrantPlugins::Shell::Provisioner.new(machine, config)\n      }\n\n      let(:config) {\n        double(\n          :config,\n          :args        => \"doesn't matter\",\n          :env         => {},\n          :upload_path => nil,\n          :remote?     => false,\n          :path        => \"doesn't matter\",\n          :inline      => \"doesn't matter\",\n          :binary      => false,\n          :reset       => true,\n          :reboot      => false,\n        )\n      }\n\n      it \"should default to /tmp/vagrant-shell\" do\n        expect(vsp.upload_path).to eq(\"/tmp/vagrant-shell\")\n      end\n\n      context \"windows\" do\n        before do\n          allow(machine).to receive_message_chain(:config, :vm, :guest).and_return(:windows)\n        end\n\n        it \"should default to C:/tmp/vagrant-shell\" do\n          expect(vsp.upload_path).to eq(\"C:/tmp/vagrant-shell\")\n        end\n      end\n    end\n\n    context \"when upload_path is set\" do\n      let(:upload_path) { \"arbitrary\" }\n\n      let(:config) {\n        double(\n          :config,\n          :args        => \"doesn't matter\",\n          :env         => {},\n          :upload_path => upload_path,\n          :remote?     => false,\n          :path        => \"doesn't matter\",\n          :inline      => \"doesn't matter\",\n          :binary      => false,\n          :reset       => true,\n          :reboot      => false,\n        )\n      }\n\n      let(:vsp) {\n        VagrantPlugins::Shell::Provisioner.new(machine, config)\n      }\n\n      it \"should use the value from from config\" do\n        expect(vsp.upload_path).to eq(\"arbitrary\")\n      end\n\n      context \"windows\" do\n        let(:upload_path) { \"C:\\\\Windows\\\\Temp\" }\n\n        before do\n          allow(machine).to receive_message_chain(:config, :vm, :guest).and_return(:windows)\n        end\n\n        it \"should normalize the slashes\" do\n          expect(vsp.upload_path).to eq(\"C:/Windows/Temp\")\n        end\n      end\n    end\n\n    context \"with cached value\" do\n      let(:config) { double(:config) }\n\n      let(:vsp) {\n        VagrantPlugins::Shell::Provisioner.new(machine, config)\n      }\n\n      before do\n        vsp.instance_variable_set(:@_upload_path, \"anything\")\n      end\n\n      it \"should use cached value\" do\n        expect(vsp.upload_path).to eq(\"anything\")\n      end\n    end\n  end\n\n  describe \"#provision_winrm\" do\n    let(:config) {\n      double(\n        :config,\n        :args                            => \"doesn't matter\",\n        :env                             => {},\n        :upload_path                     => \"arbitrary\",\n        :remote?                         => false,\n        :path                            => \"script/info.ps1\",\n        :binary                          => false,\n        :md5                             => nil,\n        :sha1                            => 'EXPECTED_VALUE',\n        :sha256                          => nil,\n        :sha384                          => nil,\n        :sha512                          => nil,\n        :reset                           => false,\n        :reboot                          => false,\n        :powershell_args                 => \"\",\n        :name                            => nil,\n        :privileged                      => false,\n        :powershell_elevated_interactive => false,\n        :keep_color                      => true,\n      )\n    }\n\n    let(:vsp) {\n      VagrantPlugins::Shell::Provisioner.new(machine, config)\n    }\n\n    let(:communicator) { double(\"communicator\") }\n    let(:guest) { double(\"guest\") }\n    let(:ui) { Vagrant::UI::Silent.new }\n\n    before {\n      allow(guest).to receive(:capability?).with(:wait_for_reboot).and_return(false)\n      allow(communicator).to receive(:sudo)\n      allow(machine).to receive(:communicate).and_return(communicator)\n      allow(machine).to receive(:guest).and_return(guest)\n      allow(machine).to receive(:ui).and_return(ui)\n      allow(vsp).to receive(:with_script_file).and_yield(config.path)\n      allow(communicator).to receive(:upload).with(config.path, /arbitrary.ps1$/)\n    }\n\n    it \"should output all received output\" do\n      stdout = [\"two lines\\n\", \"from stdout\\n\"]\n      stderr = [\"one line\\n\", \"and partial from stderr\"]\n      expect(communicator).to receive(:sudo).\n        and_yield(:stdout, stdout.first).\n        and_yield(:stderr, stderr.first).\n        and_yield(:stderr, stderr.last).\n        and_yield(:stdout, stdout.last)\n      allow(ui).to receive(:detail)\n      expect(ui).to receive(:detail).with(\"two lines\", any_args)\n      expect(ui).to receive(:detail).with(\"from stdout\", any_args)\n      expect(ui).to receive(:detail).with(\"one line\", any_args)\n      expect(ui).to receive(:detail).with(\"and partial from stderr\", any_args)\n      vsp.send(:provision_winrm, \"\")\n    end\n\n    it \"ensures that files are uploaded with an extension\" do\n      allow(vsp).to receive(:with_script_file).and_yield(config.path)\n      expect(communicator).to receive(:upload).with(config.path, /arbitrary.ps1$/)\n      vsp.send(:provision_winrm, \"\")\n    end\n\n    context \"bat file being uploaded\" do\n      before do\n        allow(config).to receive(:path).and_return(\"script/info.bat\")\n        allow(vsp).to receive(:with_script_file).and_yield(config.path)\n      end\n\n      it \"ensures that files are uploaded same extension as provided path.bat\" do\n        expect(communicator).to receive(:upload).with(config.path, /arbitrary/)\n        expect(communicator).to receive(:sudo).with(/arbitrary.bat/, anything)\n        vsp.send(:provision_winrm, \"\")\n      end\n    end\n\n    context \"inline option set\" do\n      let(:config) {\n        double(\n          :config,\n          :args                            => \"doesn't matter\",\n          :env                             => {},\n          :remote?                         => false,\n          :inline                          => \"some commands\",\n          :upload_path                     => nil,\n          :path                            => nil,\n          :binary                          => false,\n          :md5                             => nil,\n          :sha1                            => 'EXPECTED_VALUE',\n          :sha256                          => nil,\n          :sha384                          => nil,\n          :sha512                          => nil,\n          :reset                           => false,\n          :reboot                          => false,\n          :powershell_args                 => \"\",\n          :name                            => nil,\n          :privileged                      => false,\n          :powershell_elevated_interactive => false\n        )\n      }\n\n      it \"creates an executable with an extension\" do\n        allow(machine).to receive_message_chain(:config, :winssh, :shell).and_return(nil)\n        allow(vsp).to receive(:with_script_file).and_yield(default_win_path)\n        allow(communicator).to receive(:upload).with(default_win_path, /vagrant-shell/)\n        expect(communicator).to receive(:sudo).with(/vagrant-shell.ps1/, anything)\n        vsp.send(:provision_winrm, \"\")\n      end\n    end\n  end\n\n  describe \"#provision_winssh\" do\n    let(:config) {\n      double(\n        :config,\n        :args                            => \"doesn't matter\",\n        :env                             => {},\n        :upload_path                     => \"arbitrary\",\n        :remote?                         => false,\n        :path                            => nil,\n        :inline                          => \"something\",\n        :binary                          => false,\n        :md5                             => nil,\n        :sha1                            => 'EXPECTED_VALUE',\n        :sha256                          => nil,\n        :sha384                          => nil,\n        :sha512                          => nil,\n        :reset                           => false,\n        :reboot                          => false,\n        :powershell_args                 => \"\",\n        :name                            => nil,\n        :privileged                      => false,\n        :powershell_elevated_interactive => false,\n        :keep_color                      => true,\n      )\n    }\n\n    let(:vsp) {\n      VagrantPlugins::Shell::Provisioner.new(machine, config)\n    }\n\n    let(:communicator) { double(\"communicator\") }\n    let(:guest) { double(\"guest\") }\n    let(:ui) { Vagrant::UI::Silent.new }\n\n    before {\n      allow(guest).to receive(:capability?).with(:wait_for_reboot).and_return(false)\n      allow(communicator).to receive(:sudo)\n      allow(communicator).to receive(:upload)\n      allow(communicator).to receive_message_chain(:machine_config_ssh, :shell)\n      allow(machine).to receive(:communicate).and_return(communicator)\n      allow(machine).to receive(:guest).and_return(guest)\n      allow(machine).to receive(:ui).and_return(ui)\n      allow(machine).to receive(:ssh_info).and_return(true)\n    }\n\n    context \"ps1 file being uploaded\" do\n      before do\n        allow(config).to receive(:path).and_return(\"script/info.ps1\")\n        allow(vsp).to receive(:with_script_file).and_yield(config.path)\n      end\n\n      it \"ensures that files are uploaded same extension as provided path.ps1\" do\n        allow(machine).to receive_message_chain(:config, :winssh, :shell).and_return(\"cmd\")\n        expect(communicator).to receive(:upload).with(config.path, /arbitrary.ps1/)\n        expect(communicator).to receive(:execute).with(/powershell.*arbitrary.ps1/, anything)\n        vsp.send(:provision_winssh, \"\")\n      end\n\n      it \"should output all received output\" do\n        stdout = [\"two lines\\n\", \"from stdout\\n\"]\n        stderr = [\"one line\\n\", \"and partial from stderr\"]\n        expect(communicator).to receive(:execute).\n          and_yield(:stdout, stdout.first).\n          and_yield(:stderr, stderr.first).\n          and_yield(:stderr, stderr.last).\n          and_yield(:stdout, stdout.last)\n        allow(ui).to receive(:detail)\n        expect(ui).to receive(:detail).with(\"two lines\", any_args)\n        expect(ui).to receive(:detail).with(\"from stdout\", any_args)\n        expect(ui).to receive(:detail).with(\"one line\", any_args)\n        expect(ui).to receive(:detail).with(\"and partial from stderr\", any_args)\n        vsp.send(:provision_winssh, \"\")\n      end\n    end\n\n    context \"bat file being uploaded\" do\n      before do\n        allow(config).to receive(:path).and_return(\"script/info.bat\")\n        allow(vsp).to receive(:with_script_file).and_yield(config.path)\n      end\n\n      it \"ensures that files are uploaded same extension as provided path.bat\" do\n        allow(machine).to receive_message_chain(:config, :winssh, :shell).and_return(\"cmd\")\n        expect(communicator).to receive(:upload).with(config.path, /arbitrary.bat/)\n        expect(communicator).to receive(:execute).with(/cmd.*arbitrary.bat/, anything)\n        vsp.send(:provision_winssh, \"\")\n      end\n    end\n\n    context \"with inline script\" do\n      before do\n        allow(vsp).to receive(:with_script_file).and_yield(\"/tmp/file/contents\")\n      end\n\n      context \"when upload path has a .ps1 extension\" do\n        before do\n          allow(config).to receive(:upload_path).and_return(\"c:/tmp/vagrant-shell.ps1\")\n        end\n\n        it \"executes the remote script with powershell\" do\n          expect(communicator).to receive(:upload).with(anything, config.upload_path)\n          expect(communicator).to receive(:execute).with(/powershell.*\\.ps1/, anything)\n          vsp.send(:provision_winssh, \"\")\n        end\n      end\n\n      context \"when upload path has a .bat extension\" do\n        before do\n          allow(config).to receive(:upload_path).and_return(\"c:/tmp/vagrant-shell.bat\")\n        end\n\n        it \"executes the remote script with cmd\" do\n          expect(communicator).to receive(:upload).with(anything, config.upload_path)\n          expect(communicator).to receive(:execute).with(/cmd.*\\.bat/, anything)\n          vsp.send(:provision_winssh, \"\")\n        end\n      end\n\n      context \"when upload path has no extension\" do\n        before do\n          allow(config).to receive(:upload_path).and_return(\"c:/tmp/vagrant-shell\")\n        end\n\n        context \"when winssh shell is cmd\" do\n          before do\n            allow(machine).to receive_message_chain(:config, :winssh, :shell).and_return(\"cmd\")\n          end\n\n          it \"adds an extension and executes the remote script with cmd\" do\n            expect(communicator).to receive(:upload).with(anything, /\\.bat$/)\n            expect(communicator).to receive(:execute).with(/cmd.*\\.bat/, anything)\n            vsp.send(:provision_winssh, \"\")\n          end\n        end\n\n        context \"when winssh shell is powershell\" do\n          before do\n            allow(machine).to receive_message_chain(:config, :winssh, :shell).and_return(\"powershell\")\n          end\n\n          it \"adds an extension executes the remote script with powershell\" do\n            expect(communicator).to receive(:upload).with(anything, /\\.ps1$/)\n            expect(communicator).to receive(:execute).with(/powershell.*\\.ps1/, anything)\n            vsp.send(:provision_winssh, \"\")\n          end\n        end\n      end\n    end\n  end\n\n  describe \"#handle_comm\" do\n    let(:ui) { Vagrant::UI::Silent.new }\n    let(:keep_color) { false }\n    let(:config) {\n      double(\n        :config,\n        :keep_color  => keep_color,\n      )\n    }\n    let(:env){ isolated_environment }\n    let(:machine) { double(:machine, env: env, id: \"ID\") }\n    let(:vsp) {\n      VagrantPlugins::Shell::Provisioner.new(machine, config)\n    }\n\n    before do\n      allow(machine).to receive(:ui).and_return(ui)\n    end\n\n    context \"when type is stdout\" do\n      let(:type) { :stdout }\n      let(:data) { \"output data\" }\n\n      it \"should output data through the ui\" do\n        expect(ui).to receive(:detail).and_call_original\n        vsp.send(:handle_comm, type, data)\n      end\n\n      it \"should color the output\" do\n        expect(ui).to receive(:detail).with(data, hash_including(color: :green)).\n          and_call_original\n        vsp.send(:handle_comm, type, data)\n      end\n\n      context \"when configured to keep color\" do\n        let(:keep_color) { true }\n\n        it \"should not color the output\" do\n          expect(ui).to receive(:detail) do |msg, **opts|\n            expect(msg).to eq(data)\n            expect(opts).to be_empty\n          end\n          vsp.send(:handle_comm, type, data)\n        end\n      end\n    end\n\n    context \"when type is stderr\" do\n      let(:type) { :stderr }\n      let(:data) { \"output data\" }\n\n      it \"should output data through the ui\" do\n        expect(ui).to receive(:detail).and_call_original\n        vsp.send(:handle_comm, type, data)\n      end\n\n      it \"should color the output\" do\n        expect(ui).to receive(:detail).with(data, hash_including(color: :red)).\n          and_call_original\n        vsp.send(:handle_comm, type, data)\n      end\n\n      context \"when configured to keep color\" do\n        let(:keep_color) { true }\n\n        it \"should not color the output\" do\n          expect(ui).to receive(:detail) do |msg, **opts|\n            expect(msg).to eq(data)\n            expect(opts).to be_empty\n          end\n          vsp.send(:handle_comm, type, data)\n        end\n      end\n    end\n\n    context \"when type is not stdout or stderr\" do\n      let(:type) { :stdnull }\n      let(:data) { \"output data\" }\n\n      it \"should not output data through the ui\" do\n        expect(ui).not_to receive(:detail)\n        vsp.send(:handle_comm, type, data)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/provisioners/support/shared/config.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\ndef get_provisioner_option_names(provisioner_class)\n  config_options = provisioner_class.instance_methods(true).find_all { |i| i.to_s.end_with?('=') }\n  config_options.map! { |i| i.to_s.sub('=', '') }\n  (config_options - [\"!\", \"=\", \"==\"]).sort\nend\n\nshared_examples_for 'any VagrantConfigProvisioner strict boolean attribute' do |attr_name, attr_default_value|\n\n  [true, false].each do |bool|\n    it \"returns the assigned boolean value (#{bool})\" do\n      subject.send(\"#{attr_name}=\", bool)\n      subject.finalize!\n\n      expect(subject.send(attr_name)).to eql(bool)\n    end\n  end\n\n  it \"returns the default value (#{attr_default_value}) if undefined\" do\n    subject.finalize!\n\n    expect(subject.send(attr_name)).to eql(attr_default_value)\n  end\n\n  [nil, 'true', 'false', 1, 0, 'this is not a boolean'].each do |nobool|\n    it \"returns the default value when assigned value is invalid (#{nobool.class}: #{nobool})\" do\n      subject.send(\"#{attr_name}=\", nobool)\n      subject.finalize!\n\n      expect(subject.send(attr_name)).to eql(attr_default_value)\n    end\n  end\n\nend\n\n"
  },
  {
    "path": "test/unit/plugins/pushes/atlas/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/pushes/atlas/config\")\n\ndescribe VagrantPlugins::AtlasPush::Config do\n  include_context \"unit\"\n\n  before(:all) do\n    I18n.load_path << Vagrant.source_root.join(\"plugins/pushes/atlas/locales/en.yml\")\n    I18n.reload!\n  end\n\n  let(:machine) { double(\"machine\") }\n\n  around(:each) do |example|\n    with_temp_env(\"ATLAS_TOKEN\" => nil) do\n      example.run\n    end\n  end\n\n  before do\n    subject.token = \"foo\"\n  end\n\n  describe \"#address\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.address).to be(nil)\n    end\n  end\n\n  describe \"#app\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.app).to be(nil)\n    end\n  end\n\n  describe \"#dir\" do\n    it \"defaults to .\" do\n      subject.finalize!\n      expect(subject.dir).to eq(\".\")\n    end\n  end\n\n  describe \"#vcs\" do\n    it \"defaults to true\" do\n      subject.finalize!\n      expect(subject.vcs).to be(true)\n    end\n  end\n\n  describe \"#uploader_path\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.uploader_path).to be(nil)\n    end\n  end\n\n  describe \"#validate\" do\n    before do\n      allow(machine).to receive(:env)\n        .and_return(double(\"env\",\n          root_path: \"\",\n          data_dir:  Pathname.new(\"\"),\n        ))\n\n      subject.app           = \"sethvargo/bacon\"\n      subject.dir           = \".\"\n      subject.vcs           = true\n      subject.uploader_path = \"uploader\"\n    end\n\n    let(:result) { subject.validate(machine) }\n    let(:errors) { result[\"Atlas push\"] }\n\n    context \"when the token is missing\" do\n      context \"when a vagrant-login token exists\" do\n        before do\n          allow(subject).to receive(:token_from_vagrant_login)\n            .and_return(\"token_from_vagrant_login\")\n        end\n\n        it \"uses the token from vagrant-login\" do\n          subject.token = \"\"\n          subject.finalize!\n          expect(errors).to be_empty\n          expect(subject.token).to eq(\"token_from_vagrant_login\")\n        end\n      end\n\n      context \"when a token is given in the Vagrantfile\" do\n        before do\n          allow(subject).to receive(:token_from_vagrant_login)\n            .and_return(\"token_from_vagrant_login\")\n        end\n\n        it \"uses the token in the Vagrantfile\" do\n          subject.token = \"token_from_vagrantfile\"\n          subject.finalize!\n          expect(errors).to be_empty\n          expect(subject.token).to eq(\"token_from_vagrantfile\")\n        end\n      end\n\n      context \"when a token is in the environment\" do\n        it \"uses the token in the Vagrantfile\" do\n          with_temp_env(\"ATLAS_TOKEN\" => \"foo\") do\n            subject.finalize!\n          end\n\n          expect(errors).to be_empty\n          expect(subject.token).to eq(\"foo\")\n        end\n      end\n\n      context \"when no token is given\" do\n        before do\n          allow(subject).to receive(:token_from_vagrant_login)\n            .and_return(nil)\n        end\n\n        it \"returns an error\" do\n          subject.token = \"\"\n          subject.finalize!\n          expect(errors).to include(I18n.t(\"atlas_push.errors.missing_token\"))\n        end\n      end\n    end\n\n    context \"when the app is missing\" do\n      it \"returns an error\" do\n        subject.app = \"\"\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"atlas_push.errors.missing_attribute\",\n          attribute: \"app\",\n        ))\n      end\n    end\n\n    context \"when the dir is missing\" do\n      it \"returns an error\" do\n        subject.dir = \"\"\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"atlas_push.errors.missing_attribute\",\n          attribute: \"dir\",\n        ))\n      end\n    end\n\n    context \"when the vcs is missing\" do\n      it \"does not return an error\" do\n        subject.vcs = \"\"\n        subject.finalize!\n        expect(errors).to be_empty\n      end\n    end\n\n    context \"when the uploader_path is missing\" do\n      it \"returns an error\" do\n        subject.uploader_path = \"\"\n        subject.finalize!\n        expect(errors).to be_empty\n      end\n    end\n  end\n\n  describe \"#merge\" do\n    context \"when includes are given\" do\n      let(:one) { described_class.new }\n      let(:two) { described_class.new }\n\n      it \"merges the result\" do\n        one.includes = %w(a b c)\n        two.includes = %w(c d e)\n        result = one.merge(two)\n        expect(result.includes).to eq(%w(a b c d e))\n      end\n    end\n\n    context \"when excludes are given\" do\n      let(:one) { described_class.new }\n      let(:two) { described_class.new }\n\n      it \"merges the result\" do\n        one.excludes = %w(a b c)\n        two.excludes = %w(c d e)\n        result = one.merge(two)\n        expect(result.excludes).to eq(%w(a b c d e))\n      end\n    end\n  end\n\n  describe \"#include\" do\n    it \"adds the item to the list\" do\n      subject.include(\"me\")\n      expect(subject.includes).to include(\"me\")\n    end\n  end\n\n  describe \"#exclude\" do\n    it \"adds the item to the list\" do\n      subject.exclude(\"not me\")\n      expect(subject.excludes).to include(\"not me\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/pushes/atlas/push_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/pushes/atlas/config\")\nrequire Vagrant.source_root.join(\"plugins/pushes/atlas/push\")\n\ndescribe VagrantPlugins::AtlasPush::Push do\n  include_context \"unit\"\n\n  let(:bin) { VagrantPlugins::AtlasPush::Push::UPLOADER_BIN }\n\n  let(:env) do\n    iso_env = isolated_environment\n    iso_env.vagrantfile(\"\")\n    iso_env.create_vagrant_env\n  end\n\n  let(:config) do\n    VagrantPlugins::AtlasPush::Config.new.tap do |c|\n      c.finalize!\n    end\n  end\n\n  subject { described_class.new(env, config) }\n\n  before do\n    # Stub this right away to avoid real execs\n    allow(Vagrant::Util::SafeExec).to receive(:exec)\n  end\n\n  # DEPRECATED\n  # describe \"#push\" do\n  #   it \"pushes with the uploader\" do\n  #     allow(subject).to receive(:uploader_path).and_return(\"foo\")\n\n  #     expect(subject).to receive(:execute).with(\"foo\")\n\n  #     subject.push\n  #   end\n\n  #   it \"raises an exception if the uploader couldn't be found\" do\n  #     expect(subject).to receive(:uploader_path).and_return(nil)\n\n  #     expect { subject.push }.to raise_error(\n  #       VagrantPlugins::AtlasPush::Errors::UploaderNotFound)\n  #   end\n  # end\n\n  # describe \"#execute\" do\n  #   let(:app) { \"foo/bar\" }\n\n  #   before do\n  #     config.app = app\n  #   end\n\n  #   it \"sends the basic flags\" do\n  #     expect(Vagrant::Util::SafeExec).to receive(:exec).\n  #       with(\"foo\", \"-vcs\", app, env.root_path.to_s)\n\n  #     subject.execute(\"foo\")\n  #   end\n\n  #   it \"doesn't send VCS if disabled\" do\n  #     expect(Vagrant::Util::SafeExec).to receive(:exec).\n  #       with(\"foo\", app, env.root_path.to_s)\n\n  #     config.vcs = false\n  #     subject.execute(\"foo\")\n  #   end\n\n  #   it \"sends includes\" do\n  #     expect(Vagrant::Util::SafeExec).to receive(:exec).\n  #       with(\"foo\", \"-vcs\", \"-include\", \"foo\", \"-include\",\n  #            \"bar\", app, env.root_path.to_s)\n\n  #     config.includes = [\"foo\", \"bar\"]\n  #     subject.execute(\"foo\")\n  #   end\n\n  #   it \"sends excludes\" do\n  #     expect(Vagrant::Util::SafeExec).to receive(:exec).\n  #       with(\"foo\", \"-vcs\", \"-exclude\", \"foo\", \"-exclude\",\n  #            \"bar\", app, env.root_path.to_s)\n\n  #     config.excludes = [\"foo\", \"bar\"]\n  #     subject.execute(\"foo\")\n  #   end\n\n  #   it \"sends custom server address\" do\n  #     expect(Vagrant::Util::SafeExec).to receive(:exec).\n  #       with(\"foo\", \"-vcs\", \"-address\", \"foo\", app, env.root_path.to_s)\n\n  #     config.address = \"foo\"\n  #     subject.execute(\"foo\")\n  #   end\n\n  #   it \"sends custom token\" do\n  #     expect(Vagrant::Util::SafeExec).to receive(:exec).\n  #       with(\"foo\", \"-vcs\", \"-token\", \"atlas_token\", app, env.root_path.to_s)\n\n  #     config.token = \"atlas_token\"\n  #     subject.execute(\"foo\")\n  #   end\n\n  #   context \"when metadata is available\" do\n  #     let(:env) do\n  #       iso_env = isolated_environment\n  #       iso_env.vagrantfile <<-EOH\n  #         Vagrant.configure(\"2\") do |config|\n  #           config.vm.box = \"hashicorp/precise64\"\n  #           config.vm.box_url = \"https://atlas.hashicorp.com/hashicorp/precise64\"\n  #         end\n  #       EOH\n  #       iso_env.create_vagrant_env\n  #     end\n\n  #     it \"sends the metadata\" do\n  #       expect(Vagrant::Util::SafeExec).to receive(:exec).\n  #         with(\"foo\", \"-vcs\", \"-metadata\", \"box=hashicorp/precise64\",\n  #           \"-metadata\", \"box_url=https://atlas.hashicorp.com/hashicorp/precise64\",\n  #           \"-token\", \"atlas_token\", app, env.root_path.to_s)\n\n  #       config.token = \"atlas_token\"\n  #       subject.execute(\"foo\")\n  #     end\n  #   end\n  # end\n\n  # describe \"#uploader_path\" do\n  #   let(:scratch) do\n  #     Pathname.new(Dir.mktmpdir(\"vagrant-test-atlas-push-upload-path\"))\n  #   end\n\n  #   after do\n  #     FileUtils.rm_rf(scratch)\n  #   end\n\n  #   it \"should return the configured path if set\" do\n  #     config.uploader_path = \"foo\"\n  #     expect(subject.uploader_path).to eq(\"foo\")\n  #   end\n\n  #   it \"should look up the uploader via PATH if not set\" do\n  #     allow(Vagrant).to receive(:in_installer?).and_return(false)\n\n  #     expect(Vagrant::Util::Which).to receive(:which).\n  #       with(described_class.const_get(:UPLOADER_BIN)).\n  #       and_return(\"bar\")\n\n  #     expect(subject.uploader_path).to eq(\"bar\")\n  #   end\n\n  #   it \"should look up the uploader in the embedded dir if installer\" do\n  #     allow(Vagrant).to receive(:in_installer?).and_return(true)\n  #     allow(Vagrant).to receive(:installer_embedded_dir).and_return(scratch.to_s)\n\n  #     bin_path = scratch.join(\"bin\", bin)\n  #     bin_path.dirname.mkpath\n  #     bin_path.open(\"w+\") { |f| f.write(\"hi\") }\n\n  #     expect(subject.uploader_path).to eq(bin_path.to_s)\n  #   end\n\n  #   it \"should look up the uploader in the PATH if not in the installer\" do\n  #     allow(Vagrant).to receive(:in_installer?).and_return(true)\n  #     allow(Vagrant).to receive(:installer_embedded_dir).and_return(scratch.to_s)\n\n  #     expect(Vagrant::Util::Which).to receive(:which).\n  #       with(described_class.const_get(:UPLOADER_BIN)).\n  #       and_return(\"bar\")\n\n  #     expect(subject.uploader_path).to eq(\"bar\")\n  #   end\n\n  #   it \"should return nil if its not found anywhere\" do\n  #     allow(Vagrant).to receive(:in_installer?).and_return(false)\n  #     allow(Vagrant::Util::Which).to receive(:which).and_return(nil)\n\n  #     expect(subject.uploader_path).to be_nil\n  #   end\n  # end\nend\n"
  },
  {
    "path": "test/unit/plugins/pushes/ftp/adapter_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\nrequire \"fake_ftp\"\n\nrequire Vagrant.source_root.join(\"plugins/pushes/ftp/adapter\")\n\ndescribe VagrantPlugins::FTPPush::Adapter do\n  include_context \"unit\"\n\n  subject do\n    described_class.new(\"127.0.0.1:2345\", \"sethvargo\", \"bacon\",\n      foo: \"bar\",\n    )\n  end\n\n  describe \"#initialize\" do\n    it \"sets the instance variables\" do\n      expect(subject.host).to eq(\"127.0.0.1\")\n      expect(subject.port).to eq(2345)\n      expect(subject.username).to eq(\"sethvargo\")\n      expect(subject.password).to eq(\"bacon\")\n      expect(subject.options).to eq(foo: \"bar\")\n      expect(subject.server).to be(nil)\n    end\n  end\n\n  describe \"#parse_host\" do\n    it \"has a default value\" do\n      allow(subject).to receive(:default_port)\n        .and_return(5555)\n\n      result = subject.parse_host(\"127.0.0.1\")\n      expect(result[0]).to eq(\"127.0.0.1\")\n      expect(result[1]).to eq(5555)\n    end\n  end\nend\n\ndescribe VagrantPlugins::FTPPush::FTPAdapter do\n  include_context \"unit\"\n\n  before(:all) do\n    @server = nil\n    with_random_port do |port1, port2|\n      @server = FakeFtp::Server.new(port1, port2)\n    end\n    @server.start\n  end\n\n  after(:all) { @server.stop }\n\n  let(:server) { @server }\n\n  before { server.reset }\n\n  subject do\n    described_class.new(\"127.0.0.1:#{server.port}\", \"sethvargo\", \"bacon\")\n  end\n\n  describe \"#default_port\" do\n    it \"is 21\" do\n      expect(subject.default_port).to eq(21)\n    end\n  end\n\n  describe \"#upload\" do\n    before do\n      @dir = Dir.mktmpdir(\"vagrant-ftp-push-adapter-upload\")\n      FileUtils.touch(\"#{@dir}/file\")\n    end\n\n    after do\n      FileUtils.rm_rf(@dir)\n    end\n\n    it \"uploads the file\" do\n      subject.connect do |ftp|\n        ftp.upload(\"#{@dir}/file\", \"/file\")\n      end\n\n      expect(server.files).to include(\"/file\")\n    end\n\n    it \"uploads in passive mode\" do\n      subject.options[:passive] = true\n      subject.connect do |ftp|\n        ftp.upload(\"#{@dir}/file\", \"/file\")\n      end\n\n      expect(server.file(\"/file\")).to be_passive\n    end\n  end\nend\n\ndescribe VagrantPlugins::FTPPush::SFTPAdapter do\n  include_context \"unit\"\n\n  subject do\n    described_class.new(\"127.0.0.1:2345\", \"sethvargo\", \"bacon\",\n      foo: \"bar\",\n    )\n  end\n\n  describe \"#default_port\" do\n    it \"is 22\" do\n      expect(subject.default_port).to eq(22)\n    end\n  end\n\n  describe \"#upload\" do\n    it \"uploads the file\" do\n      pending \"a way to mock an SFTP server\"\n      test_with_mock_sftp_server\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/pushes/ftp/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/pushes/ftp/config\")\n\ndescribe VagrantPlugins::FTPPush::Config do\n  include_context \"unit\"\n\n  before(:all) do\n    I18n.load_path << Vagrant.source_root.join(\"plugins/pushes/ftp/locales/en.yml\")\n    I18n.reload!\n  end\n\n  subject { described_class.new }\n\n  let(:machine) { double(\"machine\") }\n\n  describe \"#host\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.host).to be(nil)\n    end\n  end\n\n  describe \"#username\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.username).to be(nil)\n    end\n  end\n\n  describe \"#password\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.password).to be(nil)\n    end\n  end\n\n  describe \"#passive\" do\n    it \"defaults to true\" do\n      subject.finalize!\n      expect(subject.passive).to be(true)\n    end\n  end\n\n  describe \"#secure\" do\n    it \"defaults to false\" do\n      subject.finalize!\n      expect(subject.secure).to be(false)\n    end\n  end\n\n  describe \"#destination\" do\n    it \"defaults to /\" do\n      subject.finalize!\n      expect(subject.destination).to eq(\"/\")\n    end\n  end\n\n  describe \"#dir\" do\n    it \"defaults to .\" do\n      subject.finalize!\n      expect(subject.dir).to eq(\".\")\n    end\n  end\n\n  describe \"#merge\" do\n    context \"when includes are given\" do\n      let(:one) { described_class.new }\n      let(:two) { described_class.new }\n\n      it \"merges the result\" do\n        one.includes = %w(a b c)\n        two.includes = %w(c d e)\n        result = one.merge(two)\n        expect(result.includes).to eq(%w(a b c d e))\n      end\n    end\n\n    context \"when excludes are given\" do\n      let(:one) { described_class.new }\n      let(:two) { described_class.new }\n\n      it \"merges the result\" do\n        one.excludes = %w(a b c)\n        two.excludes = %w(c d e)\n        result = one.merge(two)\n        expect(result.excludes).to eq(%w(a b c d e))\n      end\n    end\n  end\n\n  describe \"#validate\" do\n    before do\n      allow(machine).to receive(:env)\n        .and_return(double(\"env\",\n          root_path: \"\",\n        ))\n\n      subject.host        = \"ftp.example.com\"\n      subject.username    = \"sethvargo\"\n      subject.password    = \"bacon\"\n      subject.destination = \"/\"\n      subject.dir         = \".\"\n    end\n\n    let(:result) { subject.validate(machine) }\n    let(:errors) { result[\"FTP push\"] }\n\n    context \"when the host is missing\" do\n      it \"returns an error\" do\n        subject.host = \"\"\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"ftp_push.errors.missing_attribute\",\n          attribute: \"host\",\n        ))\n      end\n    end\n\n    context \"when the username is missing\" do\n      it \"returns an error\" do\n        subject.username = \"\"\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"ftp_push.errors.missing_attribute\",\n          attribute: \"username\",\n        ))\n      end\n    end\n\n    context \"when the password is missing\" do\n      it \"does not return an error\" do\n        subject.password = \"\"\n        subject.finalize!\n        expect(errors).to be_empty\n      end\n    end\n\n    context \"when the destination is missing\" do\n      it \"returns an error\" do\n        subject.destination = \"\"\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"ftp_push.errors.missing_attribute\",\n          attribute: \"destination\",\n        ))\n      end\n    end\n\n    context \"when the dir is missing\" do\n      it \"returns an error\" do\n        subject.dir = \"\"\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"ftp_push.errors.missing_attribute\",\n          attribute: \"dir\",\n        ))\n      end\n    end\n  end\n\n  describe \"#include\" do\n    it \"adds the item to the list\" do\n      subject.include(\"me\")\n      expect(subject.includes).to include(\"me\")\n    end\n  end\n\n  describe \"#exclude\" do\n    it \"adds the item to the list\" do\n      subject.exclude(\"not me\")\n      expect(subject.excludes).to include(\"not me\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/pushes/ftp/push_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\nrequire \"fake_ftp\"\n\nrequire Vagrant.source_root.join(\"plugins/pushes/ftp/push\")\n\ndescribe VagrantPlugins::FTPPush::Push do\n  include_context \"unit\"\n\n  let(:env) { isolated_environment }\n  let(:config) do\n    double(\"config\",\n      host:        \"127.0.0.1:#{@port}\",\n      username:    \"sethvargo\",\n      password:    \"bacon\",\n      passive:     false,\n      secure:      false,\n      destination: \"/var/www/site\",\n    )\n  end\n  let(:ui) { Vagrant::UI::Silent.new }\n\n  subject { described_class.new(env, config) }\n\n  before do\n    allow(env).to receive(:root_path)\n      .and_return(File.expand_path(\"..\", __FILE__))\n    allow(env).to receive(:ui)\n      .and_return(ui)\n  end\n\n  describe \"#push\" do\n    before(:all) do\n      @server = nil\n      with_random_port do |port1, port2|\n        @port = port1\n        @server = FakeFtp::Server.new(port1, port2)\n      end\n      @server.start\n\n      @dir = Dir.mktmpdir(\"vagrant-ftp-push\")\n      FileUtils.touch(\"#{@dir}/.hidden.rb\")\n      FileUtils.touch(\"#{@dir}/application.rb\")\n      FileUtils.touch(\"#{@dir}/config.rb\")\n      FileUtils.touch(\"#{@dir}/Gemfile\")\n      FileUtils.touch(\"#{@dir}/data.txt\")\n      FileUtils.mkdir(\"#{@dir}/empty_folder\")\n    end\n\n    after(:all) do\n      FileUtils.rm_rf(@dir)\n      @server.stop\n    end\n\n    let(:server) { @server }\n\n    before do\n      allow(config).to receive(:dir)\n        .and_return(@dir)\n\n      allow(config).to receive(:includes)\n        .and_return([])\n\n      allow(config).to receive(:excludes)\n        .and_return(%w(*.rb))\n    end\n\n    after do\n      server.reset\n    end\n\n    it \"pushes the files to the server\" do\n      subject.push\n      expect(server.files).to eq(%w[/var/www/site/Gemfile /var/www/site/data.txt])\n    end\n\n    it \"raises informative exception when too many files to process\" do\n      expect(subject).to receive(:all_files).and_raise(SystemStackError)\n      expect{ subject.push }.to raise_error(VagrantPlugins::FTPPush::Errors::TooManyFiles)\n    end\n\n    context \"when VAGRANT_CWD is set to something relative\" do\n      # this will be the PWD for the test context\n      let(:pwd) { Pathname.new(Dir.mktmpdir(\"vagrant-ftp-push-pwd\")) }\n\n      before do\n        # this path should have a ../ in it since the pwd is another temp dir\n        # and measuring path from one to the other. they're most likely to be\n        # siblings\n        relative_path = Pathname.new(@dir).relative_path_from(pwd)\n        allow(env).to receive(:root_path) { relative_path.to_s }\n        # reset config.dir to its default since it was set absolute above\n        allow(config).to receive(:dir) { \".\" }\n      end\n\n      it \"properly paths out the files to upload\" do\n        Vagrant::Util::SafeChdir.safe_chdir(pwd) do\n          subject.push\n        end\n\n        expect(server.files).to eq(%w(/var/www/site/Gemfile /var/www/site/data.txt))\n      ensure\n        FileUtils.rm_rf(pwd)\n      end\n    end\n  end\n\n  describe \"#connect\" do\n    before do\n      allow_any_instance_of(VagrantPlugins::FTPPush::FTPAdapter)\n        .to receive(:connect)\n        .and_yield(:ftp)\n      allow_any_instance_of(VagrantPlugins::FTPPush::SFTPAdapter)\n        .to receive(:connect)\n        .and_yield(:sftp)\n    end\n\n    context \"when secure is requested\" do\n      before do\n        allow(config).to receive(:secure)\n          .and_return(true)\n      end\n\n      it \"yields a new SFTPAdapter\" do\n        expect { |b| subject.connect(&b) }.to yield_with_args(:sftp)\n      end\n    end\n\n    context \"when secure is not requested\" do\n      before do\n        allow(config).to receive(:secure)\n          .and_return(false)\n      end\n\n      it \"yields a new FTPAdapter\" do\n        expect { |b| subject.connect(&b) }.to yield_with_args(:ftp)\n      end\n    end\n  end\n\n  describe \"#all_files\" do\n    before(:all) do\n      @dir = Dir.mktmpdir(\"vagrant-ftp-push-push-all-files\")\n\n      FileUtils.touch(\"#{@dir}/.hidden.rb\")\n      FileUtils.touch(\"#{@dir}/application.rb\")\n      FileUtils.touch(\"#{@dir}/config.rb\")\n      FileUtils.touch(\"#{@dir}/Gemfile\")\n      FileUtils.mkdir(\"#{@dir}/empty_folder\")\n      FileUtils.mkdir(\"#{@dir}/folder\")\n      FileUtils.mkdir(\"#{@dir}/folder/.git\")\n      FileUtils.touch(\"#{@dir}/folder/.git/config\")\n      FileUtils.touch(\"#{@dir}/folder/server.rb\")\n    end\n\n    after(:all) do\n      FileUtils.rm_rf(@dir)\n    end\n\n    let(:files) do\n      subject.all_files.map do |file|\n        file.sub(\"#{@dir}/\", \"\")\n      end\n    end\n\n    before do\n      allow(config).to receive(:dir)\n        .and_return(@dir)\n\n      allow(config).to receive(:includes)\n        .and_return(%w(not_a_file.rb still_not_a_file.rb))\n\n      allow(config).to receive(:excludes)\n        .and_return(%w(*.rb))\n    end\n\n    it \"returns the list of real files + includes, without excludes\" do\n      expect(files).to eq(%w(\n        Gemfile\n        folder/.git/config\n      ))\n    end\n  end\n\n  describe \"#includes_files\" do\n    before(:all) do\n      @dir = Dir.mktmpdir(\"vagrant-ftp-push-includes-files\")\n\n      FileUtils.touch(\"#{@dir}/.hidden.rb\")\n      FileUtils.touch(\"#{@dir}/application.rb\")\n      FileUtils.touch(\"#{@dir}/config.rb\")\n      FileUtils.touch(\"#{@dir}/Gemfile\")\n      FileUtils.mkdir(\"#{@dir}/folder\")\n      FileUtils.mkdir(\"#{@dir}/folder/.git\")\n      FileUtils.touch(\"#{@dir}/folder/.git/config\")\n      FileUtils.touch(\"#{@dir}/folder/server.rb\")\n    end\n\n    after(:all) do\n      FileUtils.rm_rf(@dir)\n    end\n\n    let(:files) do\n      subject.includes_files.map do |file|\n        file.sub(\"#{@dir}/\", \"\")\n      end\n    end\n\n    before do\n      allow(config).to receive(:dir)\n        .and_return(@dir)\n    end\n\n    def set_includes(value)\n      allow(config).to receive(:includes)\n        .and_return(value)\n    end\n\n    it \"includes the file\" do\n      set_includes([\"Gemfile\"])\n      expect(files).to eq(%w(\n        Gemfile\n      ))\n    end\n\n    it \"includes the files that are subdirectories\" do\n      set_includes([\"folder\"])\n      expect(files).to eq(%w(\n        folder\n        folder/.git\n        folder/.git/config\n        folder/server.rb\n      ))\n    end\n\n    it \"includes files that match a pattern\" do\n      set_includes([\"*.rb\"])\n      expect(files).to eq(%w(\n        .hidden.rb\n        application.rb\n        config.rb\n      ))\n    end\n  end\n\n  describe \"#filter_excludes\" do\n    let(:dir) { \"/root/dir\" }\n\n    let(:list) do\n      %W(\n        #{dir}/.hidden.rb\n        #{dir}/application.rb\n        #{dir}/config.rb\n        #{dir}/Gemfile\n        #{dir}/folder\n        #{dir}/folder/.git\n        #{dir}/folder/.git/config\n        #{dir}/folder/server.rb\n\n        /path/outside/you.rb\n        /path/outside/me.rb\n        /path/outside/folder/bacon.rb\n      )\n    end\n\n    before do\n      allow(config).to receive(:dir)\n        .and_return(dir)\n    end\n\n    it \"excludes files\" do\n      subject.filter_excludes!(list, %w(*.rb))\n\n      expect(list).to eq(%W(\n        #{dir}/Gemfile\n        #{dir}/folder\n        #{dir}/folder/.git\n        #{dir}/folder/.git/config\n      ))\n    end\n\n    it \"excludes files in a directory\" do\n      subject.filter_excludes!(list, %w(folder))\n\n      expect(list).to eq(%W(\n        #{dir}/.hidden.rb\n        #{dir}/application.rb\n        #{dir}/config.rb\n        #{dir}/Gemfile\n\n        /path/outside/you.rb\n        /path/outside/me.rb\n        /path/outside/folder/bacon.rb\n      ))\n    end\n\n    it \"excludes specific files in a directory\" do\n      subject.filter_excludes!(list, %w(/path/outside/folder/*.rb))\n\n      expect(list).to eq(%W(\n        #{dir}/.hidden.rb\n        #{dir}/application.rb\n        #{dir}/config.rb\n        #{dir}/Gemfile\n        #{dir}/folder\n        #{dir}/folder/.git\n        #{dir}/folder/.git/config\n        #{dir}/folder/server.rb\n\n        /path/outside/you.rb\n        /path/outside/me.rb\n      ))\n    end\n\n    it \"excludes files outside the #dir\" do\n      subject.filter_excludes!(list, %w(/path/outside))\n\n      expect(list).to eq(%W(\n        #{dir}/.hidden.rb\n        #{dir}/application.rb\n        #{dir}/config.rb\n        #{dir}/Gemfile\n        #{dir}/folder\n        #{dir}/folder/.git\n        #{dir}/folder/.git/config\n        #{dir}/folder/server.rb\n      ))\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/pushes/heroku/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/pushes/heroku/config\")\n\ndescribe VagrantPlugins::HerokuPush::Config do\n  include_context \"unit\"\n\n  before(:all) do\n    I18n.load_path << Vagrant.source_root.join(\"plugins/pushes/heroku/locales/en.yml\")\n    I18n.reload!\n  end\n\n  subject { described_class.new }\n\n  let(:machine) { double(\"machine\") }\n\n  describe \"#app\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.app).to be(nil)\n    end\n  end\n\n  describe \"#dir\" do\n    it \"defaults to .\" do\n      subject.finalize!\n      expect(subject.dir).to eq(\".\")\n    end\n  end\n\n  describe \"#git_bin\" do\n    it \"defaults to git\" do\n      subject.finalize!\n      expect(subject.git_bin).to eq(\"git\")\n    end\n  end\n\n  describe \"#remote\" do\n    it \"defaults to git\" do\n      subject.finalize!\n      expect(subject.remote).to eq(\"heroku\")\n    end\n  end\n\n  describe \"#validate\" do\n    before do\n      allow(machine).to receive(:env)\n        .and_return(double(\"env\",\n          root_path: \"\",\n        ))\n\n      subject.app = \"bacon\"\n      subject.dir = \".\"\n      subject.git_bin = \"git\"\n      subject.remote = \"heroku\"\n    end\n\n    let(:result) { subject.validate(machine) }\n    let(:errors) { result[\"Heroku push\"] }\n\n    context \"when the app is missing\" do\n      it \"does not return an error\" do\n        subject.app = \"\"\n        subject.finalize!\n        expect(errors).to be_empty\n      end\n    end\n\n    context \"when the git_bin is missing\" do\n      it \"returns an error\" do\n        subject.git_bin = \"\"\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"heroku_push.errors.missing_attribute\",\n          attribute: \"git_bin\",\n        ))\n      end\n    end\n\n    context \"when the remote is missing\" do\n      it \"returns an error\" do\n        subject.remote = \"\"\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"heroku_push.errors.missing_attribute\",\n          attribute: \"remote\",\n        ))\n      end\n    end\n\n    context \"when the dir is missing\" do\n      it \"returns an error\" do\n        subject.dir = \"\"\n        subject.finalize!\n        expect(errors).to include(I18n.t(\"heroku_push.errors.missing_attribute\",\n          attribute: \"dir\",\n        ))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/pushes/heroku/push_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/platform\"\n\nrequire Vagrant.source_root.join(\"plugins/pushes/heroku/push\")\n\ndescribe VagrantPlugins::HerokuPush::Push do\n  include_context \"unit\"\n\n  before(:all) do\n    I18n.load_path << Vagrant.source_root.join(\"plugins/pushes/heroku/locales/en.yml\")\n    I18n.reload!\n  end\n\n  let(:env) { isolated_environment }\n  let(:config) do\n    double(\"config\",\n      app:     \"bacon\",\n      dir:     \"lib\",\n      git_bin: \"git\",\n      remote:  \"heroku\",\n    )\n  end\n\n  subject { described_class.new(env, config) }\n\n  describe \"#push\" do\n    let(:branch) { \"master\" }\n    let(:dir) { \"#{root_path}/#{config.dir}\" }\n\n    let(:root_path) do\n      next \"/handy/dandy\" if !Vagrant::Util::Platform.windows?\n      \"C:/handy/dandy\" \n    end\n\n    before do\n      allow(subject).to receive(:git_branch)\n        .and_return(branch)\n      allow(subject).to receive(:verify_git_bin!)\n      allow(subject).to receive(:verify_git_repo!)\n      allow(subject).to receive(:has_git_remote?)\n      allow(subject).to receive(:add_heroku_git_remote)\n      allow(subject).to receive(:git_push_heroku)\n      allow(subject).to receive(:execute!)\n\n      allow(env).to receive(:root_path)\n        .and_return(root_path)\n    end\n\n    it \"verifies the git bin is present\" do\n      expect(subject).to receive(:verify_git_bin!)\n        .with(config.git_bin)\n      subject.push\n    end\n\n    it \"verifies the directory is a git repo\" do\n      expect(subject).to receive(:verify_git_repo!)\n        .with(dir)\n      subject.push\n    end\n\n    context \"when the heroku remote exists\" do\n      before do\n        allow(subject).to receive(:has_git_remote?)\n          .and_return(true)\n      end\n\n      it \"does not add the heroku remote\" do\n        expect(subject).to_not receive(:add_heroku_git_remote)\n        subject.push\n      end\n    end\n\n    context \"when the heroku remote does not exist\" do\n      before do\n        allow(subject).to receive(:has_git_remote?)\n          .and_return(false)\n      end\n\n      it \"adds the heroku remote\" do\n        expect(subject).to receive(:add_heroku_git_remote)\n          .with(config.remote, config.app, dir)\n        subject.push\n      end\n    end\n\n    it \"pushes to heroku\" do\n      expect(subject).to receive(:git_push_heroku)\n        .with(config.remote, branch, dir)\n      subject.push\n    end\n  end\n\n  describe \"#verify_git_bin!\" do\n    context \"when git does not exist\" do\n      before do\n        allow(Vagrant::Util::Which).to receive(:which)\n          .with(\"git\")\n          .and_return(nil)\n      end\n\n      it \"raises an exception\" do\n        expect {\n          subject.verify_git_bin!(\"git\")\n        } .to raise_error(VagrantPlugins::HerokuPush::Errors::GitNotFound) { |error|\n          expect(error.message).to eq(I18n.t(\"heroku_push.errors.git_not_found\",\n            bin: \"git\",\n          ))\n        }\n      end\n    end\n\n    context \"when git exists\" do\n      before do\n        allow(Vagrant::Util::Which).to receive(:which)\n          .with(\"git\")\n          .and_return(\"git\")\n      end\n\n      it \"does not raise an exception\" do\n        expect { subject.verify_git_bin!(\"git\") }.to_not raise_error\n      end\n    end\n  end\n\n  describe \"#verify_git_repo!\" do\n    context \"when the path is a git repo\" do\n      before do\n        allow(File).to receive(:directory?)\n          .with(\"/repo/path/.git\")\n          .and_return(false)\n      end\n\n      it \"raises an exception\" do\n        expect {\n          subject.verify_git_repo!(\"/repo/path\")\n        } .to raise_error(VagrantPlugins::HerokuPush::Errors::NotAGitRepo) { |error|\n          expect(error.message).to eq(I18n.t(\"heroku_push.errors.not_a_git_repo\",\n            path: \"/repo/path\",\n          ))\n        }\n      end\n    end\n\n    context \"when the path is not a git repo\" do\n      before do\n        allow(File).to receive(:directory?)\n          .with(\"/repo/path/.git\")\n          .and_return(true)\n      end\n\n      it \"does not raise an exception\" do\n        expect { subject.verify_git_repo!(\"/repo/path\") }.to_not raise_error\n      end\n    end\n  end\n\n  describe \"#git_push_heroku\" do\n    let(:dir) { \".\" }\n\n    before { allow(subject).to receive(:execute!) }\n\n    it \"executes the proper command\" do\n      expect(subject).to receive(:execute!)\n        .with(\"git\",\n          \"--git-dir\", \"#{dir}/.git\",\n          \"--work-tree\", dir,\n          \"push\", \"bacon\", \"hamlet:master\",\n        )\n      subject.git_push_heroku(\"bacon\", \"hamlet\", dir)\n    end\n  end\n\n  describe \"#has_git_remote?\" do\n    let(:dir) { \".\" }\n\n    let(:process) do\n      double(\"process\",\n        stdout: \"origin\\r\\nbacon\\nhello\"\n      )\n    end\n\n    before do\n      allow(subject).to receive(:execute!)\n        .and_return(process)\n    end\n\n    it \"executes the proper command\" do\n      expect(subject).to receive(:execute!)\n        .with(\"git\",\n          \"--git-dir\", \"#{dir}/.git\",\n          \"--work-tree\", dir,\n          \"remote\",\n        )\n      subject.has_git_remote?(\"bacon\", dir)\n    end\n\n    it \"returns true when the remote exists\" do\n      expect(subject.has_git_remote?(\"origin\", dir)).to be(true)\n      expect(subject.has_git_remote?(\"bacon\", dir)).to be(true)\n      expect(subject.has_git_remote?(\"hello\", dir)).to be(true)\n    end\n\n    it \"returns false when the remote does not exist\" do\n      expect(subject.has_git_remote?(\"nope\", dir)).to be(false)\n    end\n  end\n\n  describe \"#add_heroku_git_remote\" do\n    let(:dir) { \".\" }\n\n    before do\n      allow(subject).to receive(:execute!)\n      allow(subject).to receive(:heroku_git_url)\n        .with(\"app\")\n        .and_return(\"HEROKU_URL\")\n    end\n\n    it \"executes the proper command\" do\n      expect(subject).to receive(:execute!)\n        .with(\"git\",\n          \"--git-dir\", \"#{dir}/.git\",\n          \"--work-tree\", dir,\n          \"remote\", \"add\", \"bacon\", \"HEROKU_URL\",\n        )\n      subject.add_heroku_git_remote(\"bacon\", \"app\", dir)\n    end\n  end\n\n  describe \"#interpret_app\" do\n    it \"returns the basename of the directory\" do\n      expect(subject.interpret_app(\"/foo/bar/blitz\")).to eq(\"blitz\")\n    end\n  end\n\n  describe \"#heroku_git_url\" do\n    it \"returns the proper string\" do\n      expect(subject.heroku_git_url(\"bacon\"))\n        .to eq(\"git@heroku.com:bacon.git\")\n    end\n  end\n\n  describe \"#git_dir\" do\n    it \"returns the .git directory for the path\" do\n      expect(subject.git_dir(\"/path\")).to eq(\"/path/.git\")\n    end\n  end\n\n  describe \"#git_branch\" do\n    let(:stdout) { \"\" }\n    let(:process) { double(\"process\", stdout: stdout) }\n\n    before do\n      allow(subject).to receive(:execute!)\n        .and_return(process)\n    end\n\n    let(:branch) { subject.git_branch(\"/path\") }\n\n    context \"when the branch is not prefixed\" do\n      let(:stdout) { \"bacon\" }\n\n      it \"returns the correct name\" do\n        expect(branch).to eq(\"bacon\")\n      end\n    end\n  end\n\n  describe \"#execute!\" do\n    let(:exit_code) { 0 }\n    let(:stdout) { \"This is the output\" }\n    let(:stderr) { \"This is the errput\" }\n\n    let(:process) do\n      double(\"process\",\n        exit_code: exit_code,\n        stdout:    stdout,\n        stderr:    stderr,\n      )\n    end\n\n    before do\n      allow(Vagrant::Util::Subprocess).to receive(:execute)\n        .and_return(process)\n    end\n\n    it \"creates a subprocess\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute)\n      expect { subject.execute! }.to_not raise_error\n    end\n\n    it \"returns the resulting process\" do\n      expect(subject.execute!).to be(process)\n    end\n\n    context \"when the exit code is non-zero\" do\n      let(:exit_code) { 1 }\n\n      it \"raises an exception\" do\n        klass = VagrantPlugins::HerokuPush::Errors::CommandFailed\n        cmd = [\"foo\", \"bar\"]\n\n        expect { subject.execute!(*cmd) }.to raise_error(klass) { |error|\n          expect(error.message).to eq(I18n.t(\"heroku_push.errors.command_failed\",\n            cmd:    cmd.join(\" \"),\n            stdout: stdout,\n            stderr: stderr,\n          ))\n        }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/pushes/local-exec/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/pushes/local-exec/config\")\n\ndescribe VagrantPlugins::LocalExecPush::Config do\n  include_context \"unit\"\n\n  before(:all) do\n    I18n.load_path << Vagrant.source_root.join(\"plugins/pushes/local-exec/locales/en.yml\")\n    I18n.reload!\n  end\n\n  let(:machine) { double(\"machine\") }\n\n  describe \"#script\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.script).to be(nil)\n    end\n  end\n\n  describe \"#inline\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.inline).to be(nil)\n    end\n  end\n\n  describe \"#args\" do\n    it \"defaults to nil\" do\n      subject.finalize!\n      expect(subject.args).to be(nil)\n    end\n  end\n\n  describe \"#validate\" do\n    before do\n      allow(machine).to receive(:env)\n        .and_return(double(\"env\",\n          root_path: \"\",\n        ))\n      subject.finalize!\n    end\n\n    let(:result) { subject.validate(machine) }\n    let(:errors) { result[\"Local Exec push\"] }\n\n    context \"when script is present\" do\n      before { subject.script = \"foo.sh\" }\n\n      context \"when inline is present\" do\n        before { subject.inline = \"echo\" }\n\n        it \"returns an error\" do\n          expect(errors).to include(\n            I18n.t(\"local_exec_push.errors.cannot_specify_script_and_inline\")\n          )\n        end\n      end\n\n      context \"when inline is not present\" do\n        before { subject.inline = \"\" }\n\n        it \"does not return an error\" do\n          expect(errors).to be_empty\n        end\n\n        it \"passes with string args\" do\n          subject.args = \"a string\"\n          expect(errors).to be_empty\n        end\n\n        it \"passes with integer args\" do\n          subject.args = 1\n          expect(errors).to be_empty\n        end\n\n        it \"passes with array args\" do\n          subject.args = [\"an\", \"array\"]\n          expect(errors).to be_empty\n        end\n\n        it \"returns an error if args is neither a string nor an array\" do\n          neither_array_nor_string = Object.new\n\n          subject.args = neither_array_nor_string\n          expect(errors).to include(\n            I18n.t(\"local_exec_push.errors.args_bad_type\")\n          )\n        end\n\n        it \"handles scalar array args\" do\n          subject.args = [\"string\", 1, 2]\n          expect(errors).to be_empty\n        end\n\n        it \"returns an error if args is an array with non-scalar types\" do\n          subject.args = [[1]]\n          expect(errors).to include(\n            I18n.t(\"local_exec_push.errors.args_bad_type\")\n          )\n        end\n      end\n    end\n\n    context \"when script is not present\" do\n      before { subject.script = \"\" }\n\n      context \"when inline is present\" do\n        before { subject.inline = \"echo\" }\n\n        it \"does not return an error\" do\n          expect(errors).to be_empty\n        end\n\n        it \"passes with string args\" do\n          subject.args = \"a string\"\n          expect(errors).to be_empty\n        end\n\n        it \"passes with integer args\" do\n          subject.args = 1\n          expect(errors).to be_empty\n        end\n\n        it \"passes with array args\" do\n          subject.args = [\"an\", \"array\"]\n          expect(errors).to be_empty\n        end\n\n        it \"returns an error if args is neither a string nor an array\" do\n          neither_array_nor_string = Object.new\n\n          subject.args = neither_array_nor_string\n          expect(errors).to include(\n            I18n.t(\"local_exec_push.errors.args_bad_type\")\n          )\n        end\n\n        it \"handles scalar array args\" do\n          subject.args = [\"string\", 1, 2]\n          expect(errors).to be_empty\n        end\n\n        it \"returns an error if args is an array with non-scalar types\" do\n          subject.args = [[1]]\n          expect(errors).to include(\n            I18n.t(\"local_exec_push.errors.args_bad_type\")\n          )\n        end\n      end\n\n      context \"when inline is not present\" do\n        before { subject.inline = \"\" }\n\n        it \"returns an error\" do\n          expect(errors).to include(I18n.t(\"local_exec_push.errors.missing_attribute\",\n            attribute: \"script\",\n          ))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/pushes/local-exec/push_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/pushes/local-exec/push\")\n\ndescribe VagrantPlugins::LocalExecPush::Push do\n  include_context \"unit\"\n\n  before(:all) do\n    I18n.load_path << Vagrant.source_root.join(\"plugins/pushes/local-exec/locales/en.yml\")\n    I18n.reload!\n  end\n\n  let(:env) { isolated_environment }\n  let(:config) do\n    double(\"config\",\n      script: nil,\n      inline: nil,\n      args: \"some args\",\n    )\n  end\n\n  subject { described_class.new(env, config) }\n\n  before do\n    allow(env).to receive(:root_path)\n      .and_return(File.expand_path(\"..\", __FILE__))\n  end\n\n  describe \"#push\" do\n    before do\n      allow(subject).to receive(:execute_inline!)\n      allow(subject).to receive(:execute_script!)\n      allow(subject).to receive(:execute!)\n    end\n\n    context \"when inline is given\" do\n      before { allow(config).to receive(:inline).and_return(\"echo\") }\n\n      it \"executes the inline script\" do\n        expect(subject).to receive(:execute_inline!)\n          .with(config.inline, config.args)\n        subject.push\n      end\n    end\n\n    context \"when script is given\" do\n      before { allow(config).to receive(:script).and_return(\"foo.sh\") }\n\n      it \"executes the script\" do\n        expect(subject).to receive(:execute_script!)\n          .with(config.script, config.args)\n        subject.push\n      end\n    end\n  end\n\n  describe \"#execute_inline!\" do\n    before { allow(subject).to receive(:execute_script!) }\n\n    it \"writes the script to a tempfile\" do\n      expect(Tempfile).to receive(:new).and_call_original\n      subject.execute_inline!(\"echo\", config.args)\n    end\n\n    it \"executes the script\" do\n      expect(subject).to receive(:execute_script!)\n      subject.execute_inline!(\"echo\", config.args)\n    end\n  end\n\n  describe \"#execute_script!\" do\n    before do\n      allow(subject).to receive(:execute!)\n      allow(FileUtils).to receive(:chmod)\n    end\n\n    it \"expands the path relative to the machine root\" do\n      expect(subject).to receive(:execute!)\n        .with(File.expand_path(\"foo.sh\", env.root_path))\n      subject.execute_script!(\"./foo.sh\", nil)\n    end\n\n    it \"makes the file executable\" do\n      expect(FileUtils).to receive(:chmod)\n        .with(\"+x\", File.expand_path(\"foo.sh\", env.root_path))\n      subject.execute_script!(\"./foo.sh\", config.args)\n    end\n\n    it \"calls execute!\" do\n      expect(subject).to receive(:execute!)\n        .with(File.expand_path(\"foo.sh\", env.root_path))\n      subject.execute_script!(\"./foo.sh\", nil)\n    end\n\n    context \"when args is given\" do\n      it \"passes string args to execute!\" do\n        expect(subject).to receive(:execute!)\n          .with(File.expand_path(\"foo.sh\", env.root_path) + \" \" + config.args)\n        subject.execute_script!(\"./foo.sh\", config.args)\n      end\n\n      it \"passes array args as string to execute!\" do\n        expect(subject).to receive(:execute!)\n          .with(File.expand_path(\"foo.sh\", env.root_path) + \" \\\"one\\\" \\\"two\\\" \\\"three\\\"\")\n        subject.execute_script!(\"./foo.sh\", [\"one\", \"two\", \"three\"])\n      end\n    end\n  end\n\n  describe \"#execute!\" do\n    it \"uses exec on unix\" do\n      allow(Vagrant::Util::Platform).to receive(:windows?).and_return(false)\n      expect(Vagrant::Util::SafeExec).to receive(:exec)\n      expect { subject.execute! }.to_not raise_error\n    end\n\n    it \"uses subprocess on windows\" do\n      allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n      result = double(\"result\", exit_code: 0)\n      expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(result)\n      expect { subject.execute! }.to raise_error { |e|\n        expect(e).to be_a(SystemExit)\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/pushes/noop/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/pushes/noop/config\")\n\ndescribe VagrantPlugins::NoopDeploy::Config do\n  include_context \"unit\"\n\n  subject { described_class.new }\n\n  let(:machine) { double(\"machine\") }\n\n  describe \"#validate\" do\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/synced_folders/nfs/action_cleanup_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/synced_folders/nfs/action_cleanup\")\n\ndescribe VagrantPlugins::SyncedFolderNFS::ActionCleanup do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:host)    { double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:app) { lambda {} }\n  let(:env) { {\n    machine: machine,\n  } }\n\n  subject { described_class.new(app, env) }\n\n  before do\n    allow(machine.env).to receive(:host).and_return(host)\n  end\n\n  it \"does nothing if there are no valid IDs\" do\n    expect(app).to receive(:call).with(env)\n    subject.call(env)\n  end\n\n  it \"does nothing if the host doesn't support pruning NFS\" do\n    allow(host).to receive(:capability?).with(:nfs_prune).and_return(false)\n    expect(host).to receive(:capability).never\n    expect(app).to receive(:call).with(env)\n\n    subject.call(env)\n  end\n\n  it \"prunes the NFS entries if valid IDs are given\" do\n    env[:nfs_valid_ids] = [1,2,3]\n\n    allow(host).to receive(:capability?).with(:nfs_prune).and_return(true)\n    expect(host).to receive(:capability).with(:nfs_prune, machine.ui, [1,2,3]).ordered\n    expect(app).to receive(:call).with(env).ordered\n\n    subject.call(env)\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/synced_folders/nfs/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/synced_folders/nfs/config\")\n\ndescribe VagrantPlugins::SyncedFolderNFS::Config do\n  subject { described_class.new }\n\n  context \"defaults\" do\n    before do\n      subject.finalize!\n    end\n\n    its(:functional) { should be(true) }\n    its(:map_gid) { should eq(:auto) }\n    its(:map_uid) { should eq(:auto) }\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/synced_folders/rsync/command/rsync_auto_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/synced_folders/rsync/command/rsync_auto\")\n\ndescribe VagrantPlugins::SyncedFolderRSync::Command::RsyncAuto do\n  include_context \"unit\"\n\n  let(:argv) { [] }\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:synced_folders_empty) { {} }\n  let(:synced_folders_dupe) { {\"1234\":\n    {type: \"rsync\",\n      exclude: false,\n      hostpath: \"/Users/brian/code/vagrant-sandbox\"},\n    \"5678\":\n    {type: \"rsync\",\n      exclude: false,\n      hostpath: \"/Not/The/Same/Path\"},\n    \"0912\":\n    {type: \"rsync\",\n      exclude: false,\n      hostpath: \"/Users/brian/code/relative-dir\"}}}\n\n  let(:helper_class) { VagrantPlugins::SyncedFolderRSync::RsyncHelper }\n\n  let(:paths) { {} }\n  let(:ssh_info) {{}}\n\n  def machine_stub(name)\n    double(name).tap do |m|\n      allow(m).to receive(:id).and_return(\"foo\")\n      allow(m).to receive(:reload).and_return(nil)\n      allow(m).to receive(:ssh_info).and_return(ssh_info)\n      allow(m).to receive(:ui).and_return(iso_env.ui)\n      allow(m).to receive(:provider).and_return(double(\"provider\"))\n      allow(m).to receive(:state).and_return(double(\"state\", id: :not_created))\n      allow(m).to receive(:env).and_return(iso_env)\n      allow(m).to receive(:config).and_return(double(\"config\"))\n    end\n  end\n\n  subject do\n    described_class.new(argv, iso_env).tap\n  end\n\n\n  describe \"#execute\" do\n    let (:machine) { machine_stub(\"m\") }\n    let (:cached_folders) { { rsync: synced_folders_dupe } }\n\n    # NOTE: `relative-dir` is not actually a \"relative dir\" in this data structure\n    # due to the fact that when vagrant stores synced folders, it path expands\n    # them with root_dir, and when you grab those synced_folders options from\n    # the machines config file, they end up being a full path rather than a\n    # relative path, and so these tests reflect that.\n    # For reference:\n    # https://github.com/hashicorp/vagrant/blob/9c1b014536e61b332cfaa00774a87a240cce8ed9/lib/vagrant/action/builtin/synced_folders.rb#L45-L46\n    let(:config_synced_folders)  { {\"/vagrant\":\n      {type: \"rsync\",\n        hostpath: \"/Users/brian/code/vagrant-sandbox\"},\n      \"/vagrant/other-dir\":\n      {type: \"rsync\",\n        hostpath: \"/Users/brian/code/vagrant-sandbox/other-dir\"},\n      \"/vagrant/relative-dir\":\n      {type: \"rsync\",\n        hostpath: \"/Users/brian/code/relative-dir\"}}}\n\n    before do\n      allow(subject).to receive(:with_target_vms) { |&block| block.call machine }\n      allow(machine.state).to receive(:id).and_return(:created)\n      allow(machine.env).to receive(:cwd).\n        and_return(\"/Users/brian/code/vagrant-sandbox\")\n      allow(machine.provider).to receive(:capability?).and_return(false)\n      allow(machine.config).to receive(:vm).and_return(double(\"vm\"))\n      allow(machine.config.vm).to receive(:synced_folders).and_return(config_synced_folders)\n\n      allow(subject).to receive(:synced_folders).\n        with(machine, cached: true).and_return(cached_folders)\n      allow(helper_class).to receive(:rsync_single).and_return(true)\n      allow(Vagrant::Util::Busy).to receive(:busy).and_return(true)\n      allow(Listen).to receive(:to).and_return(true)\n    end\n\n    it \"does not sync folders outside of the cwd\" do\n      allow(machine.ui).to receive(:info).and_call_original\n      expect(machine.ui).to receive(:info).\n        with(\"Not syncing /Not/The/Same/Path as it is not part of the current working directory.\").\n        and_call_original\n      expect(machine.ui).to receive(:info).\n        with(\"Watching: /Users/brian/code/vagrant-sandbox\").\n        and_call_original\n      expect(machine.ui).to receive(:info).\n        with(\"Watching: /Users/brian/code/relative-dir\").\n        and_call_original\n      expect(helper_class).to receive(:rsync_single)\n\n      expect(Listen).to receive(:to).\n        with(\"/Users/brian/code/vagrant-sandbox\",\n             \"/Users/brian/code/relative-dir\",\n             {:ignore=>[/.vagrant\\//],\n                        :force_polling=>false})\n      subject.execute\n    end\n\n    context \"with --rsync-chown option\" do\n      let(:argv) { [\"--rsync-chown\"] }\n\n      it \"should enable rsync_ownership on folder options\" do\n        expect(helper_class).to receive(:rsync_single).\n          with(anything, anything, hash_including(rsync_ownership: true))\n        subject.execute\n      end\n    end\n  end\n\n  subject do\n    described_class.new(argv, iso_env).tap do |s|\n      allow(s).to receive(:synced_folders).and_return(synced_folders_empty)\n    end\n  end\n\n  describe \"#callback\" do\n    it \"syncs modified folders to the proper path\" do\n      paths[\"/foo\"] = [\n        { machine: machine_stub(\"m1\"), opts: double(\"opts_m1\") },\n        { machine: machine_stub(\"m2\"), opts: double(\"opts_m2\") },\n      ]\n      paths[\"/bar\"] = [\n        { machine: machine_stub(\"m3\"), opts: double(\"opts_m3\") },\n      ]\n\n      paths[\"/foo\"].each do |data|\n        expect(helper_class).to receive(:rsync_single).\n          with(data[:machine], data[:machine].ssh_info, data[:opts]).\n          once\n      end\n\n      m = [\"/foo/bar\"]\n      a = []\n      r = []\n      subject.callback(paths, m, a, r)\n    end\n\n    it \"syncs added folders to the proper path\" do\n      paths[\"/foo\"] = [\n        { machine: machine_stub(\"m1\"), opts: double(\"opts_m1\") },\n        { machine: machine_stub(\"m2\"), opts: double(\"opts_m2\") },\n      ]\n      paths[\"/bar\"] = [\n        { machine: machine_stub(\"m3\"), opts: double(\"opts_m3\") },\n      ]\n\n      paths[\"/foo\"].each do |data|\n        expect(helper_class).to receive(:rsync_single).\n          with(data[:machine], data[:machine].ssh_info, data[:opts]).\n          once\n      end\n\n      m = []\n      a = [\"/foo/bar\"]\n      r = []\n      subject.callback(paths, m, a, r)\n    end\n\n    it \"syncs removed folders to the proper path\" do\n      paths[\"/foo\"] = [\n        { machine: machine_stub(\"m1\"), opts: double(\"opts_m1\") },\n        { machine: machine_stub(\"m2\"), opts: double(\"opts_m2\") },\n      ]\n      paths[\"/bar\"] = [\n        { machine: machine_stub(\"m3\"), opts: double(\"opts_m3\") },\n      ]\n\n      paths[\"/foo\"].each do |data|\n        expect(helper_class).to receive(:rsync_single).\n          with(data[:machine], data[:machine].ssh_info, data[:opts]).\n          once\n      end\n\n      m = []\n      a = []\n      r = [\"/foo/bar\"]\n      subject.callback(paths, m, a, r)\n    end\n\n    it \"doesn't fail if guest error occurs\" do\n      paths[\"/foo\"] = [\n        { machine: machine_stub(\"m1\"), opts: double(\"opts_m1\") },\n        { machine: machine_stub(\"m2\"), opts: double(\"opts_m2\") },\n      ]\n      paths[\"/bar\"] = [\n        { machine: machine_stub(\"m3\"), opts: double(\"opts_m3\") },\n      ]\n\n      paths[\"/foo\"].each do |data|\n        expect(helper_class).to receive(:rsync_single).\n          with(data[:machine], data[:machine].ssh_info, data[:opts]).\n          and_raise(Vagrant::Errors::MachineGuestNotReady)\n      end\n\n      m = []\n      a = []\n      r = [\"/foo/bar\"]\n      expect { subject.callback(paths, m, a, r) }.\n        to_not raise_error\n    end\n\n    it \"doesn't sync machines with no ID\" do\n      paths[\"/foo\"] = [\n        { machine: machine_stub(\"m1\"), opts: double(\"opts_m1\") },\n      ]\n\n      paths[\"/foo\"].each do |data|\n        allow(data[:machine]).to receive(:id).and_return(nil)\n        expect(helper_class).to_not receive(:rsync_single)\n      end\n\n      m = []\n      a = []\n      r = [\"/foo/bar\"]\n      expect { subject.callback(paths, m, a, r) }.\n        to_not raise_error\n    end\n\n    context \"on failure\" do\n      let(:machine) { machine_stub(\"m1\") }\n      let(:opts) { double(\"opts_m1\") }\n      let(:paths) { {\"/foo\" => [machine: machine, opts: opts]} }\n      let(:args) { [paths, [\"/foo/bar\"], [], []] }\n\n      before do\n        allow_any_instance_of(Vagrant::Errors::VagrantError).\n          to receive(:translate_error)\n      end\n\n      context \"when rsync command fails\" do\n        before do\n          expect(helper_class).to receive(:rsync_single).with(machine, machine.ssh_info, opts).\n            and_raise(Vagrant::Errors::RSyncError)\n        end\n\n        it \"should notify on error\" do\n          expect(machine.ui).to receive(:error).and_call_original\n          subject.callback(*args)\n        end\n\n        it \"should not raise error\" do\n          expect { subject.callback(*args) }.not_to raise_error\n        end\n      end\n\n      context \"when rsync post command capability fails\" do\n        before do\n          expect(helper_class).to receive(:rsync_single).with(machine, machine.ssh_info, opts).\n            and_raise(Vagrant::Errors::RSyncPostCommandError)\n        end\n\n        it \"should notify on error\" do\n          expect(machine.ui).to receive(:error).and_call_original\n          subject.callback(*args)\n        end\n\n        it \"should not raise error\" do\n          expect { subject.callback(*args) }.not_to raise_error\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/synced_folders/rsync/command/rsync_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/synced_folders/rsync/command/rsync\")\n\ndescribe VagrantPlugins::SyncedFolderRSync::Command::Rsync do\n  include_context \"unit\"\n\n  let(:argv) { [] }\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:communicator) { double(\"comm\") }\n\n  let(:synced_folders) { {} }\n\n  let(:helper_class) { VagrantPlugins::SyncedFolderRSync::RsyncHelper }\n\n  subject do\n    described_class.new(argv, iso_env).tap do |s|\n      allow(s).to receive(:synced_folders).and_return(synced_folders)\n    end\n  end\n\n  before do\n    iso_env.machine_names.each do |name|\n      m = iso_env.machine(name, iso_env.default_provider)\n      allow(m).to receive(:communicate).and_return(communicator)\n    end\n  end\n\n  describe \"#execute\" do\n    context \"with a single machine\" do\n      let(:ssh_info) {{\n        private_key_path: [],\n      }}\n\n      let(:machine) { iso_env.machine(iso_env.machine_names[0], iso_env.default_provider) }\n\n      before do\n        allow(communicator).to receive(:ready?).and_return(true)\n        allow(machine).to receive(:ssh_info).and_return(ssh_info)\n\n        synced_folders[:rsync] = [\n          [:one, {}],\n          [:two, {}],\n        ]\n      end\n\n      it \"doesn't sync if communicator isn't ready and exits with 1\" do\n        allow(communicator).to receive(:ready?).and_return(false)\n\n        expect(helper_class).to receive(:rsync_single).never\n\n        expect(subject.execute).to eql(1)\n      end\n\n      it \"rsyncs each folder and exits successfully\" do\n        synced_folders[:rsync].each do |_, opts|\n          expect(helper_class).to receive(:rsync_single).\n            with(machine, ssh_info, opts).\n            ordered\n        end\n\n        expect(subject.execute).to eql(0)\n      end\n\n      context \"with --rsync-chown option\" do\n        let(:argv) { [\"--rsync-chown\"] }\n\n        it \"should enable rsync_ownership on folder options\" do\n          synced_folders[:rsync].each do |_, opts|\n            expect(helper_class).to receive(:rsync_single).\n              with(machine, ssh_info, hash_including(rsync_ownership: true)).\n              ordered\n          end\n\n          subject.execute\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/synced_folders/rsync/default_unix_cap_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/synced_folders/rsync/default_unix_cap\")\n\ndescribe VagrantPlugins::SyncedFolderRSync::DefaultUnixCap do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:subject) { Class.new { extend VagrantPlugins::SyncedFolderRSync::DefaultUnixCap } }\n\n  describe \"#rsync_installed\" do\n    it \"tests if rsync is on the path\" do\n      expect(machine.communicate).to receive(:test).with(\"which rsync\").\n        and_return(true)\n\n      subject.rsync_installed(machine)\n    end\n  end\n\n\n  describe \"#rsync_command\" do\n    it \"returns the rsync command\" do\n      expect( subject.rsync_command(machine) ).to eq(\"sudo rsync\")\n    end\n  end\n\n  describe \"#rsync_post\" do\n    let(:opts) {{:type=>:rsync,\n                 :guestpath=>\"/vagrant\",\n                 :hostpath=>\"/home/user/syncfolder\",\n                 :disabled=>false,\n                 :__vagrantfile=>true,\n                 :exclude=>[\".vagrant\"],\n                 :owner=>\"vagrant\",\n                 :group=>\"vagrant\"}}\n\n    let(:cmd) { \"find /vagrant -path /vagrant/.vagrant -prune -o '!' -type l -a '(' ! -user vagrant -or ! -group vagrant ')' -exec chown vagrant:vagrant '{}' +\" }\n\n    it \"executes the rsync post command\" do\n      expect(machine.communicate).to receive(:sudo).\n        with(cmd)\n      subject.rsync_post(machine, opts)\n    end\n  end\n\n  describe \"#build_rsync_chown\" do\n    let(:opts) {{:type=>:rsync,\n                 :guestpath=>\"/vagrant\",\n                 :hostpath=>\"/home/user/syncfolder\",\n                 :disabled=>false,\n                 :__vagrantfile=>true,\n                 :exclude=>[\".vagrant\"],\n                 :owner=>\"vagrant\",\n                 :group=>\"vagrant\"}}\n\n    let(:cmd) { \"find /vagrant -path /vagrant/.vagrant -prune -o '!' -type l -a '(' ! -user vagrant -or ! -group vagrant ')' -exec chown vagrant:vagrant '{}' +\" }\n    let(:no_exclude_cmd) { \"find /vagrant '!' -type l -a '(' ! -user vagrant -or ! -group vagrant ')' -exec chown vagrant:vagrant '{}' +\" }\n\n    let(:empty_opts) {{:type=>:rsync,\n                 :guestpath=>\"/vagrant\",\n                 :hostpath=>\"/home/user/syncfolder\",\n                 :disabled=>false,\n                 :__vagrantfile=>true,\n                 :exclude=>[],\n                 :owner=>\"vagrant\",\n                 :group=>\"vagrant\"}}\n\n    it \"builds up a command to properly chown folders\" do\n      command = subject.build_rsync_chown(opts)\n      expect(command).to eq(cmd)\n    end\n\n    it \"does not include any excludes if the array is empty\" do\n      command = subject.build_rsync_chown(empty_opts)\n      expect(command).to eq(no_exclude_cmd)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/synced_folders/rsync/helper_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/platform\"\n\nrequire Vagrant.source_root.join(\"plugins/synced_folders/rsync/helper\")\n\ndescribe VagrantPlugins::SyncedFolderRSync::RsyncHelper do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  subject { described_class }\n\n  before do\n    allow(machine).to receive(:guest).and_return(guest)\n\n    # Don't do all the crazy Cygwin stuff\n    allow(Vagrant::Util::Platform).to receive(:cygwin_path) do |path, **opts|\n      path\n    end\n  end\n\n  describe \"#exclude_to_regexp\" do\n    let(:path) { \"/foo/bar\" }\n\n    it \"converts a directory match\" do\n      expected_regex = /foo\\/.*/\n      expect(described_class.exclude_to_regexp(\"foo/\")).\n        to eq(/foo\\/.*/)\n      expect(path).to match(expected_regex)\n    end\n\n    it \"converts the start anchor\" do\n      expected_regex = /^\\/foo\\//\n      expect(described_class.exclude_to_regexp(\"/foo\")).\n        to eq(expected_regex)\n      expect(path).to match(expected_regex)\n    end\n\n    it \"converts the **\" do\n      expected_regex = /fo.*o.*/\n      expect(described_class.exclude_to_regexp(\"fo**o\")).\n        to eq(expected_regex)\n      expect(path).to match(expected_regex)\n    end\n\n    it \"converts the *\" do\n      expected_regex = /fo*o.*/\n      expect(described_class.exclude_to_regexp(\"fo*o\")).\n        to eq(expected_regex)\n      expect(path).to match(expected_regex)\n    end\n  end\n\n  describe \"#rsync_single\" do\n    let(:result) { Vagrant::Util::Subprocess::Result.new(0, \"\", \"\") }\n\n    let(:ssh_info) {{\n      private_key_path: [],\n    }}\n    let(:opts)      {{\n      hostpath: \"/foo\",\n    }}\n    let(:ui)        { machine.ui }\n\n    before do\n      allow(Vagrant::Util::Subprocess).to receive(:execute){ result }\n\n      allow(guest).to receive(:capability?){ false }\n    end\n\n    it \"doesn't raise an error if it succeeds\" do\n      subject.rsync_single(machine, ssh_info, opts)\n    end\n\n    it \"doesn't call cygwin_path on non-Windows\" do\n      allow(Vagrant::Util::Platform).to receive(:windows?).and_return(false)\n      expect(Vagrant::Util::Platform).not_to receive(:cygwin_path)\n      subject.rsync_single(machine, ssh_info, opts)\n    end\n\n    it \"calls cygwin_path on Windows\" do\n      allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n      expect(Vagrant::Util::Platform).to receive(:cygwin_path).and_return(\"foo\")\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|\n        expect(args[args.length - 3]).to eql(\"foo/\")\n      }.and_return(result)\n\n      subject.rsync_single(machine, ssh_info, opts)\n    end\n\n    it \"raises an error if the exit code is non-zero\" do\n      allow(Vagrant::Util::Subprocess).to receive(:execute)\n        .and_return(Vagrant::Util::Subprocess::Result.new(1, \"\", \"\"))\n\n      expect {subject.rsync_single(machine, ssh_info, opts) }.\n        to raise_error(Vagrant::Errors::RSyncError)\n    end\n\n    context \"host and guest paths\" do\n      it \"syncs the hostpath to the guest path\" do\n        opts[:hostpath] = \"/foo\"\n        opts[:guestpath] = \"/bar\"\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|\n          expected = Vagrant::Util::Platform.fs_real_path(\"/foo\").to_s\n          expect(args[args.length - 3]).to eql(\"#{expected}/\")\n          expect(args[args.length - 2]).to include(\"/bar\")\n        }.and_return(result)\n\n        subject.rsync_single(machine, ssh_info, opts)\n      end\n\n      it \"expands the hostpath relative to the root path\" do\n        opts[:hostpath] = \"foo\"\n        opts[:guestpath] = \"/bar\"\n\n        hostpath_expanded = File.expand_path(opts[:hostpath], machine.env.root_path)\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|\n          expect(args[args.length - 3]).to eql(\"#{hostpath_expanded}/\")\n          expect(args[args.length - 2]).to include(\"/bar\")\n        }.and_return(result)\n\n        subject.rsync_single(machine, ssh_info, opts)\n      end\n    end\n\n    it \"executes within the root path\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|\n        expect(args.last).to be_kind_of(Hash)\n\n        opts = args.last\n        expect(opts[:workdir]).to eql(machine.env.root_path.to_s)\n      }.and_return(result)\n\n      subject.rsync_single(machine, ssh_info, opts)\n    end\n\n    it \"executes the rsync_pre capability first if it exists\" do\n      expect(guest).to receive(:capability?).with(:rsync_pre).and_return(true)\n      expect(guest).to receive(:capability).with(:rsync_pre, opts).ordered\n      expect(Vagrant::Util::Subprocess).to receive(:execute).ordered.and_return(result)\n\n      subject.rsync_single(machine, ssh_info, opts)\n    end\n\n    it \"executes the rsync_post capability after if it exists\" do\n      expect(guest).to receive(:capability?).with(:rsync_post).and_return(true)\n      expect(Vagrant::Util::Subprocess).to receive(:execute).ordered.and_return(result)\n      expect(guest).to receive(:capability).with(:rsync_post, opts).ordered\n\n      subject.rsync_single(machine, ssh_info, opts)\n    end\n\n    context \"with rsync_post capability\" do\n      before do\n        allow(guest).to receive(:capability?).with(:rsync_post).and_return(true)\n        allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(result)\n      end\n\n      it \"should raise custom error when capability errors\" do\n        expect(guest).to receive(:capability).with(:rsync_post, opts).\n          and_raise(Vagrant::Errors::VagrantError)\n\n        expect { subject.rsync_single(machine, ssh_info, opts) }.\n          to raise_error(Vagrant::Errors::RSyncPostCommandError)\n      end\n\n      it \"should populate :owner and :group from ssh_info[:username] when values are nil\" do\n        opts[:owner] = nil\n        opts[:group] = nil\n        ssh_info[:username] = \"userfromssh\"\n\n        expect(guest).to receive(:capability).with(:rsync_post, a_hash_including(\n          owner: \"userfromssh\",\n          group: \"userfromssh\",\n        ))\n\n        subject.rsync_single(machine, ssh_info, opts)\n      end\n    end\n\n    context \"with rsync_ownership option\" do\n      let(:rsync_local_version) { \"3.1.1\" }\n      let(:rsync_remote_version) { \"3.1.1\" }\n      let(:rsync_result) { Vagrant::Util::Subprocess::Result.new(0, \"\", \"\") }\n\n      before do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).\n          with(\"rsync\", \"--version\").and_return(Vagrant::Util::Subprocess::Result.new(0, \" version #{rsync_local_version} \", \"\"))\n        allow(machine.communicate).to receive(:execute).with(/--version/).and_yield(:stdout, \" version #{rsync_remote_version} \")\n        allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"rsync\", any_args).and_return(rsync_result)\n        opts[:rsync_ownership] = true\n      end\n\n      after { subject.reset! }\n\n      it \"should use the rsync --chown flag\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|\n          expect(args.detect{|a| a.include?(\"--chown\")}).to be_truthy\n          rsync_result\n        }\n        subject.rsync_single(machine, ssh_info, opts)\n      end\n\n      it \"should set the chown option to false\" do\n        expect(opts.has_key?(:chown)).to eq(false)\n        subject.rsync_single(machine, ssh_info, opts)\n        expect(opts[:chown]).to eq(false)\n      end\n\n      context \"when local rsync version does not support --chown\" do\n        let(:rsync_local_version) { \"2.0\" }\n\n        it \"should not use the --chown flag\" do\n          expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|\n            expect(args.detect{|a| a.include?(\"--chown\")}).to be_falsey\n            rsync_result\n          }\n          subject.rsync_single(machine, ssh_info, opts)\n        end\n      end\n\n      context \"when remote rsync version does not support --chown\" do\n        let(:rsync_remote_version) { \"2.0\" }\n\n        it \"should not use the --chown flag\" do\n          expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|\n            expect(args.detect{|a| a.include?(\"--chown\")}).to be_falsey\n            rsync_result\n          }\n          subject.rsync_single(machine, ssh_info, opts)\n        end\n      end\n    end\n\n    context \"excluding files\" do\n      it \"excludes files if given as a string\" do\n        opts[:exclude] = \"foo\"\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|\n          index = args.find_index(\"foo\")\n          expect(index).to be > 0\n          expect(args[index-1]).to eql(\"--exclude\")\n        }.and_return(result)\n\n        subject.rsync_single(machine, ssh_info, opts)\n      end\n\n      it \"excludes multiple files\" do\n        opts[:exclude] = [\"foo\", \"bar\"]\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|\n          index = args.find_index(\"foo\")\n          expect(index).to be > 0\n          expect(args[index-1]).to eql(\"--exclude\")\n\n          index = args.find_index(\"bar\")\n          expect(index).to be > 0\n          expect(args[index-1]).to eql(\"--exclude\")\n        }.and_return(result)\n\n        subject.rsync_single(machine, ssh_info, opts)\n      end\n    end\n\n    context \"custom arguments\" do\n      it \"uses the default arguments if not given\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|\n          expect(args[1]).to eq(\"--verbose\")\n          expect(args[2]).to eq(\"--archive\")\n          expect(args[3]).to eq(\"--delete\")\n\n          expected = Vagrant::Util::Platform.fs_real_path(\"/foo\").to_s\n          expect(args[args.length - 3]).to eql(\"#{expected}/\")\n        }.and_return(result)\n\n        subject.rsync_single(machine, ssh_info, opts)\n      end\n\n      it \"uses the custom arguments if given\" do\n        opts[:args] = [\"--verbose\", \"-z\"]\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|\n          expect(args[1]).to eq(\"--verbose\")\n          expect(args[2]).to eq(\"-z\")\n\n          expected = Vagrant::Util::Platform.fs_real_path(\"/foo\").to_s\n          expect(args[args.length - 3]).to eql(\"#{expected}/\")\n        }.and_return(result)\n\n        subject.rsync_single(machine, ssh_info, opts)\n      end\n    end\n\n    context \"control sockets\" do\n      it \"creates a tmp dir\" do\n        allow(Vagrant::Util::Platform).to receive(:windows?).and_return(false)\n        allow(Dir).to receive(:mktmpdir).with(\"vagrant-rsync-\").\n          and_return(\"/tmp/vagrant-rsync-12345\")\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|\n          expect(args[9]).to include(\"ControlPath=/tmp/vagrant-rsync-12345\")\n        }.and_return(result)\n\n        expect(FileUtils).to receive(:remove_entry_secure).with(\"/tmp/vagrant-rsync-12345\", true).and_return(true)\n        subject.rsync_single(machine, ssh_info, opts)\n      end\n\n      it \"does not create tmp dir on windows platforms\" do\n        allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n        allow(Dir).to receive(:mktmpdir).with(\"vagrant-rsync-\").\n          and_return(\"/tmp/vagrant-rsync-12345\")\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|\n          expect(args).not_to include(\"ControlPath=/tmp/vagrant-rsync-12345\")\n        }.and_return(result)\n\n        expect(FileUtils).not_to receive(:remove_entry_secure).with(\"/tmp/vagrant-rsync-12345\", true)\n        subject.rsync_single(machine, ssh_info, opts)\n      end\n    end\n  end\n\n  describe \"#rsync_single with custom ssh_info\" do\n    let(:result) { Vagrant::Util::Subprocess::Result.new(0, \"\", \"\") }\n\n    let(:ssh_info) {{\n      :private_key_path => ['/path/to/key'],\n      :keys_only        => true,\n      :verify_host_key  => false,\n    }}\n    let(:opts)      {{\n      hostpath: \"/foo\",\n    }}\n    let(:ui)        { machine.ui }\n\n    before do\n      allow(Vagrant::Util::Subprocess).to receive(:execute){ result }\n\n      allow(guest).to receive(:capability?){ false }\n    end\n\n    context \"with extra args defined\" do\n      before { ssh_info[:extra_args] = [\"-o\", \"Compression=yes\"] }\n\n      it \"appends the extra arguments from ssh_info\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|\n          cmd = args.detect { |a| a.is_a?(String) && a.start_with?(\"ssh\") }\n          expect(cmd).to be\n          expect(cmd).to include(\"-o Compression=yes\")\n        }.and_return(result)\n        subject.rsync_single(machine, ssh_info, opts)\n      end\n    end\n\n    context \"with an IPv6 address\" do\n      before { ssh_info[:host] = \"fe00::0\" }\n\n      it \"formats the address correctly\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args, \"@[#{ssh_info[:host]}]:''\", instance_of(Hash))\n        subject.rsync_single(machine, ssh_info, opts)\n      end\n    end\n\n    context \"with an IPv4 address\" do\n      before { ssh_info[:host] = \"127.0.0.1\" }\n\n      it \"formats the address correctly\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args, \"@#{ssh_info[:host]}:''\", instance_of(Hash))\n        subject.rsync_single(machine, ssh_info, opts)\n      end\n    end\n\n    it \"includes IdentitiesOnly, StrictHostKeyChecking, and UserKnownHostsFile with defaults\" do\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|\n        expect(args[9]).to include('IdentitiesOnly')\n        expect(args[9]).to include('StrictHostKeyChecking')\n        expect(args[9]).to include('UserKnownHostsFile')\n        expect(args[9]).to include(\"-i '/path/to/key'\")\n      }.and_return(result)\n\n      subject.rsync_single(machine, ssh_info, opts)\n    end\n\n    it \"includes StrictHostKeyChecking, and UserKnownHostsFile when verify_host_key is false\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|\n        expect(args[9]).to include('StrictHostKeyChecking')\n        expect(args[9]).to include('UserKnownHostsFile')\n      }.and_return(result)\n\n      subject.rsync_single(machine, ssh_info, opts)\n    end\n\n    it \"includes StrictHostKeyChecking, and UserKnownHostsFile when verify_host_key is :never\" do\n      ssh_info[:verify_host_key] = :never\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|\n        expect(args[9]).to include('StrictHostKeyChecking')\n        expect(args[9]).to include('UserKnownHostsFile')\n      }.and_return(result)\n\n      subject.rsync_single(machine, ssh_info, opts)\n    end\n\n    it \"omits IdentitiesOnly with keys_only = false\" do\n      ssh_info[:keys_only] = false\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|\n        expect(args[9]).not_to include('IdentitiesOnly')\n        result\n      end\n\n      subject.rsync_single(machine, ssh_info, opts)\n    end\n\n    it \"omits StrictHostKeyChecking and UserKnownHostsFile with paranoid = true\" do\n      ssh_info[:keys_only] = false\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|\n        expect(args[9]).not_to include('StrictHostKeyChecking ')\n        expect(args[9]).not_to include('UserKnownHostsFile ')\n        result\n      end\n\n      subject.rsync_single(machine, ssh_info, opts)\n    end\n\n    it \"includes custom ssh config when set\" do\n      ssh_info[:config] = \"/path/to/ssh/config\"\n      expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|\n        ssh_config_args = \"-F /path/to/ssh/config\"\n        expect(args.any?{|a| a.include?(ssh_config_args)}).to be_truthy\n        result\n      end\n      subject.rsync_single(machine, ssh_info, opts)\n    end\n  end\n\n  describe \".rsync_chown_support?\" do\n    let(:local_version) { \"3.1.1\" }\n    let(:remote_version) { \"3.1.1\" }\n\n    before do\n      allow(subject).to receive(:local_rsync_version).and_return(local_version)\n      allow(subject).to receive(:machine_rsync_version).and_return(remote_version)\n    end\n\n    it \"should return when local and remote versions support chown\" do\n      expect(subject.rsync_chown_support?(machine)).to be_truthy\n    end\n\n    context \"when local version does not support chown\" do\n      let(:local_version) { \"2.0\" }\n\n      it \"should return false\" do\n        expect(subject.rsync_chown_support?(machine)).to be_falsey\n      end\n    end\n\n    context \"when remote version does not support chown\" do\n      let(:remote_version) { \"2.0\" }\n\n      it \"should return false\" do\n        expect(subject.rsync_chown_support?(machine)).to be_falsey\n      end\n    end\n\n    context \"when both local and remote versions do not support chown\" do\n      let(:local_version) { \"2.0\" }\n      let(:remote_version) { \"2.0\" }\n\n      it \"should return false\" do\n        expect(subject.rsync_chown_support?(machine)).to be_falsey\n      end\n    end\n  end\n\n  describe \".machine_rsync_version\" do\n    let(:version_output) {\n      <<-EOV\n      rsync  version 3.1.3  protocol version 31\n      Copyright (C) 1996-2018 by Andrew Tridgell, Wayne Davison, and others.\n      Web site: http://rsync.samba.org/\n      Capabilities:\n      64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints,\n      socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace,\n      append, ACLs, xattrs, iconv, symtimes, prealloc\n\n      rsync comes with ABSOLUTELY NO WARRANTY.  This is free software, and you\n      are welcome to redistribute it under certain conditions.  See the GNU\n      General Public Licence for details.\n      EOV\n    }\n\n    before do\n      allow(machine.communicate).to receive(:execute).with(/--version/).\n        and_yield(:stdout, version_output)\n      allow(guest).to receive(:capability?).and_return(false)\n    end\n\n    it \"should extract the version string\" do\n      expect(subject.machine_rsync_version(machine)).to eq(\"3.1.3\")\n    end\n\n    context \"when version output is an unknown format\" do\n      let(:version_output) { \"unknown\" }\n\n      it \"should return nil value\" do\n        expect(subject.machine_rsync_version(machine)).to be_nil\n      end\n    end\n\n    context \"with guest rsync_command capability\" do\n      let(:rsync_path) { \"custom_rsync\" }\n\n      before do\n        allow(guest).to receive(:capability?).with(:rsync_command).\n          and_return(true)\n        allow(guest).to receive(:capability).with(:rsync_command).\n          and_return(rsync_path)\n      end\n\n      it \"should use custom rsync_path\" do\n        expect(machine.communicate).to receive(:execute).\n          with(\"#{rsync_path} --version\").and_yield(:stdout, version_output)\n        subject.machine_rsync_version(machine)\n      end\n    end\n  end\n\n  describe \".local_rsync_version\" do\n    let(:version_output) {\n      <<-EOV\n      rsync  version 3.1.3  protocol version 31\n      Copyright (C) 1996-2018 by Andrew Tridgell, Wayne Davison, and others.\n      Web site: http://rsync.samba.org/\n      Capabilities:\n      64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints,\n      socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace,\n      append, ACLs, xattrs, iconv, symtimes, prealloc\n\n      rsync comes with ABSOLUTELY NO WARRANTY.  This is free software, and you\n      are welcome to redistribute it under certain conditions.  See the GNU\n      General Public Licence for details.\n      EOV\n    }\n    let(:result) { Vagrant::Util::Subprocess::Result.new(0, version_output, \"\") }\n\n    before do\n      allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"rsync\", \"--version\").\n        and_return(result)\n    end\n\n    after { subject.reset! }\n\n    it \"should extract the version string\" do\n      expect(subject.local_rsync_version).to eq(\"3.1.3\")\n    end\n\n    it \"should cache the version lookup\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"rsync\", \"--version\").\n        and_return(result).once\n      expect(subject.local_rsync_version).to eq(\"3.1.3\")\n      expect(subject.local_rsync_version).to eq(\"3.1.3\")\n    end\n\n    context \"when version output is an unknown format\" do\n      let(:version_output) { \"unknown\" }\n\n      it \"should return nil value\" do\n        expect(subject.local_rsync_version).to be_nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/synced_folders/rsync/synced_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/synced_folders/rsync/synced_folder\")\n\ndescribe VagrantPlugins::SyncedFolderRSync::SyncedFolder do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest)   { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n\n  let(:helper_class) { VagrantPlugins::SyncedFolderRSync::RsyncHelper }\n\n  before do\n    allow(machine.env).to receive(:host).and_return(host)\n    allow(machine).to receive(:guest).and_return(guest)\n  end\n\n  describe \"#usable?\" do\n    it \"is usable if rsync can be found\" do\n      expect(Vagrant::Util::Which).to receive(:which).with(\"rsync\").and_return(true)\n      expect(subject.usable?(machine)).to be(true)\n    end\n\n    it \"is not usable if rsync cant be found\" do\n      expect(Vagrant::Util::Which).to receive(:which).with(\"rsync\").and_return(false)\n      expect(subject.usable?(machine)).to be(false)\n    end\n\n    it \"raises an exception if asked to\" do\n      expect(Vagrant::Util::Which).to receive(:which).with(\"rsync\").and_return(false)\n      expect { subject.usable?(machine, true) }.\n        to raise_error(Vagrant::Errors::RSyncNotFound)\n    end\n  end\n\n  describe \"#enable\" do\n    let(:ssh_info) {{\n      private_key_path: [],\n    }}\n\n    before do\n      allow(machine).to receive(:ssh_info).and_return(ssh_info)\n      allow(guest).to receive(:capability?).with(:rsync_installed)\n    end\n\n    it \"rsyncs each folder\" do\n      folders = [\n        [:one, {}],\n        [:two, {}],\n      ]\n\n      folders.each do |_, opts|\n        expect(helper_class).to receive(:rsync_single).\n          with(machine, ssh_info, opts).\n          ordered\n      end\n\n      subject.enable(machine, folders, {})\n    end\n\n    it \"installs rsync if capable\" do\n      folders = [ [:foo, {}] ]\n\n      allow(helper_class).to receive(:rsync_single)\n\n      allow(guest).to receive(:capability?).with(:rsync_installed).and_return(true)\n      allow(guest).to receive(:capability?).with(:rsync_install).and_return(true)\n\n      expect(guest).to receive(:capability).with(:rsync_installed).and_return(false)\n      expect(guest).to receive(:capability).with(:rsync_install)\n\n      subject.enable(machine, folders, {})\n    end\n\n    it \"errors if rsync not installable\" do\n      folders = [ [:foo, {}] ]\n\n      allow(helper_class).to receive(:rsync_single)\n\n      allow(guest).to receive(:capability?).with(:rsync_installed).and_return(true)\n      allow(guest).to receive(:capability?).with(:rsync_install).and_return(false)\n\n      expect(guest).to receive(:capability).with(:rsync_installed).and_return(false)\n\n      expect { subject.enable(machine, folders, {}) }.\n        to raise_error(Vagrant::Errors::RSyncNotInstalledInGuest)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/synced_folders/smb/caps/mount_options_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire_relative \"../../../../../../plugins/synced_folders/smb/cap/mount_options\"\n\ndescribe VagrantPlugins::SyncedFolderSMB::Cap::MountOptions do\n\n  let(:caps) do\n    VagrantPlugins::SyncedFolderSMB::Plugin\n      .components\n      .synced_folder_capabilities[:smb]\n  end\n  let(:cap){ caps.get(:mount_options) }\n\n  let(:dummy_smb_host) { \"my.smb.host\" }\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:env)    { double(\"env\") }\n  let(:guest) { double(\"guest\") }\n  let(:host)    { double(\"host\") }\n  let(:mount_owner){ \"vagrant\" }\n  let(:mount_group){ \"vagrant\" }\n  let(:mount_uid){ \"1000\" }\n  let(:mount_gid){ \"1000\" }\n  let(:mount_name){ \"vagrant\" }\n  let(:mount_guest_path){ \"/vagrant\" }\n  let(:folder_options) do\n    {\n      owner: mount_owner,\n      group: mount_group,\n      hostpath: \"/host/directory/path\"\n    }\n  end\n\n  before do\n    allow(guest).to receive(:capability).with(:choose_addressable_ip_addr, any_args).and_return(dummy_smb_host)\n    allow(host).to receive(:capability).with(:configured_ip_addresses)\n    allow(env).to receive(:host).and_return(host)\n    allow(machine).to receive(:env).and_return(env)\n    allow(machine).to receive(:communicate).and_return(comm)\n    allow(machine).to receive(:guest).and_return(guest)\n    allow(machine).to receive_message_chain(:env, :host, :capability?).with(:smb_mount_options).and_return(false)\n    allow(ENV).to receive(:[]).with(\"VAGRANT_DISABLE_SMBMFSYMLINKS\").and_return(true)\n    allow(ENV).to receive(:[]).with(\"GEM_SKIP\").and_return(false)\n  end\n\n  describe \".mount_name\" do\n    it \"generates the mount name when smb_host and smb_id are set\" do\n      folder_options[:smb_host] = \"smb_host\"\n      folder_options[:smb_id] = \"smbid\"\n      mn = cap.mount_name(machine, \"\", folder_options)\n      expect(mn).to eq(\"//smb_host/smbid\")\n    end\n\n    it \"generates the mount name when smb_host is not set\" do\n      folder_options[:smb_id] = \"smbid\"\n      mn = cap.mount_name(machine, \"\", folder_options)\n      expect(mn).to eq(\"//#{dummy_smb_host}/smbid\")\n    end\n  end\n\n  describe \".mount_options\" do\n    context \"with valid existent owner group\" do\n\n      before do\n        expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n        expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_yield(:stdout, \"vagrant:x:#{mount_gid}:\")\n      end\n\n      it \"generates the expected default mount command\" do\n        out_opts, out_uid, out_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)\n        expect(out_opts).to eq(\"sec=ntlmssp,credentials=/etc/smb_creds_vagrant,uid=1000,gid=1000,_netdev\")\n        expect(out_uid).to eq(mount_uid)\n        expect(out_gid).to eq(mount_gid)\n      end\n      \n      it \"includes provided mount options\" do\n        folder_options[:mount_options] =[\"ro\"]\n        out_opts, out_uid, out_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)\n        expect(out_opts).to eq(\"sec=ntlmssp,credentials=/etc/smb_creds_vagrant,uid=1000,gid=1000,_netdev,ro\")\n        expect(out_uid).to eq(mount_uid)\n        expect(out_gid).to eq(mount_gid)\n      end\n\n      it \"overwrites default mount options\" do\n        folder_options[:mount_options] =[\"ro\", \"sec=custom\"]\n        out_opts, out_uid, out_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)\n        expect(out_opts).to eq(\"sec=custom,credentials=/etc/smb_creds_vagrant,uid=1000,gid=1000,_netdev,ro\")\n        expect(out_uid).to eq(mount_uid)\n        expect(out_gid).to eq(mount_gid)\n      end\n\n      it \"does not add mfsymlinks option if env var VAGRANT_DISABLE_SMBMFSYMLINKS exists\" do\n        expect(ENV).to receive(:[]).with(\"VAGRANT_DISABLE_SMBMFSYMLINKS\").and_return(false)\n        out_opts, out_uid, out_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)\n        expect(out_opts).to eq(\"sec=ntlmssp,credentials=/etc/smb_creds_vagrant,uid=1000,gid=1000,mfsymlinks,_netdev\")\n        expect(out_uid).to eq(mount_uid)\n        expect(out_gid).to eq(mount_gid)\n      end\n    end\n\n    context \"with non-existent owner group\" do\n      it \"raises an error\" do\n        expect(comm).to receive(:execute).with(\"id -u #{mount_owner}\", anything).and_yield(:stdout, mount_uid)\n        expect(comm).to receive(:execute).with(\"id -g #{mount_group}\", anything).and_yield(:stdout, mount_gid)\n        expect(comm).to receive(:execute).with(\"getent group #{mount_group}\", anything).and_raise(Vagrant::Errors::VirtualBoxMountFailed, {command: '', output: ''})\n        expect do\n          cap.mount_options(machine, mount_name, mount_guest_path, folder_options)\n        end.to raise_error Vagrant::Errors::VirtualBoxMountFailed\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/synced_folders/smb/synced_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/synced_folders/smb/synced_folder\")\n\ndescribe VagrantPlugins::SyncedFolderSMB::SyncedFolder do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    env = isolated_environment\n    env.vagrantfile(\"\")\n    env.create_vagrant_env\n  end\n\n  let(:guest){ double(\"guest\") }\n  let(:host){ double(\"host\") }\n  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }\n  let(:host_caps){ [] }\n  let(:guest_caps){ [] }\n  let(:folders){ {\"/first/path\" => {}, \"/second/path\" => {}} }\n  let(:options){ {} }\n\n  before do\n    allow(machine.env).to receive(:host).and_return(host)\n    allow(machine).to receive(:guest).and_return(guest)\n    allow(machine).to receive(:ssh_info).and_return(username: 'sshuser')\n    allow(guest).to receive(:name).and_return(\"guest_name\")\n    allow(host).to receive(:capability?).and_return(false)\n    host_caps.each do |cap|\n      allow(host).to receive(:capability?).with(cap).and_return(true)\n      allow(host).to receive(:capability).with(cap, any_args).and_return(true)\n    end\n    allow(guest).to receive(:capability?).and_return(false)\n    guest_caps.each do |cap|\n      allow(guest).to receive(:capability?).with(cap).and_return(true)\n      allow(guest).to receive(:capability).with(cap, any_args).and_return(true)\n    end\n  end\n\n  describe \"#usable?\" do\n    context \"without supporting capabilities\" do\n      it \"is not usable\" do\n        expect(subject.usable?(machine)).to be(false)\n      end\n\n      it \"raises exception when raise_error enabled\" do\n        expect{subject.usable?(machine, true)}.to raise_error(\n          VagrantPlugins::SyncedFolderSMB::Errors::SMBNotSupported)\n      end\n    end\n\n    context \"with smb not installed\" do\n      let(:host_caps){ [:smb_installed] }\n\n      it \"is not usable\" do\n        expect(host).to receive(:capability).with(:smb_installed).and_return(false)\n        expect(subject.usable?(machine)).to be(false)\n      end\n    end\n\n    context \"with smb installed\" do\n      let(:host_caps){ [:smb_installed] }\n\n      it \"is usable\" do\n        expect(subject.usable?(machine)).to be(true)\n      end\n    end\n  end\n\n  describe \"#prepare\" do\n    let(:host_caps){ [:smb_start, :smb_prepare] }\n\n    context \"with username credentials provided\" do\n      let(:folders){ {'/first/path' => {smb_username: 'smbuser'}} }\n\n      it \"should prompt for credentials\" do\n        expect(machine.env.ui).to receive(:ask).with(/name/, any_args).and_return('username').at_least(1)\n        expect(machine.env.ui).to receive(:ask).with(/word/, any_args).and_return('password').at_least(1)\n\n        subject.prepare(machine, folders, options)\n      end\n\n      it \"should set credential information into all folder options and override username\" do\n        expect(machine.env.ui).to receive(:ask).with(/name/, any_args).and_return('username').at_least(1)\n        expect(machine.env.ui).to receive(:ask).with(/word/, any_args).and_return('password').at_least(1)\n\n        subject.prepare(machine, folders, options)\n        expect(folders['/first/path'][:smb_username]).to eq('username')\n        expect(folders['/first/path'][:smb_password]).to eq('password')\n      end\n\n\n      it \"will use configured default with no input\" do\n        expect(machine.env.ui).to receive(:ask).with(/name/, any_args).and_return('').at_least(1)\n        expect(machine.env.ui).to receive(:ask).with(/word/, any_args).and_return('password').at_least(1)\n\n        subject.prepare(machine, folders, options)\n        expect(folders['/first/path'][:smb_username]).to eq('smbuser')\n        expect(folders['/first/path'][:smb_password]).to eq('password')\n      end\n    end\n\n    context \"without credentials provided\" do\n      before do\n        expect(machine.env.ui).to receive(:ask).with(/name/, any_args).and_return('username').at_least(1)\n        expect(machine.env.ui).to receive(:ask).with(/word/, any_args).and_return('password').at_least(1)\n      end\n\n      it \"should prompt for credentials\" do\n        subject.prepare(machine, folders, options)\n      end\n\n      it \"should set credential information into all folder options\" do\n        subject.prepare(machine, folders, options)\n        expect(folders['/first/path'][:smb_username]).to eq('username')\n        expect(folders['/first/path'][:smb_password]).to eq('password')\n        expect(folders['/second/path'][:smb_username]).to eq('username')\n        expect(folders['/second/path'][:smb_password]).to eq('password')\n      end\n\n      it \"should start the SMB service if capability is available\" do\n        expect(host).to receive(:capability).with(:smb_start, any_args)\n        subject.prepare(machine, folders, options)\n      end\n\n      context \"with host smb_validate_password capability\" do\n        let(:host_caps){ [:smb_start, :smb_prepare, :smb_validate_password] }\n\n        it \"should validate the password\" do\n          expect(host).to receive(:capability).with(:smb_validate_password, machine, 'username', 'password').and_return(true)\n          subject.prepare(machine, folders, options)\n        end\n\n        it \"should retry when validation fails\" do\n          expect(host).to receive(:capability).with(:smb_validate_password, machine, 'username', 'password').and_return(false)\n          expect(host).to receive(:capability).with(:smb_validate_password, machine, 'username', 'password').and_return(true)\n          subject.prepare(machine, folders, options)\n        end\n\n        it \"should raise an error if it exceeds the maximum number of retries\" do\n          expect(host).to receive(:capability).with(:smb_validate_password, machine, 'username', 'password').and_return(false).\n            exactly(VagrantPlugins::SyncedFolderSMB::SyncedFolder::CREDENTIAL_RETRY_MAX).times\n          expect{ subject.prepare(machine, folders, options) }.to raise_error(VagrantPlugins::SyncedFolderSMB::Errors::CredentialsRequestError)\n        end\n      end\n    end\n\n    context \"with credentials provided\" do\n      context \"in single share entry\" do\n        let(:folders){ {'/first/path' => {}, '/second/path' => {smb_username: 'smbuser', smb_password: 'smbpass'}} }\n\n        it \"should not prompt for credentials\" do\n          expect(machine.env.ui).not_to receive(:ask)\n          subject.prepare(machine, folders, options)\n        end\n\n        it \"should add existing credentials to folder options without\" do\n          subject.prepare(machine, folders, options)\n          expect(folders['/first/path'][:smb_username]).to eq('smbuser')\n          expect(folders['/first/path'][:smb_password]).to eq('smbpass')\n        end\n      end\n\n      context \"in both entries\" do\n        let(:folders){ {'/first/path' => {smb_username: 'user', smb_password: 'pass'},\n          '/second/path' => {smb_username: 'smbuser', smb_password: 'smbpass'}} }\n\n        it \"should not modify existing credentials\" do\n          subject.prepare(machine, folders, options)\n          expect(folders['/first/path'][:smb_username]).to eq('user')\n          expect(folders['/first/path'][:smb_password]).to eq('pass')\n          expect(folders['/second/path'][:smb_username]).to eq('smbuser')\n          expect(folders['/second/path'][:smb_password]).to eq('smbpass')\n        end\n\n        it \"should register passwords with scrubber\" do\n          expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with('pass')\n          expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with('smbpass')\n          subject.prepare(machine, folders, options)\n        end\n      end\n    end\n  end\n\n  describe \"#enable\" do\n    it \"fails when guest does not support capability\" do\n      expect{\n        subject.enable(machine, folders, options)\n      }.to raise_error(Vagrant::Errors::GuestCapabilityNotFound)\n    end\n\n    context \"with guest capability supported\" do\n      let(:guest_caps){ [:mount_smb_shared_folder, :choose_addressable_ip_addr] }\n      let(:host_caps){ [:configured_ip_addresses] }\n\n      it \"should attempt to install smb on guest\" do\n        expect(guest).to receive(:capability?).with(:smb_install).and_return(true)\n        expect(guest).to receive(:capability).with(:smb_install, any_args)\n        subject.enable(machine, folders, options)\n      end\n\n      it \"should request host IP addresses\" do\n        expect(host).to receive(:capability).with(:configured_ip_addresses)\n        subject.enable(machine, folders, options)\n      end\n\n      it \"should determine guest accessible address\" do\n        expect(guest).to receive(:capability).with(:choose_addressable_ip_addr, any_args)\n        subject.enable(machine, folders, options)\n      end\n\n      it \"should error if no guest accessible address is available\" do\n        expect(guest).to receive(:capability).with(:choose_addressable_ip_addr, any_args).and_return(nil)\n        expect{ subject.enable(machine, folders, options) }.to raise_error(\n          VagrantPlugins::SyncedFolderSMB::Errors::NoHostIPAddr)\n      end\n\n      it \"should default owner and group to ssh username\" do\n        subject.enable(machine, folders, options)\n        expect(folders[\"/first/path\"][:owner]).to eq(\"sshuser\")\n        expect(folders[\"/first/path\"][:group]).to eq(\"sshuser\")\n        expect(folders[\"/second/path\"][:owner]).to eq(\"sshuser\")\n        expect(folders[\"/second/path\"][:group]).to eq(\"sshuser\")\n      end\n\n      it \"should set the host address in folder options\" do\n        expect(guest).to receive(:capability).with(:choose_addressable_ip_addr, any_args).and_return(\"ADDR\")\n        subject.enable(machine, folders, options)\n        expect(folders[\"/first/path\"][:smb_host]).to eq(\"ADDR\")\n        expect(folders[\"/second/path\"][:smb_host]).to eq(\"ADDR\")\n      end\n\n      it \"should scrub folder configuration\" do\n        expect(subject).to receive(:clean_folder_configuration).at_least(:once)\n        subject.enable(machine, folders, options)\n      end\n\n      context \"with smb_host option set\" do\n        let(:folders){ {\"/first/path\" => {smb_host: \"ADDR\"}, \"/second/path\" => {}} }\n\n        it \"should not update the value\" do\n          expect(guest).to receive(:capability).with(:choose_addressable_ip_addr, any_args).and_return(\"OTHER\")\n          subject.enable(machine, folders, options)\n          expect(folders[\"/first/path\"][:smb_host]).to eq(\"ADDR\")\n          expect(folders[\"/second/path\"][:smb_host]).to eq(\"OTHER\")\n        end\n      end\n\n      context \"with owner and group set\" do\n        let(:folders){ {\"/first/path\" => {owner: \"smbowner\"}, \"/second/path\" => {group: \"smbgroup\"}} }\n\n        it \"should not update set owner or group\" do\n          subject.enable(machine, folders, options)\n          expect(folders[\"/first/path\"][:owner]).to eq(\"smbowner\")\n          expect(folders[\"/first/path\"][:group]).to eq(\"sshuser\")\n          expect(folders[\"/second/path\"][:owner]).to eq(\"sshuser\")\n          expect(folders[\"/second/path\"][:group]).to eq(\"smbgroup\")\n        end\n      end\n\n      context \"with smb_username and smb_password set\" do\n        let(:folders){ {\n          \"/first/path\" => {owner: \"smbowner\", smb_username: \"user\", smb_password: \"pass\"},\n          \"/second/path\" => {group: \"smbgroup\", smb_username: \"user\", smb_password: \"pass\"}\n        } }\n\n        it \"should retain non password configuration options\" do\n          subject.enable(machine, folders, options)\n          folder1 = folders[\"/first/path\"]\n          folder2 = folders[\"/second/path\"]\n          expect(folder1.key?(:owner)).to be_truthy\n          expect(folder1.key?(:smb_username)).to be_truthy\n          expect(folder2.key?(:group)).to be_truthy\n          expect(folder2.key?(:smb_username)).to be_truthy\n        end\n\n        it \"should remove the smb_password option when set\" do\n          subject.enable(machine, folders, options)\n          expect(folders[\"/first/path\"].key?(:smb_password)).to be_falsey\n          expect(folders[\"/second/path\"].key?(:smb_password)).to be_falsey\n        end\n      end\n    end\n  end\n\n  describe \"#disable\" do\n    it \"should scrub folder configuration\" do\n      expect(subject).to receive(:clean_folder_configuration).at_least(:once)\n      subject.disable(machine, folders, options)\n    end\n\n    context \"with smb_username and smb_password set\" do\n      let(:folders){ {\n        \"/first/path\" => {owner: \"smbowner\", smb_username: \"user\", smb_password: \"pass\"},\n        \"/second/path\" => {group: \"smbgroup\", smb_username: \"user\", smb_password: \"pass\"}\n      } }\n\n      it \"should retain non password configuration options\" do\n        subject.disable(machine, folders, options)\n        folder1 = folders[\"/first/path\"]\n        folder2 = folders[\"/second/path\"]\n        expect(folder1.key?(:owner)).to be_truthy\n        expect(folder1.key?(:smb_username)).to be_truthy\n        expect(folder2.key?(:group)).to be_truthy\n        expect(folder2.key?(:smb_username)).to be_truthy\n      end\n\n      it \"should remove the smb_password option when set\" do\n        subject.disable(machine, folders, options)\n        expect(folders[\"/first/path\"].key?(:smb_password)).to be_falsey\n        expect(folders[\"/second/path\"].key?(:smb_password)).to be_falsey\n      end\n    end\n  end\n\n  describe \"#cleanup\" do\n    context \"without supporting capability\" do\n      it \"does nothing\" do\n        subject.cleanup(machine, options)\n      end\n    end\n\n    context \"with supporting capability\" do\n      let(:host_caps){ [:smb_cleanup] }\n\n      it \"runs cleanup\" do\n        expect(host).to receive(:capability).with(:smb_cleanup, any_args)\n        subject.cleanup(machine, options)\n      end\n    end\n  end\n\n  describe \"#clean_folder_configuration\" do\n    it \"should remove smb_password if defined\" do\n      data = {smb_password: \"password\"}\n      subject.send(:clean_folder_configuration, data)\n      expect(data.key?(:smb_password)).to be_falsey\n    end\n\n    it \"should not error if non-hash value provided\" do\n      expect { subject.send(:clean_folder_configuration, nil) }.\n        not_to raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/plugins/synced_folders/unix_mount_helpers_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../base\"\n\nrequire Vagrant.source_root.join(\"plugins/synced_folders/unix_mount_helpers\")\n\ndescribe VagrantPlugins::SyncedFolder::UnixMountHelpers do\n  include_context \"unit\"\n\n  subject{\n    Class.new do |c|\n      def self.name; \"UnixMountHelpersTest\"; end\n      extend VagrantPlugins::SyncedFolder::UnixMountHelpers\n    end\n  }\n\n\n  describe \".merge_mount_options\" do\n    let(:base){ [\"opt1\", \"opt2=on\", \"opt3\", \"opt4,opt5=off\"] }\n    let(:override){ [\"opt8\", \"opt4=on,opt6,opt7=true\"] }\n\n    context \"with no override\" do\n      it \"should split options into individual options\" do\n        result = subject.merge_mount_options(base, [])\n        expect(result.size).to eq(5)\n      end\n    end\n\n    context \"with overrides\" do\n      it \"should merge all options\" do\n        result = subject.merge_mount_options(base, override)\n        expect(result.size).to eq(8)\n      end\n\n      it \"should override options defined in base\" do\n        result = subject.merge_mount_options(base, override)\n        expect(result).to include(\"opt4=on\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/support/dummy_communicator.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantTests\n  module DummyCommunicator\n    class Communicator < Vagrant.plugin(\"2\", :communicator)\n      def ready?\n        true\n      end\n\n      attr_reader :known_commands\n\n      def initialize(machine)\n        @known_commands = Hash.new do |hash, key|\n          hash[key] = { expected: 0, received: 0, response: nil }\n        end\n      end\n\n      def expected_commands\n        known_commands.select do |command, info|\n          info[:expected] > 0\n        end\n      end\n\n      def received_commands\n        known_commands.select do |command, info|\n          info[:received] > 0\n        end.keys\n      end\n\n      def stub_command(command, response)\n        known_commands[command][:response] = response\n      end\n\n      def expect_command(command)\n        known_commands[command][:expected] += 1\n      end\n\n      def received_summary\n        received_commands.map { |cmd| \" - #{cmd}\" }.unshift('received:').join(\"\\n\")\n      end\n\n      def verify_expectations!\n        expected_commands.each do |command, info|\n          if info[:expected] != info[:received]\n            fail([\n              \"expected to receive '#{command}' #{info[:expected]} times\",\n              \"got #{info[:received]} times instead\",\n              received_summary\n            ].join(\"\\n\"))\n          end\n        end\n      end\n\n      def execute(command, opts=nil)\n        known = known_commands[command]\n        known[:received] += 1\n        response = known[:response]\n        return unless response\n\n        if block_given?\n          [:stdout, :stderr].each do |type|\n            Array(response[type]).each do |line|\n              yield type, line\n            end\n          end\n        end\n\n        if response[:raise]\n          raise response[:raise]\n        end\n\n        response[:exit_code]\n      end\n\n      def sudo(command, opts=nil, &block)\n        execute(command, opts, &block)\n      end\n\n      def test(command, opts=nil)\n        execute(command, opts) == 0\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "test/unit/support/dummy_provider.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nmodule VagrantTests\n  class DummyProviderPlugin < Vagrant.plugin(\"2\")\n    name \"Dummy Provider\"\n    description <<-EOF\n    This creates a provider named \"dummy\" which does nothing, so that\n    the unit tests aren't reliant on VirtualBox (or any other real\n    provider for that matter).\n    EOF\n\n    provider(:dummy) { DummyProvider }\n  end\n\n  class DummyProvider < Vagrant.plugin(\"2\", :provider)\n    def initialize(machine)\n      @machine = machine\n    end\n\n    def state=(id)\n      state_file.open(\"w+\") do |f|\n        f.write(id.to_s)\n      end\n    end\n\n    def state\n      if !state_file.file?\n        new_state = @machine.id\n        new_state = Vagrant::MachineState::NOT_CREATED_ID if !new_state\n        self.state = new_state\n      end\n\n      state_id = state_file.read.to_sym\n      Vagrant::MachineState.new(state_id, state_id.to_s, state_id.to_s)\n    end\n\n    protected\n\n    def state_file\n      @machine.data_dir.join(\"dummy_state\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/support/isolated_environment.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"fileutils\"\nrequire \"pathname\"\nrequire \"tempfile\"\nrequire \"tmpdir\"\n\nrequire \"json\"\nrequire \"log4r\"\n\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/subprocess\"\n\nrequire \"support/isolated_environment\"\n\nmodule Unit\n  class IsolatedEnvironment < ::IsolatedEnvironment\n    def create_vagrant_env(options=nil)\n      options = {\n        cwd: @workdir,\n        home_path: @homedir\n      }.merge(options || {})\n\n      Vagrant::Environment.new(options)\n    end\n\n    # This creates a file in the isolated environment. By default this file\n    # will be created in the working directory of the isolated environment.\n    def file(name, contents)\n      @workdir.join(name).open(\"w+\") do |f|\n        f.write(contents)\n      end\n    end\n\n    def vagrantfile(contents, root=nil)\n      root ||= @workdir\n      root.join(\"Vagrantfile\").open(\"w+\") do |f|\n        f.write(contents)\n      end\n    end\n\n    def box(name, vagrantfile_contents=\"\")\n      # Create the box directory\n      box_dir = boxes_dir.join(name)\n      box_dir.mkpath\n\n      # Create the \"box.ovf\" file because that is how Vagrant heuristically\n      # determines a box is a V1 box.\n      box_dir.join(\"box.ovf\").open(\"w\") { |f| f.write(\"\") }\n\n      # Populate the vagrantfile\n      vagrantfile(vagrantfile_contents, box_dir)\n\n      # Return the directory\n      box_dir\n    end\n\n    # Create an alias because \"box\" makes a V1 box, so \"box1\"\n    alias :box1 :box\n\n    # Creates a fake box to exist in this environment.\n    #\n    # @param [String] name Name of the box\n    # @param [Symbol] provider Provider the box was built for.\n    # @return [Pathname] Path to the box directory.\n    def box2(name, provider, options=nil)\n      # Default options\n      options = {\n        vagrantfile: \"\"\n      }.merge(options || {})\n\n      # Make the box directory\n      box_dir = boxes_dir.join(name, provider.to_s)\n      box_dir.mkpath\n\n      # Create a metadata.json file\n      box_metadata_file = box_dir.join(\"metadata.json\")\n      box_metadata_file.open(\"w\") do |f|\n        f.write(JSON.generate({\n          provider: provider.to_s\n        }))\n      end\n\n      # Create a Vagrantfile\n      box_vagrantfile = box_dir.join(\"Vagrantfile\")\n      box_vagrantfile.open(\"w\") do |f|\n        f.write(options[:vagrantfile])\n      end\n\n      # Return the box directory\n      box_dir\n    end\n\n    # Creates a fake box to exist in this environment according\n    # to the \"gen-3\" box format.\n    #\n    # @param [String] name\n    # @param [String] version\n    # @param [String] provider\n    # @return [Pathname]\n    def box3(name, version, provider, **opts)\n      args = [name, version]\n      args << opts[:architecture].to_s if opts[:architecture]\n      args << provider.to_s\n\n      # Create the directory for the box\n      box_dir = boxes_dir.join(*args)\n      box_dir.mkpath\n\n      # Create the metadata.json for it\n      box_metadata_file = box_dir.join(\"metadata.json\")\n      box_metadata_file.open(\"w\") do |f|\n        f.write(JSON.generate({\n          provider: provider.to_s\n        }))\n      end\n\n      # Create a Vagrantfile\n      if opts[:vagrantfile]\n        box_vagrantfile = box_dir.join(\"Vagrantfile\")\n        box_vagrantfile.open(\"w\") do |f|\n          f.write(opts[:vagrantfile])\n        end\n      end\n\n      # Create the metadata URL\n      if opts[:metadata_url]\n        boxes_dir.join(name, \"metadata_url\").open(\"w\") do |f|\n          f.write(opts[:metadata_url])\n        end\n      end\n\n      box_dir\n    end\n\n    # This creates a \"box\" file that is a valid V1 box.\n    #\n    # @return [Pathname] Path to the newly created box.\n    def box1_file\n      # Create a temporary directory to store our data we will tar up\n      td_source = Dir.mktmpdir(\"vagrant-box1-source\")\n      td_dest   = Dir.mktmpdir(\"vagrant-box-1-dest\")\n\n      # Store the temporary directory so it is not deleted until\n      # this instance is garbage collected.\n      @_box2_file_temp ||= []\n      @_box2_file_temp << td_dest\n\n      # The source as a Pathname, which is easier to work with\n      source = Pathname.new(td_source)\n\n      # The destination file\n      result = Pathname.new(td_dest).join(\"temporary.box\")\n\n      # Put a \"box.ovf\" in there.\n      source.join(\"box.ovf\").open(\"w\") do |f|\n        f.write(\"FOO!\")\n      end\n\n      Dir.chdir(source) do\n        # Find all the files in our current directory and tar it up!\n        files = Dir.glob(File.join(\".\", \"**\", \"*\"))\n\n        # Package!\n        Vagrant::Util::Subprocess.execute(\"bsdtar\", \"-czf\", result.to_s, *files)\n      end\n\n      # Resulting box\n      result\n    end\n\n    # This creates a \"box\" file with the given provider.\n    #\n    # @param [Symbol] provider Provider for the box.\n    # @return [Pathname] Path to the newly created box.\n    def box2_file(provider, options=nil)\n      options ||= {}\n\n      # This is the metadata we want to store in our file\n      metadata = {\n        \"type\"     => \"v2_box\",\n        \"provider\" => provider\n      }.merge(options[:metadata] || {})\n\n      # Create a temporary directory to store our data we will tar up\n      td_source = Dir.mktmpdir(\"vagrant-box-2-source\")\n      td_dest   = Dir.mktmpdir(\"vagrant-box-2-dest\")\n\n      # Store the temporary directory so it is not deleted until\n      # this instance is garbage collected.\n      @_box2_file_temp ||= []\n      @_box2_file_temp << td_dest\n\n      # The source as a Pathname, which is easier to work with\n      source = Pathname.new(td_source)\n\n      # The destination file\n      result = Pathname.new(td_dest).join(\"temporary.box\")\n\n      # Put the metadata.json in here.\n      source.join(\"metadata.json\").open(\"w\") do |f|\n        f.write(JSON.generate(metadata))\n      end\n\n      Dir.chdir(source) do\n        # Find all the files in our current directory and tar it up!\n        files = Dir.glob(File.join(\".\", \"**\", \"*\"))\n\n        # Package!\n        Vagrant::Util::Subprocess.execute(\"bsdtar\", \"-czf\", result.to_s, *files)\n      end\n\n      # Resulting box\n      result\n    end\n\n    def boxes_dir\n      dir = @homedir.join(\"boxes\")\n      dir.mkpath\n      dir\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/support/shared/action_synced_folders_context.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nshared_context \"synced folder actions\" do\n  # This creates a synced folder implementation.\n  def impl(usable, name)\n    Class.new(Vagrant.plugin(\"2\", :synced_folder)) do\n      define_method(:name) do\n        name\n      end\n\n      define_method(:usable?) do |machine, raise_error=false|\n        raise \"#{name}: usable\" if raise_error && !usable\n        usable\n      end\n\n      define_method(:_initialize) do |machine, type|\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/support/shared/base_context.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tempfile\"\nrequire \"tmpdir\"\n\nrequire \"vagrant/util/platform\"\n\nrequire \"unit/support/isolated_environment\"\n\nshared_context \"unit\" do\n  before(:each) do\n    # State to store the list of registered plugins that we have to\n    # unregister later.\n    @_plugins = []\n\n    # Create a thing to store our temporary files so that they aren't\n    # unlinked right away.\n    @_temp_files = []\n\n    # Roughly simulate the embedded Bundler availability\n    $vagrant_bundler_runtime = Object.new\n  end\n\n  after(:each) do\n    # Unregister each of the plugins we have may have temporarily\n    # registered for the duration of this test.\n    @_plugins.each do |plugin|\n      Vagrant.plugin(\"1\").manager.unregister(plugin)\n      Vagrant.plugin(\"2\").manager.unregister(plugin)\n    end\n  end\n\n  # This creates an isolated environment so that Vagrant doesn't\n  # muck around with your real system during unit tests.\n  #\n  # The returned isolated environment has a variety of helper\n  # methods on it to easily create files, Vagrantfiles, boxes,\n  # etc.\n  def isolated_environment\n    env = Unit::IsolatedEnvironment.new\n    yield env if block_given?\n    env\n  end\n\n  # This registers a Vagrant plugin for the duration of a single test.\n  # This will yield a new plugin class that you can then call the\n  # public plugin methods on.\n  #\n  # @yield [plugin] Yields the plugin class for you to call the public\n  #   API that you need to.\n  def register_plugin(version=nil)\n    version ||= Vagrant::Config::CURRENT_VERSION\n    plugin = Class.new(Vagrant.plugin(version))\n    plugin.name(\"Test Plugin #{plugin.inspect}\")\n    yield plugin if block_given?\n    @_plugins << plugin\n    plugin\n  end\n\n  # This helper creates a temporary file and returns a Pathname\n  # object pointed to it.\n  #\n  # @return [Pathname]\n  def temporary_file(contents=nil)\n    dir = temporary_dir\n    f = dir.join(\"tempfile\")\n\n    contents ||= \"\"\n    f.open(\"w\") do |f|\n      f.write(contents)\n      f.flush\n    end\n\n    return Pathname.new(Vagrant::Util::Platform.fs_real_path(f.to_s))\n  end\n\n  # This creates a temporary directory and returns a {Pathname}\n  # pointing to it. If a block is given, the pathname is yielded and the\n  # temporary directory is removed at the end of the block.\n  #\n  # @return [Pathname]\n  def temporary_dir\n    # Create a temporary directory and append it to the instance\n    # variable so that it isn't garbage collected and deleted\n    d = Dir.mktmpdir(\"vagrant-temporary-dir\")\n    @_temp_files ||= []\n    @_temp_files << d\n\n    # Return the pathname\n    result = Pathname.new(Vagrant::Util::Platform.fs_real_path(d))\n    if block_given?\n      begin\n        yield result\n      ensure\n        FileUtils.rm_rf(result)\n      end\n    end\n\n    return result\n  end\n\n  # Stub the given environment in ENV, without actually touching ENV. Keys and\n  # values are converted to strings because that's how the real ENV works.\n  def stub_env(hash)\n    allow(ENV).to receive(:[]).and_call_original\n\n    hash.each do |key, value|\n      v = value.nil? ? nil : value.to_s\n      allow(ENV).to receive(:[])\n        .with(key.to_s)\n        .and_return(v)\n    end\n  end\n\n  # This helper provides temporary environmental variable changes.\n  def with_temp_env(environment)\n    # Build up the new environment, preserving the old values so we\n    # can replace them back in later.\n    old_env = {}\n    environment.each do |key, value|\n      key          = key.to_s\n      old_env[key] = ENV[key]\n      ENV[key]     = value\n    end\n\n    # Call the block, returning its return value\n    return yield\n  ensure\n    # Reset the environment no matter what\n    old_env.each do |key, value|\n      ENV[key] = value\n    end\n  end\n\n  # This helper provides a randomly available port(s) for each argument to the\n  # block.\n  def with_random_port(&block)\n    ports = []\n\n    block.arity.times do\n      server = TCPServer.new('127.0.0.1', 0)\n      ports << server.addr[1]\n      server.close\n    end\n\n    block.call(*ports)\n  end\nend\n"
  },
  {
    "path": "test/unit/support/shared/capability_helpers_context.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nshared_context \"capability_helpers\" do\n  def detect_class(result)\n    Class.new do\n      define_method(:detect?) do |*args|\n        result\n      end\n    end\n  end\n\n  def provider_usable_class(result)\n    Class.new do\n      define_singleton_method(:usable?) do |*args|\n        result\n      end\n    end\n  end\n\n  def cap_instance(name, options=nil)\n    options ||= {}\n\n    Class.new do\n      if !options[:corrupt]\n        define_method(name) do |*args|\n          raise \"cap: #{name} #{args.inspect}\"\n        end\n      end\n    end.new\n  end\nend\n"
  },
  {
    "path": "test/unit/support/shared/plugin_command_context.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nshared_context \"command plugin helpers\" do\n  def command_lambda(name, result, **opts)\n    lambda do\n      Class.new(Vagrant.plugin(\"2\", \"command\")) do\n        define_method(:execute) do\n          raise opts[:exception] if opts[:exception]\n          result\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/support/shared/virtualbox_context.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nshared_context \"virtualbox\" do\n  include_context \"unit\"\n\n  let(:vbox_context) { true                                }\n  let(:uuid)         { \"1234-abcd-5678-efgh\"               }\n  let(:vbox_version) { \"4.3.4\"                             }\n  let(:subprocess)   { double(\"Vagrant::Util::Subprocess\") }\n\n  # this is a helper that returns a duck type suitable from a system command\n  # execution; allows setting exit_code, stdout, and stderr in stubs.\n  def subprocess_result(options={})\n    defaults = {exit_code: 0, stdout: \"\", stderr: \"\"}\n    double(\"subprocess_result\", defaults.merge(options))\n  end\n\n  before do\n    # we don't want unit tests to ever run commands on the system; so we wire\n    # in a double to ensure any unexpected messages raise exceptions\n    stub_const(\"Vagrant::Util::Subprocess\", subprocess)\n\n    # drivers will blow up on instantiation if they cannot determine the\n    # virtualbox version, so wire this stub in automatically\n    allow(subprocess).to receive(:execute).\n      with(\"VBoxManage\", \"--version\", an_instance_of(Hash)).\n      and_return(subprocess_result(stdout: vbox_version))\n\n    # drivers also call vm_exists? during init;\n    allow(subprocess).to receive(:execute).\n      with(\"VBoxManage\", \"showvminfo\", kind_of(String), kind_of(Hash)).\n      and_return(subprocess_result(exit_code: 0))\n\n    allow(Vagrant::Util::Which).to receive(:which).and_call_original\n    allow(Vagrant::Util::Which).to receive(:which).with(\"locale\").and_return(false)\n  end\n\n  around do |example|\n    # On Windows, we don't want to accidentally call the actual VirtualBox\n    with_temp_env(\"VBOX_INSTALL_PATH\" => nil) do\n      example.run\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/commands/init/Vagrantfile.erb",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\n# All Vagrant configuration is done below. The \"2\" in Vagrant.configure\n# configures the configuration version (we support older styles for\n# backwards compatibility). Please don't change it unless you know what\n# you're doing.\nVagrant.configure(\"2\") do |config|\n  config.vm.hostname = \"vagrant.dev\"  \n  config.vm.box = \"<%= box_name %>\"\n  <% if box_version -%>\n  config.vm.box_version = \"<%= box_version %>\"\n  <% end -%>\n\n  <% if box_url -%>\n  # The url from where the 'config.vm.box' box will be fetched if it\n  # doesn't already exist on the user's system.\n  config.vm.box_url = \"<%= box_url %>\"\n  <% end -%>\nend\n"
  },
  {
    "path": "test/unit/templates/guests/arch/default_network/network_dhcp_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/arch/default_network/network_dhcp\" do\n  let(:template) { \"guests/arch/default_network/network_dhcp\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device: \"eth1\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      Description='A basic dhcp ethernet connection'\n      Interface=eth1\n      Connection=ethernet\n      IP=dhcp\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/arch/default_network/network_static_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/arch/default_network/network_static\" do\n  let(:template) { \"guests/arch/default_network/network_static\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"eth1\",\n      ip:      \"1.1.1.1\",\n      netmask: \"24\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      Connection=ethernet\n      Description='A basic static ethernet connection'\n      Interface=eth1\n      IP=static\n      Address=('1.1.1.1/24')\n    EOH\n  end\n\n  it \"includes the gateway\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"eth1\",\n      ip:      \"1.1.1.1\",\n      gateway: \"1.2.3.4\",\n      netmask: \"24\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      Connection=ethernet\n      Description='A basic static ethernet connection'\n      Interface=eth1\n      IP=static\n      Address=('1.1.1.1/24')\n      Gateway='1.2.3.4'\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/arch/systemd_networkd/network_dhcp_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/arch/systemd_networkd/network_dhcp\" do\n  let(:template) { \"guests/arch/systemd_networkd/network_dhcp\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device: \"eth1\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      [Match]\n      Name=eth1\n\n      [Network]\n      Description=A basic DHCP ethernet connection\n      DHCP=ipv4\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/arch/systemd_networkd/network_static_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/arch/systemd_networkd/network_static\" do\n  let(:template) { \"guests/arch/systemd_networkd/network_static\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"eth1\",\n      ip:      \"1.1.1.1\",\n      netmask: \"24\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      [Match]\n      Name=eth1\n\n      [Network]\n      Description=A basic static ethernet connection\n      Address=1.1.1.1/24\n    EOH\n  end\n\n  it \"includes the gateway\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"eth1\",\n      ip:      \"1.1.1.1\",\n      gateway: \"1.2.3.4\",\n      netmask: \"24\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      [Match]\n      Name=eth1\n\n      [Network]\n      Description=A basic static ethernet connection\n      Address=1.1.1.1/24\n      Gateway=1.2.3.4\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/debian/network_dhcp_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/debian/network_dhcp\" do\n  let(:template) { \"guests/debian/network_dhcp\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device: \"eth1\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      auto eth1\n      iface eth1 inet dhcp\n          post-up ip route del default dev $IFACE || true\n      #VAGRANT-END\n    EOH\n  end\n\n  context \"when use_dhcp_assigned_default_route is set\" do\n    it \"renders the template\" do\n      result = Vagrant::Util::TemplateRenderer.render(template, options: {\n        device: \"eth1\",\n        root_device: \"eth0\",\n        use_dhcp_assigned_default_route: true,\n      })\n      expect(result).to eq <<-EOH.gsub(/^ {8}/, \"\")\n        #VAGRANT-BEGIN\n        # The contents below are automatically generated by Vagrant. Do not modify.\n        auto eth1\n        iface eth1 inet dhcp\n            # We need to disable eth0, see GH-2648\n            post-up ip route del default dev eth0 || true\n            post-up dhclient $IFACE\n            pre-down ip route add default dev eth0\n        #VAGRANT-END\n      EOH\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/debian/network_static_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/debian/network_static\" do\n  let(:template) { \"guests/debian/network_static\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"eth1\",\n      ip:      \"1.1.1.1\",\n      netmask: \"255.255.0.0\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      auto eth1\n      iface eth1 inet static\n            address 1.1.1.1\n            netmask 255.255.0.0\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"includes the gateway\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"eth1\",\n      ip:      \"1.1.1.1\",\n      netmask: \"255.255.0.0\",\n      gateway: \"1.2.3.4\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      auto eth1\n      iface eth1 inet static\n            address 1.1.1.1\n            netmask 255.255.0.0\n            gateway 1.2.3.4\n      #VAGRANT-END\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/freebsd/network_dhcp_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/freebsd/network_dhcp\" do\n  let(:template) { \"guests/freebsd/network_dhcp\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device: \"eth1\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      ifconfig_eth1=\"DHCP\"\n      synchronous_dhclient=\"YES\"\n      #VAGRANT-END\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/freebsd/network_static_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/freebsd/network_static\" do\n  let(:template) { \"guests/freebsd/network_static\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"eth1\",\n      ip:      \"1.1.1.1\",\n      netmask: \"255.255.0.0\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      ifconfig_eth1=\"inet 1.1.1.1 netmask 255.255.0.0\"\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"includes the gateway\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"eth1\",\n      ip:      \"1.1.1.1\",\n      netmask: \"255.255.0.0\",\n      gateway: \"1.2.3.4\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      ifconfig_eth1=\"inet 1.1.1.1 netmask 255.255.0.0\"\n      defaultrouter=\"1.2.3.4\"\n      #VAGRANT-END\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/funtoo/network_dhcp_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/funtoo/network_dhcp\" do\n  let(:template) { \"guests/funtoo/network_dhcp\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device: \"en0\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      template='dhcp'\n      #VAGRANT-END\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/funtoo/network_static_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/funtoo/network_static\" do\n  let(:template) { \"guests/funtoo/network_static\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"en0\",\n      ip:      \"1.1.1.1\",\n      netmask: \"255.255.0.0\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      template='interface'\n      ipaddr='1.1.1.1/255.255.0.0'\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"includes the gateway\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"en0\",\n      ip:      \"1.1.1.1\",\n      netmask: \"255.255.0.0\",\n      gateway: \"1.2.3.4\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      template='interface'\n      ipaddr='1.1.1.1/255.255.0.0'\n      gateway='1.2.3.4'\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"includes the nameservers\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:      \"en0\",\n      ip:          \"1.1.1.1\",\n      netmask:     \"255.255.0.0\",\n      nameservers: \"ns1.company.com\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      template='interface'\n      ipaddr='1.1.1.1/255.255.0.0'\n      nameservers='ns1.company.com'\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"includes the domain\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"en0\",\n      ip:      \"1.1.1.1\",\n      netmask: \"255.255.0.0\",\n      domain:  \"company.com\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      template='interface'\n      ipaddr='1.1.1.1/255.255.0.0'\n      domain='company.com'\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"includes the route\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"en0\",\n      ip:      \"1.1.1.1\",\n      netmask: \"255.255.0.0\",\n      route:   \"5.6.7.8\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      template='interface'\n      ipaddr='1.1.1.1/255.255.0.0'\n      route='5.6.7.8'\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"includes the gateway6\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:   \"en0\",\n      ip:       \"1.1.1.1\",\n      netmask:  \"255.255.0.0\",\n      gateway6: \"aaaa:0000\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      template='interface'\n      ipaddr='1.1.1.1/255.255.0.0'\n      gateway6='aaaa:0000'\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"includes the route6\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"en0\",\n      ip:      \"1.1.1.1\",\n      netmask: \"255.255.0.0\",\n      route6:  \"bbbb:1111\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      template='interface'\n      ipaddr='1.1.1.1/255.255.0.0'\n      route6='bbbb:1111'\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"includes the mtu\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"en0\",\n      ip:      \"1.1.1.1\",\n      netmask: \"255.255.0.0\",\n      mtu:     \"1\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      template='interface'\n      ipaddr='1.1.1.1/255.255.0.0'\n      mtu='1'\n      #VAGRANT-END\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/gentoo/network_dhcp_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/gentoo/network_dhcp\" do\n  let(:template) { \"guests/gentoo/network_dhcp\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device: \"en0\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      config_en0=\"dhcp\"\n      #VAGRANT-END\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/gentoo/network_static_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/gentoo/network_static\" do\n  let(:template) { \"guests/gentoo/network_static\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"en0\",\n      ip:      \"1.1.1.1\",\n      netmask: \"255.255.0.0\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      config_en0=(\"1.1.1.1 netmask 255.255.0.0\")\n      modules_en0=(\"!plug\")\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"includes the gateway\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"en0\",\n      ip:      \"1.1.1.1\",\n      netmask: \"255.255.0.0\",\n      gateway: \"1.2.3.4\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      config_en0=(\"1.1.1.1 netmask 255.255.0.0\")\n      modules_en0=(\"!plug\")\n      gateways_en0=\"1.2.3.4\"\n      #VAGRANT-END\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/gentoo/systemd_network_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/gentoo/network_systemd\" do\n  let(:template) { \"guests/gentoo/network_systemd\" }\n\n  it \"renders the template with a static ip\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, networks: [{\n      device:  \"eth0\",\n      type:    \"dhcp\",\n    }])\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      [Match]\n      Name=eth0\n\n      [Network]\n      DHCP=yes\n    EOH\n  end\n\n  it \"renders the template with multiple ips\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, networks: [{\n      device:  \"eth0\",\n      ip:      \"1.1.1.1\",\n      netmask: \"16\",\n    },{\n      device:  \"eth0\",\n      ip:      \"1.1.2.2\",\n      netmask: \"16\",\n    }])\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      [Match]\n      Name=eth0\n\n      [Network]\n      Address=1.1.1.1/16\n      Address=1.1.2.2/16\n    EOH\n  end\n\n  it \"renders the template with a static ip\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, networks: [{\n      device:  \"eth0\",\n      ip:      \"1.1.1.1\",\n      netmask: \"16\",\n    }])\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      [Match]\n      Name=eth0\n\n      [Network]\n      Address=1.1.1.1/16\n    EOH\n  end\n\n  it \"includes the gateway\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, networks: [{\n      device:  \"eth0\",\n      ip:      \"1.1.1.1\",\n      netmask: \"16\",\n      gateway: \"1.2.3.4\",\n    }])\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      [Match]\n      Name=eth0\n\n      [Network]\n      Address=1.1.1.1/16\n      Gateway=1.2.3.4\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/netbsd/network_dhcp_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/netbsd/network_dhcp\" do\n  let(:template) { \"guests/netbsd/network_dhcp\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      interface: \"en0\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      ifconfig_wmen0=dhcp\n      #VAGRANT-END\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/netbsd/network_static_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/netbsd/network_static\" do\n  let(:template) { \"guests/netbsd/network_static\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      interface: \"en0\",\n      ip:        \"1.1.1.1\",\n      netmask:   \"255.255.0.0\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      ifconfig_wmen0=\"media autoselect up;inet 1.1.1.1 netmask 255.255.0.0\"\n      #VAGRANT-END\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/nixos/network_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/nixos/network\" do\n  let(:template) { \"guests/nixos/network\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, networks: [{\n      device: \"en0\",\n      ip: \"1.1.1.1\",\n      prefix_length: \"24\",\n      type: :static,\n    }])\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      { config, pkgs, ... }:\n      {\n        networking.interfaces = {\n          en0.ipv4.addresses = [{\n            address = \"1.1.1.1\";\n            prefixLength = 24;\n          }];\n        };\n      }\n      EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/redhat/network_dhcp_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/redhat/network_dhcp\" do\n  let(:template) { \"guests/redhat/network_dhcp\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device: \"en0\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      BOOTPROTO=dhcp\n      ONBOOT=yes\n      DEVICE=en0\n      NM_CONTROLLED=no\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"renders the template with NetworkManager enabled\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device: \"en0\",\n      nm_controlled: \"yes\"\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      BOOTPROTO=dhcp\n      ONBOOT=yes\n      DEVICE=en0\n      NM_CONTROLLED=yes\n      #VAGRANT-END\n    EOH\n  end\n\nend\n"
  },
  {
    "path": "test/unit/templates/guests/redhat/network_static_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/redhat/network_static\" do\n  let(:template) { \"guests/redhat/network_static\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:  \"en0\",\n      ip:      \"1.1.1.1\",\n      netmask: \"255.255.0.0\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      NM_CONTROLLED=no\n      BOOTPROTO=none\n      ONBOOT=yes\n      IPADDR=1.1.1.1\n      NETMASK=255.255.0.0\n      DEVICE=en0\n      PEERDNS=no\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"includes the gateway\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:   \"en0\",\n      ip:       \"1.1.1.1\",\n      gateway:  \"1.2.3.4\",\n      netmask:  \"255.255.0.0\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      NM_CONTROLLED=no\n      BOOTPROTO=none\n      ONBOOT=yes\n      IPADDR=1.1.1.1\n      NETMASK=255.255.0.0\n      DEVICE=en0\n      GATEWAY=1.2.3.4\n      PEERDNS=no\n      #VAGRANT-END\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/suse/network_dhcp_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/suse/network_dhcp\" do\n  let(:template) { \"guests/suse/network_dhcp\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device: \"eth0\"\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      BOOTPROTO='dhcp'\n      STARTMODE='auto'\n      DEVICE='eth0'\n      #VAGRANT-END\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/suse/network_static6_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/suse/network_static6\" do\n  let(:template) { \"guests/suse/network_static6\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:    \"eth2\",\n      ip:        \"fde4:8dba:82e1::c4\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      STARTMODE='auto'\n      BOOTPROTO='static'\n      IPADDR=fde4:8dba:82e1::c4\n      DEVICE=eth2\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"includes the prefix-length and gateway\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:           \"eth1\",\n      ip:               \"fde4:8dba:82e1::c4\",\n      gateway:          \"fde4:8dba:82e1::01\",\n      prefix_length:    \"64\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      STARTMODE='auto'\n      BOOTPROTO='static'\n      IPADDR=fde4:8dba:82e1::c4\n      DEVICE=eth1\n      GATEWAY=fde4:8dba:82e1::01\n      PREFIXLEN=64\n      #VAGRANT-END\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/guests/suse/network_static_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/guests/suse/network_static\" do\n  let(:template) { \"guests/suse/network_static\" }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:    \"eth1\",\n      ip:        \"1.1.1.1\",\n      netmask:   \"255.255.0.0\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      BOOTPROTO='static'\n      IPADDR='1.1.1.1'\n      NETMASK='255.255.0.0'\n      DEVICE='eth1'\n      PEERDNS='no'\n      STARTMODE='auto'\n      USERCONTROL='no'\n      #VAGRANT-END\n    EOH\n  end\n\n  it \"includes the gateway\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, options: {\n      device:     \"eth1\",\n      ip:         \"1.1.1.1\",\n      gateway:    \"1.2.3.4\",\n      netmask:    \"255.255.0.0\",\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      #VAGRANT-BEGIN\n      # The contents below are automatically generated by Vagrant. Do not modify.\n      BOOTPROTO='static'\n      IPADDR='1.1.1.1'\n      NETMASK='255.255.0.0'\n      DEVICE='eth1'\n      GATEWAY='1.2.3.4'\n      PEERDNS='no'\n      STARTMODE='auto'\n      USERCONTROL='no'\n      #VAGRANT-END\n    EOH\n  end\nend\n"
  },
  {
    "path": "test/unit/templates/nfs/exports_darwin_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../base\"\n\nrequire \"vagrant/util/template_renderer\"\n\ndescribe \"templates/nfs/exports_darwin\" do\n  let(:template) { \"nfs/exports_darwin\" }\n  let(:user) { \"501\" }\n  let(:uuid) { \"UUID\" }\n  let(:opts) { {:bsd__compiled_nfs_options => \"-alldirs -mapall=501:80\"} }\n  let(:ips) { [\"172.16.0.2\"] }\n\n  it \"renders the template\" do\n    result = Vagrant::Util::TemplateRenderer.render(template, {\n      user:    user,\n      uuid:    uuid,\n      folders: []\n    })\n    expect(result).to eq <<-EOH.gsub(/^ {6}/, \"\")\n      # VAGRANT-BEGIN: 501 UUID\n      # VAGRANT-END: 501 UUID\n    EOH\n  end\n\n  context \"one nfs mount\" do\n    let(:folders) {\n      {\n        [\"/vagrant\"] => opts\n      }\n    }\n\n    it \"renders the template\" do\n      result = Vagrant::Util::TemplateRenderer.render(template, {\n        user:    user,\n        uuid:    uuid,\n        folders: folders,\n        ips:     ips\n      })\n      expect(result).to eq <<-EOH.gsub(/^ {8}/, \"\")\n        # VAGRANT-BEGIN: 501 UUID\n        \"/vagrant\" -alldirs -mapall=501:80 172.16.0.2\n        # VAGRANT-END: 501 UUID\n      EOH\n    end\n  end\n\n  context \"subdirectory that should also be exported\" do\n    let(:folders) {\n      {\n        [\"/vagrant\", \"/vagrant/other\"] => opts\n      }\n    }\n\n    it \"puts each directory on its own line\" do\n      result = Vagrant::Util::TemplateRenderer.render(template, {\n        user:    user,\n        uuid:    uuid,\n        folders: folders,\n        ips:     ips\n      })\n      expect(result).to eq <<-EOH.gsub(/^ {8}/, \"\")\n        # VAGRANT-BEGIN: 501 UUID\n        \"/vagrant\" -alldirs -mapall=501:80 172.16.0.2\n        \"/vagrant/other\" -alldirs -mapall=501:80 172.16.0.2\n        # VAGRANT-END: 501 UUID\n      EOH\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builder do\n  let(:data) { { data: [] } }\n  let(:primary) { true }\n  let(:subject) do\n    described_class.new.tap do |b|\n      b.primary = primary\n    end\n  end\n\n  # This returns a proc that can be used with the builder\n  # that simply appends data to an array in the env.\n  def appender_proc(data)\n    result = Proc.new { |env| env[:data] << data }\n\n    # Define a to_s on it for helpful output\n    result.define_singleton_method(:to_s) do\n      \"<Appender: #{data}>\"\n    end\n\n    result\n  end\n\n  def wrapper_proc(data)\n    Class.new do\n      def initialize(app, env)\n        @app = app\n      end\n\n      def self.name\n        \"TestAction\"\n      end\n\n      define_method(:call) do |env|\n        env[:data] << \"#{data}_in\"\n        @app.call(env)\n        env[:data] << \"#{data}_out\"\n      end\n    end\n  end\n\n  context \"copying\" do\n    it \"should copy the stack\" do\n      copy = subject.dup\n      expect(copy.stack.object_id).not_to eq(subject.stack.object_id)\n    end\n  end\n\n  context \"build\" do\n    it \"should provide build as a shortcut for basic sequences\" do\n      data = {}\n      proc = Proc.new { |env| env[:data] = true }\n\n      subject = described_class.build(proc)\n      subject.call(data)\n\n      expect(data[:data]).to eq(true)\n    end\n  end\n\n  context \"basic `use`\" do\n    it \"should add items to the stack and make them callable\" do\n      data = {}\n      proc = Proc.new { |env| env[:data] = true }\n\n      subject.use proc\n      subject.call(data)\n\n      expect(data[:data]).to eq(true)\n    end\n\n    it \"should be able to add multiple items\" do\n      data = {}\n      proc1 = Proc.new { |env| env[:one] = true }\n      proc2 = Proc.new { |env| env[:two] = true }\n\n      subject.use proc1\n      subject.use proc2\n      subject.call(data)\n\n      expect(data[:one]).to eq(true)\n      expect(data[:two]).to eq(true)\n    end\n\n    it \"should be able to add another builder\" do\n      data  = {}\n      proc1 = Proc.new { |env| env[:one] = true }\n\n      # Build the first builder\n      one   = described_class.new\n      one.use proc1\n\n      # Add it to this builder\n      two   = described_class.new\n      two.use one\n\n      # Call the 2nd and verify results\n      two.call(data)\n      expect(data[:one]).to eq(true)\n    end\n  end\n\n  context \"inserting\" do\n    it \"can insert at an index\" do\n      subject.use appender_proc(1)\n      subject.insert(0, appender_proc(2))\n      subject.call(data)\n\n      expect(data[:data]).to eq([2, 1])\n    end\n\n    it \"can insert by name\" do\n      # Create the proc then make sure it has a name\n      bar_proc = appender_proc(2)\n      def bar_proc.name; :bar; end\n\n      subject.use appender_proc(1)\n      subject.use bar_proc\n      subject.insert_before :bar, appender_proc(3)\n      subject.call(data)\n\n      expect(data[:data]).to eq([1, 3, 2])\n    end\n\n    it \"can insert next to a previous object\" do\n      proc2 = appender_proc(2)\n      subject.use appender_proc(1)\n      subject.use proc2\n      subject.insert(proc2, appender_proc(3))\n      subject.call(data)\n\n      expect(data[:data]).to eq([1, 3, 2])\n    end\n\n    it \"can insert before\" do\n      subject.use appender_proc(1)\n      subject.insert_before 0, appender_proc(2)\n      subject.call(data)\n\n      expect(data[:data]).to eq([2, 1])\n    end\n\n    it \"can insert after\" do\n      subject.use appender_proc(1)\n      subject.use appender_proc(3)\n      subject.insert_after 0, appender_proc(2)\n      subject.call(data)\n\n      expect(data[:data]).to eq([1, 2, 3])\n    end\n\n    it \"merges middleware stacks of other builders\" do\n      wrapper_class = Proc.new do |letter|\n        Class.new do\n          def initialize(app, env)\n            @app = app\n          end\n\n          def self.name\n            \"TestAction\"\n          end\n\n          define_method(:call) do |env|\n            env[:data] << \"#{letter}1\"\n            @app.call(env)\n            env[:data] << \"#{letter}2\"\n          end\n        end\n      end\n\n      proc2 = appender_proc(2)\n      subject.use appender_proc(1)\n      subject.use proc2\n\n      builder = described_class.new\n      builder.use wrapper_class.call(\"A\")\n      builder.use wrapper_class.call(\"B\")\n\n      subject.insert(proc2, builder)\n      subject.call(data)\n\n      expect(data[:data]).to eq([1, \"A1\", \"B1\", 2, \"B2\", \"A2\"])\n    end\n\n    it \"raises an exception if an invalid object given for insert\" do\n      expect { subject.insert \"object\", appender_proc(1) }.\n        to raise_error(RuntimeError)\n    end\n\n    it \"raises an exception if an invalid object given for insert_after\" do\n      expect { subject.insert_after \"object\", appender_proc(1) }.\n        to raise_error(RuntimeError)\n    end\n  end\n\n  context \"replace\" do\n    it \"can replace an object\" do\n      proc1 = appender_proc(1)\n      proc2 = appender_proc(2)\n\n      subject.use proc1\n      subject.replace proc1, proc2\n      subject.call(data)\n\n      expect(data[:data]).to eq([2])\n    end\n\n    it \"can replace by index\" do\n      proc1 = appender_proc(1)\n      proc2 = appender_proc(2)\n\n      subject.use proc1\n      subject.replace 0, proc2\n      subject.call(data)\n\n      expect(data[:data]).to eq([2])\n    end\n  end\n\n  context \"deleting\" do\n    it \"can delete by object\" do\n      proc1 = appender_proc(1)\n\n      subject.use proc1\n      subject.use appender_proc(2)\n      subject.delete proc1\n      subject.call(data)\n\n      expect(data[:data]).to eq([2])\n    end\n\n    it \"can delete by index\" do\n      proc1 = appender_proc(1)\n\n      subject.use proc1\n      subject.use appender_proc(2)\n      subject.delete 0\n      subject.call(data)\n\n      expect(data[:data]).to eq([2])\n    end\n  end\n\n  describe \"action hooks\" do\n    let(:hook) { double(\"hook\") }\n    let(:manager) { Vagrant.plugin(\"2\").manager }\n\n    before do\n      allow(manager).to receive(:action_hooks).and_return([])\n    end\n\n    it \"applies them properly\" do\n      hook_proc = proc{ |h| h.append(appender_proc(:hook)) }\n      allow(manager).to receive(:action_hooks).with(:test_action).\n        and_return([hook_proc])\n\n      data[:action_name] = :test_action\n\n      subject.use appender_proc(1)\n      subject.call(data)\n\n      expect(data[:data]).to eq([1, :hook])\n    end\n\n    it \"applies them properly even with nested stacks\" do\n      hook_proc = proc{ |h| h.append(appender_proc(:hook)) }\n      allow(manager).to receive(:action_hooks).with(:test_action).\n        and_return([hook_proc])\n\n      data[:action_name] = :test_action\n\n      subject.use appender_proc(1)\n      subject.use Vagrant::Action::Builtin::Call, proc {} do |env, b2|\n        b2.use appender_proc(2)\n      end\n      subject.call(data)\n\n      expect(data[:data]).to eq([1, 2, :hook])\n    end\n  end\n\n  describe \"calling another app later\" do\n    it \"calls in the proper order\" do\n      # We have to do this because inside the Class.new, it can't see these\n      # rspec methods...\n      described_klass = described_class\n      wrapper_proc    = self.method(:wrapper_proc)\n\n      wrapper = Class.new do\n        def initialize(app, env)\n          @app = app\n        end\n\n        def self.name\n          \"TestAction\"\n        end\n\n        define_method(:call) do |env|\n          inner = described_klass.new\n          inner.use wrapper_proc[2]\n          inner.use @app\n          inner.call(env)\n        end\n      end\n\n      subject.use wrapper_proc(1)\n      subject.use wrapper\n      subject.use wrapper_proc(3)\n      subject.call(data)\n\n      expect(data[:data]).to eq([\n        \"1_in\", \"2_in\", \"3_in\", \"3_out\", \"2_out\", \"1_out\"])\n    end\n  end\n\n  describe \"dynamic action hooks\" do\n    class ActionOne\n      def initialize(app, env)\n        @app = app\n      end\n\n      def call(env)\n        env[:data] << 1 if env[:data]\n        @app.call(env)\n      end\n\n      def recover(env)\n        env[:recover] << 1\n      end\n    end\n\n    class ActionTwo\n      def initialize(app, env)\n        @app = app\n      end\n\n      def call(env)\n        env[:data] << 2 if env[:data]\n        @app.call(env)\n      end\n\n      def recover(env)\n        env[:recover] << 2\n      end\n    end\n\n    let(:data) { {data: []} }\n    let(:hook_action_name) { :action_two }\n\n    let(:plugin) do\n      h_name = hook_action_name\n      @plugin ||= Class.new(Vagrant.plugin(\"2\")) do\n        name \"Test Plugin\"\n        action_hook(:before_test, h_name) do |hook|\n          hook.prepend(proc{ |env| env[:data] << :first })\n        end\n      end\n    end\n\n    before { plugin }\n\n    after do\n      Vagrant.plugin(\"2\").manager.unregister(@plugin) if @plugin\n      @plugin = nil\n    end\n\n    it \"should call hook before running action\" do\n      instance = described_class.build(ActionTwo).tap { |b| b.primary = true }\n      instance.call(data)\n      expect(data[:data].first).to eq(:first)\n      expect(data[:data].last).to eq(2)\n    end\n\n    context \"when hook matches action in subsequent builder\" do\n      let(:hook_action_name) { ActionOne }\n\n      before do\n        data[:action_name] = :test_action_name\n        data[:raw_action_name] = :machine_test_action_name\n      end\n\n      it \"should execute the hook\" do\n        described_class.build(ActionTwo).tap { |b| b.primary = true }.call(data)\n        described_class.build(ActionOne).tap { |b| b.primary = true }.call(data)\n        expect(data[:data]).to include(:first)\n      end\n    end\n\n    context \"when hook matches action name in subsequent builder\" do\n      let(:hook_action_name) { :test_action_name }\n\n      before do\n        data[:action_name] = :test_action_name\n        data[:raw_action_name] = :machine_test_action_name\n      end\n\n      it \"should execute the hook\" do\n        described_class.build(ActionTwo).tap { |b| b.primary = true }.call(data)\n        described_class.build(ActionOne).tap { |b| b.primary = true }.call(data)\n        expect(data[:data]).to include(:first)\n      end\n\n      it \"should execute the hook multiple times\" do\n        described_class.build(ActionTwo).tap { |b| b.primary = true }.call(data)\n        described_class.build(ActionOne).tap { |b| b.primary = true }.call(data)\n        expect(data[:data].count{|d| d == :first}).to eq(2)\n      end\n    end\n\n    context \"when applying triggers\" do\n      let(:triggers) { double(\"triggers\") }\n\n      before do\n        data[:action_name] = :test_action_name\n        data[:raw_action_name] = :machine_test_action_name\n        data[:triggers] = triggers\n        allow(triggers).to receive(:find).and_return([])\n      end\n\n      it \"should attempt to find triggers based on raw action\" do\n        expect(triggers).to receive(:find).with(data[:raw_action_name], any_args).and_return([])\n        described_class.build(ActionOne).call(data)\n      end\n\n      it \"should only attempt to find triggers based on raw action once\" do\n        expect(triggers).to receive(:find).with(data[:raw_action_name], :before, any_args).once.and_return([])\n        expect(triggers).to receive(:find).with(data[:raw_action_name], :after, any_args).once.and_return([])\n        described_class.build(ActionOne).call(data)\n        described_class.build(ActionOne).call(data)\n      end\n    end\n\n    context \"when hook is appending to action\" do\n      let(:plugin) do\n        @plugin ||= Class.new(Vagrant.plugin(\"2\")) do\n          name \"Test Plugin\"\n          action_hook(:before_test, :action_two) do |hook|\n            hook.append(proc{ |env| env[:data] << :first })\n          end\n        end\n      end\n\n      it \"should call hook after action when action is nested\" do\n        instance = described_class.build(ActionTwo).use(described_class.build(ActionOne))\n        instance.call(data)\n        expect(data[:data][0]).to eq(2)\n        expect(data[:data][1]).to eq(:first)\n        expect(data[:data][2]).to eq(1)\n      end\n    end\n\n    context \"when hook uses class name\" do\n      let(:hook_action_name) { \"ActionTwo\" }\n\n      it \"should execute the hook\" do\n        instance = described_class.build(ActionTwo)\n        instance.call(data)\n        expect(data[:data]).to include(:first)\n      end\n    end\n\n    context \"when action includes a namespace\" do\n      module Vagrant\n        module Test\n          class ActionTest\n            def initialize(app, env)\n              @app = app\n            end\n\n            def call(env)\n              env[:data] << :test if env[:data]\n              @app.call(env)\n            end\n          end\n        end\n      end\n\n      let(:instance) { described_class.build(Vagrant::Test::ActionTest) }\n\n      context \"when hook uses short snake case name\" do\n        let(:hook_action_name) { :action_test }\n\n        it \"should execute the hook\" do\n          instance.call(data)\n          expect(data[:data]).to include(:first)\n        end\n      end\n\n      context \"when hook uses partial snake case name\" do\n        let(:hook_action_name) { :test_action_test }\n\n        it \"should execute the hook\" do\n          instance.call(data)\n          expect(data[:data]).to include(:first)\n        end\n      end\n\n      context \"when hook uses full snake case name\" do\n        let(:hook_action_name) { :vagrant_test_action_test }\n\n        it \"should execute the hook\" do\n          instance.call(data)\n          expect(data[:data]).to include(:first)\n        end\n      end\n\n      context \"when hook uses short class name\" do\n        let(:hook_action_name) { \"ActionTest\" }\n\n        it \"should execute the hook\" do\n          instance.call(data)\n          expect(data[:data]).to include(:first)\n        end\n      end\n\n      context \"when hook uses partial namespace class name\" do\n        let(:hook_action_name) { \"Test::ActionTest\" }\n\n        it \"should execute the hook\" do\n          instance.call(data)\n          expect(data[:data]).to include(:first)\n        end\n      end\n\n      context \"when hook uses full namespace class name\" do\n        let(:hook_action_name) { \"Vagrant::Test::ActionTest\" }\n\n        it \"should execute the hook\" do\n          instance.call(data)\n          expect(data[:data]).to include(:first)\n        end\n      end\n    end\n  end\n\n  describe \"#apply_dynamic_updates\" do\n    let(:env) { {triggers: triggers, machine: machine} }\n    let(:machine) { nil }\n    let(:triggers) { nil }\n\n    let(:subject) do\n      @subject ||= described_class.new.tap do |b|\n        b.primary = primary\n        b.use Vagrant::Action::Builtin::EnvSet\n        b.use Vagrant::Action::Builtin::Confirm\n      end\n    end\n\n    after { @subject = nil }\n\n    it \"should not modify the builder stack by default\" do\n      s1 = subject.stack.dup\n      subject.apply_dynamic_updates(env)\n      s2 = subject.stack.dup\n      expect(s1).to eq(s2)\n    end\n\n    context \"when an action hooks is defined\" do\n      let(:plugin) do\n        @plugin ||= Class.new(Vagrant.plugin(\"2\")) do\n          name \"Test Plugin\"\n          action_hook(:before_action, Vagrant::Action::Builtin::Confirm) do |hook|\n            hook.prepend(Vagrant::Action::Builtin::Call)\n          end\n        end\n      end\n\n      before { plugin }\n\n      after do\n        Vagrant.plugin(\"2\").manager.unregister(@plugin) if @plugin\n        @plugin = nil\n      end\n\n      it \"should modify the builder stack\" do\n        s1 = subject.stack.dup\n        subject.apply_dynamic_updates(env)\n        s2 = subject.stack.dup\n        expect(s1).not_to eq(s2)\n      end\n\n      it \"should add new action to the middle of the call stack\" do\n        subject.apply_dynamic_updates(env)\n        expect(subject.stack[1].first).to eq(Vagrant::Action::Builtin::Call)\n      end\n    end\n\n    context \"when triggers are enabled\" do\n      let(:triggers) { double(\"triggers\") }\n\n      before do\n        allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).\n          with(\"typed_triggers\").and_return(true)\n        allow(triggers).to receive(:find).and_return([])\n      end\n\n      it \"should not modify the builder stack by default\" do\n        s1 = subject.stack.dup\n        subject.apply_dynamic_updates(env)\n        s2 = subject.stack.dup\n        expect(s1).to eq(s2)\n      end\n\n      context \"when triggers are found\" do\n        let(:action) { Vagrant::Action::Builtin::EnvSet }\n\n        before { expect(triggers).to receive(:find).\n            with(action, timing, nil, type).and_return([true]) }\n\n        context \"for action type\" do\n          let(:type) { :action }\n\n          context \"for before timing\" do\n            let(:timing) { :before }\n\n            it \"should add trigger action to start of stack\" do\n              subject.apply_dynamic_updates(env)\n              expect(subject.stack[0].middleware).to eq(Vagrant::Action::Builtin::Trigger)\n            end\n\n            it \"should have timing and type arguments\" do\n              subject.apply_dynamic_updates(env)\n              args = subject.stack[0].arguments.parameters\n              expect(args).to include(type)\n              expect(args).to include(timing)\n              expect(args).to include(action.to_s)\n            end\n          end\n\n          context \"for after timing\" do\n            let(:timing) { :after }\n\n            it \"should add trigger action to middle of stack\" do\n              subject.apply_dynamic_updates(env)\n              expect(subject.stack[1].middleware).to eq(Vagrant::Action::Builtin::Trigger)\n            end\n\n            it \"should have timing and type arguments\" do\n              subject.apply_dynamic_updates(env)\n              args = subject.stack[1].arguments.parameters\n              expect(args).to include(type)\n              expect(args).to include(timing)\n              expect(args).to include(action.to_s)\n            end\n          end\n        end\n\n        context \"for hook type\" do\n          let(:type) { :hook }\n\n          context \"for before timing\" do\n            let(:timing) { :before }\n\n            it \"should add trigger action to start of stack\" do\n              subject.apply_dynamic_updates(env)\n              expect(subject.stack[0].middleware).to eq(Vagrant::Action::Builtin::Trigger)\n            end\n\n            it \"should have timing and type arguments\" do\n              subject.apply_dynamic_updates(env)\n              args = subject.stack[0].arguments.parameters\n              expect(args).to include(type)\n              expect(args).to include(timing)\n              expect(args).to include(action.to_s)\n            end\n          end\n\n          context \"for after timing\" do\n            let(:timing) { :after }\n\n            it \"should add trigger action to middle of stack\" do\n              subject.apply_dynamic_updates(env)\n              expect(subject.stack[1].first).to eq(Vagrant::Action::Builtin::Trigger)\n            end\n\n            it \"should have timing and type arguments\" do\n              subject.apply_dynamic_updates(env)\n              args = subject.stack[1].arguments.parameters\n              expect(args).to include(type)\n              expect(args).to include(timing)\n              expect(args).to include(action.to_s)\n            end\n          end\n        end\n      end\n    end\n  end\n\n  describe \"#apply_action_name\" do\n    let(:env) { {triggers: triggers, machine: machine, action_name: action_name, raw_action_name: raw_action_name} }\n    let(:raw_action_name) { :up }\n    let(:action_name) { \"machine_#{raw_action_name}\".to_sym }\n    let(:machine) { nil }\n    let(:triggers) { double(\"triggers\") }\n\n    let(:subject) do\n      @subject ||= described_class.new.tap do |b|\n        b.primary = primary\n        b.use Vagrant::Action::Builtin::EnvSet\n        b.use Vagrant::Action::Builtin::Confirm\n      end\n    end\n\n    before { allow(triggers).to receive(:find).and_return([]) }\n    after { @subject = nil }\n\n    context \"when a plugin has added an action hook using prepend\" do\n      let(:plugin) do\n        @plugin ||= Class.new(Vagrant.plugin(\"2\")) do\n          name \"Test Plugin\"\n          action_hook(:before_action, :machine_up) do |hook|\n            hook.prepend(Vagrant::Action::Builtin::Call)\n          end\n        end\n      end\n\n      before { plugin }\n\n      after do\n        Vagrant.plugin(\"2\").manager.unregister(@plugin) if @plugin\n        @plugin = nil\n      end\n\n      it \"should add new action to the beginning of the call stack\" do\n        subject.apply_action_name(env)\n        expect(subject.stack[0].first).to eq(Vagrant::Action::Builtin::Call)\n      end\n    end\n\n    context \"when trigger has been defined for raw action\" do\n      before do\n        expect(triggers).to receive(:find).with(raw_action_name, timing, nil, :action, all: true).\n          and_return([true])\n      end\n\n      context \"when timing is before\" do\n        let(:timing) { :before }\n\n        it \"should add a trigger action to the start of the stack\" do\n          subject.apply_action_name(env)\n          expect(subject.stack[0].first).to eq(Vagrant::Action::Builtin::Trigger)\n        end\n\n        it \"should include arguments to the trigger action\" do\n          subject.apply_action_name(env)\n          args = subject.stack[0].arguments.parameters\n          expect(args).to include(raw_action_name)\n          expect(args).to include(timing)\n          expect(args).to include(:action)\n        end\n      end\n\n      context \"when timing is after\" do\n        let(:timing) { :after }\n\n        it \"should add a trigger action to the end of the stack\" do\n          subject.apply_action_name(env)\n          expect(subject.stack.first.first).to eq(Vagrant::Action::Builtin::Delayed)\n        end\n\n        it \"should include arguments to the trigger action\" do\n          subject.apply_action_name(env)\n          builder = subject.stack.first.arguments.parameters.first\n          expect(builder).not_to be_nil\n          args = builder.stack.first.arguments.parameters\n          expect(args).to include(raw_action_name)\n          expect(args).to include(timing)\n          expect(args).to include(:action)\n        end\n      end\n    end\n\n    context \"when trigger has been defined for hook\" do\n      before do\n        allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).\n          with(\"typed_triggers\").and_return(true)\n        expect(triggers).to receive(:find).with(action_name, timing, nil, :hook).\n          and_return([true])\n      end\n\n      context \"when timing is before\" do\n        let(:timing) { :before }\n\n        it \"should add a trigger action to the start of the stack\" do\n          subject.apply_action_name(env)\n          expect(subject.stack[0].middleware).to eq(Vagrant::Action::Builtin::Trigger)\n        end\n\n        it \"should include arguments to the trigger action\" do\n          subject.apply_action_name(env)\n          args = subject.stack[0].arguments.parameters\n          expect(args).to include(action_name)\n          expect(args).to include(timing)\n          expect(args).to include(:hook)\n        end\n      end\n\n      context \"when timing is after\" do\n        let(:timing) { :after }\n\n        it \"should add a trigger action to the end of the stack\" do\n          subject.apply_action_name(env)\n          expect(subject.stack.last.first).to eq(Vagrant::Action::Builtin::Trigger)\n        end\n\n        it \"should include arguments to the trigger action\" do\n          subject.apply_action_name(env)\n          args = subject.stack.last.arguments.parameters\n          expect(args).to include(action_name)\n          expect(args).to include(timing)\n          expect(args).to include(:hook)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/box_add_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"digest/sha1\"\nrequire \"pathname\"\nrequire \"tempfile\"\nrequire \"tmpdir\"\nrequire \"webrick\"\n\nrequire \"fake_ftp\"\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire \"vagrant/util/file_checksum\"\n\ndescribe Vagrant::Action::Builtin::BoxAdd, :skip_windows, :bsdtar do\n  include_context \"unit\"\n\n  let(:app) { lambda { |env| } }\n  let(:env) { {\n    box_collection: box_collection,\n    hook: Proc.new { |name, env| env },\n    tmp_path: Pathname.new(Dir.mktmpdir(\"vagrant-test-builtin-box-add\")),\n    ui: Vagrant::UI::Silent.new,\n  } }\n\n  subject { described_class.new(app, env) }\n\n  let(:box_collection) { double(\"box_collection\") }\n  let(:iso_env) { isolated_environment }\n\n  let(:box) do\n    box_dir = iso_env.box3(\"foo\", \"1.0\", :virtualbox)\n    Vagrant::Box.new(\"foo\", :virtualbox, \"1.0\", box_dir)\n  end\n\n  after do\n    FileUtils.rm_rf(env[:tmp_path])\n  end\n\n  # Helper to quickly SHA1 checksum a path\n  def checksum(path)\n    FileChecksum.new(path, Digest::SHA1).checksum\n  end\n\n  def with_ftp_server(path, **opts)\n    path = Pathname.new(path)\n\n    port = nil\n    server = nil\n    with_random_port do |port1, port2|\n      port = port1\n      server = FakeFtp::Server.new(port1, port2)\n    end\n    server.add_file(path.basename.to_s, path.read)\n    server.start\n    yield port\n  ensure\n    server.stop rescue nil\n  end\n\n  def with_web_server(path, **opts)\n    tf = Tempfile.new(\"vagrant-web-server\")\n    tf.close\n\n    opts[:json_type] ||= \"application/json\"\n\n    mime_types = WEBrick::HTTPUtils::DefaultMimeTypes\n    mime_types.store \"json\", opts[:json_type]\n\n    port   = 3838\n    server = WEBrick::HTTPServer.new(\n      AccessLog: [],\n      Logger: WEBrick::Log.new(tf.path, 7),\n      Port: port,\n      DocumentRoot: path.dirname.to_s,\n      MimeTypes: mime_types)\n    thr = Thread.new { server.start }\n    yield port\n  ensure\n    tf.unlink\n    server.shutdown rescue nil\n    thr.join rescue nil\n  end\n\n  before do\n    allow(box_collection).to receive(:find).and_return(nil)\n  end\n\n  context \"the download location is locked\" do\n    let(:box_path) { iso_env.box2_file(:virtualbox) }\n    let(:mutex_path) {  env[:tmp_path].join(\"box\" + Digest::SHA1.hexdigest(\"file://\" + box_path.to_s) + \".lock\").to_s }\n\n    before do\n      # Lock file\n      @f = File.open(mutex_path, \"w+\") \n      @f.flock(File::LOCK_EX|File::LOCK_NB) \n    end\n  \n    after { @f.close }\n    \n    it \"raises a download error\" do\n      env[:box_name] = \"foo\"\n      env[:box_url] = box_path.to_s\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::DownloadAlreadyInProgress)\n    end\n  end\n\n  context \"with box file directly\" do\n    it \"adds it\" do\n      box_path = iso_env.box2_file(:virtualbox)\n\n      env[:box_name] = \"foo\"\n      env[:box_url] = box_path.to_s\n      env[:box_architecture] = \"x86_64\"\n\n      expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n        expect(checksum(path)).to eq(checksum(box_path))\n        expect(name).to eq(\"foo\")\n        expect(version).to eq(\"0\")\n        expect(opts[:architecture]).to eq(\"x86_64\")\n        expect(opts[:metadata_url]).to be_nil\n        true\n      }.and_return(box)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n    end\n\n    it \"adds from multiple URLs\" do\n      box_path = iso_env.box2_file(:virtualbox)\n\n      env[:box_name] = \"foo\"\n      env[:box_url] = [\n        \"/foo/bar/baz\",\n        box_path.to_s,\n      ]\n\n      expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n        expect(checksum(path)).to eq(checksum(box_path))\n        expect(name).to eq(\"foo\")\n        expect(version).to eq(\"0\")\n        expect(opts[:metadata_url]).to be_nil\n        true\n      }.and_return(box)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n    end\n\n    it \"adds from HTTP URL\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      with_web_server(box_path) do |port|\n        env[:box_name] = \"foo\"\n        env[:box_url] = \"http://127.0.0.1:#{port}/#{box_path.basename}\"\n\n        expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n          expect(checksum(path)).to eq(checksum(box_path))\n          expect(name).to eq(\"foo\")\n          expect(version).to eq(\"0\")\n          expect(opts[:metadata_url]).to be_nil\n          true\n        }.and_return(box)\n\n        expect(app).to receive(:call).with(env)\n\n        subject.call(env)\n      end\n    end\n\n    it \"adds from FTP URL\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      with_ftp_server(box_path) do |port|\n        env[:box_name] = \"foo\"\n        env[:box_url] = \"ftp://127.0.0.1:#{port}/#{box_path.basename}\"\n\n        expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n          expect(checksum(path)).to eq(checksum(box_path))\n          expect(name).to eq(\"foo\")\n          expect(version).to eq(\"0\")\n          expect(opts[:metadata_url]).to be_nil\n          true\n        }.and_return(box)\n\n        expect(app).to receive(:call).with(env)\n\n        subject.call(env)\n      end\n    end\n\n    it \"raises an error if no name is given\" do\n      box_path = iso_env.box2_file(:virtualbox)\n\n      env[:box_url] = box_path.to_s\n\n      expect(box_collection).to receive(:add).never\n      expect(app).to receive(:call).never\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::BoxAddNameRequired)\n    end\n\n    it \"raises an error if the box already exists\" do\n      box_path = iso_env.box2_file(:virtualbox)\n\n      env[:box_name] = \"foo\"\n      env[:box_url] = box_path.to_s\n      env[:box_provider] = \"virtualbox\"\n\n      expect(box_collection).to receive(:find).with(\n        \"foo\", [\"virtualbox\"], \"0\", nil).and_return(box)\n      expect(box_collection).to receive(:add).never\n      expect(app).to receive(:call).never\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::BoxAlreadyExists)\n    end\n\n    it \"raises an error if checksum specified and doesn't match\" do\n      box_path = iso_env.box2_file(:virtualbox)\n\n      env[:box_name] = \"foo\"\n      env[:box_url] = box_path.to_s\n      env[:box_checksum] = checksum(box_path) + \"A\"\n      env[:box_checksum_type] = \"sha1\"\n\n      expect(box_collection).to receive(:add).never\n      expect(app).to receive(:call).never\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::BoxChecksumMismatch)\n    end\n\n    it \"strips space if checksum specified ends or begins with blank space\" do\n      box_path = iso_env.box2_file(:virtualbox)\n\n      box = double(\n        name: \"foo\",\n        version: \"1.2.3\",\n        provider: \"virtualbox\",\n      )\n\n      env[:box_name] = \"foo\"\n      env[:box_url] = box_path.to_s\n      env[:box_checksum] = \" #{checksum(box_path)} \"\n      env[:box_checksum_type] = \"sha1\"\n\n      expect(box_collection).to receive(:add).and_return(box)\n\n      expect { subject.call(env) }.to_not raise_error\n    end\n\n    it \"ignores checksums if empty string\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      with_web_server(box_path) do |port|\n        env[:box_name] = \"foo\"\n        env[:box_url] = \"http://127.0.0.1:#{port}/#{box_path.basename}\"\n        env[:box_checksum] = \"\"\n        env[:box_checksum_type] = \"\"\n\n\n        expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n          expect(checksum(path)).to eq(checksum(box_path))\n          expect(name).to eq(\"foo\")\n          expect(version).to eq(\"0\")\n          expect(opts[:metadata_url]).to be_nil\n          true\n        }.and_return(box)\n\n        expect(app).to receive(:call).with(env)\n\n        subject.call(env)\n      end\n    end\n\n    it \"does not raise an error if the checksum has different case\" do\n      box_path = iso_env.box2_file(:virtualbox)\n\n      box = double(\n        name: \"foo\",\n        version: \"1.2.3\",\n        provider: \"virtualbox\",\n      )\n\n      env[:box_name] = box.name\n      env[:box_url] = box_path.to_s\n      env[:box_checksum] = checksum(box_path)\n      env[:box_checksum_type] = \"sha1\"\n\n      # Convert to a different case\n      env[:box_checksum].upcase!\n\n      expect(box_collection).to receive(:add).and_return(box)\n\n      expect { subject.call(env) }.to_not raise_error\n    end\n\n    it \"raises an error if the box path doesn't exist\" do\n      box_path = iso_env.box2_file(:virtualbox)\n\n      env[:box_name] = \"foo\"\n      env[:box_url] = box_path.to_s + \"nope\"\n\n      expect(box_collection).to receive(:add).never\n      expect(app).to receive(:call).never\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::DownloaderError)\n    end\n\n    it \"raises an error if a version was specified\" do\n      box_path = iso_env.box2_file(:virtualbox)\n\n      env[:box_name] = \"foo\"\n      env[:box_url] = box_path.to_s\n      env[:box_version] = \"1\"\n\n      expect(box_collection).to receive(:add).never\n\n      expect(app).to receive(:call).never\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::BoxAddDirectVersion)\n    end\n\n    it \"force adds if exists and specified\" do\n      box_path = iso_env.box2_file(:virtualbox)\n\n      env[:box_force] = true\n      env[:box_name] = \"foo\"\n      env[:box_url] = box_path.to_s\n      env[:box_provider] = \"virtualbox\"\n\n      allow(box_collection).to receive(:find).and_return(box)\n      expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n        expect(checksum(path)).to eq(checksum(box_path))\n        expect(name).to eq(\"foo\")\n        expect(version).to eq(\"0\")\n        expect(opts[:metadata_url]).to be_nil\n        true\n      }.and_return(box)\n      expect(app).to receive(:call).with(env).once\n\n      subject.call(env)\n    end\n\n    context \"with a box name accidentally set as a URL\" do\n      it \"displays a warning to the user\" do\n        box_path = iso_env.box2_file(:virtualbox)\n        port = 9999\n\n        box_url_name = \"http://127.0.0.1:#{port}/#{box_path.basename}\"\n        env[:box_name] = box_url_name\n\n        expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n          expect(name).to eq(box_url_name)\n          expect(version).to eq(\"0\")\n          expect(opts[:metadata_url]).to be_nil\n          true\n        }.and_return(box)\n\n        expect(app).to receive(:call).with(env)\n\n        expect(env[:ui]).to receive(:warn).and_call_original\n          .with(/It looks like you attempted to add a box with a URL for the name/)\n\n        subject.call(env)\n      end\n    end\n\n    context \"with a box name containing invalid URI characters\" do\n      it \"should not raise an error\" do\n        box_path = iso_env.box2_file(:virtualbox)\n        box_url_name = \"box name with spaces\"\n        env[:box_name] = box_url_name\n\n        expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n          expect(name).to eq(box_url_name)\n          expect(version).to eq(\"0\")\n          expect(opts[:metadata_url]).to be_nil\n          true\n        }.and_return(box)\n\n        expect(app).to receive(:call).with(env)\n\n        subject.call(env)\n      end\n    end\n\n    context \"with URL containing credentials\" do\n      let(:username){ \"box-username\" }\n      let(:password){ \"box-password\" }\n\n      it \"scrubs credentials in output\" do\n        box_path = iso_env.box2_file(:virtualbox)\n        with_web_server(box_path) do |port|\n          env[:box_name] = \"foo\"\n          env[:box_url] = \"http://#{username}:#{password}@127.0.0.1:#{port}/#{box_path.basename}\"\n\n          expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n            expect(checksum(path)).to eq(checksum(box_path))\n            expect(name).to eq(\"foo\")\n            expect(version).to eq(\"0\")\n            expect(opts[:metadata_url]).to be_nil\n            true\n          }.and_return(box)\n\n          allow(env[:ui]).to receive(:detail).and_call_original\n          expect(env[:ui]).to receive(:detail).with(/.*http:\\/\\/\\*+(?::\\*+)?@127\\.0\\.0\\.1:#{port}\\/#{box_path.basename}.*/).\n            and_call_original\n          expect(app).to receive(:call).with(env)\n\n          subject.call(env)\n        end\n      end\n    end\n  end\n\n  context \"with box metadata\" do\n    it \"adds from HTTP URL\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      tf = Tempfile.new([\"vagrant-test-box-http-url\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      md_path = Pathname.new(tf.path)\n      with_web_server(md_path) do |port|\n        env[:box_url] = \"http://127.0.0.1:#{port}/#{md_path.basename}\"\n\n        expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n          expect(name).to eq(\"foo/bar\")\n          expect(version).to eq(\"0.7\")\n          expect(checksum(path)).to eq(checksum(box_path))\n          expect(opts[:metadata_url]).to eq(env[:box_url])\n          true\n        }.and_return(box)\n\n        expect(app).to receive(:call).with(env)\n\n        subject.call(env)\n      end\n    end\n\n    it \"adds from HTTP URL with complex JSON mime type\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      tf = Tempfile.new([\"vagrant-test-http-json\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      opts = { json_type: \"application/json; charset=utf-8\" }\n\n      md_path = Pathname.new(tf.path)\n      with_web_server(md_path, **opts) do |port|\n        env[:box_url] = \"http://127.0.0.1:#{port}/#{md_path.basename}\"\n\n        expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n          expect(name).to eq(\"foo/bar\")\n          expect(version).to eq(\"0.7\")\n          expect(checksum(path)).to eq(checksum(box_path))\n          expect(opts[:metadata_url]).to eq(env[:box_url])\n          true\n        }.and_return(box)\n\n        expect(app).to receive(:call).with(env)\n\n        subject.call(env)\n      end\n    end\n\n    it \"adds from shorthand path\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      td = Pathname.new(Dir.mktmpdir(\"vagrant-test-box-add-shorthand-path\"))\n      tf = td.join(\"mitchellh\", \"precise64.json\")\n      tf.dirname.mkpath\n      tf.open(\"w\") do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"mitchellh/precise64\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n      end\n\n      with_web_server(tf.dirname) do |port|\n        url = \"http://127.0.0.1:#{port}\"\n        env[:box_url] = \"mitchellh/precise64.json\"\n\n        expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n          expect(name).to eq(\"mitchellh/precise64\")\n          expect(version).to eq(\"0.7\")\n          expect(checksum(path)).to eq(checksum(box_path))\n          expect(opts[:metadata_url]).to eq(\n            \"#{url}/#{env[:box_url]}\")\n          true\n        }.and_return(box)\n\n        expect(app).to receive(:call).with(env)\n\n        with_temp_env(\"VAGRANT_SERVER_URL\" => url) do\n          subject.call(env)\n        end\n      end\n\n      FileUtils.rm_rf(td)\n    end\n\n    it \"adds from API endpoint when available\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      td = Pathname.new(Dir.mktmpdir(\"vagrant-test-box-api-endpoint\"))\n      tf = td.join(\"mitchellh\", \"precise64.json\")\n      tf.dirname.mkpath\n      tf.open(\"w\") do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"mitchellh/precise64\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n      end\n      apif = td.join(\"api\", \"v2\", \"vagrant\", \"mitchellh\", \"precise64.json\")\n      apif.dirname.mkpath\n      apif.open(\"w\") do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"mitchellh/precise64\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.8\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n      end\n\n      with_web_server(tf.dirname) do |port|\n        url = \"http://127.0.0.1:#{port}\"\n        env[:box_url] = \"mitchellh/precise64.json\"\n        env[:box_server_url] = url\n\n        expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n          expect(name).to eq(\"mitchellh/precise64\")\n          expect(version).to eq(\"0.8\")\n          expect(checksum(path)).to eq(checksum(box_path))\n          expect(opts[:metadata_url]).to eq(\n            \"#{url}/api/v2/vagrant/#{env[:box_url]}\")\n          true\n        }.and_return(box)\n\n        expect(app).to receive(:call).with(env)\n\n        subject.call(env)\n      end\n\n      FileUtils.rm_rf(td)\n    end\n\n    it \"add from shorthand path with configured server url\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      td = Pathname.new(Dir.mktmpdir(\"vagrant-test-box-add-server-url\"))\n      tf = td.join(\"mitchellh\", \"precise64.json\")\n      tf.dirname.mkpath\n      tf.open(\"w\") do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"mitchellh/precise64\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n      end\n\n      with_web_server(tf.dirname) do |port|\n        url = \"http://127.0.0.1:#{port}\"\n        env[:box_url] = \"mitchellh/precise64.json\"\n        env[:box_server_url] = url\n\n        expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n          expect(name).to eq(\"mitchellh/precise64\")\n          expect(version).to eq(\"0.7\")\n          expect(checksum(path)).to eq(checksum(box_path))\n          expect(opts[:metadata_url]).to eq(\n            \"#{url}/#{env[:box_url]}\")\n          true\n        }.and_return(box)\n\n        expect(app).to receive(:call).with(env)\n\n        subject.call(env)\n      end\n\n      FileUtils.rm_rf(td)\n    end\n\n    it \"authenticates HTTP URLs and adds them\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      tf = Tempfile.new([\"vagrant-test-http\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"bar\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      md_path = Pathname.new(tf.path)\n      with_web_server(md_path) do |port|\n        real_url = \"http://127.0.0.1:#{port}/#{md_path.basename}\"\n\n        # Set the box URL to something fake so we can modify it in place\n        env[:box_url] = \"foo\"\n\n        env[:hook] = double(\"hook\")\n\n        expect(env[:hook]).to receive(:call).with(:authenticate_box_downloader, any_args).at_least(:once)\n\n        allow(env[:hook]).to receive(:call).with(:authenticate_box_url, any_args).at_least(:once) do |name, opts|\n          if opts[:box_urls] == [\"foo\"]\n            next { box_urls: [real_url] }\n          elsif opts[:box_urls] == [\"bar\"]\n            next { box_urls: [box_path.to_s] }\n          else\n            raise \"UNKNOWN: #{opts[:box_urls].inspect}\"\n          end\n        end\n\n        expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n          expect(name).to eq(\"foo/bar\")\n          expect(version).to eq(\"0.7\")\n          expect(checksum(path)).to eq(checksum(box_path))\n          expect(opts[:metadata_url]).to eq(\"foo\")\n          true\n        }.and_return(box)\n\n        expect(app).to receive(:call).with(env)\n\n        subject.call(env)\n      end\n    end\n\n    it \"authenticates HTTP URLs and adds them directly\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      tf = Tempfile.new([\"vagrant-test-http\", \".box\"]).tap do |f|\n        f.write()\n        f.close\n      end\n\n      md_path = Pathname.new(tf.path)\n      with_web_server(md_path) do |port|\n        real_url = \"http://127.0.0.1:#{port}/#{md_path.basename}\"\n\n        # Set the box URL to something fake so we can modify it in place\n        env[:box_url] = \"foo\"\n        env[:hook] = double(\"hook\")\n        env[:box_name] = \"foo/bar\"\n        env[:box_provider] = \"virtualbox\"\n        env[:box_checksum] = checksum(box_path)\n\n        expect(env[:hook]).to receive(:call).with(:authenticate_box_downloader, any_args).at_least(:once)\n\n        allow(env[:hook]).to receive(:call).with(:authenticate_box_url, any_args).at_least(:once) do |name, opts|\n          if opts[:box_urls] == [\"foo\"]\n            next { box_urls: [real_url] }\n          else\n            raise \"UNKNOWN: #{opts[:box_urls].inspect}\"\n          end\n        end\n\n        expect(subject).to receive(:add_direct).with([real_url], anything)\n        expect(app).to receive(:call).with(env)\n\n        subject.call(env)\n      end\n    end\n\n    it \"adds from HTTP URL with a checksum\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      tf = Tempfile.new([\"vagrant-test-http-checksum\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\",\n                  \"checksum_type\": \"sha1\",\n                  \"checksum\": \"#{checksum(box_path)}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      md_path = Pathname.new(tf.path)\n      with_web_server(md_path) do |port|\n        env[:box_url] = \"http://127.0.0.1:#{port}/#{md_path.basename}\"\n\n        expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n          expect(name).to eq(\"foo/bar\")\n          expect(version).to eq(\"0.7\")\n          expect(checksum(path)).to eq(checksum(box_path))\n          expect(opts[:metadata_url]).to eq(env[:box_url])\n          true\n        }.and_return(box)\n\n        expect(app).to receive(:call).with(env)\n\n        subject.call(env)\n      end\n    end\n\n    it \"raises an exception if checksum given but not correct\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      tf = Tempfile.new([\"vagrant-test-bad-checksum\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\",\n                  \"checksum_type\": \"sha1\",\n                  \"checksum\": \"thisisnotcorrect\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      md_path = Pathname.new(tf.path)\n      with_web_server(md_path) do |port|\n        env[:box_url] = \"http://127.0.0.1:#{port}/#{md_path.basename}\"\n\n        expect(box_collection).to receive(:add).never\n        expect(app).to receive(:call).never\n\n        expect { subject.call(env) }.\n          to raise_error(Vagrant::Errors::BoxChecksumMismatch)\n      end\n    end\n\n    it \"raises an error if no Vagrant server is set\" do\n      env[:box_url] = \"mitchellh/precise64.json\"\n\n      expect(box_collection).to receive(:add).never\n      expect(app).to receive(:call).never\n\n      allow(Vagrant).to receive(:server_url).and_return(nil)\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::BoxServerNotSet)\n    end\n\n    it \"raises an error if shorthand is invalid\" do\n      path = Dir::Tmpname.create(\"vagrant-shorthand-invalid\") {}\n\n      with_web_server(Pathname.new(path)) do |port|\n        env[:box_url] = \"mitchellh/precise64.json\"\n\n        expect(box_collection).to receive(:add).never\n        expect(app).to receive(:call).never\n\n        url = \"http://127.0.0.1:#{port}\"\n        with_temp_env(\"VAGRANT_SERVER_URL\" => url) do\n          expect { subject.call(env) }.\n            to raise_error(Vagrant::Errors::BoxAddShortNotFound)\n        end\n      end\n    end\n\n    it \"raises an error if downloading metadata fails\" do\n      path = Dir::Tmpname.create(\"vagrant-shorthand-invalid\") {}\n\n      with_web_server(Pathname.new(path)) do |port|\n        env[:box_url] = \"http://127.0.0.1:#{port}/bad\"\n\n        expect(box_collection).to receive(:add).never\n        expect(app).to receive(:call).never\n\n        expect { subject.call(env) }.\n          to raise_error(Vagrant::Errors::BoxMetadataDownloadError)\n      end\n    end\n\n    it \"raises an error if multiple metadata URLs are given\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      tf = Tempfile.new([\"vagrant-box-multi-metadata\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      env[:box_url] = [\n        \"/foo/bar/baz\",\n        tf.path,\n      ]\n      expect(box_collection).to receive(:add).never\n      expect(app).to receive(:call).never\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::BoxAddMetadataMultiURL)\n    end\n\n    it \"adds the latest version of a box with only one provider\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      tf = Tempfile.new([\"vagrant-box-latest-version\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      env[:box_url] = tf.path\n      expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n        expect(checksum(path)).to eq(checksum(box_path))\n        expect(name).to eq(\"foo/bar\")\n        expect(version).to eq(\"0.7\")\n        expect(opts[:metadata_url]).to eq(\"file://#{tf.path}\")\n        true\n      }.and_return(box)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n    end\n\n    context \"with box architecture configured\" do\n      before do\n        allow(Vagrant::Util::Platform).to receive(:architecture).and_return(\"test-arch\")\n      end\n\n      context \"set to :auto\" do\n        before do\n          env[:box_architecture] = :auto\n        end\n\n        it \"adds the latest version of a box with only one provider and matching architecture\" do\n          box_path = iso_env.box2_file(:virtualbox)\n          tf = Tempfile.new([\"vagrant-box-latest-version\", \".json\"]).tap do |f|\n            f.write(\n              {\n                name: \"foo/bar\",\n                versions: [\n                  {\n                    version: \"0.5\",\n                  },\n                  {\n                    version: \"0.7\",\n                    providers: [\n                      {\n                        name: \"virtualbox\",\n                        url: \"/dev/null/invalid.path\",\n                        architecture: \"amd64\",\n                        default_architecture: true,\n                      },\n                      {\n                        name: \"virtualbox\",\n                        url: \"#{box_path}\",\n                        architecture: \"test-arch\",\n                        default_architecture: false,\n                      }\n                    ]\n                  }\n                ]\n              }.to_json\n            )\n            f.close\n          end\n\n          env[:box_url] = tf.path\n          expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n            expect(checksum(path)).to eq(checksum(box_path))\n            expect(name).to eq(\"foo/bar\")\n            expect(version).to eq(\"0.7\")\n            expect(opts[:metadata_url]).to eq(\"file://#{tf.path}\")\n            expect(opts[:architecture]).to eq(\"test-arch\")\n            true\n          }.and_return(box)\n\n          expect(app).to receive(:call).with(env)\n\n          subject.call(env)\n        end\n\n        it \"adds the latest version of a box with multiple providers and only one provider matching architecture\" do\n          box_path = iso_env.box2_file(:virtualbox)\n          tf = Tempfile.new([\"vagrant-box-latest-version\", \".json\"]).tap do |f|\n            # NOTE: The order of the providers here matters. The correct match needs\n            # to be last in order to trigger the error this test was written for to\n            # catch any regression.\n            f.write(\n              {\n                name: \"foo/bar\",\n                versions: [\n                  {\n                    version: \"0.5\",\n                  },\n                  {\n                    version: \"0.7\",\n                    providers: [\n                      {\n                        name: \"virtualbox\",\n                        url: \"/dev/null/invalid.path\",\n                        architecture: \"amd64\",\n                        default_architecture: true,\n                      },\n                      {\n                        name: \"hyperv\",\n                        url: \"#{box_path}\",\n                        architecture: \"test-arch\",\n                        default_architecture: true,\n                      }\n                    ]\n                  }\n                ]\n              }.to_json\n            )\n            f.close\n          end\n\n          env[:box_url] = tf.path\n          expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n            expect(checksum(path)).to eq(checksum(box_path))\n            expect(name).to eq(\"foo/bar\")\n            expect(version).to eq(\"0.7\")\n            expect(opts[:metadata_url]).to eq(\"file://#{tf.path}\")\n            expect(opts[:architecture]).to eq(\"test-arch\")\n            true\n          }.and_return(box)\n\n          expect(app).to receive(:call).with(env)\n\n          subject.call(env)\n        end\n\n        it \"adds the latest version of a box with only one provider and unknown architecture set as default\" do\n          box_path = iso_env.box2_file(:virtualbox)\n          tf = Tempfile.new([\"vagrant-box-latest-version\", \".json\"]).tap do |f|\n            f.write(\n              {\n                name: \"foo/bar\",\n                versions: [\n                  {\n                    version: \"0.5\",\n                  },\n                  {\n                    version: \"0.7\",\n                    providers: [\n                      {\n                        name: \"virtualbox\",\n                        url: \"#{box_path}\",\n                        architecture: \"unknown\",\n                        default_architecture: true,\n                      },\n                      {\n                        name: \"virtualbox\",\n                        url: \"/dev/null/invalid.path\",\n                        architecture: \"amd64\",\n                        default_architecture: false,\n                      }\n                    ]\n                  }\n                ]\n              }.to_json\n            )\n            f.close\n          end\n\n          env[:box_url] = tf.path\n          expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n            expect(checksum(path)).to eq(checksum(box_path))\n            expect(name).to eq(\"foo/bar\")\n            expect(version).to eq(\"0.7\")\n            expect(opts[:metadata_url]).to eq(\"file://#{tf.path}\")\n            expect(opts[:architecture]).to be_nil\n            true\n          }.and_return(box)\n\n          expect(app).to receive(:call).with(env)\n\n          subject.call(env)\n        end\n\n        it \"errors adding latest version of a box with only one provider and no matching architecture\" do\n          allow(Vagrant::Util::Platform).to receive(:architecture).and_return(\"test-arch\")\n\n          box_path = iso_env.box2_file(:virtualbox)\n          tf = Tempfile.new([\"vagrant-box-latest-version\", \".json\"]).tap do |f|\n            f.write(\n              {\n                name: \"foo/bar\",\n                versions: [\n                  {\n                    version: \"0.5\",\n                  },\n                  {\n                    version: \"0.7\",\n                    providers: [\n                      {\n                        name: \"virtualbox\",\n                        url: \"#{box_path}\",\n                        architecture: \"amd64\",\n                        default_architecture: true,\n                      },\n                      {\n                        name: \"virtualbox\",\n                        url: \"/dev/null/invalid.path\",\n                        architecture: \"arm64\",\n                        default_architecture: false,\n                      }\n                    ]\n                  }\n                ]\n              }.to_json\n            )\n            f.close\n          end\n\n          env[:box_url] = tf.path\n\n          expect {\n            subject.call(env)\n          }.to raise_error(Vagrant::Errors::BoxAddNoMatchingVersion)\n        end\n      end\n\n      context \"set to nil\" do\n        before do\n          env[:box_architecture] = nil\n        end\n\n        it \"adds the latest version of a box with only one provider and default architecture\" do\n          allow(Vagrant::Util::Platform).to receive(:architecture).and_return(\"default-arch\")\n          box_path = iso_env.box2_file(:virtualbox)\n          tf = Tempfile.new([\"vagrant-box-latest-version\", \".json\"]).tap do |f|\n            f.write(\n              {\n                name: \"foo/bar\",\n                versions: [\n                  {\n                    version: \"0.5\",\n                  },\n                  {\n                    version: \"0.7\",\n                    providers: [\n                      {\n                        name: \"virtualbox\",\n                        url: \"#{box_path}\",\n                        architecture: \"default-arch\",\n                        default_architecture: true,\n                      },\n                      {\n                        name: \"virtualbox\",\n                        url: \"/dev/null/invalid.path\",\n                        architecture: \"test-arch\",\n                        default_architecture: false,\n                      }\n                    ]\n                  }\n                ]\n              }.to_json\n            )\n            f.close\n          end\n\n          env[:box_url] = tf.path\n          expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n            expect(checksum(path)).to eq(checksum(box_path))\n            expect(name).to eq(\"foo/bar\")\n            expect(version).to eq(\"0.7\")\n            expect(opts[:metadata_url]).to eq(\"file://#{tf.path}\")\n            expect(opts[:architecture]).to eq(\"default-arch\")\n            true\n          }.and_return(box)\n\n          expect(app).to receive(:call).with(env)\n\n          subject.call(env)\n        end\n      end\n\n      context \"set to explicit value\" do\n        before do\n          env[:box_architecture] = \"arm64\"\n        end\n\n        it \"adds the latest version of a box with only one provider and matching architecture\" do\n          box_path = iso_env.box2_file(:virtualbox)\n          tf = Tempfile.new([\"vagrant-box-latest-version\", \".json\"]).tap do |f|\n            f.write(\n              {\n                name: \"foo/bar\",\n                versions: [\n                  {\n                    version: \"0.5\",\n                  },\n                  {\n                    version: \"0.7\",\n                    providers: [\n                      {\n                        name: \"virtualbox\",\n                        url: \"#{box_path}\",\n                        architecture: \"arm64\",\n                        default_architecture: false,\n                      },\n                      {\n                        name: \"virtualbox\",\n                        url: \"/dev/null/invalid.path\",\n                        architecture: \"test-arch\",\n                        default_architecture: true,\n                      }\n                    ]\n                  }\n                ]\n              }.to_json\n            )\n            f.close\n          end\n\n          env[:box_url] = tf.path\n          expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n            expect(checksum(path)).to eq(checksum(box_path))\n            expect(name).to eq(\"foo/bar\")\n            expect(version).to eq(\"0.7\")\n            expect(opts[:metadata_url]).to eq(\"file://#{tf.path}\")\n            expect(opts[:architecture]).to eq(\"arm64\")\n            true\n          }.and_return(box)\n\n          expect(app).to receive(:call).with(env)\n\n          subject.call(env)\n        end\n\n        it \"errors adding the latest version of a box with only one provider and no matching architecture\" do\n          box_path = iso_env.box2_file(:virtualbox)\n          tf = Tempfile.new([\"vagrant-box-latest-version\", \".json\"]).tap do |f|\n            f.write(\n              {\n                name: \"foo/bar\",\n                versions: [\n                  {\n                    version: \"0.5\",\n                  },\n                  {\n                    version: \"0.7\",\n                    providers: [\n                      {\n                        name: \"virtualbox\",\n                        url: \"#{box_path}\",\n                        architecture: \"amd64\",\n                        default_architecture: true,\n                      },\n                      {\n                        name: \"virtualbox\",\n                        url: \"/dev/null/invalid.path\",\n                        architecture: \"386\",\n                        default_architecture: false,\n                      }\n                    ]\n                  }\n                ]\n              }.to_json\n            )\n            f.close\n          end\n\n          env[:box_url] = tf.path\n\n          expect {\n            subject.call(env)\n          }.to raise_error(Vagrant::Errors::BoxAddNoMatchingVersion)\n        end\n      end\n    end\n\n    it \"adds the latest version of a box with the specified provider\" do\n      box_path = iso_env.box2_file(:vmware)\n      tf = Tempfile.new([\"vagrant-box-specific-provider\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{iso_env.box2_file(:virtualbox)}\"\n                },\n                {\n                  \"name\": \"vmware\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      env[:box_url] = tf.path\n      env[:box_provider] = \"vmware\"\n      expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n        expect(checksum(path)).to eq(checksum(box_path))\n        expect(name).to eq(\"foo/bar\")\n        expect(version).to eq(\"0.7\")\n        expect(opts[:metadata_url]).to eq(\"file://#{tf.path}\")\n        true\n      }.and_return(box)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n\n      expect(env[:box_added]).to equal(box)\n    end\n\n    it \"adds the latest version of a box with the specified provider, even if not latest\" do\n      box_path = iso_env.box2_file(:vmware)\n      tf = Tempfile.new([\"vagrant-box-specified-provider\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{iso_env.box2_file(:virtualbox)}\"\n                },\n                {\n                  \"name\": \"vmware\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            },\n            {\n              \"version\": \"1.5\"\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      env[:box_url] = tf.path\n      env[:box_provider] = \"vmware\"\n      expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n        expect(checksum(path)).to eq(checksum(box_path))\n        expect(name).to eq(\"foo/bar\")\n        expect(version).to eq(\"0.7\")\n        expect(opts[:metadata_url]).to eq(\"file://#{tf.path}\")\n        true\n      }.and_return(box)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n\n      expect(env[:box_added]).to equal(box)\n    end\n\n    it \"adds the constrained version of a box with the only provider\" do\n      box_path = iso_env.box2_file(:vmware)\n      tf = Tempfile.new([\"vagrant-box-constrained\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\",\n              \"providers\": [\n                {\n                  \"name\": \"vmware\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            },\n            { \"version\": \"1.1\" }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      env[:box_url] = tf.path\n      env[:box_version] = \"~> 0.1\"\n      expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n        expect(checksum(path)).to eq(checksum(box_path))\n        expect(name).to eq(\"foo/bar\")\n        expect(version).to eq(\"0.5\")\n        expect(opts[:metadata_url]).to eq(\"file://#{tf.path}\")\n        true\n      }.and_return(box)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n\n      expect(env[:box_added]).to equal(box)\n    end\n\n    it \"adds the constrained version of a box with the specified provider\" do\n      box_path = iso_env.box2_file(:vmware)\n      tf = Tempfile.new([\"vagrant-box-constrained-provider\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\",\n              \"providers\": [\n                {\n                  \"name\": \"vmware\",\n                  \"url\":  \"#{box_path}\"\n                },\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{iso_env.box2_file(:virtualbox)}\"\n                }\n              ]\n            },\n            { \"version\": \"1.1\" }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      env[:box_url] = tf.path\n      env[:box_provider] = \"vmware\"\n      env[:box_version] = \"~> 0.1\"\n      expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n        expect(checksum(path)).to eq(checksum(box_path))\n        expect(name).to eq(\"foo/bar\")\n        expect(version).to eq(\"0.5\")\n        expect(opts[:metadata_url]).to eq(\"file://#{tf.path}\")\n        true\n      }.and_return(box)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n\n      expect(env[:box_added]).to equal(box)\n    end\n\n    it \"adds the latest version of a box with any specified provider\" do\n      box_path = iso_env.box2_file(:vmware)\n      tf = Tempfile.new([\"vagrant-box-latest-version\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{iso_env.box2_file(:virtualbox)}\"\n                }\n              ]\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"vmware\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      env[:box_url] = tf.path\n      env[:box_provider] = [\"virtualbox\", \"vmware\"]\n      expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n        expect(checksum(path)).to eq(checksum(box_path))\n        expect(name).to eq(\"foo/bar\")\n        expect(version).to eq(\"0.7\")\n        expect(opts[:metadata_url]).to eq(\"file://#{tf.path}\")\n        true\n      }.and_return(box)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n\n      expect(env[:box_added]).to equal(box)\n    end\n\n    it \"asks the user what provider if multiple options\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      tf = Tempfile.new([\"vagrant-box-provider-asks\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\"\n                },\n                {\n                  \"name\": \"vmware\",\n                  \"url\":  \"#{iso_env.box2_file(:vmware)}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      env[:box_url] = tf.path\n\n      expect(env[:ui]).to receive(:ask).and_return(\"1\")\n\n      expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n        expect(checksum(path)).to eq(checksum(box_path))\n        expect(name).to eq(\"foo/bar\")\n        expect(version).to eq(\"0.7\")\n        expect(opts[:metadata_url]).to eq(\"file://#{tf.path}\")\n        true\n      }.and_return(box)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n    end\n\n    it \"raises an exception if the name doesn't match a requested name\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      tf = Tempfile.new([\"vagrant-box-name-mismatch\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      env[:box_name] = \"foo\"\n      env[:box_url] = tf.path\n\n      expect(box_collection).to receive(:add).never\n      expect(app).to receive(:call).never\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::BoxAddNameMismatch)\n    end\n\n    it \"raises an exception if no matching version\" do\n      box_path = iso_env.box2_file(:vmware)\n      tf = Tempfile.new([\"vagrant-box-no-matching-version\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\",\n              \"providers\": [\n                {\n                  \"name\": \"vmware\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            },\n            { \"version\": \"1.1\" }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      env[:box_url] = tf.path\n      env[:box_version] = \"~> 2.0\"\n      expect(box_collection).to receive(:add).never\n      expect(app).to receive(:call).never\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::BoxAddNoMatchingVersion)\n    end\n\n    it \"raises an error if there is no matching provider\" do\n      tf = Tempfile.new([\"vagrant-box-no-matching-provider\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{iso_env.box2_file(:virtualbox)}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      env[:box_url] = tf.path\n      env[:box_provider] = \"vmware\"\n      expect(box_collection).to receive(:add).never\n      expect(app).to receive(:call).never\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::BoxAddNoMatchingProvider)\n    end\n\n    it \"raises an error if a box already exists\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      tf = Tempfile.new([\"vagrant-box-already-exists\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      env[:box_url] = tf.path\n      expect(box_collection).to receive(:find).\n        with(\"foo/bar\", \"virtualbox\", \"0.7\", nil).and_return(box)\n      expect(box_collection).to receive(:add).never\n      expect(app).to receive(:call).never\n\n      expect { subject.call(env) }.\n        to raise_error(Vagrant::Errors::BoxAlreadyExists)\n    end\n\n    it \"force adds a box if specified\" do\n      box_path = iso_env.box2_file(:virtualbox)\n      tf = Tempfile.new([\"vagrant-box-force-add\", \".json\"]).tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo/bar\",\n          \"versions\": [\n            {\n              \"version\": \"0.5\"\n            },\n            {\n              \"version\": \"0.7\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\":  \"#{box_path}\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n        f.close\n      end\n\n      env[:box_force] = true\n      env[:box_url] = tf.path\n      allow(box_collection).to receive(:find).and_return(box)\n      expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts|\n        expect(checksum(path)).to eq(checksum(box_path))\n        expect(name).to eq(\"foo/bar\")\n        expect(version).to eq(\"0.7\")\n        expect(opts[:force]).to be(true)\n        expect(opts[:metadata_url]).to eq(\"file://#{tf.path}\")\n        true\n      }.and_return(box)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n\n      expect(env[:box_added]).to equal(box)\n    end\n  end\n\n  describe \"#downloader\" do\n    let(:options) { {} }\n\n    after { subject.send(:downloader, url, env, **options) }\n\n    context \"when box is local\" do\n      let(:url) { \"/dev/null/vagrant.box\" }\n\n      it \"should prepend file protocol to path\" do\n        expect(Vagrant::Util::Downloader).to receive(:new) do |durl, *_|\n          expect(durl).to eq(\"file://#{url}\")\n        end\n      end\n    end\n\n    context \"when box is remote\" do\n      let(:url) { \"http://localhost/vagrant.box\" }\n\n      it \"should not modify the url\" do\n        expect(Vagrant::Util::Downloader).to receive(:new) do |durl, *_|\n          expect(durl).to eq(url)\n        end\n      end\n    end\n\n    context \"when options are set in the environment\" do\n      let(:url) { \"http://localhost/vagrant.box\" }\n\n      context \"when ca cert value is set\" do\n        let(:ca_cert) { \"/dev/null/ca.cert\" }\n        before { env[:box_download_ca_cert] = ca_cert }\n\n        it \"should include ca cert value\" do\n          expect(Vagrant::Util::Downloader).to receive(:new) do |_, _, opts|\n            expect(opts[:ca_cert]).to eq(ca_cert)\n          end\n        end\n      end\n\n      context \"when ca path value is set\" do\n        let(:ca_path) { \"/dev/null/ca.path\" }\n        before { env[:box_download_ca_path] = ca_path }\n\n        it \"should include ca path value\" do\n          expect(Vagrant::Util::Downloader).to receive(:new) do |_, _, opts|\n            expect(opts[:ca_path]).to eq(ca_path)\n          end\n        end\n      end\n\n      context \"when insecure value is set\" do\n        before { env[:box_download_insecure] = true }\n\n        it \"should include insecure value\" do\n          expect(Vagrant::Util::Downloader).to receive(:new) do |_, _, opts|\n            expect(opts[:insecure]).to be_truthy\n          end\n        end\n      end\n\n      context \"when client cert value is set\" do\n        let(:client_cert_path) { \"/dev/null/client.cert\" }\n        before { env[:box_download_client_cert] = client_cert_path }\n\n        it \"should include client cert value\" do\n          expect(Vagrant::Util::Downloader).to receive(:new) do |_, _, opts|\n            expect(opts[:client_cert]).to eq(client_cert_path)\n          end\n        end\n      end\n\n      context \"when location trusted is set\" do\n        before { env[:box_download_location_trusted] = true }\n\n        it \"should include client cert value\" do\n          expect(Vagrant::Util::Downloader).to receive(:new) do |_, _, opts|\n            expect(opts[:location_trusted]).to be_truthy\n          end\n        end\n      end\n\n      context \"when disable ssl revoke best effort is set\" do\n        before { env[:box_download_disable_ssl_revoke_best_effort] = true }\n\n        it \"should include disable revoke best effort value value\" do\n          expect(Vagrant::Util::Downloader).to receive(:new) do |_, _, opts|\n            expect(opts[:disable_ssl_revoke_best_effort]).to be_truthy\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/box_check_outdated_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::BoxCheckOutdated do\n  include_context \"unit\"\n\n  let(:app) { lambda { |env| } }\n  let(:env) { {\n    box_collection: iso_vagrant_env.boxes,\n    machine: machine,\n    ui: Vagrant::UI::Silent.new,\n  } }\n\n  subject { described_class.new(app, env) }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    isolated_environment.tap do |env|\n      env.vagrantfile(\"\")\n    end\n  end\n\n  let(:iso_vagrant_env) { iso_env.create_vagrant_env }\n\n  let(:box) do\n    box_dir = iso_env.box3(\"foo\", \"1.0\", :virtualbox)\n    Vagrant::Box.new(\"foo\", :virtualbox, \"1.0\", box_dir).tap do |b|\n      allow(b).to receive(:has_update?).and_return(nil)\n    end\n  end\n\n  let(:machine) do\n    m = iso_vagrant_env.machine(iso_vagrant_env.machine_names[0], :dummy)\n    m.config.vm.box_check_update = true\n    m\n  end\n\n  before do\n    allow(machine).to receive(:box).and_return(box)\n  end\n\n  context \"disabling outdated checking\" do\n    it \"doesn't check\" do\n      machine.config.vm.box_check_update = false\n\n      expect(app).to receive(:call).with(env).once\n\n      subject.call(env)\n\n      expect(env).to_not have_key(:box_outdated)\n    end\n\n    it \"checks if forced\" do\n      machine.config.vm.box_check_update = false\n      env[:box_outdated_force] = true\n\n      expect(app).to receive(:call).with(env).once\n      expect(box).to receive(:has_update?)\n\n      subject.call(env)\n\n      expect(env).to have_key(:box_outdated)\n    end\n\n    it \"checks if not forced\" do\n      machine.config.vm.box_check_update = false\n      env[:box_outdated_force] = false\n\n      expect(app).to receive(:call).with(env).once\n\n      subject.call(env)\n    end\n  end\n\n  context \"no box\" do\n    it \"raises an exception if the machine doesn't have a box yet\" do\n      allow(machine).to receive(:box).and_return(nil)\n\n      expect(app).to receive(:call).with(env).once\n\n      subject.call(env)\n\n      expect(env).to_not have_key(:box_outdated)\n    end\n  end\n\n  context \"with a non-versioned box\" do\n    it \"does nothing\" do\n      allow(box).to receive(:metadata_url).and_return(nil)\n      allow(box).to receive(:version).and_return(\"0\")\n\n      expect(app).to receive(:call).once\n      expect(box).to receive(:has_update?).never\n\n      subject.call(env)\n    end\n  end\n\n  context \"with a box\" do\n    it \"sets env if no update\" do\n      expect(box).to receive(:has_update?).and_return(nil)\n\n      expect(app).to receive(:call).with(env).once\n\n      subject.call(env)\n\n      expect(env[:box_outdated]).to be(false)\n    end\n\n    it \"sets env if there is an update\" do\n      md = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW))\n      {\n        \"name\": \"foo\",\n        \"versions\": [\n          {\n            \"version\": \"1.0\"\n          },\n          {\n            \"version\": \"1.1\",\n            \"providers\": [\n              {\n                \"name\": \"virtualbox\",\n                \"url\": \"bar\"\n              }\n            ]\n          }\n        ]\n      }\n      RAW\n\n      expect(box).to receive(:has_update?).with(machine.config.vm.box_version,\n          {download_options:\n            {automatic_check: true, ca_cert: nil, ca_path: nil, client_cert: nil, insecure: false, disable_ssl_revoke_best_effort: false, box_extra_download_options: []}}).\n        and_return([md, md.version(\"1.1\"), md.version(\"1.1\").provider(\"virtualbox\")])\n\n      expect(app).to receive(:call).with(env).once\n\n      subject.call(env)\n\n      expect(env[:box_outdated]).to be(true)\n    end\n\n    it \"has an update if it is local\" do\n      iso_env.box3(\"foo\", \"1.1\", :virtualbox)\n\n      expect(box).to receive(:has_update?).and_return(nil)\n\n      expect(app).to receive(:call).with(env).once\n\n      subject.call(env)\n\n      expect(env[:box_outdated]).to be(true)\n    end\n\n    context \"both local and remote update exist\" do\n      it \"should prompt user to update\" do\n        iso_env.box3(\"foo\", \"1.1\", :virtualbox)\n\n        md = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW))\n        {\n          \"name\": \"foo\",\n          \"versions\": [\n            {\n              \"version\": \"1.2\",\n              \"providers\": [\n                {\n                  \"name\": \"virtualbox\",\n                  \"url\": \"bar\"\n                }\n              ]\n            }\n          ]\n        }\n        RAW\n\n        expect(box).to receive(:has_update?).with(\n          machine.config.vm.box_version,\n          { download_options: { automatic_check: true, ca_cert: nil, ca_path: nil,\n            client_cert: nil, disable_ssl_revoke_best_effort: false, insecure: false, box_extra_download_options: []\n          }}).and_return(\n            [md, md.version(\"1.2\"), md.version(\"1.2\").provider(\"virtualbox\")]\n          )\n\n        allow(I18n).to receive(:t) { :ok }\n        expect(I18n).to receive(:t).with(/box_outdated_single/, hash_including(latest: \"1.2\")).once\n\n        expect(app).to receive(:call).with(env).once\n\n        subject.call(env)\n      end\n    end\n\n    it \"does not have a local update if not within constraints\" do\n      iso_env.box3(\"foo\", \"1.1\", :virtualbox)\n\n      machine.config.vm.box_version = \"> 1.0, < 1.1\"\n\n      expect(box).to receive(:has_update?).and_return(nil)\n\n      expect(app).to receive(:call).with(env).once\n\n      subject.call(env)\n\n      expect(env[:box_outdated]).to be(false)\n    end\n\n    it \"does nothing if metadata download fails\" do\n      expect(box).to receive(:has_update?).and_raise(\n        Vagrant::Errors::BoxMetadataDownloadError.new(message: \"foo\"))\n\n      expect(app).to receive(:call).once\n\n      subject.call(env)\n\n      expect(env[:box_outdated]).to be(false)\n    end\n\n    it \"does nothing if metadata cannot be parsed\" do\n      expect(box).to receive(:has_update?).and_raise(\n        Vagrant::Errors::BoxMetadataMalformed.new(error: \"Whoopsie\"))\n\n      expect(app).to receive(:call).once\n\n      subject.call(env)\n\n      expect(env[:box_outdated]).to be(false)\n    end\n\n    it \"raises error if has_update? errors\" do\n      expect(box).to receive(:has_update?).and_raise(Vagrant::Errors::VagrantError)\n\n      expect(app).to receive(:call).never\n\n      expect { subject.call(env) }.to raise_error(Vagrant::Errors::VagrantError)\n    end\n\n    it \"doesn't raise an error if ignore errors is on\" do\n      env[:box_outdated_ignore_errors] = true\n\n      expect(box).to receive(:has_update?).and_raise(Vagrant::Errors::VagrantError)\n      expect(app).to receive(:call).with(env).once\n\n      expect { subject.call(env) }.to_not raise_error\n    end\n\n    context \"when machine download options are specified\" do\n      before do\n        machine.config.vm.box_download_ca_cert = \"foo\"\n        machine.config.vm.box_download_ca_path = \"bar\"\n        machine.config.vm.box_download_client_cert = \"baz\"\n        machine.config.vm.box_download_insecure = true\n        machine.config.vm.box_download_options = {\"opt\": \"val\"}\n        machine.config.vm.box_download_disable_ssl_revoke_best_effort = true\n        machine.config.vm.finalize!\n      end\n\n      it \"uses download options from machine\" do\n        expect(box).to receive(:has_update?).with(machine.config.vm.box_version,\n          { download_options: {\n            automatic_check: true, ca_cert: \"foo\", ca_path: \"bar\", client_cert: \"baz\", \n            insecure: true, disable_ssl_revoke_best_effort: true,\n            box_extra_download_options: [\"--opt\", \"val\"]}})\n\n        expect(app).to receive(:call).with(env).once\n\n        subject.call(env)\n      end\n\n      it \"overrides download options from machine with options from env\" do\n        expect(box).to receive(:has_update?).with(\n          machine.config.vm.box_version,\n          { download_options: {automatic_check: true, ca_cert: \"oof\", ca_path: \"rab\", client_cert: \"zab\", \n            insecure: false, disable_ssl_revoke_best_effort: true,\n            box_extra_download_options: [\"--tpo\"],\n          }})\n\n        env[:ca_cert] = \"oof\"\n        env[:ca_path] = \"rab\"\n        env[:client_cert] = \"zab\"\n        env[:insecure] = false\n        env[:box_extra_download_options] = [\"--tpo\"]\n        expect(app).to receive(:call).with(env).once\n\n        subject.call(env)\n      end\n    end\n  end\n\n  describe \".check_outdated_local\" do\n    let(:updated_box) do\n      box_dir = iso_env.box3(\"foo\", \"1.1\", :virtualbox)\n      Vagrant::Box.new(\"foo\", :virtualbox, \"1.1\", box_dir).tap do |b|\n        allow(b).to receive(:has_update?).and_return(nil)\n      end\n    end\n\n    it \"should return the updated box if it is already installed\" do\n      expect(env[:box_collection]).to receive(:find).with(\"foo\", :virtualbox, \"> 1.0\").and_return(updated_box)\n\n      local_update = subject.check_outdated_local(env)\n\n      expect(local_update).to eq(updated_box)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/box_remove_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::BoxRemove do\n  include_context \"unit\"\n\n  let(:app) { lambda { |env| } }\n  let(:env) { {\n    box_collection: box_collection,\n    home_path: home_path,\n    machine_index: machine_index,\n    ui: Vagrant::UI::Silent.new,\n  } }\n\n  subject { described_class.new(app, env) }\n\n  let(:box_collection) { double(\"box_collection\") }\n  let(:home_path) { double(\"home-path\") }\n  let(:machine_index) { [] }\n  let(:iso_env) { isolated_environment }\n\n  let(:box) do\n    box_dir = iso_env.box3(\"foo\", \"1.0\", :virtualbox)\n    Vagrant::Box.new(\"foo\", :virtualbox, \"1.0\", box_dir)\n  end\n\n  it \"deletes the box if it is the only option\" do\n    allow(box_collection).to receive(:all).and_return([[\"foo\", \"1.0\", :virtualbox, nil]])\n\n    env[:box_name] = \"foo\"\n\n    expect(box_collection).to receive(:find).with(\n      \"foo\", :virtualbox, \"1.0\", nil).and_return(box)\n    expect(box_collection).to receive(:clean).with(box.name)\n      .and_return(true)\n    expect(box).to receive(:destroy!).once\n    expect(app).to receive(:call).with(env).once\n\n    subject.call(env)\n\n    expect(env[:box_removed]).to equal(box)\n  end\n\n  it \"deletes the box with the specified provider if given\" do\n    allow(box_collection).to receive(:all)\n      .and_return([\n        [\"foo\", \"1.0\", :virtualbox],\n        [\"foo\", \"1.0\", :vmware],\n      ])\n\n    env[:box_name] = \"foo\"\n    env[:box_provider] = \"virtualbox\"\n\n    expect(box_collection).to receive(:find).with(\n      \"foo\", :virtualbox, \"1.0\", nil).and_return(box)\n    expect(box_collection).to receive(:clean).with(box.name)\n      .and_return(false)\n    expect(box).to receive(:destroy!).once\n    expect(app).to receive(:call).with(env).once\n\n    subject.call(env)\n\n    expect(env[:box_removed]).to equal(box)\n  end\n\n  it \"deletes the box with the specified version if given\" do\n    allow(box_collection).to receive(:all)\n      .and_return([\n        [\"foo\", \"1.0\", :virtualbox],\n        [\"foo\", \"1.1\", :virtualbox],\n      ])\n\n    env[:box_name] = \"foo\"\n    env[:box_version] = \"1.0\"\n\n    expect(box_collection).to receive(:find).with(\n      \"foo\", :virtualbox, \"1.0\", nil).and_return(box)\n    expect(box_collection).to receive(:clean).with(box.name)\n      .and_return(false)\n    expect(box).to receive(:destroy!).once\n    expect(app).to receive(:call).with(env).once\n\n    subject.call(env)\n\n    expect(env[:box_removed]).to equal(box)\n  end\n\n  context \"with architecture\" do\n    let(:architecture) { \"test-arch\" }\n    let(:box) do\n      box_dir = iso_env.box3(\"foo\", \"1.0\", :virtualbox, architecture: architecture)\n      Vagrant::Box.new(\"foo\", :virtualbox, \"1.0\", box_dir, architecture: architecture)\n    end\n\n    it \"deletes the box if it is the only option\" do\n      allow(box_collection).to receive(:all).and_return([[\"foo\", \"1.0\", :virtualbox, architecture]])\n\n      env[:box_name] = \"foo\"\n\n      expect(box_collection).to receive(:find).\n        with(\"foo\", :virtualbox, \"1.0\", architecture).\n        and_return(box)\n      expect(box_collection).to receive(:clean).\n        with(box.name).\n        and_return(true)\n      expect(box).to receive(:destroy!).once\n      expect(app).to receive(:call).with(env).once\n\n      subject.call(env)\n\n      expect(env[:box_removed]).to equal(box)\n    end\n\n    it \"deletes the box with the specified provider if given\" do\n      allow(box_collection).to receive(:all)\n        .and_return([\n          [\"foo\", \"1.0\", :virtualbox, architecture],\n          [\"foo\", \"1.0\", :vmware, architecture],\n        ])\n\n      env[:box_name] = \"foo\"\n      env[:box_provider] = \"virtualbox\"\n\n      expect(box_collection).to receive(:find).with(\n        \"foo\", :virtualbox, \"1.0\", architecture).and_return(box)\n      expect(box_collection).to receive(:clean).with(box.name)\n        .and_return(false)\n      expect(box).to receive(:destroy!).once\n      expect(app).to receive(:call).with(env).once\n\n      subject.call(env)\n\n      expect(env[:box_removed]).to equal(box)\n    end\n\n    it \"deletes the box with the specified version if given\" do\n      allow(box_collection).to receive(:all)\n        .and_return([\n          [\"foo\", \"1.0\", :virtualbox, architecture],\n          [\"foo\", \"1.1\", :virtualbox, architecture],\n        ])\n\n      env[:box_name] = \"foo\"\n      env[:box_version] = \"1.0\"\n\n      expect(box_collection).to receive(:find).with(\n        \"foo\", :virtualbox, \"1.0\", architecture).and_return(box)\n      expect(box_collection).to receive(:clean).with(box.name)\n        .and_return(false)\n      expect(box).to receive(:destroy!).once\n      expect(app).to receive(:call).with(env).once\n\n      subject.call(env)\n\n      expect(env[:box_removed]).to equal(box)\n    end\n\n    it \"deletes the box with the specificed version and architecture\" do\n      allow(box_collection).to receive(:all)\n        .and_return([\n          [\"foo\", \"1.0\", :virtualbox, architecture],\n          [\"foo\", \"1.0\", :virtualbox, \"other-arch\"],\n        ])\n\n      env[:box_name] = \"foo\"\n      env[:box_version] = \"1.0\"\n      env[:box_architecture] = architecture\n\n      expect(box_collection).to receive(:find).with(\n        \"foo\", :virtualbox, \"1.0\", architecture).and_return(box)\n      expect(box_collection).to receive(:clean).with(box.name)\n        .and_return(false)\n      expect(box).to receive(:destroy!).once\n      expect(app).to receive(:call).with(env).once\n\n      subject.call(env)\n\n      expect(env[:box_removed]).to equal(box)\n    end\n\n    it \"errors when box with specified version does not included specified architecture\" do\n      allow(box_collection).to receive(:all)\n        .and_return([\n          [\"foo\", \"1.0\", :virtualbox, architecture],\n          [\"foo\", \"1.0\", :virtualbox, \"other-arch\"],\n        ])\n\n      env[:box_name] = \"foo\"\n      env[:box_version] = \"1.0\"\n      env[:box_architecture] = \"unknown-arch\"\n\n      expect {\n        subject.call(env)\n      }.to raise_error(Vagrant::Errors::BoxRemoveArchitectureNotFound)\n    end\n\n    it \"errors when box with specified version has multiple architectures\" do\n      allow(box_collection).to receive(:all)\n        .and_return([\n          [\"foo\", \"1.0\", :virtualbox, architecture],\n          [\"foo\", \"1.0\", :virtualbox, \"other-arch\"],\n        ])\n\n      env[:box_name] = \"foo\"\n      env[:box_version] = \"1.0\"\n\n      expect {\n        subject.call(env)\n      }.to raise_error(Vagrant::Errors::BoxRemoveMultiArchitecture)\n    end\n  end\n\n  context \"checking if a box is in use\" do\n    def new_entry(name, provider, version, valid=true)\n      Vagrant::MachineIndex::Entry.new.tap do |entry|\n        entry.extra_data[\"box\"] = {\n          \"name\" => \"foo\",\n          \"provider\" => \"virtualbox\",\n          \"version\" => \"1.0\",\n        }\n\n        allow(entry).to receive(:valid?).and_return(valid)\n      end\n    end\n\n    let(:action_runner) { double(\"action_runner\") }\n\n    before do\n      env[:action_runner] = action_runner\n\n      allow(box_collection).to receive(:all)\n        .and_return([\n          [\"foo\", \"1.0\", :virtualbox],\n          [\"foo\", \"1.1\", :virtualbox],\n        ])\n\n      env[:box_name] = \"foo\"\n      env[:box_version] = \"1.0\"\n    end\n\n    it \"does delete if the box is not in use\" do\n      expect(box_collection).to receive(:find).with(\n        \"foo\", :virtualbox, \"1.0\", nil).and_return(box)\n      expect(box).to receive(:destroy!).once\n      expect(box_collection).to receive(:clean).with(box.name)\n        .and_return(true)\n\n      subject.call(env)\n    end\n\n    it \"does delete if the box is in use and user confirms\" do\n      machine_index << new_entry(\"foo\", \"virtualbox\", \"1.0\")\n\n      result = { result: true }\n      expect(action_runner).to receive(:run).\n        with(anything, env).and_return(result)\n\n      expect(box_collection).to receive(:find).with(\n        \"foo\", :virtualbox, \"1.0\", nil).and_return(box)\n      expect(box_collection).to receive(:clean).with(box.name)\n        .and_return(true)\n      expect(box).to receive(:destroy!).once\n\n      subject.call(env)\n    end\n\n    it \"doesn't delete if the box is in use\" do\n      machine_index << new_entry(\"foo\", \"virtualbox\", \"1.0\")\n\n      result = { result: false }\n      expect(action_runner).to receive(:run).\n        with(anything, env).and_return(result)\n\n      expect(box_collection).to receive(:find).with(\n        \"foo\", :virtualbox, \"1.0\", nil).and_return(box)\n      expect(box).to receive(:destroy!).never\n\n      subject.call(env)\n    end\n\n    it \"doesn't delete if the box is in use and keep_used_boxes is set\" do\n      env[:keep_used_boxes] = true\n      machine_index << new_entry(\"foo\", \"virtualbox\", \"1.0\")\n\n      result = { result: true }\n      expect(action_runner).to receive(:run).\n        with(anything, env).and_return(result)\n\n      expect(box_collection).to receive(:find).with(\n        \"foo\", :virtualbox, \"1.0\", nil).and_return(box)\n      expect(box).to receive(:destroy!).never\n\n      subject.call(env)\n    end\n\n    it \"deletes if the box is in use and force is used\" do\n      machine_index << new_entry(\"foo\", \"virtualbox\", \"1.0\")\n\n      result = { result: true }\n      expect(action_runner).to receive(:run).\n        with(anything, env).and_return(result)\n\n      expect(box_collection).to receive(:find).with(\n        \"foo\", :virtualbox, \"1.0\", nil).and_return(box)\n      expect(box_collection).to receive(:clean).with(box.name)\n        .and_return(true)\n      expect(box).to receive(:destroy!)\n\n      subject.call(env)\n    end\n  end\n\n  it \"errors if the box doesn't exist\" do\n    allow(box_collection).to receive(:all).and_return([])\n\n    expect(app).to receive(:call).never\n\n    expect { subject.call(env) }.\n      to raise_error(Vagrant::Errors::BoxRemoveNotFound)\n  end\n\n  it \"errors if the specified provider doesn't exist\" do\n    env[:box_name] = \"foo\"\n    env[:box_provider] = \"bar\"\n\n    allow(box_collection).to receive(:all).and_return([[\"foo\", \"1.0\", :virtualbox]])\n\n    expect(app).to receive(:call).never\n\n    expect { subject.call(env) }.\n      to raise_error(Vagrant::Errors::BoxRemoveProviderNotFound)\n  end\n\n  it \"errors if there are multiple providers\" do\n    env[:box_name] = \"foo\"\n\n    allow(box_collection).to receive(:all)\n      .and_return([\n        [\"foo\", \"1.0\", :virtualbox],\n        [\"foo\", \"1.0\", :vmware],\n      ])\n\n    expect(app).to receive(:call).never\n\n    expect { subject.call(env) }.\n      to raise_error(Vagrant::Errors::BoxRemoveMultiProvider)\n  end\n\n  it \"errors if the specified provider has multiple versions\" do\n    env[:box_name] = \"foo\"\n    env[:box_provider] = \"virtualbox\"\n\n    allow(box_collection).to receive(:all)\n      .and_return([\n        [\"foo\", \"1.0\", :virtualbox],\n        [\"foo\", \"1.1\", :virtualbox],\n      ])\n\n    expect(app).to receive(:call).never\n\n    expect { subject.call(env) }.\n      to raise_error(Vagrant::Errors::BoxRemoveMultiVersion)\n  end\n\n  it \"errors if the specified version doesn't exist\" do\n    env[:box_name] = \"foo\"\n    env[:box_version] = \"1.1\"\n\n    allow(box_collection).to receive(:all).and_return([[\"foo\", \"1.0\", :virtualbox]])\n\n    expect(app).to receive(:call).never\n\n    expect { subject.call(env) }.\n      to raise_error(Vagrant::Errors::BoxRemoveVersionNotFound)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/call_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::Call do\n  let(:app) { lambda { |env| } }\n  let(:env) { {} }\n\n  def wrapper_proc(data)\n    Class.new do\n      def initialize(app, env)\n        @app = app\n      end\n\n      def self.name\n        \"TestAction\"\n      end\n\n      define_method(:call) do |env|\n        env[:data] << \"#{data}_in\"\n        @app.call(env)\n        env[:data] << \"#{data}_out\"\n      end\n    end\n  end\n\n  it \"should yield the env to the block\" do\n    received = nil\n\n    callable = lambda do |env|\n      env[:result] = \"value\"\n    end\n\n    described_class.new(app, env, callable) do |env, builder|\n      received = env[:result]\n    end.call({})\n\n    expect(received).to eq(\"value\")\n  end\n\n  it \"should update the original env with any changes\" do\n    callable = lambda { |env| }\n    next_step = lambda { |env| env[:inner] = true }\n\n    described_class.new(app, env, callable) do |_env, builder|\n      builder.use next_step\n    end.call(env)\n\n    expect(env[:inner]).to eq(true)\n  end\n\n  it \"should call the callable with the original environment\" do\n    received = nil\n    callable = lambda { |env| received = env[:foo] }\n\n    described_class.new(app, env, callable) do |_env, _builder|\n      # Nothing.\n    end.call({ foo: :bar })\n\n    expect(received).to eq(:bar)\n   end\n\n  it \"should call the next builder\" do\n    received = nil\n    callable = lambda { |env| }\n    next_step = lambda { |env| received = \"value\" }\n\n    described_class.new(app, env, callable) do |_env, builder|\n      builder.use next_step\n    end.call({})\n\n    expect(received).to eq(\"value\")\n  end\n\n  it \"should call the next builder with the original environment\" do\n    received = nil\n    callable = lambda { |env| }\n    next_step = lambda { |env| received = env[:foo] }\n\n    described_class.new(app, env, callable) do |_env, builder|\n      builder.use next_step\n    end.call({ foo: :bar })\n\n    expect(received).to eq(:bar)\n  end\n\n  it \"should call the next builder inserted in our own stack\" do\n    callable = lambda { |env| }\n\n    builder = Vagrant::Action::Builder.new.tap do |b|\n      b.use wrapper_proc(1)\n      b.use described_class, callable do |_env, b2|\n        b2.use wrapper_proc(2)\n      end\n      b.use wrapper_proc(3)\n    end\n\n    env = { data: [] }\n    builder.call(env)\n    expect(env[:data]).to eq([\n      \"1_in\", \"2_in\", \"3_in\", \"3_out\", \"2_out\", \"1_out\"])\n  end\n\n  it \"should instantiate the callable with the extra args\" do\n    env = {}\n\n    callable = Class.new do\n      def initialize(app, env, arg)\n        env[:arg] = arg\n      end\n\n      def self.name\n        \"TestAction\"\n      end\n\n      def call(env); end\n    end\n\n    result = nil\n    instance = described_class.new(app, env, callable, :foo) do |inner_env, _builder|\n      result = inner_env[:arg]\n    end\n    instance.call(env)\n\n    expect(result).to eq(:foo)\n  end\n\n  it \"should call the recover method for the sequence in an error\" do\n    # Basic variables\n    callable = lambda { |env| }\n\n    # Build the steps for the test\n    basic_step = Class.new do\n      def initialize(app, env)\n        @app = app\n        @env = env\n      end\n\n      def self.name\n        \"TestAction\"\n      end\n\n      def call(env)\n        @app.call(env)\n      end\n    end\n\n    step_a = Class.new(basic_step) do\n      def call(env)\n        env[:steps] << :call_A\n        super\n      end\n\n      def self.name\n        \"TestAction\"\n      end\n\n      def recover(env)\n        env[:steps] << :recover_A\n      end\n    end\n\n    step_b = Class.new(basic_step) do\n      def call(env)\n        env[:steps] << :call_B\n        super\n      end\n\n      def self.name\n        \"TestAction\"\n      end\n\n      def recover(env)\n        env[:steps] << :recover_B\n      end\n    end\n\n    instance = described_class.new(app, env, callable) do |_env, builder|\n      builder.use step_a\n      builder.use step_b\n    end\n\n    env[:steps] = []\n    instance.call(env)\n    instance.recover(env)\n\n    expect(env[:steps]).to eq([:call_A, :call_B, :recover_B, :recover_A])\n  end\n\n  it \"should recover even if it failed in the callable\" do\n    callable = lambda { |env| raise \"error\" }\n\n    instance = described_class.new(app, env, callable) { |_env, _builder| }\n    instance.call(env) rescue nil\n    expect { instance.recover(env) }.\n      to_not raise_error\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/cleanup_disks_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::CleanupDisks do\n  let(:app) { lambda { |env| } }\n  let(:vm) { double(\"vm\") }\n  let(:config) { double(\"config\", vm: vm) }\n  let(:provider) { double(\"provider\") }\n  let(:machine) { double(\"machine\", config: config, provider: provider, name: \"machine\",\n                         provider_name: \"provider\", data_dir: Pathname.new(\"/fake/dir\")) }\n  let(:env) { { ui: ui, machine: machine} }\n\n  let(:disks) { [double(\"disk\")] }\n\n  let(:ui)  { Vagrant::UI::Silent.new }\n\n  let(:disk_meta_file) { {disk: [{uuid: \"123456789\", name: \"storage\"}], floppy: [], dvd: []} }\n\n  describe \"#call\" do\n    it \"calls configure_disks if disk config present\" do\n      allow(vm).to receive(:disks).and_return(disks)\n      allow(machine).to receive(:disks).and_return(disks)\n      allow(machine.provider).to receive(:capability?).with(:cleanup_disks).and_return(true)\n      subject = described_class.new(app, env)\n\n      expect(app).to receive(:call).with(env).ordered\n      expect(subject).to receive(:read_disk_metadata).with(machine).and_return(disk_meta_file)\n      expect(machine.provider).to receive(:capability).\n        with(:cleanup_disks, disks, disk_meta_file)\n\n      subject.call(env)\n    end\n\n    it \"continues on if no disk config present\" do\n      allow(vm).to receive(:disks).and_return([])\n      subject = described_class.new(app, env)\n\n      expect(app).to receive(:call).with(env).ordered\n      expect(machine.provider).not_to receive(:capability).with(:cleanup_disks, disks)\n\n      subject.call(env)\n    end\n\n    it \"prints a warning if disk config capability is unsupported\" do\n      allow(vm).to receive(:disks).and_return(disks)\n      allow(machine.provider).to receive(:capability?).with(:cleanup_disks).and_return(false)\n      subject = described_class.new(app, env)\n      expect(subject).to receive(:read_disk_metadata).with(machine).and_return(disk_meta_file)\n\n      expect(app).to receive(:call).with(env).ordered\n      expect(machine.provider).not_to receive(:capability).with(:cleanup_disks, disks)\n      expect(ui).to receive(:warn)\n\n      subject.call(env)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/cloud_init_setup_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\nrequire \"vagrant/util/mime\"\n\ndescribe Vagrant::Action::Builtin::CloudInitSetup do\n  let(:app) { lambda { |env| } }\n  let(:vm) { double(\"vm\", disk: disk, disks: disks, cloud_init_first_boot_only: first_boot_only) }\n  let(:first_boot_only) { true }\n  let(:disk) { double(\"disk\") }\n  let(:disks) { double(\"disk\") }\n  let(:config) { double(\"config\", vm: vm) }\n  let(:provider) { double(\"provider\") }\n  let(:machine) { double(\"machine\", config: config, provider: provider, name: \"machine\",\n                         provider_name: \"provider\", data_dir: Pathname.new(\"/fake/dir\"),\n                         ui: ui, env: machine_env, id: \"123-456-789\") }\n  let(:host) { double(\"host\") }\n  let(:machine_env) { double(\"machine_env\", root_path: \"root\", host: host) }\n  let(:env) { { ui: ui, machine: machine, env: machine_env} }\n\n  let(:ui)  { Vagrant::UI::Silent.new }\n\n  let(:cfg) { double(\"cfg\", type: :user_data, content_type: \"text/cloud-config\",\n                     content_disposition_filename: nil, path: \"my/path\",\n                     inline: nil) }\n  let(:cfg_inline) { double(\"cfg\", type: :user_data, content_type: \"text/cloud-config\",\n                            content_disposition_filename: nil, inline: \"data: true\",\n                            path: nil) }\n  let(:cfg_with_content_disposition_filename_inline) {\n    double(\"cfg\", type: :user_data, content_type: \"text/x-shellscript\",\n           content_disposition_filename: \"test.ps1\",\n           inline: \"#ps1_sysnative\\n\", path: nil) }\n  let(:cloud_init_configs) { [cfg, cfg_inline] }\n\n  let(:text_cfgs) { [Vagrant::Util::Mime::Entity.new(\"data: true\", \"text/cloud-config\"),\n                     Vagrant::Util::Mime::Entity.new(\"data: false\", \"text/cloud-config\") ] }\n\n  let(:meta_data) { { \"instance-id\" => \"i-123456789\" } }\n\n\n  let(:subject) { described_class.new(app, env) }\n\n  describe \"#call\" do\n    it \"calls setup_user_data if cloud_init config present\" do\n      allow(vm).to receive(:cloud_init_configs).and_return(cloud_init_configs)\n\n      expect(app).to receive(:call).with(env).ordered\n\n      expect(subject).to receive(:setup_user_data).and_return(true)\n      expect(subject).to receive(:write_cfg_iso).and_return(true)\n\n      subject.call(env)\n    end\n\n    it \"continues on if no cloud_init config present\" do\n      allow(vm).to receive(:cloud_init_configs).and_return([])\n\n      expect(app).to receive(:call).with(env).ordered\n\n      expect(subject).not_to receive(:setup_user_data)\n      expect(subject).not_to receive(:write_cfg_iso)\n      expect(subject).not_to receive(:attach_disk_config)\n\n      subject.call(env)\n    end\n\n    context \"sentinel file\" do\n      let(:sentinel) { double(\"sentinel\") }\n      let(:sentinel_exists) { false }\n      let(:sentinel_contents) { \"\" }\n\n      before do\n        allow(machine).to receive_message_chain(:data_dir, :join).with(\"action_cloud_init\").and_return(sentinel)\n        allow(sentinel).to receive(:file?).and_return(sentinel_exists)\n        allow(sentinel).to receive(:read).and_return(sentinel_contents)\n        allow(sentinel).to receive(:unlink)\n\n        allow(vm).to receive(:cloud_init_configs).and_return(cloud_init_configs)\n        allow(subject).to receive(:setup_user_data)\n        allow(subject).to receive(:write_cfg_iso)\n      end\n\n      context \"when file exists\" do\n        let(:sentinel_exists) { true }\n\n        context \"when file contains machine id\" do\n          let(:sentinel_contents) { machine.id.to_s }\n\n          it \"should not write iso configuration\" do\n            expect(subject).not_to receive(:write_cfg_iso)\n\n            subject.call(env)\n          end\n\n          context \"when configuration enables on all boots\" do\n            let(:first_boot_only) { false }\n\n            it \"should write the iso configuration\" do\n              expect(subject).to receive(:write_cfg_iso)\n              subject.call(env)\n            end\n\n            it \"should remove sentinel file\" do\n              expect(sentinel).to receive(:unlink)\n              subject.call(env)\n            end\n          end\n        end\n\n        context \"when file does not contain machine id\" do\n          let(:sentinel_contents) { \"unknown-id\" }\n\n          it \"should write iso configuration\" do\n            expect(subject).to receive(:write_cfg_iso)\n            subject.call(env)\n          end\n\n          it \"should remove sentinel file\" do\n            expect(sentinel).to receive(:unlink)\n            subject.call(env)\n          end\n        end\n      end\n    end\n  end\n\n  describe \"#setup_user_data\" do\n    it \"builds a MIME message and prepares a disc to be attached\" do\n      expect(subject).to receive(:read_text_cfg).twice\n\n      expect(subject).to receive(:generate_cfg_msg)\n\n      subject.setup_user_data(machine, env, cloud_init_configs)\n    end\n  end\n\n  describe \"#read_text_cfg\" do\n    let(:cfg_text) { \"config: true\" }\n\n    it \"takes a text cfg path and saves it as a MIME text message\" do\n      mime_text_part = double(\"mime_text_part\")\n      expect(mime_text_part).not_to receive(:disposition=)\n      allow(File).to receive(:read).and_return(cfg_text)\n      expect(Vagrant::Util::Mime::Entity).to receive(:new).with(cfg_text, \"text/cloud-config\").and_return(mime_text_part)\n      subject.read_text_cfg(machine, cfg)\n    end\n\n    it \"takes a text cfg inline string and saves it as a MIME text message\" do\n      mime_text_part = double(\"mime_text_part\")\n      expect(mime_text_part).not_to receive(:disposition=)\n      expect(Vagrant::Util::Mime::Entity).to receive(:new).with(\"data: true\", \"text/cloud-config\").and_return(mime_text_part)\n      subject.read_text_cfg(machine, cfg_inline)\n    end\n\n    it \"takes a text cfg inline string with content_disposition_filename and saves it as a MIME text message\" do\n      mime_text_part = double(\"mime_text_part\")\n      expect(mime_text_part).to receive(:disposition=).with(\"attachment; filename=\\\"test.ps1\\\"\")\n      expect(Vagrant::Util::Mime::Entity).to receive(:new).with(\"#ps1_sysnative\\n\", \"text/x-shellscript\").and_return(mime_text_part)\n      subject.read_text_cfg(machine, cfg_with_content_disposition_filename_inline)\n    end\n  end\n\n  describe \"#generate_cfg_msg\" do\n    it \"creates a miltipart mixed message of combined configs\" do\n      message = subject.generate_cfg_msg(machine, text_cfgs)\n      expect(message).to be_a(Vagrant::Util::Mime::Multipart)\n    end\n\n    it \"sets a MIME-Version header\" do\n      message = subject.generate_cfg_msg(machine, text_cfgs)\n      expect(message.headers[\"MIME-Version\"]).to eq(\"1.0\")\n    end\n  end\n\n  describe \"#write_cfg_iso\" do\n    let(:iso_path) { Pathname.new(\"fake/iso/path\") }\n    let(:source_dir) { Pathname.new(\"fake/source/path\") }\n    let(:meta_data_file) { double(\"meta_data_file\") }\n    let(:sentinel) { double(\"sentinel\") }\n    let(:sentinel_exists) { false }\n    let(:file_checksum) { double(\"file_checksum\", checksum: checksum) }\n    let(:checksum) { \"DUMMY-CHECKSUM-VALUE\" }\n\n    before do\n      allow(meta_data_file).to receive(:write).and_return(true)\n      allow(machine).to receive_message_chain(:data_dir, :join).with(\"action_cloud_init_iso\").and_return(sentinel)\n      allow(sentinel).to receive(:file?).and_return(sentinel_exists)\n      allow(sentinel).to receive(:write)\n      allow(Vagrant::Util::FileChecksum).to receive(:new).with(iso_path, :sha256).and_return(file_checksum)\n      allow(Vagrant::Util::FileChecksum).to receive(:new).with(iso_path.to_s, :sha256).and_return(file_checksum)\n    end\n\n    it \"raises an error if the host capability is not supported\" do\n      message = subject.generate_cfg_msg(machine, text_cfgs)\n      allow(host).to receive(:capability?).with(:create_iso).and_return(false)\n\n      expect{subject.write_cfg_iso(machine, env, message, {})}.to raise_error(Vagrant::Errors::CreateIsoHostCapNotFound)\n    end\n\n    it \"creates a temp dir with the cloud_init config and generates an iso\" do\n      message = subject.generate_cfg_msg(machine, text_cfgs)\n      allow(host).to receive(:capability?).with(:create_iso).and_return(true)\n      allow(Dir).to receive(:mktmpdir).and_return(source_dir)\n      expect(File).to receive(:open).with(\"#{source_dir}/user-data\", 'w').and_return(true)\n      expect(File).to receive(:open).with(\"#{source_dir}/meta-data\", 'w').and_yield(meta_data_file)\n      expect(FileUtils).to receive(:remove_entry).with(source_dir).and_return(true)\n      allow(host).to receive(:capability).with(:create_iso, source_dir, volume_id: \"cidata\").and_return(iso_path)\n      expect(vm.disks).to receive(:each)\n      expect(meta_data).to receive(:to_yaml)\n\n\n      subject.write_cfg_iso(machine, env, message, meta_data)\n    end\n\n    context \"sentinel file\" do\n      let(:user_data) { double(\"user_data\") }\n      let(:sentinel_contents) { \"\" }\n\n      before do\n        allow(sentinel).to receive(:read).and_return(sentinel_contents)\n        allow(sentinel).to receive(:unlink)\n\n        allow(host).to receive(:capability?).with(:create_iso).and_return(true)\n        allow(Dir).to receive(:mktmpdir).and_return(source_dir)\n        allow(File).to receive(:open).with(\"#{source_dir}/user-data\", 'w').and_return(true)\n        allow(File).to receive(:open).with(\"#{source_dir}/meta-data\", 'w').and_yield(meta_data_file)\n        allow(FileUtils).to receive(:remove_entry).with(source_dir).and_return(true)\n        allow(FileUtils).to receive(:remove_entry).with(source_dir).and_return(true)\n        allow(host).to receive(:capability).with(:create_iso, source_dir, volume_id: \"cidata\").and_return(iso_path)\n        allow(subject).to receive(:attach_disk_config)\n      end\n\n      context \"when file exists\" do\n        let(:sentinel_exists) { true }\n\n        context \"when file contents is iso path\" do\n          let(:sentinel_contents) { \"#{checksum}:#{iso_path}\" }\n\n          context \"when file contents path exists\" do\n            before do\n              expect(File).to receive(:exist?).with(iso_path.to_s).and_return(true)\n            end\n\n            it \"should not create iso\" do\n              expect(host).not_to receive(:capability)\n              subject.write_cfg_iso(machine, env, user_data, meta_data)\n            end\n\n            it \"should attach with the iso path\" do\n              expect(subject).to receive(:attach_disk_config).with(machine, env, iso_path.to_path)\n              subject.write_cfg_iso(machine, env, user_data, meta_data)\n            end\n\n            it \"should not write the sentinel file\" do\n              expect(sentinel).not_to receive(:write)\n              subject.write_cfg_iso(machine, env, user_data, meta_data)\n            end\n          end\n\n          context \"when file contents path does not exist\" do\n            before do\n              expect(File).to receive(:exist?).with(iso_path.to_s).and_return(false)\n            end\n\n            it \"should create iso\" do\n              expect(host).to receive(:capability).with(:create_iso, source_dir, volume_id: \"cidata\").and_return(iso_path)\n              subject.write_cfg_iso(machine, env, user_data, meta_data)\n            end\n\n            it \"should remove the sentinel file\" do\n              expect(sentinel).to receive(:unlink)\n              subject.write_cfg_iso(machine, env, user_data, meta_data)\n            end\n\n            it \"should write the sentinel file\" do\n              expect(sentinel).to receive(:write).with(\"#{checksum}:#{iso_path}\")\n              subject.write_cfg_iso(machine, env, user_data, meta_data)\n            end\n          end\n\n          context \"when file contents checksum does not match existing file checksum\" do\n            let(:sentinel_contents) { \"BAD-CHECKSUM-VALUE:#{iso_path}\" }\n\n            it \"should create iso\" do\n              expect(host).to receive(:capability).with(:create_iso, source_dir, volume_id: \"cidata\").and_return(iso_path)\n              subject.write_cfg_iso(machine, env, user_data, meta_data)\n            end\n\n            it \"should remove the sentinel file\" do\n              expect(sentinel).to receive(:unlink)\n              subject.write_cfg_iso(machine, env, user_data, meta_data)\n            end\n\n            it \"should write the sentinel file\" do\n              expect(sentinel).to receive(:write).with(\"#{checksum}:#{iso_path}\")\n              subject.write_cfg_iso(machine, env, user_data, meta_data)\n            end\n          end\n        end\n      end\n\n      context \"when file does not exist\" do\n        let(:sentinel_exists) { false }\n\n        it \"should create iso\" do\n          expect(host).to receive(:capability).with(:create_iso, source_dir, volume_id: \"cidata\").and_return(iso_path)\n          subject.write_cfg_iso(machine, env, user_data, meta_data)\n        end\n\n        it \"should write the sentinel file\" do\n          expect(sentinel).to receive(:write).with(\"#{checksum}:#{iso_path}\")\n          subject.write_cfg_iso(machine, env, user_data, meta_data)\n        end\n      end\n    end\n  end\n\n  describe \"#attach_disk_config\" do\n    let(:iso_path) { Pathname.new(\"fake/iso/path\") }\n\n    it \"creates a new disk config based on the iso_path\" do\n      expect(vm.disks).to receive(:each)\n      subject.attach_disk_config(machine, env, iso_path)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/cloud_init_wait_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::CloudInitWait do\n\n  let(:app) { lambda { |env| } }\n  let(:config) { double(\"config\", :vm => vm) }\n  let(:comm) { double(\"comm\") }\n  let(:machine) { double(\"machine\", :config => config, :communicate => comm, :name => \"test\", id: \"m-id\", data_dir: data_dir) }\n  let(:data_dir) { double(\"data_dir\") }\n  let(:ui) { Vagrant::UI::Silent.new }\n  let(:env) { { machine: machine, ui: ui} }\n  let(:sentinel) { double(\"sentinel_path\", unlink: nil) }\n\n  let(:subject) { described_class.new(app, env) }\n\n  describe \"#call\" do\n    let(:sentinel_exists) { false }\n    let(:sentinel_contents) { \"\" }\n\n    before do\n      allow(data_dir).to receive(:join).with(\"action_cloud_init\").and_return(sentinel)\n      allow(sentinel).to receive(:file?).and_return(sentinel_exists)\n      allow(sentinel).to receive(:read).and_return(sentinel_contents)\n      allow(sentinel).to receive(:write).with(machine.id)\n      allow(comm).to receive(:test).with(\"command -v cloud-init\").and_return(true)\n      allow(comm).to receive(:sudo).with(\"cloud-init status --wait\", error_check: false).and_return(0)\n    end\n\n    context \"cloud init configuration exists\" do\n      let(:vm) { double(\"vm\", cloud_init_configs: [\"some config\"]) }\n\n      it \"waits for cloud init to be executed\" do\n        expect(comm).to receive(:sudo).with(\"cloud-init status --wait\", any_args).and_return(0)\n        subject.call(env)\n      end\n\n      it \"raises an error when cloud init not installed\" do\n        allow(comm).to receive(:test).with(\"command -v cloud-init\").and_return(false)\n        expect { subject.call(env) }.\n          to raise_error(Vagrant::Errors::CloudInitNotFound)\n      end\n\n      it \"raises an error when cloud init command fails\" do\n        expect(comm).to receive(:sudo).with(\"cloud-init status --wait\", any_args).and_return(1)\n        expect { subject.call(env) }.\n          to raise_error(Vagrant::Errors::CloudInitCommandFailed)\n      end\n\n      context \"when sentinel file exists\" do\n        let(:sentinel_exists) { true }\n\n        context \"when sentinel contents is machine id\" do\n          let(:sentinel_contents) { machine.id.to_s }\n\n          it \"should not test for cloud-init\" do\n            expect(comm).not_to receive(:test).with(/cloud-init/)\n            subject.call(env)\n          end\n\n          it \"should not run cloud-init\" do\n            expect(comm).not_to receive(:sudo).with(/cloud-init/, anything)\n            subject.call(env)\n          end\n\n          it \"should not write sentinel file\" do\n            expect(sentinel).not_to receive(:write)\n            subject.call(env)\n          end\n        end\n\n        context \"when sentinel content is not machine id\" do\n          let(:sentinel_contents) { \"unknown-id\" }\n\n          it \"should test for cloud-init\" do\n            expect(comm).to receive(:test).with(/cloud-init/)\n            subject.call(env)\n          end\n\n          it \"should run cloud-init\" do\n            expect(comm).to receive(:sudo).with(/cloud-init/, anything)\n            subject.call(env)\n          end\n\n          it \"should write sentinel file\" do\n            expect(sentinel).to receive(:write).with(machine.id)\n            subject.call(env)\n          end\n        end\n      end\n    end\n\n    context \"no cloud init configuration\" do\n      let(:vm) { double(\"vm\", cloud_init_configs: []) }\n\n      before do\n        allow(comm).to receive(:test).with(\"command -v cloud-init\").and_return(true)\n      end\n\n      it \"does not wait for cloud init if there are no cloud init configs\" do\n        expect(comm).to_not receive(:sudo).with(\"cloud-init status --wait\", any_args)\n        subject.call(env)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/confirm_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::Confirm do\n  let(:app) { lambda { |env| } }\n  let(:env) { { ui: Vagrant::UI::Silent.new } }\n  let(:message) { \"foo\" }\n\n  [\"y\", \"Y\"].each do |valid|\n    it \"should set the result to true if '#{valid}' is given\" do\n      expect(env[:ui]).to receive(:ask).with(message).and_return(valid)\n      described_class.new(app, env, message).call(env)\n      expect(env[:result]).to be\n    end\n  end\n\n  it \"should set the result to true if force matches\" do\n    force_key = :tubes\n    env[force_key] = true\n    described_class.new(app, env, message, force_key).call(env)\n    expect(env[:result]).to be\n  end\n\n  it \"should ask if force is not true\" do\n    force_key = :tubes\n    env[force_key] = false\n    expect(env[:ui]).to receive(:ask).with(message).and_return(\"nope\")\n    described_class.new(app, env, message).call(env)\n    expect(env[:result]).not_to be\n  end\n\n  it \"should set result to false if anything else is given\" do\n    expect(env[:ui]).to receive(:ask).with(message).and_return(\"nope\")\n    described_class.new(app, env, message).call(env)\n    expect(env[:result]).not_to be\n  end\n\n  it \"should ask multiple times if an allowed set is given and response isn't in that set\" do\n    times = 0\n    allow(env[:ui]).to receive(:ask) do |arg|\n      expect(arg).to eql(message)\n      times += 1\n\n      if times <= 3\n        \"nope\"\n      else\n        \"y\"\n      end\n    end\n    described_class.new(app, env, message, allowed: [\"y\", \"N\"]).call(env)\n    expect(env[:result]).to be(true)\n    expect(times).to eq(4)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/delayed_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::Delayed do\n  let(:app) { lambda {|*_|} }\n  let(:env) { {} }\n\n  it \"should raise error when callable does not provide #call\" do\n    expect { described_class.new(app, env, true) }.\n      to raise_error(TypeError)\n  end\n\n  it \"should delay executing action to end of stack\" do\n    result = []\n    one = proc{ |*_| result << :one }\n    two = proc{ |*_| result << :two }\n    builder = Vagrant::Action::Builder.build(described_class, two)\n    builder.use(one)\n    builder.call(env)\n    expect(result.first).to eq(:one)\n    expect(result.last).to eq(:two)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/disk_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::Disk do\n  let(:app) { lambda { |env| } }\n  let(:vm) { double(\"vm\") }\n  let(:config) { double(\"config\", vm: vm) }\n  let(:provider) { double(\"provider\") }\n  let(:machine) { double(\"machine\", config: config, provider: provider,\n                         provider_name: \"provider\", data_dir: Pathname.new(\"/fake/dir\")) }\n  let(:env) { { ui: ui, machine: machine} }\n\n  let(:disks) { [double(\"disk\")] }\n\n  let(:ui)  { Vagrant::UI::Silent.new }\n\n  let(:disk_data) { {disk: [{uuid: \"123456789\", name: \"storage\"}], floppy: [], dvd: []} }\n\n  describe \"#call\" do\n    it \"calls configure_disks if disk config present\" do\n      allow(vm).to receive(:disks).and_return(disks)\n      allow(machine).to receive(:disks).and_return(disks)\n      allow(machine.provider).to receive(:capability?).with(:configure_disks).and_return(true)\n      subject = described_class.new(app, env)\n\n      expect(app).to receive(:call).with(env).ordered\n      expect(machine.provider).to receive(:capability).\n        with(:configure_disks, disks).and_return(disk_data)\n\n      expect(subject).to receive(:write_disk_metadata).and_return(true)\n\n      subject.call(env)\n    end\n\n    it \"writes a disk_meta file if no disk config is present\" do\n      allow(vm).to receive(:disks).and_return([])\n      subject = described_class.new(app, env)\n\n      expect(app).to receive(:call).with(env).ordered\n      expect(machine.provider).not_to receive(:capability).with(:configure_disks, disks)\n\n      expect(subject).to receive(:write_disk_metadata)\n\n      subject.call(env)\n    end\n\n    it \"prints a warning if disk config capability is unsupported\" do\n      allow(vm).to receive(:disks).and_return(disks)\n      allow(machine.provider).to receive(:capability?).with(:configure_disks).and_return(false)\n      subject = described_class.new(app, env)\n      allow(subject).to receive(:write_disk_metadata)\n\n      expect(app).to receive(:call).with(env).ordered\n      expect(machine.provider).not_to receive(:capability).with(:configure_disks, disks)\n      expect(ui).to receive(:warn)\n\n      subject.call(env)\n    end\n\n    it \"writes down a disk_meta file if disks are configured\" do\n      subject = described_class.new(app, env)\n\n      expect(File).to receive(:open).with(\"/fake/dir/disk_meta\", \"w+\").and_return(true)\n\n      subject.write_disk_metadata(machine, disk_data)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/env_set_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::EnvSet do\n  let(:app) { lambda { |env| } }\n  let(:env) { {} }\n\n  it \"should set the new environment\" do\n    described_class.new(app, env, foo: :bar).call(env)\n\n    expect(env[:foo]).to eq(:bar)\n  end\n\n  it \"should call the next middleware\" do\n    callable = lambda { |env| env[:called] = env[:foo] }\n\n    expect(env[:called]).to be_nil\n    described_class.new(callable, env, foo: :yep).call(env)\n    expect(env[:called]).to eq(:yep)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/graceful_halt_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::GracefulHalt do\n  let(:app) { lambda { |env| } }\n  let(:env) { { machine: machine, ui: Vagrant::UI::Silent.new } }\n  let(:machine) do\n    result = double(\"machine\")\n    allow(result).to receive(:config).and_return(machine_config)\n    allow(result).to receive(:guest).and_return(machine_guest)\n    allow(result).to receive(:state).and_return(machine_state)\n    result\n  end\n  let(:machine_config) do\n    double(\"machine_config\").tap do |top_config|\n      vm_config = double(\"machine_vm_config\")\n      allow(vm_config).to receive(:graceful_halt_timeout).and_return(10)\n      allow(top_config).to receive(:vm).and_return(vm_config)\n    end\n  end\n  let(:machine_guest) { double(\"machine_guest\") }\n  let(:machine_state) do\n    double(\"machine_state\").tap do |result|\n      allow(result).to receive(:id).and_return(:unknown)\n    end\n  end\n  let(:target_state) { :target }\n  let(:ui) do\n    double(\"ui\").tap do |result|\n      allow(result).to receive(:output)\n    end\n  end\n\n  it \"should do nothing if force is specified\" do\n    env[:force_halt] = true\n\n    expect(machine_guest).not_to receive(:capability)\n\n    described_class.new(app, env, target_state).call(env)\n\n    expect(env[:result]).to eq(false)\n  end\n\n  it \"should do nothing if there is an invalid source state\" do\n    allow(machine_state).to receive(:id).and_return(:invalid_source)\n    expect(machine_guest).not_to receive(:capability)\n\n    described_class.new(app, env, target_state, :target_source).call(env)\n\n    expect(env[:result]).to eq(false)\n  end\n\n  it \"should gracefully halt and wait for the target state\" do\n    expect(machine_guest).to receive(:capability).with(:halt).once\n    allow(machine_state).to receive(:id).and_return(target_state)\n\n    described_class.new(app, env, target_state).call(env)\n\n    expect(env[:result]).to eq(true)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/handle_box_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::HandleBox do\n  include_context \"unit\"\n\n  let(:app) { lambda { |env| } }\n  let(:env) { {\n    action_runner: action_runner,\n    machine: machine,\n    ui: Vagrant::UI::Silent.new,\n  } }\n\n  subject { described_class.new(app, env) }\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    isolated_environment.tap do |env|\n      env.vagrantfile(\"\")\n    end\n  end\n\n  let(:iso_vagrant_env) { iso_env.create_vagrant_env }\n\n  let(:action_runner) { double(\"action_runner\") }\n  let(:box) do\n    box_dir = iso_env.box3(\"foo\", \"1.0\", :virtualbox)\n    Vagrant::Box.new(\"foo\", :virtualbox, \"1.0\", box_dir)\n  end\n  let(:machine) { iso_vagrant_env.machine(iso_vagrant_env.machine_names[0], :dummy) }\n\n  it \"works if there is no box set\" do\n    machine.config.vm.box = nil\n    machine.config.vm.box_url = nil\n\n    expect(app).to receive(:call).with(env)\n\n    subject.call(env)\n  end\n\n  it \"works if box is empty string\" do\n    machine.config.vm.box = \"\"\n    machine.config.vm.box_url = nil\n\n    expect(app).to receive(:call).with(env)\n\n    subject.call(env)\n  end\n\n  it \"doesn't do anything if a box exists\" do\n    allow(machine).to receive(:box).and_return(box)\n\n    expect(action_runner).to receive(:run).never\n    expect(app).to receive(:call).with(env)\n\n    subject.call(env)\n  end\n\n  context \"with a box set and no box_url\" do\n    before do\n      allow(machine).to receive(:box).and_return(nil)\n\n      machine.config.vm.box = \"foo\"\n    end\n\n    it \"adds a box that doesn't exist\" do\n      expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n        expect(opts[:box_name]).to eq(machine.config.vm.box)\n        expect(opts[:box_url]).to eq(machine.config.vm.box)\n        expect(opts[:box_provider]).to eq(:dummy)\n        expect(opts[:box_version]).to eq(machine.config.vm.box_version)\n        true\n      }\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n    end\n\n    it \"adds a box using any format the provider allows\" do\n      machine.provider_options[:box_format] = [:foo, :bar]\n\n      expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n        expect(opts[:box_name]).to eq(machine.config.vm.box)\n        expect(opts[:box_url]).to eq(machine.config.vm.box)\n        expect(opts[:box_provider]).to eq([:foo, :bar])\n        expect(opts[:box_version]).to eq(machine.config.vm.box_version)\n        true\n      }\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n    end\n  end\n\n  context \"with a box and box_url set\" do\n    before do\n      allow(machine).to receive(:box).and_return(nil)\n\n      machine.config.vm.box = \"foo\"\n      machine.config.vm.box_url = \"bar\"\n    end\n\n    it \"adds a box that doesn't exist\" do\n      expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n        expect(opts[:box_name]).to eq(machine.config.vm.box)\n        expect(opts[:box_url]).to eq(machine.config.vm.box_url)\n        expect(opts[:box_provider]).to eq(:dummy)\n        expect(opts[:box_version]).to eq(machine.config.vm.box_version)\n        true\n      }\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n    end\n  end\n\n  context \"with a box with a checksum set\" do\n    before do\n      allow(machine).to receive(:box).and_return(nil)\n\n      machine.config.vm.box = \"foo\"\n      machine.config.vm.box_url = \"bar\"\n      machine.config.vm.box_download_checksum_type = \"sha256\"\n      machine.config.vm.box_download_checksum = \"1f42ac2decf0169c4af02b2d8c77143ce35f7ba87d5d844e19bf7cbb34fbe74e\"\n    end\n\n    it \"adds a box that doesn't exist and maps checksum options correctly\" do\n      expect(action_runner).to receive(:run).with(any_args) { |action, opts|\n        expect(opts[:box_name]).to eq(machine.config.vm.box)\n        expect(opts[:box_url]).to eq(machine.config.vm.box_url)\n        expect(opts[:box_provider]).to eq(:dummy)\n        expect(opts[:box_version]).to eq(machine.config.vm.box_version)\n        expect(opts[:box_checksum_type]).to eq(machine.config.vm.box_download_checksum_type)\n        expect(opts[:box_checksum]).to eq(machine.config.vm.box_download_checksum)\n        true\n      }\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/handle_forwarded_port_collisions_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/vm\")\n\ndescribe Vagrant::Action::Builtin::HandleForwardedPortCollisions do\n  include_context \"unit\"\n\n  let(:app) { lambda { |env| } }\n  let(:env) {\n    { machine: machine, ui: ui, port_collision_extra_in_use: extra_in_use,\n      port_collision_remap: collision_remap, port_collision_repair: collision_repair,\n      port_collision_port_check: collision_port_check }\n  }\n  let(:provider_name) { \"default\" }\n  let(:extra_in_use){ nil }\n  let(:collision_remap){ nil }\n  let(:collision_repair){ nil }\n  let(:collision_port_check){ nil }\n  let(:port_check_method){ nil }\n\n  let(:machine) do\n    double(\"machine\").tap do |machine|\n      allow(machine).to receive(:provider_name).and_return(provider_name)\n      allow(machine).to receive(:config).and_return(machine_config)\n      allow(machine).to receive(:env).and_return(machine_env)\n    end\n  end\n\n  let(:machine_config) do\n    double(\"machine_config\").tap do |config|\n      allow(config).to receive(:vm).and_return(vm_config)\n    end\n  end\n\n  let(:data_dir){ temporary_dir }\n\n  let(:machine_env) do\n    isolated_environment.tap do |i_env|\n      allow(i_env).to receive(:data_dir).and_return(data_dir)\n      allow(i_env).to receive(:lock).and_yield\n    end\n  end\n\n  let(:vm_config) do\n    double(\"machine_vm_config\").tap do |config|\n      allow(config).to receive(:usable_port_range).and_return(1000..2000)\n      allow(config).to receive(:networks).and_return([])\n    end\n  end\n\n  let(:ui){ Vagrant::UI::Silent.new }\n\n  let(:instance){ described_class.new(app, env) }\n\n  describe \"#call\" do\n    it \"should create a lock while action runs\" do\n      expect(machine_env).to receive(:lock).with(\"fpcollision\").and_yield\n      instance.call(env)\n    end\n\n    context \"with extra ports in use provided as Array type\" do\n      let(:extra_in_use){ [80] }\n\n      it \"should not generate an error\" do\n        expect{ instance.call(env) }.not_to raise_error\n      end\n    end\n\n    context \"with forwarded port defined\" do\n      let(:port_options){ {guest: 80, host: 8080} }\n      before do\n        expect(vm_config).to receive(:networks).and_return([[:forwarded_port, port_options]]).twice\n        allow(instance).to receive(:ipv4_addresses).and_return([\"127.0.0.1\"])\n      end\n\n      it \"should check if host port is in use\" do\n        expect(instance).to receive(:is_forwarded_already).and_return(false)\n        expect(Vagrant::Util::IsPortOpen).to receive(:is_port_open?).and_return(false)\n        instance.call(env)\n      end\n\n      context \"with forwarded port already in use\" do\n        let(:extra_in_use){ [8080] }\n\n        it \"should raise a port collision error\" do\n          expect{ instance.call(env) }.to raise_error(Vagrant::Errors::ForwardPortCollision)\n        end\n\n        context \"with auto_correct enabled\" do\n          before{ port_options[:auto_correct] = true }\n\n          it \"should raise a port collision error\" do\n            expect{ instance.call(env) }.to raise_error(Vagrant::Errors::ForwardPortCollision)\n          end\n\n          context \"with collision repair enabled\" do\n            let(:collision_repair){ true }\n\n            it \"should automatically correct collision\" do\n              expect{ instance.call(env) }.not_to raise_error\n            end\n          end\n        end\n      end\n\n      context \"with custom port_check method\" do\n        let(:check_result){ [] }\n        let(:port_options){ {guest: 80, host: 8080, host_ip: \"127.0.1.1\"} }\n\n        context \"that accepts two parameters\" do\n          let(:collision_port_check) do\n            lambda do |host_ip, host_port|\n              check_result.push(host_ip)\n              check_result.push(host_port)\n              false\n            end\n          end\n\n          it \"should receive both host_ip and host_port\" do\n            instance.call(env)\n            expect(check_result).to include(port_options[:host])\n            expect(check_result).to include(port_options[:host_ip])\n          end\n        end\n\n        context \"that accepts one parameter\" do\n          let(:collision_port_check) do\n            lambda do |host_port|\n              check_result.push(host_port)\n              false\n            end\n          end\n\n          it \"should receive the host_port only\" do\n            instance.call(env)\n            expect(check_result).to eq([port_options[:host]])\n          end\n        end\n      end\n    end\n  end\n\n  describe \"#recover\" do\n  end\n\n  describe \"#port_check\" do\n    let(:host_ip){ \"127.0.0.1\" }\n    let(:host_port){ 8080 }\n    let(:interfaces) { [ [\"lo0\", \"127.0.0.1\"], [\"eth0\", \"192.168.1.7\"] ] }\n\n    before do\n      instance.instance_variable_set(:@machine, machine)\n      allow(Vagrant::Util::IPv4Interfaces).to receive(:ipv4_interfaces).and_return(interfaces)\n    end\n\n    it \"should check if the port is open\" do\n      expect(Vagrant::Util::IsPortOpen).to receive(:is_port_open?).with(host_ip, host_port).and_return(true)\n      instance.send(:port_check, host_ip, host_port)\n    end\n\n    context \"when host ip is 0.0.0.0\" do\n      let(:host_ip) { \"0.0.0.0\" }\n\n      context \"on windows\" do\n        before do\n          expect(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n        end\n\n        it \"should check the port on every IPv4 interface\" do\n          expect(Vagrant::Util::IsPortOpen).to receive(:is_port_open?).with(interfaces[0][1], host_port)\n          expect(Vagrant::Util::IsPortOpen).to receive(:is_port_open?).with(interfaces[1][1], host_port)\n          instance.send(:port_check, host_ip, host_port)\n        end\n\n        it \"should return false if the port is closed on any IPv4 interfaces\" do\n          expect(Vagrant::Util::IsPortOpen).to receive(:is_port_open?).with(interfaces[0][1], host_port).\n            and_return(true)\n          expect(Vagrant::Util::IsPortOpen).to receive(:is_port_open?).with(interfaces[1][1], host_port).\n            and_return(false)\n          expect(instance.send(:port_check, host_ip, host_port)).to be(false)\n        end\n\n        it \"should return true if the port is open on all IPv4 interfaces\" do\n          expect(Vagrant::Util::IsPortOpen).to receive(:is_port_open?).with(interfaces[0][1], host_port).\n            and_return(true)\n          expect(Vagrant::Util::IsPortOpen).to receive(:is_port_open?).with(interfaces[1][1], host_port).\n            and_return(true)\n          expect(instance.send(:port_check, host_ip, host_port)).to be(true)\n        end\n      end\n    end\n\n    context \"when host ip does not exist\" do\n      let(:host_ip) { \"192.168.99.100\" }\n      let(:name) { \"default\" }\n\n      it \"should not raise an error\" do\n        allow(machine).to receive(:name).and_return(name)\n        expect{ instance.send(:port_check, host_ip, host_port) }.\n          not_to raise_error\n      end\n    end\n\n    context \"with loopback address\" do\n      let (:host_ip) { \"127.1.2.40\" }\n\n      it \"should check if the port is open\" do\n        expect(Vagrant::Util::IsPortOpen).to receive(:is_port_open?).with(host_ip, host_port).and_return(true)\n        instance.send(:port_check, host_ip, host_port)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/has_provisioner_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\n\ndescribe Vagrant::Action::Builtin::HasProvisioner do\n  include_context \"unit\"\n\n  let(:provisioner_one) { double(\"provisioner_one\") }\n  let(:provisioner_two) { double(\"provisioner_two\") }\n  let(:provisioners) { [provisioner_one, provisioner_two] }\n  let(:machine) { double(\"machine\") }\n  let(:ui) { Vagrant::UI::Silent.new }\n  let(:env)    {{ machine: machine, ui: ui, root_path: Pathname.new(\".\") }}\n  let(:app)    { lambda { |*args| }}\n\n  subject { described_class.new(app, env) }\n\n  describe \"#call\" do\n    before do\n      allow(provisioner_one).to receive(:communicator_required).and_return(true)\n      allow(provisioner_one).to receive(:name)\n      allow(provisioner_one).to receive(:type)\n      allow(provisioner_two).to receive(:communicator_required).and_return(false)\n      allow(provisioner_two).to receive(:name)\n      allow(provisioner_two).to receive(:type)\n      allow(machine).to receive_message_chain(:config, :vm, :provisioners).and_return(provisioners)\n    end\n\n    context \"provider has capability :has_communicator\" do\n      before do\n        allow(machine).to receive_message_chain(:provider, :capability?).with(:has_communicator).and_return(true)\n      end\n\n      it \"does not skip any provisioners if provider has ssh\" do\n        allow(machine).to receive_message_chain(:provider, :capability).with(:has_communicator).and_return(true)\n        expect(provisioner_one).to_not receive(:communicator_required)\n        expect(provisioner_two).to_not receive(:communicator_required)\n\n        subject.call(env)\n        expect(env[:skip]).to eq([])\n      end\n\n      it \"skips provisioners that require a communicator if provider does not have ssh\" do\n        allow(machine).to receive_message_chain(:provider, :capability).with(:has_communicator).and_return(false)\n        expect(provisioner_one).to receive(:communicator_required)\n        expect(provisioner_two).to receive(:communicator_required)\n        expect(provisioner_one).to receive(:run=).with(:never)\n\n        subject.call(env)\n        expect(env[:skip]).to eq([provisioner_one])\n      end\n    end\n\n    context \"provider does not have capability :has_communicator\" do\n      before do\n        allow(machine).to receive_message_chain(:provider, :capability?).with(:has_communicator).and_return(false)\n      end\n\n      it \"does not skip any provisioners\" do\n        expect(provisioner_one).to_not receive(:communicator_required)\n        expect(provisioner_two).to_not receive(:communicator_required)\n        subject.call(env)\n        expect(env[:skip]).to eq([])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/is_env_set_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tmpdir\"\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::IsEnvSet do\n  let(:app) { lambda { |env| } }\n  let(:env) { { } }\n\n  describe \"#call\" do\n    it \"sets result to true if it is set\" do\n      env[:bar] = true\n\n      subject = described_class.new(app, env, :bar)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n      expect(env[:result]).to be(true)\n    end\n\n    it \"sets result to false if it isn't set\" do\n      subject = described_class.new(app, env, :bar)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n      expect(env[:result]).to be(false)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/is_state_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tmpdir\"\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::IsState do\n  let(:app) { lambda { |env| } }\n  let(:env) { { machine: machine } }\n  let(:machine) do\n    double(\"machine\").tap do |machine|\n      allow(machine).to receive(:state).and_return(state)\n    end\n  end\n\n  let(:state) { double(\"state\") }\n\n  describe \"#call\" do\n    it \"sets result to false if is proper state\" do\n      allow(state).to receive(:id).and_return(:foo)\n\n      subject = described_class.new(app, env, :bar)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n      expect(env[:result]).to be(false)\n    end\n\n    it \"sets result to true if is proper state\" do\n      allow(state).to receive(:id).and_return(:foo)\n\n      subject = described_class.new(app, env, :foo)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n      expect(env[:result]).to be(true)\n    end\n\n    it \"inverts the result if specified\" do\n      allow(state).to receive(:id).and_return(:foo)\n\n      subject = described_class.new(app, env, :foo, invert: true)\n\n      expect(app).to receive(:call).with(env)\n\n      subject.call(env)\n      expect(env[:result]).to be(false)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/lock_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::Lock do\n  let(:app) { lambda { |env| } }\n  let(:env) { {} }\n  let(:lock_path) do\n    Dir::Tmpname.create(\"vagrant-test-lock\") {}\n  end\n\n  let(:options) do\n    {\n      exception: Class.new(StandardError),\n      path:      lock_path\n    }\n  end\n\n  after do\n    File.unlink(lock_path) if File.file?(lock_path)\n  end\n\n  it \"should require a path\" do\n    expect { described_class.new(app, env) }.\n      to raise_error(ArgumentError)\n\n    expect { described_class.new(app, env, path: \"foo\") }.\n      to raise_error(ArgumentError)\n\n    expect { described_class.new(app, env, exception: \"foo\") }.\n      to raise_error(ArgumentError)\n\n    expect { described_class.new(app, env, path: \"bar\", exception: \"foo\") }.\n      to_not raise_error\n  end\n\n  it \"should allow the path to be a proc\" do\n    inner_acquire = true\n    app = lambda do |env|\n      File.open(lock_path, \"w+\") do |f|\n        inner_acquire = f.flock(File::LOCK_EX | File::LOCK_NB)\n      end\n    end\n\n    options[:path] = lambda { |env| lock_path }\n\n    instance = described_class.new(app, env, options)\n    instance.call(env)\n\n    expect(inner_acquire).to eq(false)\n  end\n\n  it \"should allow the exception to be a proc\" do\n    exception = options[:exception]\n    options[:exception] = lambda { |env| exception }\n\n    File.open(lock_path, \"w+\") do |f|\n      # Acquire lock\n      expect(f.flock(File::LOCK_EX | File::LOCK_NB)).to eq(0)\n\n      # Test!\n      instance = described_class.new(app, env, options)\n      expect { instance.call(env) }.\n        to raise_error(exception)\n    end\n  end\n\n  it \"should call the middleware with the lock held\" do\n    inner_acquire = true\n    app = lambda do |env|\n      File.open(lock_path, \"w+\") do |f|\n        inner_acquire = f.flock(File::LOCK_EX | File::LOCK_NB)\n      end\n    end\n\n    instance = described_class.new(app, env, options)\n    instance.call(env)\n\n    expect(inner_acquire).to eq(false)\n  end\n\n  it \"should raise an exception if the lock is already held\" do\n    File.open(lock_path, \"w+\") do |f|\n      # Acquire lock\n      expect(f.flock(File::LOCK_EX | File::LOCK_NB)).to eq(0)\n\n      # Test!\n      instance = described_class.new(app, env, options)\n      expect { instance.call(env) }.\n        to raise_error(options[:exception])\n    end\n  end\n\n  it \"should allow nesting locks within the same middleware sequence\" do\n    called = false\n    app = lambda { |env| called = true }\n    inner = described_class.new(app, env, options)\n    outer = described_class.new(inner, env, options)\n    outer.call(env)\n\n    expect(called).to eq(true)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/message_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tmpdir\"\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::Message do\n  let(:app) { lambda { |env| } }\n  let(:env) { { ui: ui } }\n\n  let(:ui)  { Vagrant::UI::Silent.new }\n\n  describe \"#call\" do\n    it \"outputs the given message\" do\n      subject = described_class.new(app, env, \"foo\")\n\n      expect(ui).to receive(:output).with(\"foo\").ordered\n      expect(app).to receive(:call).with(env).ordered\n\n      subject.call(env)\n    end\n\n    it \"outputs the given message after the call\" do\n      subject = described_class.new(app, env, \"foo\", post: true)\n\n      expect(app).to receive(:call).with(env).ordered\n      expect(ui).to receive(:output).with(\"foo\").ordered\n\n      subject.call(env)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/mixin_provisioners_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/vm\")\n\nrequire \"vagrant/action/builtin/mixin_provisioners\"\n\ndescribe Vagrant::Action::Builtin::MixinProvisioners do\n  include_context \"unit\"\n\n  let(:sandbox) { isolated_environment }\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    sandbox.vagrantfile(\"\")\n    sandbox.create_vagrant_env\n  end\n\n  let(:provisioner_config){ double(\"provisioner_config\", name: nil) }\n  let(:provisioner_one) do\n    prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"spec-test\", :shell)\n    prov.config = provisioner_config\n    prov\n  end\n  let(:provisioner_two) do\n    prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"spec-test\", :shell)\n    prov.config = provisioner_config\n    prov\n  end\n  let(:provisioner_three) do\n    prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(nil, :shell)\n    provisioner_config = double(\"provisioner_config\", name: \"my_shell\")\n    prov.config = provisioner_config\n    prov\n  end\n\n  let(:provisioner_instances) { [provisioner_one,provisioner_two,provisioner_three] }\n\n  let(:ui) { Vagrant::UI::Silent.new }\n  let(:vm) { double(\"vm\", provisioners: provisioner_instances) }\n  let(:config) { double(\"config\", vm: vm) }\n  let(:machine) { double(\"machine\", ui: ui, config: config) }\n\n  let(:env) {{ machine: machine, ui: machine.ui, root_path: Pathname.new(\".\") }}\n\n  subject do\n    Class.new do\n      extend Vagrant::Action::Builtin::MixinProvisioners\n    end\n  end\n\n  after do\n    sandbox.close\n    described_class.reset!\n  end\n\n  describe \"#provisioner_instances\" do\n    it \"returns all the instances of configured provisioners\" do\n      result = subject.provisioner_instances(env)\n      expect(result.size).to eq(provisioner_instances.size)\n      shell_one = result.first\n      expect(shell_one.first).to be_a(VagrantPlugins::Shell::Provisioner)\n      shell_two = result[1]\n      expect(shell_two.first).to be_a(VagrantPlugins::Shell::Provisioner)\n    end\n\n    it \"returns all the instances of configured provisioners\" do\n      result = subject.provisioner_instances(env)\n      expect(result.size).to eq(provisioner_instances.size)\n      shell_one = result.first\n      expect(shell_one[1][:name]).to eq(:\"spec-test\")\n      shell_two = result[1]\n      expect(shell_two[1][:name]).to eq(:\"spec-test\")\n      shell_three = result[2]\n      expect(shell_three[1][:name]).to eq(:\"my_shell\")\n    end\n  end\n\n  context \"#sort_provisioner_instances\" do\n    describe \"with no dependency provisioners\" do\n      it \"returns the original array\" do\n        result = subject.provisioner_instances(env)\n        expect(result.size).to eq(provisioner_instances.size)\n        shell_one = result.first\n        expect(shell_one.first).to be_a(VagrantPlugins::Shell::Provisioner)\n        shell_two = result[1]\n        expect(shell_two.first).to be_a(VagrantPlugins::Shell::Provisioner)\n      end\n    end\n\n    describe \"with before and after dependency provisioners\" do\n      let(:provisioner_config){ {} }\n      let(:provisioner_root) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"root-test\", :shell)\n        prov.config = provisioner_config\n        prov\n      end\n      let(:provisioner_before) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"before-test\", :shell)\n        prov.config = provisioner_config\n        prov.before = \"root-test\"\n        prov\n      end\n      let(:provisioner_after) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"after-test\", :shell)\n        prov.config = provisioner_config\n        prov.after = \"root-test\"\n        prov\n      end\n      let(:provisioner_instances) { [provisioner_root,provisioner_before,provisioner_after] }\n\n      it \"returns the array in the correct order\" do\n        result = subject.provisioner_instances(env)\n        expect(result[0].last[:name]).to eq(:\"before-test\")\n        expect(result[1].last[:name]).to eq(:\"root-test\")\n        expect(result[2].last[:name]).to eq(:\"after-test\")\n      end\n    end\n\n    describe \"with before :each dependency provisioners\" do\n      let(:provisioner_config){ {} }\n      let(:provisioner_root) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"root-test\", :shell)\n        prov.config = provisioner_config\n        prov\n      end\n      let(:provisioner_root2) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"root2-test\", :shell)\n        prov.config = provisioner_config\n        prov\n      end\n      let(:provisioner_before) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"before-test\", :shell)\n        prov.config = provisioner_config\n        prov.before = :each\n        prov\n      end\n\n      let(:provisioner_instances) { [provisioner_root,provisioner_root2,provisioner_before] }\n\n      it \"puts the each shortcut provisioners in place\" do\n        result = subject.provisioner_instances(env)\n\n        expect(result[0].last[:name]).to eq(:\"before-test\")\n        expect(result[1].last[:name]).to eq(:\"root-test\")\n        expect(result[2].last[:name]).to eq(:\"before-test\")\n        expect(result[3].last[:name]).to eq(:\"root2-test\")\n      end\n    end\n\n    describe \"with after :each dependency provisioners\" do\n      let(:provisioner_config){ {} }\n      let(:provisioner_root) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"root-test\", :shell)\n        prov.config = provisioner_config\n        prov\n      end\n      let(:provisioner_root2) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"root2-test\", :shell)\n        prov.config = provisioner_config\n        prov\n      end\n      let(:provisioner_after) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"after-test\", :shell)\n        prov.config = provisioner_config\n        prov.after = :each\n        prov\n      end\n\n      let(:provisioner_instances) { [provisioner_root,provisioner_root2,provisioner_after] }\n\n      it \"puts the each shortcut provisioners in place\" do\n        result = subject.provisioner_instances(env)\n\n        expect(result[0].last[:name]).to eq(:\"root-test\")\n        expect(result[1].last[:name]).to eq(:\"after-test\")\n        expect(result[2].last[:name]).to eq(:\"root2-test\")\n        expect(result[3].last[:name]).to eq(:\"after-test\")\n      end\n    end\n\n    describe \"with before and after :each dependency provisioners\" do\n      let(:provisioner_config){ {} }\n      let(:provisioner_root) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"root-test\", :shell)\n        prov.config = provisioner_config\n        prov\n      end\n      let(:provisioner_root2) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"root2-test\", :shell)\n        prov.config = provisioner_config\n        prov\n      end\n      let(:provisioner_after) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"after-test\", :shell)\n        prov.config = provisioner_config\n        prov.after = :each\n        prov\n      end\n      let(:provisioner_before) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"before-test\", :shell)\n        prov.config = provisioner_config\n        prov.before = :each\n        prov\n      end\n\n      let(:provisioner_instances) { [provisioner_root,provisioner_root2,provisioner_before,provisioner_after] }\n\n      it \"puts the each shortcut provisioners in place\" do\n        result = subject.provisioner_instances(env)\n\n        expect(result[0].last[:name]).to eq(:\"before-test\")\n        expect(result[1].last[:name]).to eq(:\"root-test\")\n        expect(result[2].last[:name]).to eq(:\"after-test\")\n        expect(result[3].last[:name]).to eq(:\"before-test\")\n        expect(result[4].last[:name]).to eq(:\"root2-test\")\n        expect(result[5].last[:name]).to eq(:\"after-test\")\n      end\n    end\n\n    describe \"with before and after :all dependency provisioners\" do\n      let(:provisioner_config){ {} }\n      let(:provisioner_root) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"root-test\", :shell)\n        prov.config = provisioner_config\n        prov\n      end\n      let(:provisioner_root2) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"root2-test\", :shell)\n        prov.config = provisioner_config\n        prov\n      end\n      let(:provisioner_after) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"after-test\", :shell)\n        prov.config = provisioner_config\n        prov.after = :all\n        prov\n      end\n      let(:provisioner_before) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"before-test\", :shell)\n        prov.config = provisioner_config\n        prov.before = :all\n        prov\n      end\n\n      let(:provisioner_instances) { [provisioner_root,provisioner_root2,provisioner_before,provisioner_after] }\n\n      it \"puts the each shortcut provisioners in place\" do\n        result = subject.provisioner_instances(env)\n\n        expect(result[0].last[:name]).to eq(:\"before-test\")\n        expect(result[1].last[:name]).to eq(:\"root-test\")\n        expect(result[2].last[:name]).to eq(:\"root2-test\")\n        expect(result[3].last[:name]).to eq(:\"after-test\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/mixin_synced_folders_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tmpdir\"\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire \"vagrant/action/builtin/mixin_synced_folders\"\n\ndescribe Vagrant::Action::Builtin::MixinSyncedFolders do\n  include_context \"synced folder actions\"\n\n  subject do\n    Class.new do\n      extend Vagrant::Action::Builtin::MixinSyncedFolders\n    end\n  end\n\n  let(:data_dir) { Pathname.new(Dir.mktmpdir(\"vagrant-test-mixin-synced-folders\")) }\n  let(:folders_class) { Vagrant::Plugin::V2::SyncedFolder::Collection }\n\n  let(:machine) do\n    double(\"machine\").tap do |machine|\n      allow(machine).to receive(:config).and_return(machine_config)\n      allow(machine).to receive(:data_dir).and_return(data_dir)\n    end\n  end\n\n  let(:machine_config) do\n    double(\"machine_config\").tap do |top_config|\n      allow(top_config).to receive(:vm).and_return(vm_config)\n    end\n  end\n\n  let(:vm_config) { double(\"machine_vm_config\", :allowed_synced_folder_types => nil) }\n\n  after do\n    FileUtils.rm_rf(data_dir)\n  end\n\n  describe \"default_synced_folder_type\" do\n    it \"returns the usable implementation\" do\n      plugins = {\n        \"bad\" => [impl(false, \"bad\"), 0],\n        \"good\" => [impl(true, \"good\"), 1],\n        \"best\" => [impl(true, \"best\"), 5],\n      }\n\n      result = subject.default_synced_folder_type(machine, plugins)\n      expect(result).to eq(\"best\")\n    end\n\n    it \"filters based on allowed_synced_folder_types\" do\n      expect(vm_config).to receive(:allowed_synced_folder_types).and_return([\"bad\", \"good\"])\n      plugins = {\n        \"bad\" => [impl(false, \"bad\"), 0],\n        \"good\" => [impl(true, \"good\"), 1],\n        \"best\" => [impl(true, \"best\"), 5],\n      }\n\n      result = subject.default_synced_folder_type(machine, plugins)\n      expect(result).to eq(\"good\")\n    end\n\n    it \"reprioritizes based on allowed_synced_folder_types\" do\n      plugins = {\n        \"bad\" => [impl(false, \"bad\"), 0],\n        \"good\" => [impl(true, \"good\"), 1],\n        \"same\" => [impl(true, \"same\"), 1],\n      }\n\n      expect(vm_config).to receive(:allowed_synced_folder_types).and_return([\"good\", \"same\"])\n      result = subject.default_synced_folder_type(machine, plugins)\n      expect(result).to eq(\"good\")\n\n      expect(vm_config).to receive(:allowed_synced_folder_types).and_return([\"same\", \"good\"])\n      result = subject.default_synced_folder_type(machine, plugins)\n      expect(result).to eq(\"same\")\n    end\n  end\n\n  describe \"impl_opts\" do\n    it \"should return only relevant keys\" do\n      env = {\n        foo_bar: \"baz\",\n        bar_bar: \"nope\",\n        foo_baz: \"bar\",\n      }\n\n      result = subject.impl_opts(\"foo\", env)\n      expect(result.length).to eq(2)\n      expect(result[:foo_bar]).to eq(\"baz\")\n      expect(result[:foo_baz]).to eq(\"bar\")\n    end\n  end\n\n  describe \"synced_folders\" do\n    let(:folders) { folders_class.new }\n    let(:plugins) { {} }\n\n    before do\n      plugins[:default] = [impl(true, \"default\"), 10]\n      plugins[:nfs] = [impl(true, \"nfs\"), 5]\n\n      allow(subject).to receive(:plugins).and_return(plugins)\n      allow(vm_config).to receive(:synced_folders).and_return(folders)\n    end\n\n    it \"should raise exception if bad type is given\" do\n      folders[\"root\"] = { type: \"bad\" }\n\n      expect { subject.synced_folders(machine) }.\n        to raise_error(StandardError)\n    end\n\n    it \"should return the proper set of folders\" do\n      folders[\"root\"] = {}\n      folders[\"another\"] = { type: \"\" }\n      folders[\"foo\"] = { type: \"default\" }\n      folders[\"nfs\"] = { type: \"nfs\" }\n\n      result = subject.synced_folders(machine)\n      expect(result.length).to eq(2)\n      expect(result[:default]).to eq({\n        \"another\" => folders[\"another\"].merge(__vagrantfile: true, plugin: true),\n        \"foo\" => folders[\"foo\"].merge(__vagrantfile: true, plugin: true),\n        \"root\" => folders[\"root\"].merge(__vagrantfile: true, plugin: true),\n      })\n      expect(result[:nfs]).to eq({\n        \"nfs\" => folders[\"nfs\"].merge(__vagrantfile: true, plugin: true),\n      })\n      expect(result.types).to eq([:default, :nfs])\n    end\n\n    it \"should return the proper set of folders of a custom config\" do\n      folders[\"root\"] = {}\n      folders[\"another\"] = {}\n\n      other_folders = { \"bar\" => {} }\n      other = double(\"config\")\n      allow(other).to receive(:synced_folders).and_return(other_folders)\n\n      result = subject.synced_folders(machine, config: other)\n      expect(result.length).to eq(1)\n      expect(result[:default]).to eq({\n        \"bar\" => other_folders[\"bar\"].merge(plugin: true),\n      })\n      expect(result.types).to eq([:default])\n    end\n\n    it \"should error if an explicit type is unusable\" do\n      plugins[:unusable] = [impl(false, \"bad\"), 15]\n      folders[\"root\"] = { type: \"unusable\" }\n\n      expect { subject.synced_folders(machine) }.\n        to raise_error(RuntimeError)\n    end\n\n    it \"should ignore disabled folders\" do\n      folders[\"root\"] = {}\n      folders[\"foo\"] = { disabled: true }\n\n      result = subject.synced_folders(machine)\n      expect(result.length).to eq(1)\n      expect(result[:default].length).to eq(1)\n      expect(result.types).to eq([:default])\n    end\n\n    it \"should scope hash override the settings\" do\n      folders[\"root\"] = {\n        hostpath: \"foo\",\n        type: \"nfs\",\n        nfs__foo: \"bar\",\n      }\n\n      result = subject.synced_folders(machine)\n      expect(result[:nfs][\"root\"][:foo]).to eql(\"bar\")\n      expect(result.types).to eq([:nfs])\n    end\n\n    it \"returns {} if cached read with no cache\" do\n      result = subject.synced_folders(machine, cached: true)\n      expect(result).to eql({})\n      expect(result.types).to eq([])\n    end\n\n    it \"should be able to save and retrieve cached versions\" do\n      folders[\"root\"] = {}\n      folders[\"another\"] = { type: \"\" }\n      folders[\"foo\"] = { type: \"default\" }\n      folders[\"nfs\"] = { type: \"nfs\" }\n\n      result = subject.synced_folders(machine)\n      subject.save_synced_folders(machine, result)\n\n      # Clear the folders so we know its reading from cache\n      old_folders = folders.dup\n      folders.clear\n\n      result = subject.synced_folders(machine, cached: true)\n      expect(result.length).to eq(2)\n      expect(result[:default]).to eq({\n        \"another\" => old_folders[\"another\"].merge(__vagrantfile: true, plugin: true),\n        \"foo\" => old_folders[\"foo\"].merge(__vagrantfile: true, plugin: true),\n        \"root\" => old_folders[\"root\"].merge(__vagrantfile: true, plugin: true),\n      })\n      expect(result[:nfs]).to eq({ \"nfs\" => old_folders[\"nfs\"].merge(__vagrantfile: true, plugin: true) })\n      expect(result.types).to eq([:default, :nfs])\n    end\n\n    it \"should be able to save and retrieve cached versions\" do\n      other_folders = {}\n      other = double(\"config\")\n      allow(other).to receive(:synced_folders).and_return(other_folders)\n\n      other_folders[\"foo\"] = { type: \"default\" }\n      result = subject.synced_folders(machine, config: other)\n      subject.save_synced_folders(machine, result)\n\n      # Clear the folders and set some more\n      folders.clear\n      folders[\"bar\"] = { type: \"default\" }\n      folders[\"baz\"] = { type: \"nfs\" }\n      result = subject.synced_folders(machine)\n      subject.save_synced_folders(machine, result, merge: true)\n\n      # Clear one last time\n      folders.clear\n\n      # Read them all back\n      result = subject.synced_folders(machine, cached: true)\n      expect(result.length).to eq(2)\n      expect(result[:default]).to eq({\n        \"foo\" => { type: \"default\", plugin: true },\n        \"bar\" => { type: \"default\", __vagrantfile: true, plugin: true },\n      })\n      expect(result[:nfs]).to eq({\n        \"baz\" => { type: \"nfs\", __vagrantfile: true, plugin: true }\n      })\n      expect(result.types).to eq([:default, :nfs])\n    end\n\n    it \"should remove items from the vagrantfile that were removed\" do\n      folders[\"foo\"] = { type: \"default\" }\n      result = subject.synced_folders(machine)\n      subject.save_synced_folders(machine, result)\n\n      # Clear the folders and set some more\n      folders.clear\n      folders[\"bar\"] = { type: \"default\" }\n      folders[\"baz\"] = { type: \"nfs\" }\n      result = subject.synced_folders(machine)\n      subject.save_synced_folders(machine, result, merge: true, vagrantfile: true)\n\n      # Clear one last time\n      folders.clear\n\n      # Read them all back\n      result = subject.synced_folders(machine, cached: true)\n      expect(result.length).to eq(2)\n      expect(result[:default]).to eq({\n        \"bar\" => { type: \"default\", __vagrantfile: true, plugin: true},\n      })\n      expect(result[:nfs]).to eq({\n        \"baz\" => { type: \"nfs\", __vagrantfile: true, plugin: true }\n      })\n      expect(result.types).to eq([:default, :nfs])\n    end\n  end\n\n  describe \"#save_synced_folders\" do\n    let(:folders) { folders_class.new }\n    let(:options) { {} }\n    let(:output_file) { double(\"output_file\") }\n\n    before do\n      allow(machine.data_dir).to receive(:join).with(\"synced_folders\").\n        and_return(output_file)\n      allow(output_file).to receive(:open).and_yield(output_file)\n      allow(output_file).to receive(:write)\n    end\n\n    it \"should write empty hash to file\" do\n      expect(output_file).to receive(:write).with(\"{}\")\n      subject.save_synced_folders(machine, folders, options)\n    end\n\n    context \"when folder data is defined\" do\n      let(:folders) {\n        {\"root\" => {\n          hostpath: \"foo\", type: \"nfs\", nfs__foo: \"bar\"}}\n      }\n\n      it \"should write folder information to file\" do\n        expect(output_file).to receive(:write).with(JSON.dump(folders))\n        subject.save_synced_folders(machine, folders, options)\n      end\n    end\n  end\n\n  describe \"#synced_folders_diff\" do\n    it \"sees two equal \" do\n      one = {\n        default: { \"foo\" => {} },\n      }\n\n      two = {\n        default: { \"foo\" => {} },\n      }\n\n      expect(subject.synced_folders_diff(one, two)).to be_empty\n    end\n\n    it \"sees modifications\" do\n      one = {\n        default: { \"foo\" => {} },\n      }\n\n      two = {\n        default: { \"foo\" => { hostpath: \"foo\" } },\n      }\n\n      result = subject.synced_folders_diff(one, two)\n      expect(result[:modified]).to_not be_empty\n    end\n\n    it \"sees adding\" do\n      one = {\n        default: { \"foo\" => {} },\n      }\n\n      two = {\n        default: {\n          \"foo\" => {},\n          \"bar\" => {},\n        },\n      }\n\n      result = subject.synced_folders_diff(one, two)\n      expect(result[:added]).to_not be_empty\n      expect(result[:removed]).to be_empty\n      expect(result[:modified]).to be_empty\n    end\n\n    it \"sees removing\" do\n      one = {\n        default: { \"foo\" => {} },\n      }\n\n      two = {\n        default: {},\n      }\n\n      result = subject.synced_folders_diff(one, two)\n      expect(result[:added]).to be_empty\n      expect(result[:removed]).to_not be_empty\n      expect(result[:modified]).to be_empty\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/provision_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/vm\")\n\ndescribe Vagrant::Action::Builtin::Provision do\n  include_context \"unit\"\n\n  let(:app) { lambda { |env| } }\n  let(:env) {\n    { machine: machine, ui: ui, hook: hook, provision_ignore_sentinel: false }\n  }\n  let(:hook){ double(\"hook\") }\n\n  let(:machine) do\n    double(\"machine\").tap do |machine|\n      allow(machine).to receive(:id).and_return('machine-id')\n      allow(machine).to receive(:data_dir).and_return(data_dir)\n      allow(machine).to receive(:config).and_return(machine_config)\n      allow(machine).to receive(:env).and_return(machine_env)\n    end\n  end\n\n  let(:machine_config) do\n    double(\"machine_config\").tap do |config|\n      allow(config).to receive(:vm).and_return(vm_config)\n    end\n  end\n\n  let(:data_dir){ temporary_dir }\n\n  let(:machine_env) do\n    isolated_environment.tap do |i_env|\n      allow(i_env).to receive(:data_dir).and_return(data_dir)\n      allow(i_env).to receive(:lock).and_yield\n    end\n  end\n\n  let(:vm_config) do\n    double(\"machine_vm_config\").tap do |config|\n      allow(config).to receive(:provisioners).and_return([])\n    end\n  end\n\n  let(:ui) { Vagrant::UI::Silent.new }\n\n  let(:instance){ described_class.new(app, env) }\n\n  describe \"#call\" do\n    context \"with no provisioners defined\" do\n      it \"should process empty set of provisioners\" do\n        expect(instance.call(env)).to eq([])\n      end\n\n      context \"with provisioning disabled\" do\n        before{ env[:provision_enabled] = false }\n        after{ env.delete(:provision_enabled) }\n\n        it \"should not process any provisioners\" do\n          expect(instance.call(env)).to be_nil\n        end\n      end\n    end\n\n    context \"with single provisioner defined\" do\n      let(:provisioner) do\n        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new(\"spec-test\", :shell)\n        prov.config = provisioner_config\n        prov\n      end\n      let(:provisioner_config){ double(\"provisioner_config\", name: \"spec-test\") }\n\n      before{ expect(vm_config).to receive(:provisioners).and_return([provisioner]) }\n\n      it \"should call the defined provisioner\" do\n        expect(hook).to receive(:call).with(:provisioner_run, anything)\n        instance.call(env)\n      end\n\n      context \"with provisioning disabled\" do\n        before{ env[:provision_enabled] = false }\n        after{ env.delete(:provision_enabled) }\n\n        it \"should not process any provisioners\" do\n          expect(hook).not_to receive(:call).with(:provisioner_run, anything)\n          expect(instance.call(env)).to be_nil\n        end\n      end\n\n      context \"with provisioner configured to run once\" do\n        before{ provisioner.run = :once }\n\n        it \"should run if machine is not provisioned\" do\n          expect(hook).to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n\n        it \"should not run if machine is provisioned\" do\n          File.open(File.join(data_dir.to_s, \"action_provision\"), \"w\") do |file|\n            file.write(\"1.5:machine-id\")\n          end\n          expect(hook).not_to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n\n        it \"should not run if provision types are set and provisioner is not included\" do\n          env[:provision_types] = [:\"other-provisioner\", :\"other-test\"]\n          expect(hook).not_to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n\n        it \"should run if provision types are set and include provisioner name\" do\n          env[:provision_types] = [:\"spec-test\"]\n          expect(hook).to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n\n        it \"should run if provision types are set and include provisioner type\" do\n          env[:provision_types] = [:shell]\n          expect(hook).to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n      end\n\n      context \"with provisioner configured to run always\" do\n        before{ provisioner.run = :always }\n\n        it \"should run if machine is not provisioned\" do\n          expect(hook).to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n\n        it \"should run if machine is provisioned\" do\n          File.open(File.join(data_dir.to_s, \"action_provision\"), \"w\") do |file|\n            file.write(\"1.5:machine-id\")\n          end\n          expect(hook).to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n\n        it \"should not run if provision types are set and provisioner is not included\" do\n          env[:provision_types] = [:\"other-provisioner\", :\"other-test\"]\n          expect(hook).not_to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n\n        it \"should run if provision types are set and include provisioner name\" do\n          env[:provision_types] = [:\"spec-test\"]\n          expect(hook).to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n\n        it \"should run if provision types are set and include provisioner type\" do\n          env[:provision_types] = [:shell]\n          expect(hook).to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n      end\n\n      context \"with provisioner configured to never run\" do\n        before{ provisioner.run = :never }\n\n        it \"should not run if machine is not provisioned\" do\n          expect(hook).not_to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n\n        it \"should not run if machine is provisioned\" do\n          File.open(File.join(data_dir.to_s, \"action_provision\"), \"w\") do |file|\n            file.write(\"1.5:machine-id\")\n          end\n          expect(hook).not_to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n\n        it \"should not run if provision types are set and provisioner is not included\" do\n          env[:provision_types] = [:\"other-provisioner\", :\"other-test\"]\n          expect(hook).not_to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n\n        it \"should run if provision types are set and include provisioner name\" do\n          env[:provision_types] = [:\"spec-test\"]\n          expect(hook).to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n\n        it \"should run if provision types are set and include provisioner name and machine is provisioned\" do\n          File.open(File.join(data_dir.to_s, \"action_provision\"), \"w\") do |file|\n            file.write(\"1.5:machine-id\")\n          end\n          env[:provision_types] = [:\"spec-test\"]\n          expect(hook).to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n\n        it \"should not run if provision types are set and include provisioner type\" do\n          env[:provision_types] = [:shell]\n          expect(hook).not_to receive(:call).with(:provisioner_run, anything)\n          instance.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/provisioner_cleanup_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tmpdir\"\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::ProvisionerCleanup do\n  let(:app) { lambda { |env| } }\n  let(:env) { { machine: machine, ui: ui } }\n\n  let(:machine) do\n    double(\"machine\").tap do |machine|\n      allow(machine).to receive(:config).and_return(machine_config)\n    end\n  end\n\n  let(:machine_config) do\n    double(\"machine_config\").tap do |config|\n      allow(config).to receive(:vm).and_return(vm_config)\n    end\n  end\n\n  let(:vm_config) { double(\"machine_vm_config\") }\n  let(:ui) { Vagrant::UI::Silent.new }\n\n  let(:provisioner) do\n    Class.new(Vagrant.plugin(\"2\", :provisioner))\n  end\n\n  before do\n    allow_any_instance_of(described_class).to receive(:provisioner_type_map)\n      .and_return(provisioner => :test_provisioner)\n    allow_any_instance_of(described_class).to receive(:provisioner_instances)\n      .and_return([provisioner])\n  end\n\n  describe \"initialize with :before\" do\n    it \"runs cleanup before\" do\n      instance = described_class.new(app, env, :before)\n      expect(provisioner).to receive(:cleanup).ordered\n      expect(app).to receive(:call).ordered\n      instance.call(env)\n    end\n  end\n\n  describe \"initialize with :after\" do\n    it \"runs cleanup after\" do\n      instance = described_class.new(app, env, :after)\n      expect(app).to receive(:call).ordered\n      expect(provisioner).to receive(:cleanup).ordered\n      instance.call(env)\n    end\n  end\n\n  it \"only runs cleanup tasks if the subclass defines it\" do\n    parent = Class.new do\n      class_variable_set(:@@cleanup, false)\n\n      def self.called?\n        class_variable_get(:@@cleanup)\n      end\n\n      def cleanup\n        self.class.class_variable_set(:@@cleanup)\n      end\n    end\n\n    child = Class.new(parent)\n\n    allow_any_instance_of(described_class).to receive(:provisioner_type_map)\n      .and_return(child => :test_provisioner)\n    allow_any_instance_of(described_class).to receive(:provisioner_instances)\n      .and_return([child])\n\n    expect(parent.called?).to be(false)\n    instance = described_class.new(app, env)\n    instance.call(env)\n    expect(parent.called?).to be(false)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/set_hostname_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::SetHostname do\n  let(:env) { { machine: machine, ui: ui } }\n  let(:app) { lambda { |env| } }\n  let(:machine) { double(\"machine\") }\n  let(:ui) { Vagrant::UI::Silent.new }\n\n  subject { described_class.new(app, env) }\n\n  before do\n    allow(machine).to receive_message_chain(:config, :vm, :hostname).and_return(\"whatever\")\n    allow(machine).to receive_message_chain(:guest, :capability)\n  end\n\n  it \"should change hostname if hosts modification enabled\" do\n    allow(machine).to receive_message_chain(:config, :vm, :allow_hosts_modification).and_return(true)\n    expect(machine).to receive(:guest)\n    subject.call(env)\n  end\n\n  it \"should not change hostname if hosts modification disabled\" do\n    allow(machine).to receive_message_chain(:config, :vm, :allow_hosts_modification).and_return(false)\n    expect(machine).not_to receive(:guest)\n    subject.call(env)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/ssh_exec_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::SSHExec do\n  let(:app) { lambda { |env| } }\n  let(:env) { { machine: machine } }\n  let(:machine) do\n    result = double(\"machine\")\n    allow(result).to receive(:ssh_info).and_return(machine_ssh_info)\n    result\n  end\n  let(:machine_ssh_info) { {} }\n  let(:ssh_klass) { Vagrant::Util::SSH }\n\n  before(:each) do\n    # Stub the methods so that even if we test incorrectly, no side\n    # effects actually happen.\n    allow(ssh_klass).to receive(:exec)\n  end\n\n  it \"should raise an exception if SSH is not ready\" do\n    not_ready_machine = double(\"machine\")\n    allow(not_ready_machine).to receive(:ssh_info).and_return(nil)\n\n    env[:machine] = not_ready_machine\n    expect { described_class.new(app, env).call(env) }.\n      to raise_error(Vagrant::Errors::SSHNotReady)\n  end\n\n  it \"should exec with the SSH info in the env if given\" do\n    ssh_info = { foo: :bar }\n\n    expect(ssh_klass).to receive(:exec).\n      with(ssh_info, nil)\n\n    env[:ssh_info] = ssh_info\n    described_class.new(app, env).call(env)\n  end\n\n  it \"should exec with the options given in `ssh_opts`\" do\n    ssh_opts = { foo: :bar }\n\n    expect(ssh_klass).to receive(:exec).\n      with(machine_ssh_info, ssh_opts)\n\n    env[:ssh_opts] = ssh_opts\n    described_class.new(app, env).call(env)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/ssh_run_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::SSHRun do\n  let(:app) { lambda { |env| } }\n  let(:env) { { machine: machine, tty: true } }\n\n  # SSH configuration information mock\n  let(:ssh) do\n    double(\"ssh\",\n      timeout: 1,\n      host: nil,\n      port: 5986,\n      guest_port: 5986,\n      pty: false,\n      keep_alive: false,\n      insert_key: false,\n      shell: 'bash -l'\n    )\n  end\n\n  let(:vm) do\n    double(\"vm\",\n      communicator: nil\n    )\n  end\n\n  # Configuration mock\n  let(:config) { double(\"config\", ssh: ssh, vm: vm) }\n\n  let(:machine) do\n    double(\"machine\",\n      config: config,)\n  end\n\n  let(:machine_ssh_info) { {} }\n  let(:ssh_klass) { Vagrant::Util::SSH }\n\n  before(:each) do\n    # Stub the methods so that even if we test incorrectly, no side\n    # effects actually happen.\n    allow(ssh_klass).to receive(:exec)\n    allow(machine).to receive(:ssh_info).and_return(machine_ssh_info)\n  end\n\n  it \"should raise an exception if SSH is not ready\" do\n    not_ready_machine = double(\"machine\")\n    allow(not_ready_machine).to receive(:ssh_info).and_return(nil)\n\n    env[:machine] = not_ready_machine\n    expect { described_class.new(app, env).call(env) }.\n      to raise_error(Vagrant::Errors::SSHNotReady)\n  end\n\n  it \"should exec with the SSH info in the env if given\" do\n    ssh_info = { foo: :bar }\n    opts = {:extra_args=>[\"-t\", \"bash -l -c 'echo test'\"], :subprocess=>true}\n\n    expect(ssh_klass).to receive(:exec).\n      with(ssh_info, opts)\n\n    env[:ssh_info] = ssh_info\n    env[:ssh_run_command] = \"echo test\"\n    described_class.new(app, env).call(env)\n  end\n\n  it \"should exec with the SSH info in the env if given and disable tty\" do\n    ssh_info = { foo: :bar }\n    opts = {:extra_args=>[\"bash -l -c 'echo test'\"], :subprocess=>true}\n    env[:tty] = false\n\n    expect(ssh_klass).to receive(:exec).\n      with(ssh_info, opts)\n\n    env[:ssh_info] = ssh_info\n    env[:ssh_run_command] = \"echo test\"\n    described_class.new(app, env).call(env)\n  end\n\n  it \"should exec with the options given in `ssh_opts`\" do\n    ssh_opts = { foo: :bar }\n\n    expect(ssh_klass).to receive(:exec).\n      with(machine_ssh_info, ssh_opts)\n\n    env[:ssh_opts] = ssh_opts\n    env[:ssh_run_command] = \"echo test\"\n    described_class.new(app, env).call(env)\n  end\n\n  context \"when using the WinSSH communicator\" do\n    let(:winssh) { double(\"winssh\", shell: \"foo\") }\n\n    before do\n      expect(vm).to receive(:communicator).and_return(:winssh)\n      expect(config).to receive(:winssh).and_return(winssh)\n      env[:tty] = nil\n    end\n\n    it \"should use the WinSSH shell for running ssh commands\" do\n      ssh_info = { foo: :bar }\n      opts = {:extra_args=>[\"foo -c 'dir'\"], :subprocess=>true}\n\n      expect(ssh_klass).to receive(:exec).\n        with(ssh_info, opts)\n\n      env[:ssh_info] = ssh_info\n      env[:ssh_run_command] = \"dir\"\n      described_class.new(app, env).call(env)\n    end\n\n    context \"when shell is cmd\" do\n      before do\n        expect(winssh).to receive(:shell).and_return('cmd')\n      end\n\n      it \"should use appropriate options for cmd\" do\n        ssh_info = { foo: :bar }\n        opts = {:extra_args=>[\"cmd /C dir \"], :subprocess=>true}\n\n        expect(ssh_klass).to receive(:exec).\n          with(ssh_info, opts)\n\n        env[:ssh_info] = ssh_info\n        env[:ssh_run_command] = \"dir\"\n        described_class.new(app, env).call(env)\n      end\n    end\n\n    context \"when shell is powershell\" do\n      before do\n        expect(winssh).to receive(:shell).and_return('powershell')\n      end\n\n      it \"should base64 encode the command\" do\n        ssh_info = { foo: :bar }\n        encoded_command = \"JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgA7ACAAZABpAHIA\"\n        opts = {:extra_args=>[\"powershell -encodedCommand #{encoded_command}\"], :subprocess=>true}\n\n        expect(ssh_klass).to receive(:exec).\n          with(ssh_info, opts)\n\n        env[:ssh_info] = ssh_info\n        env[:ssh_run_command] = \"dir\"\n        described_class.new(app, env).call(env)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/synced_folder_cleanup_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tmpdir\"\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::SyncedFolderCleanup do\n  include_context \"synced folder actions\"\n\n  let(:app) { lambda { |env| } }\n  let(:env) { { machine: machine, ui: ui } }\n  let(:machine) do\n    double(\"machine\").tap do |machine|\n      allow(machine).to receive(:config).and_return(machine_config)\n    end\n  end\n\n  let(:machine_config) do\n    double(\"machine_config\").tap do |top_config|\n      allow(top_config).to receive(:vm).and_return(vm_config)\n    end\n  end\n\n  let(:vm_config) { double(\"machine_vm_config\") }\n\n  let(:ui) { Vagrant::UI::Silent.new }\n\n  subject { described_class.new(app, env) }\n\n  def create_cleanup_tracker\n    Class.new(impl(true, \"good\")) do\n      class_variable_set(:@@clean, false)\n\n      def self.clean\n        class_variable_get(:@@clean)\n      end\n\n      def cleanup(machine, opts)\n        self.class.class_variable_set(:@@clean, true)\n      end\n    end\n  end\n\n  describe \"call\" do\n    let(:synced_folders) { {} }\n    let(:plugins) { {} }\n\n    before do\n      plugins[:default] = [impl(true, \"default\"), 10]\n      plugins[:nfs] = [impl(true, \"nfs\"), 5]\n\n      env[:machine] = Object.new\n      env[:root_path] = Pathname.new(Dir.mktmpdir(\"vagrant-test-synced-folder-cleanup-call\"))\n\n      allow(subject).to receive(:plugins).and_return(plugins)\n      allow(subject).to receive(:synced_folders).and_return(synced_folders)\n    end\n\n    after do\n      FileUtils.rm_rf(env[:root_path])\n    end\n\n    it \"should invoke cleanup\" do\n      tracker = create_cleanup_tracker\n      plugins[:tracker] = [tracker, 15]\n\n      synced_folders[\"tracker\"] = {\n        \"root\" => {\n          hostpath: \"foo\",\n        },\n\n        \"other\" => {\n          hostpath: \"bar\",\n          create: true,\n        }\n      }\n\n      expect_any_instance_of(tracker).to receive(:cleanup).\n        with(env[:machine], { tracker_foo: :bar })\n\n      # Test that the impl-specific opts are passed through\n      env[:tracker_foo] = :bar\n\n      subject.call(env)\n    end\n\n    it \"should invoke cleanup once per implementation\" do\n      trackers = []\n      (0..2).each do |tracker|\n        trackers << create_cleanup_tracker\n      end\n\n      plugins[:tracker_0] = [trackers[0], 15]\n      plugins[:tracker_1] = [trackers[1], 15]\n      plugins[:tracker_2] = [trackers[2], 15]\n\n      synced_folders[\"tracker_0\"] = {\n        \"root\" => {\n          hostpath: \"foo\"\n        },\n\n        \"other\" => {\n          hostpath: \"bar\",\n          create: true\n        }\n      }\n\n      synced_folders[\"tracker_1\"] = {\n        \"root\" => {\n          hostpath: \"foo\"\n        }\n      }\n\n      synced_folders[\"tracker_2\"] = {\n        \"root\" => {\n          hostpath: \"foo\"\n        },\n\n        \"other\" => {\n          hostpath: \"bar\",\n          create: true\n        },\n\n        \"another\" => {\n          hostpath: \"baz\"\n        }\n      }\n\n      subject.call(env)\n\n      expect(trackers[0].clean).to be(true)\n      expect(trackers[1].clean).to be(true)\n      expect(trackers[2].clean).to be(true)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/synced_folders_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tmpdir\"\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/vm\")\n\ndescribe Vagrant::Action::Builtin::SyncedFolders do\n  include_context \"unit\"\n  include_context \"synced folder actions\"\n\n  let(:app) { lambda { |env| } }\n  let(:env) { { machine: machine, ui: ui } }\n  let(:machine) do\n    double(\"machine\").tap do |machine|\n      allow(machine).to receive(:config).and_return(machine_config)\n    end\n  end\n\n  let(:machine_config) do\n    double(\"machine_config\").tap do |top_config|\n      allow(top_config).to receive(:vm).and_return(vm_config)\n    end\n  end\n\n  let(:vm_config) { double(\"machine_vm_config\") }\n\n  let(:ui) { Vagrant::UI::Silent.new }\n\n  subject { described_class.new(app, env) }\n\n  describe \"call\" do\n    let(:synced_folders) { {} }\n    let(:plugins) { {} }\n\n    before do\n      plugins[:default] = [impl(true, \"default\"), 10]\n      plugins[:nfs] = [impl(true, \"nfs\"), 5]\n\n      env[:root_path] = Pathname.new(Dir.mktmpdir(\"vagrant-test-synced-folders-call\"))\n      allow(subject).to receive(:plugins).and_return(plugins)\n      allow(subject).to receive(:synced_folders).and_return(synced_folders)\n      allow(subject).to receive(:save_synced_folders)\n      allow(machine).to receive_message_chain(:guest, :capability?).with(:persist_mount_shared_folder).and_return(false)\n    end\n\n    after do\n      FileUtils.rm_rf(env[:root_path])\n    end\n\n    it \"should create on the host if specified\" do\n      synced_folders[\"default\"] = {\n        \"root\" => {\n          hostpath: \"foo\",\n        },\n\n        \"other\" => {\n          hostpath: \"bar\",\n          create: true,\n        }\n      }\n\n      subject.call(env)\n\n      expect(env[:root_path].join(\"foo\")).not_to be_directory\n      expect(env[:root_path].join(\"bar\")).to be_directory\n    end\n\n    it \"doesn't expand the host path if told not to\" do\n      called_folders = nil\n      tracker = Class.new(impl(true, \"good\")) do\n        define_method(:prepare) do |machine, folders, opts|\n          called_folders = folders\n        end\n      end\n\n      plugins[:tracker] = [tracker, 15]\n\n      synced_folders[\"tracker\"] = {\n        \"root\" => {\n          hostpath: \"foo\",\n          hostpath_exact: true,\n        },\n\n        \"other\" => {\n          hostpath: \"/bar\",\n        }\n      }\n\n      subject.call(env)\n\n      expect(called_folders).to_not be_nil\n      expect(called_folders[\"root\"][:hostpath]).to eq(\"foo\")\n    end\n\n    it \"expands the host path relative to the root path\" do\n      called_folders = nil\n      tracker = Class.new(impl(true, \"good\")) do\n        define_method(:prepare) do |machine, folders, opts|\n          called_folders = folders\n        end\n      end\n\n      plugins[:tracker] = [tracker, 15]\n\n      synced_folders[\"tracker\"] = {\n        \"root\" => {\n          hostpath: \"foo\",\n        },\n\n        \"other\" => {\n          hostpath: \"/bar\",\n        }\n      }\n\n      subject.call(env)\n\n      expect(called_folders).to_not be_nil\n      expect(called_folders[\"root\"][:hostpath]).to eq(\n        Pathname.new(File.expand_path(\n          called_folders[\"root\"][:hostpath],\n          env[:root_path])).to_s)\n    end\n\n    it \"should invoke prepare then enable\" do\n      ids   = []\n      order = []\n      tracker = Class.new(impl(true, \"good\")) do\n        define_method(:prepare) do |machine, folders, opts|\n          ids   << self.object_id\n          order << :prepare\n        end\n\n        define_method(:enable) do |machine, folders, opts|\n          ids   << self.object_id\n          order << :enable\n        end\n      end\n\n      plugins[:tracker] = [tracker, 15]\n\n      synced_folders[\"tracker\"] = {\n        \"root\" => {\n          hostpath: \"foo\",\n        },\n\n        \"other\" => {\n          hostpath: \"bar\",\n          create: true,\n        }\n      }\n\n      subject.call(env)\n\n      expect(order).to eq([:prepare, :enable])\n      expect(ids.length).to eq(2)\n      expect(ids[0]).to eq(ids[1])\n    end\n\n    it \"syncs custom folders\" do\n      ids   = []\n      order = []\n      tracker = Class.new(impl(true, \"good\")) do\n        define_method(:prepare) do |machine, folders, opts|\n          ids   << self.object_id\n          order << :prepare\n        end\n\n        define_method(:enable) do |machine, folders, opts|\n          ids   << self.object_id\n          order << :enable\n        end\n      end\n\n      plugins[:tracker] = [tracker, 15]\n\n      synced_folders[\"tracker\"] = {\n        \"root\" => {\n          hostpath: \"foo\",\n        },\n\n        \"other\" => {\n          hostpath: \"bar\",\n          create: true,\n        }\n      }\n\n      new_config = double(\"config\")\n      env[:synced_folders_config] = new_config\n\n      expect(subject).to receive(:synced_folders).\n        with(machine, config: new_config, cached: false).\n        and_return(synced_folders)\n\n      subject.call(env)\n\n      expect(order).to eq([:prepare, :enable])\n      expect(ids.length).to eq(2)\n      expect(ids[0]).to eq(ids[1])\n    end\n\n    context \"with folders from the machine\" do\n      it \"removes outdated folders not present in config\" do\n        expect(subject).to receive(:save_synced_folders).with(\n          machine, anything, merge: true, vagrantfile: true)\n\n        subject.call(env)\n      end\n    end\n\n    context \"with custom folders\" do\n      before do\n        new_config = double(\"config\")\n        env[:synced_folders_config] = new_config\n\n        allow(subject).to receive(:synced_folders).\n          with(machine, config: new_config, cached: false).\n          and_return({})\n      end\n\n      it \"doesn't remove outdated folders not present in config\" do\n        expect(subject).to receive(:save_synced_folders).with(\n          machine, anything, merge: true)\n\n        subject.call(env)\n      end\n    end\n\n    context \"with guest capability to persist synced folders\" do\n      it \"persists folders\" do\n        synced_folders[\"default\"] = {\n          \"root\" => {\n            hostpath: \"foo\",\n          },\n\n          \"other\" => {\n            hostpath: \"bar\",\n            create: true,\n          }\n        }\n        allow(machine).to receive_message_chain(:guest, :capability?).with(:persist_mount_shared_folder).and_return(true)\n        expect(vm_config).to receive(:allow_fstab_modification).and_return(true)\n        expect(machine).to receive_message_chain(:guest, :capability).with(:persist_mount_shared_folder, synced_folders)\n        subject.call(env)\n      end\n\n      it \"does not persists folders if configured to not do so\" do\n        allow(machine).to receive_message_chain(:guest, :capability?).with(:persist_mount_shared_folder).and_return(true)\n        expect(vm_config).to receive(:allow_fstab_modification).and_return(false)\n        expect(machine).to receive_message_chain(:guest, :capability).with(:persist_mount_shared_folder, nil)\n        subject.call(env)\n      end\n    end\n\n    context \"when guest is not available\" do\n      it \"does not persist folders if guest is not available\" do\n      allow(machine).to receive_message_chain(:guest, :capability?).and_raise(Vagrant::Errors::MachineGuestNotReady)\n      expect(vm_config).to_not receive(:allow_fstab_modification)\n      subject.call(env)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/trigger_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::Trigger do\n  let(:app) { lambda { |env| } }\n  let(:env) { {machine: machine} }\n  let(:machine) { nil }\n  let(:triggers) { double(\"triggers\") }\n  let(:name) { \"trigger-name\" }\n  let(:timing) { :before }\n  let(:type) { :action }\n\n  let(:subject) { described_class.\n      new(app, env, name, triggers, timing, type) }\n\n  before do\n    allow(triggers).to receive(:fire)\n    allow(app).to receive(:call)\n  end\n\n\n  it \"should properly create a new instance\" do\n    expect(subject).to be_a(described_class)\n  end\n\n  it \"should fire triggers\" do\n    expect(triggers).to receive(:fire)\n    subject.call(env)\n  end\n\n  it \"should fire triggers without machine name\" do\n    expect(triggers).to receive(:fire).with(name, timing, nil, type, anything)\n    subject.call(env)\n  end\n\n  context \"when machine is provided\" do\n    let(:machine) { double(\"machine\", name: \"machine-name\") }\n\n    it \"should include machine name when firing triggers\" do\n      expect(triggers).to receive(:fire).with(name, timing, \"machine-name\", type, anything)\n      subject.call(env)\n    end\n  end\n\n  context \"when timing is :before\" do\n    it \"should not error\" do\n      expect { subject }.not_to raise_error\n    end\n  end\n\n  context \"when timing is :after\" do\n    it \"should not error\" do\n      expect { subject }.not_to raise_error\n    end\n  end\n\n  context \"when timing is not :before or :after\" do\n    let(:timing) { :unknown }\n\n    it \"should raise error\" do\n      expect { subject }.to raise_error(ArgumentError)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/builtin/wait_for_communicator_test.rb",
    "content": "require File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Builtin::WaitForCommunicator do\n  let(:app) { lambda { |env| } }\n  let(:ui) { lambda { |env| } }\n  let(:env) { { machine: machine, ui: ui } }\n\n  let(:vm) do\n    double(\"vm\",\n      communicator: nil\n    )\n  end\n\n  # Configuration mock\n  let(:config) { double(\"config\", vm: vm) }\n\n  # Communicate mock\n  let(:communicate) { double(\"communicate\") }\n\n  let(:state) { double(\"state\") }\n\n  let(:ui) { Vagrant::UI::Silent.new }\n\n  let(:machine) do\n    double(\"machine\",\n      config: config,\n      communicate: communicate, \n      state: state,)\n  end\n\n  before do \n    allow(vm).to receive(:boot_timeout).and_return(1)\n    allow(communicate).to receive(:wait_for_ready).with(1).and_return(true)\n  end\n\n  it \"raise an error if a bad state is encountered\" do\n    allow(state).to receive(:id).and_return(:stopped)\n    \n    expect { described_class.new(app, env, [:running]).call(env) }.\n    to raise_error(Vagrant::Errors::VMBootBadState)\n  end\n\n  it \"raise an error if the vm doesn't boot\" do\n    allow(communicate).to receive(:wait_for_ready).and_return(false)\n    allow(state).to receive(:id).and_return(:running)\n    \n    expect { described_class.new(app, env, [:running]).call(env) }.\n    to raise_error(Vagrant::Errors::VMBootTimeout)\n  end\n\n  it \"succeed when a valid state is encountered\" do\n    allow(communicate).to receive(:wait_for_ready).and_return(true)\n    allow(state).to receive(:id).and_return(:running)\n    \n    expect { described_class.new(app, env, [:running]).call(env) }.\n    to_not raise_error\n  end\nend"
  },
  {
    "path": "test/unit/vagrant/action/general/package_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Action::General::Package do\n\n  let(:app) { double(\"app\", call: nil) }\n  let(:env) {\n    {env: environment,\n      machine: machine, ui: ui}\n  }\n  let(:environment) { double(\"environment\") }\n  let(:machine) { double(\"machine\") }\n  let(:ui) { Vagrant::UI::Silent.new }\n\n  let(:subject) { described_class.new(app, env) }\n\n  before do\n    allow_any_instance_of(Vagrant::Errors::VagrantError).\n      to receive(:translate_error)\n  end\n\n  describe \".validate!\" do\n    let(:output) { double(\"output\", to_s: \"output-path\") }\n    let(:directory) { double(\"directory\") }\n\n    before do\n      allow(described_class).to receive(:fullpath).and_return(output)\n      allow(File).to receive(:directory?).with(output).and_return(false)\n      allow(File).to receive(:directory?).with(directory).and_return(true)\n      allow(File).to receive(:exist?).and_return(false)\n      allow(Vagrant::Util::Presence).to receive(:present?).with(directory).and_return(true)\n    end\n\n    it \"should not raise an error when options are valid\" do\n      expect { described_class.validate!(output, directory) }.not_to raise_error\n    end\n\n    it \"should raise error when output directory exists\" do\n      expect(File).to receive(:directory?).with(output).and_return(true)\n      expect {\n        described_class.validate!(output, directory)\n      }.to raise_error(Vagrant::Errors::PackageOutputDirectory)\n    end\n\n    it \"should raise error if output path exists\" do\n      expect(File).to receive(:exist?).with(output).and_return(true)\n      expect {\n        described_class.validate!(output, directory)\n      }.to raise_error(Vagrant::Errors::PackageOutputExists)\n    end\n\n    it \"should raise error if directory value not provided\" do\n      expect(Vagrant::Util::Presence).to receive(:present?).and_call_original\n      expect {\n        described_class.validate!(output, nil)\n      }.to raise_error(Vagrant::Errors::PackageRequiresDirectory)\n    end\n\n    it \"should raise error if directory path is not a directory\" do\n      expect(File).to receive(:directory?).with(directory).and_return(false)\n      expect {\n        described_class.validate!(output, directory)\n      }.to raise_error(Vagrant::Errors::PackageRequiresDirectory)\n    end\n  end\n\n  describe \"#package_with_folder_path\" do\n    let(:expanded_path) { double(\"expanded_path\") }\n\n    before do\n      allow(File).to receive(:expand_path).and_return(expanded_path)\n    end\n\n    it \"should create box folder if it does not exist\" do\n      expect(File).to receive(:directory?).with(expanded_path).and_return(false)\n      expect(subject).to receive(:create_box_folder).with(expanded_path)\n      subject.package_with_folder_path\n    end\n\n    it \"should not create box folder if it already exists\" do\n      expect(File).to receive(:directory?).with(expanded_path).and_return(true)\n      expect(subject).not_to receive(:create_box_folder)\n      subject.package_with_folder_path\n    end\n  end\n\n  describe \"#create_box_folder\" do\n    let(:path) { double(\"path\") }\n\n    before do\n      allow(FileUtils).to receive(:mkdir_p)\n      subject.instance_variable_set(:@env, env)\n    end\n\n    it \"should notify user of new directory creation\" do\n      expect(I18n).to receive(:t).with(an_instance_of(String), hash_including(folder_path: path))\n      subject.create_box_folder(path)\n    end\n\n    it \"should create the directory\" do\n      expect(FileUtils).to receive(:mkdir_p).with(path)\n      subject.create_box_folder(path)\n    end\n  end\n\n  describe \"#recover\" do\n    let(:env) { {\"vagrant.error\" => error} }\n    let(:error) { nil }\n    let(:fullpath) { double(\"fullpath\") }\n\n    before { allow(described_class).to receive(:fullpath).and_return(fullpath) }\n\n    it \"should delete packaged files if they exist\" do\n      expect(File).to receive(:exist?).with(fullpath).and_return(true)\n      expect(File).to receive(:delete).with(fullpath)\n      subject.recover(env)\n    end\n\n    it \"should not delete anything if package files do not exist\" do\n      expect(File).to receive(:exist?).with(fullpath).and_return(false)\n      expect(File).not_to receive(:delete).with(fullpath)\n      subject.recover(env)\n    end\n\n    context \"when vagrant error is PackageOutputDirectory\" do\n      let(:error) { Vagrant::Errors::PackageOutputDirectory.new }\n\n      it \"should not do anything\" do\n        expect(File).not_to receive(:exist?)\n        expect(File).not_to receive(:delete)\n        subject.recover(env)\n      end\n    end\n\n    context \"when vagrant error is PackageOutputExists\" do\n      let(:error) { Vagrant::Errors::PackageOutputExists.new }\n\n      it \"should not do anything\" do\n        expect(File).not_to receive(:exist?)\n        expect(File).not_to receive(:delete)\n        subject.recover(env)\n      end\n    end\n  end\n\n  describe \"#copy_include_files\" do\n    let(:package_directory) { @package_directory }\n    let(:package_files) {\n      Dir.glob(File.join(@package_files_directory, \"*\")).map {|i|\n        [i, File.basename(i)]\n      }\n    }\n\n    before do\n      @package_directory = Dir.mktmpdir\n      @package_files_directory = Dir.mktmpdir\n      3.times { |i| FileUtils.touch(File.join(@package_files_directory, \"file.#{i}\")) }\n      env[\"package.files\"] = package_files\n      env[\"package.directory\"] = package_directory\n      subject.instance_variable_set(:@env, env)\n    end\n\n    after do\n      FileUtils.rm_rf(@package_directory)\n      FileUtils.rm_rf(@package_files_directory)\n    end\n\n    it \"should copy all files to package directory\" do\n      subject.copy_include_files\n      expected_files = package_files.map(&:last).map { |f|\n        File.join(package_directory, \"include\", f)\n      }.sort\n      expect(\n        Dir.glob(File.join(package_directory, \"**\", \"*.*\")).sort\n      ).to eq(expected_files)\n    end\n\n    it \"should notify user of copy\" do\n      expect(ui).to receive(:info).at_least(1).and_call_original\n      subject.copy_include_files\n    end\n  end\n\n  describe \"#copy_info\" do\n    let(:package_directory) { @package_directory }\n    let(:package_info) { File.join(@package_info_directory, \"info.json\") }\n\n    before do\n      @package_directory = Dir.mktmpdir\n      @package_info_directory = Dir.mktmpdir\n      FileUtils.touch(File.join(@package_info_directory, \"info.json\"))\n      env[\"package.info\"] = package_info\n      env[\"package.directory\"] = package_directory\n      subject.instance_variable_set(:@env, env)\n\n      allow(ui).to receive(:info)\n    end\n\n    after do\n      FileUtils.rm_rf(@package_directory)\n      FileUtils.rm_rf(@package_info_directory)\n    end\n\n    it \"should copy the specified info.json to package directory\" do\n      subject.copy_info\n      expected_info = File.join(package_directory, \"info.json\")\n\n      expect(File.file? expected_info).to be_truthy\n    end\n  end\n\n  describe \"#compress\" do\n    let(:package_directory) { @package_directory }\n    let(:fullpath) { \"PATH\" }\n\n    before do\n      @package_directory = Dir.mktmpdir\n      FileUtils.touch(File.join(@package_directory, \"test-file1\"))\n      env[\"package.directory\"] = package_directory\n      subject.instance_variable_set(:@env, env)\n\n      allow(subject).to receive(:fullpath).and_return(fullpath)\n    end\n\n    after do\n      FileUtils.rm_rf(package_directory)\n    end\n\n    it \"should change directory into package directory\" do\n      expect(Vagrant::Util::SafeChdir).to receive(:safe_chdir).with(package_directory)\n      subject.compress\n    end\n\n    it \"should compress files using bsdtar\" do\n      expect(Vagrant::Util::SafeChdir).to receive(:safe_chdir).with(package_directory).and_call_original\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"bsdtar\", any_args, \"./test-file1\")\n      subject.compress\n    end\n  end\n\n  describe \"#write_metadata_json\" do\n    let(:metadata_path) { File.join(package_directory, \"metadata.json\") }\n    let(:package_directory) { @package_directory }\n    let(:machine_provider) { \"machine-provider\" }\n    let(:default_provider) { \"default-provider\" }\n\n    before do\n      @package_directory = Dir.mktmpdir\n      env[\"package.directory\"] = @package_directory\n      subject.instance_variable_set(:@env, env)\n\n      allow(machine).to receive(:provider_name).and_return(machine_provider)\n      allow(environment).to receive(:default_provider).and_return(default_provider)\n    end\n\n    after { FileUtils.rm_rf(@package_directory) }\n\n    it \"should not create a metadata.json file if it already exists\" do\n      expect(File).to receive(:exist?).with(metadata_path).and_return(true)\n      expect(File).not_to receive(:write)\n      subject.write_metadata_json\n    end\n\n    it \"should write a metadata file\" do\n      expect(File).to receive(:write).with(metadata_path, any_args)\n      subject.write_metadata_json\n    end\n\n    it \"should write machine provider to metadata file\" do\n      subject.write_metadata_json\n      content = JSON.load(File.read(metadata_path))\n      expect(content[\"provider\"]).to eq(machine_provider)\n    end\n\n    context \"when machine provider is unset\" do\n      let(:machine_provider) { nil }\n\n      it \"should write default provider to metadata file\" do\n        subject.write_metadata_json\n        content = JSON.load(File.read(metadata_path))\n        expect(content[\"provider\"]).to eq(default_provider)\n      end\n    end\n\n    context \"when machine provider and default provider are unset\" do\n      let(:machine_provider) { nil }\n      let(:default_provider) { nil }\n\n      it \"should not write metadata file\" do\n        subject.write_metadata_json\n        expect(File.exist?(metadata_path)).to be_falsey\n      end\n    end\n  end\n\n  describe \"#setup_private_key\" do\n    let(:package_directory) { @package_directory }\n    let(:private_key_path) { File.join(package_directory, \"datadir\", \"private_key\") }\n    let(:new_key_path) { File.join(package_directory, \"vagrant_private_key\") }\n    let(:vagrantfile_path) { File.join(package_directory, \"Vagrantfile\") }\n    let(:data_dir) { Pathname.new(File.join(package_directory, \"datadir\")) }\n    let(:config) {\n      double(\"config\", ssh: double(\"ssh\", private_key_path: [private_key_path]))\n    }\n\n    before do\n      @package_directory = Dir.mktmpdir\n      env[\"package.directory\"] = @package_directory\n      FileUtils.mkdir(File.join(@package_directory, \"datadir\"))\n      File.write(private_key_path, \"SSH KEY\")\n      subject.instance_variable_set(:@env, env)\n\n      allow(machine).to receive(:data_dir).and_return(data_dir)\n      allow(machine).to receive(:config).and_return(config)\n    end\n\n    after { FileUtils.rm_rf(@package_directory) }\n\n    it \"should create a new Vagrantfile\" do\n      subject.setup_private_key\n      expect(File.exist?(vagrantfile_path)).to be_truthy\n    end\n\n    it \"should create the new private ssh key\" do\n      subject.setup_private_key\n      expect(File.exist?(new_key_path)).to be_truthy\n    end\n\n    it \"should copy the contents of the ssh key\" do\n      subject.setup_private_key\n      expect(File.read(new_key_path)).to eq(File.read(private_key_path))\n    end\n\n    context \"with no machine provided\" do\n      before { env.delete(:machine) }\n\n      it \"should not create a private ssh key file\" do\n        subject.setup_private_key\n        expect(File.exist?(new_key_path)).to be_falsey\n      end\n    end\n\n    context \"when vagrant_private_key exists\" do\n      let(:private_key_path) { File.join(package_directory, \"datadir\", \"vagrant_private_key\") }\n\n      it \"should create the new private ssh key\" do\n        subject.setup_private_key\n        expect(File.exist?(new_key_path)).to be_truthy\n      end\n\n      it \"should copy the contents of the ssh key\" do\n        subject.setup_private_key\n        expect(File.read(new_key_path)).to eq(File.read(private_key_path))\n      end\n    end\n  end\n\n  describe \"#call\" do\n    let(:fullpath) { \"FULLPATH\" }\n\n    before do\n      allow(described_class).to receive(:validate!)\n      allow(subject).to receive(:fullpath).and_return(fullpath)\n      allow(subject).to receive(:package_with_folder_path)\n      allow(subject).to receive(:copy_include_files)\n      allow(subject).to receive(:setup_private_key)\n      allow(subject).to receive(:write_metadata_json)\n      allow(subject).to receive(:compress)\n    end\n\n    it \"should validate required arguments\" do\n      expect(described_class).to receive(:validate!)\n      subject.call(env)\n    end\n\n    it \"should raise error if output path is a directory\" do\n      expect(File).to receive(:directory?).with(fullpath).and_return(true)\n      expect {\n        subject.call(env)\n      }.to raise_error(Vagrant::Errors::PackageOutputDirectory)\n    end\n\n    it \"should call the next middleware\" do\n      expect(app).to receive(:call)\n      subject.call(env)\n    end\n\n    it \"should notify of package compressing\" do\n      expect(ui).to receive(:info).and_call_original\n      subject.call(env)\n    end\n\n    it \"should copy include files\" do\n      expect(subject).to receive(:copy_include_files)\n      subject.call(env)\n    end\n\n    it \"should setup private ssh key\" do\n      expect(subject).to receive(:setup_private_key)\n      subject.call(env)\n    end\n\n    it \"should write metadata json file\" do\n      expect(subject).to receive(:write_metadata_json)\n      subject.call(env)\n    end\n\n    it \"should compress the box\" do\n      expect(subject).to receive(:compress)\n      subject.call(env)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/hook_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/action/builder\"\nrequire \"vagrant/action/hook\"\n\ndescribe Vagrant::Action::Hook do\n  describe \"defaults\" do\n    describe '#after_hooks' do\n      subject { super().after_hooks }\n      it   { should be_empty }\n    end\n\n    describe '#before_hooks' do\n      subject { super().before_hooks }\n      it  { should be_empty }\n    end\n\n    describe '#append_hooks' do\n      subject { super().append_hooks }\n      it  { should be_empty }\n    end\n\n    describe '#prepend_hooks' do\n      subject { super().prepend_hooks }\n      it { should be_empty }\n    end\n  end\n\n  describe \"before hooks\" do\n    let(:existing) { \"foo\" }\n\n    it \"should append them\" do\n      block = Proc.new {}\n\n      subject.before(existing, 1)\n      subject.before(existing, 2)\n      subject.before(existing, 3, :arg, &block)\n\n      hooks = subject.before_hooks[existing]\n      expect(hooks.size).to eq(3)\n      expect(hooks[0].middleware).to eq(1)\n      expect(hooks[0].arguments.parameters).to eq([])\n      expect(hooks[0].arguments.keywords).to eq({})\n      expect(hooks[0].arguments.block).to be_nil\n      expect(hooks[1].middleware).to eq(2)\n      expect(hooks[1].arguments.parameters).to eq([])\n      expect(hooks[1].arguments.keywords).to eq({})\n      expect(hooks[1].arguments.block).to be_nil\n      expect(hooks[2].middleware).to eq(3)\n      expect(hooks[2].arguments.parameters).to eq([:arg])\n      expect(hooks[2].arguments.keywords).to eq({})\n      expect(hooks[2].arguments.block).to eq(block)\n    end\n  end\n\n  describe \"after hooks\" do\n    let(:existing) { \"foo\" }\n\n    it \"should append them\" do\n      block = Proc.new {}\n\n      subject.after(existing, 1)\n      subject.after(existing, 2)\n      subject.after(existing, 3, :arg, &block)\n\n      hooks = subject.after_hooks[existing]\n      expect(hooks.size).to eq(3)\n      expect(hooks[0].middleware).to eq(1)\n      expect(hooks[0].arguments.parameters).to eq([])\n      expect(hooks[0].arguments.keywords).to eq({})\n      expect(hooks[0].arguments.block).to be_nil\n      expect(hooks[1].middleware).to eq(2)\n      expect(hooks[1].arguments.parameters).to eq([])\n      expect(hooks[1].arguments.keywords).to eq({})\n      expect(hooks[1].arguments.block).to be_nil\n      expect(hooks[2].middleware).to eq(3)\n      expect(hooks[2].arguments.parameters).to eq([:arg])\n      expect(hooks[2].arguments.keywords).to eq({})\n      expect(hooks[2].arguments.block).to eq(block)\n    end\n  end\n\n  describe \"append\" do\n    it \"should make a list\" do\n      block = Proc.new {}\n\n      subject.append(1)\n      subject.append(2)\n      subject.append(3, :arg, &block)\n\n      hooks = subject.append_hooks\n      expect(hooks.size).to eq(3)\n      expect(hooks[0].middleware).to eq(1)\n      expect(hooks[0].arguments.parameters).to eq([])\n      expect(hooks[0].arguments.keywords).to eq({})\n      expect(hooks[0].arguments.block).to be_nil\n      expect(hooks[1].middleware).to eq(2)\n      expect(hooks[1].arguments.parameters).to eq([])\n      expect(hooks[1].arguments.keywords).to eq({})\n      expect(hooks[1].arguments.block).to be_nil\n      expect(hooks[2].middleware).to eq(3)\n      expect(hooks[2].arguments.parameters).to eq([:arg])\n      expect(hooks[2].arguments.keywords).to eq({})\n      expect(hooks[2].arguments.block).to eq(block)\n    end\n  end\n\n  describe \"prepend\" do\n    it \"should make a list\" do\n      block = Proc.new {}\n\n      subject.prepend(1)\n      subject.prepend(2)\n      subject.prepend(3, :arg, &block)\n\n      hooks = subject.prepend_hooks\n      expect(hooks.size).to eq(3)\n      expect(hooks[0].middleware).to eq(1)\n      expect(hooks[0].arguments.parameters).to eq([])\n      expect(hooks[0].arguments.keywords).to eq({})\n      expect(hooks[0].arguments.block).to be_nil\n      expect(hooks[1].middleware).to eq(2)\n      expect(hooks[1].arguments.parameters).to eq([])\n      expect(hooks[1].arguments.keywords).to eq({})\n      expect(hooks[1].arguments.block).to be_nil\n      expect(hooks[2].middleware).to eq(3)\n      expect(hooks[2].arguments.parameters).to eq([:arg])\n      expect(hooks[2].arguments.keywords).to eq({})\n      expect(hooks[2].arguments.block).to eq(block)\n    end\n  end\n\n  describe \"applying\" do\n    let(:builder) { Vagrant::Action::Builder.new }\n\n    it \"should build the proper stack\" do\n      subject.prepend(\"1\", 2)\n      subject.append(\"9\")\n      subject.after(\"1\", \"2\")\n      subject.before(\"9\", \"8\")\n\n      subject.apply(builder)\n\n      stack = builder.stack\n      expect(stack[0].middleware).to eq(\"1\")\n      expect(stack[0].arguments.parameters).to eq([2])\n      expect(stack[1].middleware).to eq(\"2\")\n      expect(stack[1].arguments.parameters).to eq([])\n      expect(stack[2].middleware).to eq(\"8\")\n      expect(stack[2].arguments.parameters).to eq([])\n      expect(stack[3].middleware).to eq(\"9\")\n      expect(stack[3].arguments.parameters).to eq([])\n    end\n\n    it \"should not prepend or append if disabled\" do\n      builder.use(\"3\")\n      builder.use(\"8\")\n\n      subject.prepend(\"1\", 2)\n      subject.append(\"9\")\n      subject.after(\"3\", \"4\")\n      subject.before(\"8\", \"7\")\n\n      subject.apply(builder, no_prepend_or_append: true)\n\n      stack = builder.stack\n      expect(stack[0].middleware).to eq(\"3\")\n      expect(stack[1].middleware).to eq(\"4\")\n      expect(stack[2].middleware).to eq(\"7\")\n      expect(stack[3].middleware).to eq(\"8\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/runner_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Runner do\n  let(:instance) { described_class.new(action_name: \"test\") }\n\n  it \"should raise an error if an invalid callable is given\" do\n    expect { instance.run(7) }.to raise_error(ArgumentError, /must be a callable/)\n  end\n\n  it \"should be able to use a Proc as a callable\" do\n    callable = Proc.new { raise Exception, \"BOOM\" }\n    expect { instance.run(callable) }.to raise_error(Exception, \"BOOM\")\n  end\n\n  it \"should be able to use a Method instance as a callable\" do\n    klass = Class.new do\n      def action(env)\n        raise Exception, \"BANG\"\n      end\n    end\n\n    callable = klass.new.method(:action)\n    expect { instance.run(callable) }.to raise_error(Exception, \"BANG\")\n  end\n\n  it \"should be able to use a Class as a callable\" do\n    callable = Class.new do\n      def initialize(app, env)\n      end\n\n      def self.name\n        \"TestAction\"\n      end\n\n      def call(env)\n        raise Exception, \"BOOM\"\n      end\n    end\n\n    expect { instance.run(callable) }.to raise_error(Exception, \"BOOM\")\n  end\n\n  it \"should be able to use a Class as a callable with no name attribute\" do\n    callable = Class.new do\n      def initialize(app, env)\n      end\n\n      def call(env)\n        raise Exception, \"BOOM\"\n      end\n    end\n\n    expect { instance.run(callable) }.to raise_error(Exception, \"BOOM\")\n  end\n\n  it \"should return the resulting environment\" do\n    callable = lambda do |env|\n      env[:data] = \"value\"\n\n      # Return nil so we can make sure it isn't using this return value\n      nil\n    end\n\n    result = instance.run(callable)\n    expect(result[:data]).to eq(\"value\")\n  end\n\n  it \"should pass options into hash given to callable\" do\n    result = nil\n    callable = lambda do |env|\n      result = env[\"data\"]\n    end\n\n    instance.run(callable, \"data\" => \"foo\")\n    expect(result).to eq(\"foo\")\n  end\n\n  it \"should pass global options into the hash\" do\n    result = nil\n    callable = lambda do |env|\n      result = env[\"data\"]\n    end\n\n    instance = described_class.new(\"data\" => \"bar\", action_name: \"test\")\n    instance.run(callable)\n    expect(result).to eq(\"bar\")\n  end\n\n  it \"should yield the block passed to the init method to get lazy loaded globals\" do\n    result = nil\n    callable = lambda do |env|\n      result = env[\"data\"]\n    end\n\n    instance = described_class.new { { \"data\" => \"bar\", action_name: \"test\" } }\n    instance.run(callable)\n    expect(result).to eq(\"bar\")\n  end\n\n  describe \"triggers\" do\n    let(:environment) { double(\"environment\", ui: nil) }\n    let(:machine) { double(\"machine\", triggers: machine_triggers, name: \"\") }\n    let(:env_triggers) { double(\"env_triggers\", find: []) }\n    let(:machine_triggers) { double(\"machine_triggers\", find: []) }\n\n    before do\n      allow(environment).to receive_message_chain(:vagrantfile, :config, :trigger)\n      allow(Vagrant::Plugin::V2::Trigger).to receive(:new).\n        and_return(env_triggers)\n    end\n\n    context \"when only environment is provided\" do\n      let(:instance) { described_class.new(action_name: \"test\", env: environment) }\n\n      it \"should use environment to build new trigger\" do\n        expect(environment).to receive_message_chain(:vagrantfile, :config, :trigger)\n        instance.run(lambda{|_|})\n      end\n\n      it \"should pass environment based triggers to callable\" do\n        callable = lambda { |env| expect(env[:triggers]).to eq(env_triggers) }\n        instance.run(callable)\n      end\n    end\n\n    context \"when only machine is provided\" do\n      let(:instance) { described_class.new(action_name: \"test\", machine: machine) }\n\n      it \"should pass machine based triggers to callable\" do\n        callable = lambda { |env| expect(env[:triggers]).to eq(machine_triggers) }\n        instance.run(callable)\n      end\n    end\n\n    context \"when both environment and machine is provided\" do\n      let(:instance) { described_class.new(action_name: \"test\", machine: machine, env: environment) }\n\n      it \"should pass machine based triggers to callable\" do\n        callable = lambda { |env| expect(env[:triggers]).to eq(machine_triggers) }\n        instance.run(callable)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/action/warden_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\ndescribe Vagrant::Action::Warden do\n  class ActionOne\n    def initialize(app, env)\n      @app = app\n    end\n\n    def call(env)\n      env[:data] << 1 if env[:data]\n      @app.call(env)\n    end\n\n    def recover(env)\n      env[:recover] << 1\n    end\n  end\n\n  class ActionTwo\n    def initialize(app, env)\n      @app = app\n    end\n\n    def call(env)\n      env[:data] << 2 if env[:data]\n      @app.call(env)\n    end\n\n    def recover(env)\n      env[:recover] << 2\n    end\n  end\n\n  class ExitAction\n    def initialize(app, env)\n      @app = app\n    end\n\n    def call(env)\n      @app.call(env)\n    end\n\n    def recover(env)\n      env[:recover] = true\n    end\n  end\n\n  let(:data) { { data: [] } }\n  let(:instance) { described_class.new }\n\n  # This returns a proc that can be used with the builder\n  # that simply appends data to an array in the env.\n  def appender_proc(data)\n    Proc.new { |env| env[:data] << data }\n  end\n\n  it \"calls the actions like normal\" do\n    instance = described_class.new([appender_proc(1), appender_proc(2)], data)\n    instance.call(data)\n\n    expect(data[:data]).to eq([1, 2])\n  end\n\n  it \"starts a recovery sequence when an exception is raised\" do\n    error_proc = Proc.new { raise \"ERROR!\" }\n\n    data     = { recover: [] }\n    instance = described_class.new([ActionOne, ActionTwo, error_proc], data)\n\n    # The error should be raised back up\n    expect { instance.call(data) }.\n      to raise_error(RuntimeError)\n\n    # Verify the recovery process goes in reverse order\n    expect(data[:recover]).to eq([2, 1])\n\n    # Verify that the error is available in the data\n    expect(data[\"vagrant.error\"]).to be_kind_of(RuntimeError)\n  end\n\n  it \"does not do a recovery sequence if SystemExit is raised\" do\n    # Make a proc that just calls \"abort\" which raises a\n    # SystemExit exception.\n    error_proc = Proc.new { abort }\n\n    instance = described_class.new([ExitAction, error_proc], data)\n\n    # The SystemExit should come through\n    expect { instance.call(data) }.to raise_error(SystemExit)\n\n    # The recover should not have been called\n    expect(data.key?(:recover)).not_to be\n  end\n\n  it \"does not do a recovery sequence if NoMemoryError is raised\" do\n    error_proc = Proc.new { raise NoMemoryError }\n\n    instance = described_class.new([ExitAction, error_proc], data)\n\n    # The SystemExit should come through\n    expect { instance.call(data) }.to raise_error(NoMemoryError)\n\n    # The recover should not have been called\n    expect(data.key?(:recover)).not_to be\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/alias_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\nrequire \"vagrant/alias\"\n\ndescribe Vagrant::Alias do\n  include_context \"unit\"\n  include_context \"command plugin helpers\"\n\n  let(:iso_env) { isolated_environment }\n  let(:env)     { iso_env.create_vagrant_env }\n\n  describe \"#interpret\" do\n    let(:interpreter) { described_class.new(env) }\n\n    it \"returns nil for comments\" do\n      comments = [\n        \"# this is a comment\",\n        \"# so is this       \",\n        \"       # and this\",\n        \"       # this too       \"\n      ]\n\n      comments.each do |comment|\n        expect(interpreter.interpret(comment)).to be_nil\n      end\n    end\n\n    it \"raises an error on invalid keywords\" do\n      keywords = [\n        \"keyword with a space = command\",\n        \"keyword\\twith a tab = command\",\n        \"keyword\\nwith a newline = command\",\n      ]\n\n      keywords.each do |keyword|\n        expect { interpreter.interpret(keyword) }.to raise_error(Vagrant::Errors::AliasInvalidError)\n      end\n    end\n\n    it \"properly interprets a simple alias\" do\n      keyword, command = interpreter.interpret(\"keyword=command\")\n\n      expect(keyword).to eq(\"keyword\")\n      expect(command).to eq(\"command\")\n    end\n\n    it \"properly interprets an alias with excess whitespace\" do\n      keyword, command = interpreter.interpret(\"     keyword      =     command    \")\n\n      expect(keyword).to eq(\"keyword\")\n      expect(command).to eq(\"command\")\n    end\n\n    it \"properly interprets an alias with an equals sign in the command\" do\n      keyword, command = interpreter.interpret(\"     keyword      =     command = command    \")\n\n      expect(keyword).to eq(\"keyword\")\n      expect(command).to eq(\"command = command\")\n    end\n\n    it \"allows keywords with non-alpha-numeric characters\" do\n      keyword, command = interpreter.interpret(\"keyword! = command\")\n\n      expect(keyword).to eq(\"keyword!\")\n      expect(command).to eq(\"command\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/batch_action_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'thread'\nrequire 'timeout'\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\ndescribe Vagrant::BatchAction do\n  let(:called_actions) { [] }\n  let!(:lock) { Mutex.new }\n  let(:provider_name) { \"test\" }\n  let(:provider_options) { {} }\n\n  def new_machine(options)\n    double(\"machine\").tap do |m|\n      allow(m).to receive(:provider_name).and_return(provider_name)\n      allow(m).to receive(:provider_options).and_return(options)\n      allow(m).to receive(:action) do |action, opts|\n        lock.synchronize do\n          called_actions << [m, action, opts]\n        end\n      end\n    end\n  end\n\n  describe \"#run\" do\n    let(:machine) { new_machine(provider_options) }\n    let(:machine2) { new_machine(provider_options) }\n\n    it \"should run the actions on the machines in order\" do\n      subject.action(machine, \"up\")\n      subject.action(machine2, \"destroy\")\n      subject.run\n\n      expect(called_actions.include?([machine, \"up\", nil])).to be\n      expect(called_actions.include?([machine2, \"destroy\", nil])).to be\n    end\n\n    it \"should run the arbitrary methods in order\" do\n      called = []\n      subject.custom(machine)  { |m| called << m }\n      subject.custom(machine2) { |m| called << m }\n      subject.run\n\n      expect(called[0]).to equal(machine)\n      expect(called[1]).to equal(machine2)\n    end\n\n    it \"should handle forks gracefully\", :skip_windows do\n      # Doesn't need to be tested on Windows since Windows doesn't\n      # support fork(1)\n      allow(machine).to receive(:action) do |action, opts|\n        pid = fork\n        if !pid\n          # Child process\n          exit\n        end\n\n        # Parent process, wait for the child to exit\n        Timeout.timeout(1) do\n          Process.waitpid(pid)\n        end\n      end\n\n      subject.action(machine, \"up\")\n      subject.run\n    end\n\n    context \"with provider supporting parallel actions\" do\n      let(:provider_options) { {parallel: true} }\n\n      it \"should flag threads as being parallel actions\" do\n        parallel = nil\n        subject.custom(machine) { |m| parallel = Thread.current[:batch_parallel_action] }\n        subject.custom(machine) { |*_| }\n        subject.run\n        expect(parallel).to eq(true)\n      end\n\n      it \"should exit the process if exit_code has been set\" do\n        subject.custom(machine) { |m| Thread.current[:exit_code] = 1}\n        subject.custom(machine) { |*_| }\n        expect(Process).to receive(:exit!).with(1)\n        subject.run\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/box_collection_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\nrequire \"pathname\"\nrequire 'tempfile'\n\ndescribe Vagrant::BoxCollection, :skip_windows, :bsdtar do\n  include_context \"unit\"\n\n  let(:box_class)   { Vagrant::Box }\n  let(:environment) { isolated_environment }\n\n  subject { described_class.new(environment.boxes_dir) }\n\n  it \"should tell us the directory it is using\" do\n    expect(subject.directory).to eq(environment.boxes_dir)\n  end\n\n  describe \"#all\" do\n    let(:ui) { Vagrant::UI::Silent.new }\n\n    it \"should return an empty array when no boxes are there\" do\n      expect(subject.all).to eq([])\n    end\n\n    it \"should return the boxes and their providers\" do\n      # Create some boxes\n      environment.box3(\"foo\", \"1.0\", :virtualbox)\n      environment.box3(\"foo\", \"1.0\", :vmware)\n      environment.box3(\"bar\", \"0\", :ec2)\n      environment.box3(\"foo-VAGRANTSLASH-bar\", \"1.0\", :virtualbox)\n      environment.box3(\"foo-VAGRANTCOLON-colon\", \"1.0\", :virtualbox)\n\n      # Verify some output\n      results = subject.all\n      expect(results.length).to eq(5)\n      expect(results).to include([\"foo\", \"1.0\", :virtualbox, nil])\n      expect(results).to include([\"foo\", \"1.0\", :vmware, nil])\n      expect(results).to include([\"bar\", \"0\", :ec2, nil])\n      expect(results).to include([\"foo/bar\", \"1.0\", :virtualbox, nil])\n      expect(results).to include([\"foo:colon\", \"1.0\", :virtualbox, nil])\n    end\n\n    it \"should return the boxes and their providers even if box has wrong version\" do\n      allow(Vagrant::UI::Prefixed).to receive(:new).and_return(ui)\n      # Create some boxes\n      environment.box3(\"foo\", \"fake-invalid-version\", :virtualbox)\n      environment.box3(\"foo\", \"1.0\", :vmware)\n      environment.box3(\"bar\", \"0\", :ec2)\n      environment.box3(\"foo-VAGRANTSLASH-bar\", \"1.0\", :virtualbox)\n      environment.box3(\"foo-VAGRANTCOLON-colon\", \"1.0\", :virtualbox)\n\n      expect(ui).to receive(:warn).once.and_call_original\n\n      # Verify some output\n      results = subject.all\n      expect(results.length).to eq(4)\n      expect(results).not_to include([\"foo\", \"1.0\", :virtualbox, nil])\n      expect(results).to include([\"foo\", \"1.0\", :vmware, nil])\n      expect(results).to include([\"bar\", \"0\", :ec2, nil])\n      expect(results).to include([\"foo/bar\", \"1.0\", :virtualbox, nil])\n      expect(results).to include([\"foo:colon\", \"1.0\", :virtualbox, nil])\n    end\n\n    it 'does not raise an exception when a file appears in the boxes dir' do\n      Tempfile.open('vagrant-a_file', environment.boxes_dir) do\n        expect { subject.all }.to_not raise_error\n      end\n    end\n\n    context \"with multiple versions of the same box\" do\n      before do\n        environment.box3(\"foo\", \"1.0\", :virtualbox)\n        environment.box3(\"foo\", \"2.0.3\", :virtualbox)\n        environment.box3(\"foo\", \"2.0.4\", :virtualbox)\n        environment.box3(\"foo\", \"10.3\", :virtualbox)\n        environment.box3(\"foo\", \"1.0\", :vmware)\n        environment.box3(\"foo\", \"0.4.3\", :vmware)\n        environment.box3(\"foo\", \"2.0.1\", :vmware)\n        environment.box3(\"foo\", \"2.0.2.dev\", :vmware)\n        environment.box3(\"foo\", \"2.0.2\", :vmware)\n        environment.box3(\"bar\", \"20161203.2\", :ec2)\n        environment.box3(\"bar\", \"20161203.2.3\", :ec2)\n        environment.box3(\"bar\", \"20151102.0.0\", :ec2)\n        environment.box3(\"foo-VAGRANTSLASH-bar\", \"1.0\", :virtualbox)\n        environment.box3(\"foo-VAGRANTCOLON-colon\", \"1.0\", :virtualbox)\n      end\n\n      it \"should sort boxes by name\" do\n        result = subject.all.map(&:first).uniq\n        expect(result).to eq([\"bar\", \"foo\", \"foo/bar\", \"foo:colon\"])\n      end\n\n      it \"should group boxes by provider\" do\n        expect do\n          current = \"\"\n          seen_pairs = {}\n          subject.all.each do |box_info|\n            box_key = \"#{box_info[0]}_#{box_info[2]}\"\n            if current != box_key\n              if seen_pairs[box_key]\n                raise KeyError.new(\"Box/provider pair already seen. Invalid sort!\")\n              else\n                current = box_key\n                seen_pairs[box_key] = true\n              end\n            end\n          end\n        end.not_to raise_error\n      end\n\n      context \"with architectures defined\" do\n        before do\n          environment.box3(\"foo-VAGRANTSLASH-bar\", \"1.0\", :virtualbox, architecture: :arm64)\n        end\n\n        it \"should sort boxes by name\" do\n          result = subject.all.map(&:first).uniq\n          expect(result).to eq([\"bar\", \"foo\", \"foo/bar\", \"foo:colon\"])\n        end\n      end\n\n      it \"should sort boxes by version\" do\n        box_list = subject.all.find_all do |box_info|\n          box_info[0] == \"foo\" && box_info[2].to_s == \"virtualbox\"\n        end\n        result = box_list.map{|box_info| box_info[1]}\n        expect(result).to eq([\n          \"1.0\",\n          \"2.0.3\",\n          \"2.0.4\",\n          \"10.3\"\n        ])\n      end\n\n      it \"should sort boxes with pre-release versions\" do\n        box_list = subject.all.find_all do |box_info|\n          box_info[0] == \"foo\" && box_info[2].to_s == \"vmware\"\n        end\n        result = box_list.map{|box_info| box_info[1]}\n        expect(result).to eq([\n          \"0.4.3\",\n          \"1.0\",\n          \"2.0.1\",\n          \"2.0.2.dev\",\n          \"2.0.2\"\n        ])\n      end\n    end\n  end\n\n  describe \"#clean\" do\n    it \"removes the directory if no other versions of the box exists\" do\n      # Create a few boxes, immediately destroy them\n      environment.box3(\"foo\", \"1.0\", :virtualbox)\n      environment.box3(\"foo\", \"1.0\", :vmware)\n\n      # Delete them all\n      subject.all.each do |parts|\n        subject.find(parts[0], parts[2], \">= 0\").destroy!\n      end\n\n      # Cleanup\n      subject.clean(\"foo\")\n\n      # Make sure the whole directory is empty\n      expect(environment.boxes_dir.children).to be_empty\n    end\n\n    it \"doesn't remove the directory if a provider exists\" do\n      # Create a few boxes, immediately destroy them\n      environment.box3(\"foo\", \"1.0\", :virtualbox)\n      environment.box3(\"foo\", \"1.0\", :vmware)\n\n      # Delete them all\n      subject.find(\"foo\", :virtualbox, \">= 0\").destroy!\n\n      # Cleanup\n      subject.clean(\"foo\")\n\n      # Make sure the whole directory is not empty\n      expect(environment.boxes_dir.children).to_not be_empty\n\n      # Make sure the results still exist\n      results = subject.all\n      expect(results.length).to eq(1)\n      expect(results).to include([\"foo\", \"1.0\", :vmware, nil])\n    end\n\n    it \"doesn't remove the directory if a version exists\" do\n      # Create a few boxes, immediately destroy them\n      environment.box3(\"foo\", \"1.0\", :virtualbox)\n      environment.box3(\"foo\", \"1.2\", :virtualbox)\n\n      # Delete them all\n      subject.find(\"foo\", :virtualbox, \">= 1.1\").destroy!\n\n      # Cleanup\n      subject.clean(\"foo\")\n\n      # Make sure the whole directory is not empty\n      expect(environment.boxes_dir.children).to_not be_empty\n\n      # Make sure the results still exist\n      results = subject.all\n      expect(results.length).to eq(1)\n      expect(results).to include([\"foo\", \"1.0\", :virtualbox, nil])\n    end\n  end\n\n  describe \"#find\" do\n    it \"fails with custom error on invalid version\" do\n      expect { subject.find(\"foo\", :i_dont_exist, \"v1.2.2\") }.\n        to raise_error(Vagrant::Errors::BoxVersionInvalid)\n    end\n\n    it \"returns nil if the box does not exist\" do\n      expect(subject.find(\"foo\", :i_dont_exist, \">= 0\")).to be_nil\n    end\n\n    it \"returns a box if the box does exist\" do\n      # Create the \"box\"\n      environment.box3(\"foo\", \"0\", :virtualbox)\n\n      # Actual test\n      result = subject.find(\"foo\", :virtualbox, \">= 0\")\n      expect(result).to_not be_nil\n      expect(result).to be_kind_of(box_class)\n      expect(result.name).to eq(\"foo\")\n      expect(result.metadata_url).to be_nil\n    end\n\n    it \"returns a box if the box does exist, with no constraints\" do\n      # Create the \"box\"\n      environment.box3(\"foo\", \"0\", :virtualbox)\n\n      # Actual test\n      result = subject.find(\"foo\", :virtualbox, nil)\n      expect(result).to_not be_nil\n      expect(result).to be_kind_of(box_class)\n      expect(result.name).to eq(\"foo\")\n      expect(result.metadata_url).to be_nil\n    end\n\n    it \"sets a metadata URL if it has one\" do\n      # Create the \"box\"\n      environment.box3(\"foo\", \"0\", :virtualbox,\n        metadata_url: \"foourl\")\n\n      # Actual test\n      result = subject.find(\"foo\", :virtualbox, \">= 0\")\n      expect(result).to_not be_nil\n      expect(result).to be_kind_of(box_class)\n      expect(result.name).to eq(\"foo\")\n      expect(result.metadata_url).to eq(\"foourl\")\n    end\n\n    it \"sets the metadata URL to an authenticated URL if it has one\" do\n      hook    = double(\"hook\")\n      subject = described_class.new(environment.boxes_dir, hook: hook)\n\n      # Create the \"box\"\n      environment.box3(\"foo\", \"0\", :virtualbox,\n        metadata_url: \"foourl\")\n\n      expect(hook).to receive(:call).with(any_args) { |name, env|\n        expect(name).to eq(:authenticate_box_url)\n        expect(env[:box_urls]).to eq([\"foourl\"])\n        true\n      }.and_return(box_urls: [\"bar\"])\n\n      # Actual test\n      result = subject.find(\"foo\", :virtualbox, \">= 0\")\n      expect(result).to_not be_nil\n      expect(result).to be_kind_of(box_class)\n      expect(result.name).to eq(\"foo\")\n      expect(result.metadata_url).to eq(\"bar\")\n    end\n\n    it \"returns latest version matching constraint\" do\n      # Create the \"box\"\n      environment.box3(\"foo\", \"1.0\", :virtualbox)\n      environment.box3(\"foo\", \"1.5\", :virtualbox)\n\n      # Actual test\n      result = subject.find(\"foo\", :virtualbox, \">= 0\")\n      expect(result).to_not be_nil\n      expect(result).to be_kind_of(box_class)\n      expect(result.name).to eq(\"foo\")\n      expect(result.version).to eq(\"1.5\")\n    end\n\n    it \"can satisfy complex constraints\" do\n      # Create the \"box\"\n      environment.box3(\"foo\", \"0.1\", :virtualbox)\n      environment.box3(\"foo\", \"1.0\", :virtualbox)\n      environment.box3(\"foo\", \"2.1\", :virtualbox)\n\n      # Actual test\n      result = subject.find(\"foo\", :virtualbox, \">= 0.9, < 1.5\")\n      expect(result).to_not be_nil\n      expect(result).to be_kind_of(box_class)\n      expect(result.name).to eq(\"foo\")\n      expect(result.version).to eq(\"1.0\")\n    end\n\n    it \"handles prerelease versions\" do\n      # Create the \"box\"\n      environment.box3(\"foo\", \"0.1.0-alpha.1\", :virtualbox)\n      environment.box3(\"foo\", \"0.1.0-alpha.2\", :virtualbox)\n\n      # Actual test\n      result = subject.find(\"foo\", :virtualbox, \">= 0\")\n      expect(result).to_not be_nil\n      expect(result).to be_kind_of(box_class)\n      expect(result.name).to eq(\"foo\")\n      expect(result.version).to eq(\"0.1.0-alpha.2\")\n    end\n\n    it \"returns nil if a box's constraints can't be satisfied\" do\n      # Create the \"box\"\n      environment.box3(\"foo\", \"0.1\", :virtualbox)\n      environment.box3(\"foo\", \"1.0\", :virtualbox)\n      environment.box3(\"foo\", \"2.1\", :virtualbox)\n\n      # Actual test\n      result = subject.find(\"foo\", :virtualbox, \"> 1.0, < 1.5\")\n      expect(result).to be_nil\n    end\n\n    context \"with multiple versions of the same box\" do\n      before do\n        environment.box3(\"foo\", \"1.0\", :virtualbox)\n        environment.box3(\"foo\", \"2.0.3\", :virtualbox)\n        environment.box3(\"foo\", \"2.0.4\", :virtualbox)\n        environment.box3(\"foo\", \"10.3\", :virtualbox)\n        environment.box3(\"foo\", \"1.0\", :vmware)\n        environment.box3(\"foo\", \"0.4.3\", :vmware)\n        environment.box3(\"foo\", \"2.0.1\", :vmware)\n        environment.box3(\"foo\", \"2.0.2.dev\", :vmware)\n        environment.box3(\"foo\", \"2.0.2\", :vmware)\n        environment.box3(\"bar\", \"20161203.2\", :ec2)\n        environment.box3(\"bar\", \"20161203.2.3\", :ec2)\n        environment.box3(\"bar\", \"20151102.0.0\", :ec2)\n        environment.box3(\"foo-VAGRANTSLASH-bar\", \"1.0\", :virtualbox)\n        environment.box3(\"foo-VAGRANTCOLON-colon\", \"1.0\", :virtualbox)\n      end\n\n      it \"should return expected latest version\" do\n        result = subject.find(\"foo\", :virtualbox, \"> 2, < 3\")\n        expect(result.version).to eq(\"2.0.4\")\n      end\n\n      it \"should sort boxes with pre-release versions\" do\n        result = subject.find(\"foo\", :vmware, \"> 2, < 3\")\n        expect(result.version).to eq(\"2.0.2\")\n      end\n    end\n  end\n\n  describe \"#add\" do\n    it \"should add a valid box to the system\" do\n      box_path = environment.box2_file(:virtualbox)\n\n      # Add the box\n      box = subject.add(box_path, \"foo\", \"1.0\", providers: :virtualbox)\n      expect(box).to be_kind_of(box_class)\n      expect(box.name).to eq(\"foo\")\n      expect(box.provider).to eq(:virtualbox)\n\n      # Verify we can find it as well\n      expect(subject.find(\"foo\", :virtualbox, \"1.0\")).to_not be_nil\n    end\n\n    it \"should add a box with a name with '/' in it\" do\n      box_path = environment.box2_file(:virtualbox)\n\n      # Add the box\n      box = subject.add(box_path, \"foo/bar\", \"1.0\")\n      expect(box).to be_kind_of(box_class)\n      expect(box.name).to eq(\"foo/bar\")\n      expect(box.provider).to eq(:virtualbox)\n\n      # Verify we can find it as well\n      expect(subject.find(\"foo/bar\", :virtualbox, \"1.0\")).to_not be_nil\n    end\n\n    it \"should add a box without specifying a provider\" do\n      box_path = environment.box2_file(:vmware)\n\n      # Add the box\n      box = subject.add(box_path, \"foo\", \"1.0\")\n      expect(box).to be_kind_of(box_class)\n      expect(box.name).to eq(\"foo\")\n      expect(box.provider).to eq(:vmware)\n    end\n\n    it \"should store a metadata URL\" do\n      box_path = environment.box2_file(:virtualbox)\n\n      subject.add(\n        box_path, \"foo\", \"1.0\",\n        metadata_url: \"bar\")\n\n      box = subject.find(\"foo\", :virtualbox, \"1.0\")\n      expect(box.metadata_url).to eq(\"bar\")\n    end\n\n    it \"should add a V1 box\" do\n      # Create a V1 box.\n      box_path = environment.box1_file\n\n      # Add the box\n      box = subject.add(box_path, \"foo\", \"1.0\")\n      expect(box).to be_kind_of(box_class)\n      expect(box.name).to eq(\"foo\")\n      expect(box.provider).to eq(:virtualbox)\n    end\n\n    it \"should raise an exception if the box already exists\" do\n      prev_box_name = \"foo\"\n      prev_box_provider = :virtualbox\n      prev_box_version = \"1.0\"\n\n      # Create the box we're adding\n      environment.box3(prev_box_name, \"1.0\", prev_box_provider)\n\n      # Attempt to add the box with the same name\n      box_path = environment.box2_file(prev_box_provider)\n      expect {\n        subject.add(box_path, prev_box_name,\n                    prev_box_version, providers: prev_box_provider)\n      }.to raise_error(Vagrant::Errors::BoxAlreadyExists)\n    end\n\n    it \"should replace the box if force is specified\" do\n      prev_box_name = \"foo\"\n      prev_box_provider = :vmware\n      prev_box_version = \"1.0\"\n\n      # Setup the environment with the box pre-added\n      environment.box3(prev_box_name, prev_box_version, prev_box_provider)\n\n      # Attempt to add the box with the same name\n      box_path = environment.box2_file(prev_box_provider, metadata: { \"replaced\" => \"yes\" })\n      box = subject.add(box_path, prev_box_name, prev_box_version, force: true)\n      expect(box.metadata[\"replaced\"]).to eq(\"yes\")\n    end\n\n    it \"should raise an exception if the box already exists and no provider is given\" do\n      # Create some box file\n      box_name = \"foo\"\n      box_path = environment.box2_file(:vmware)\n\n      # Add it once, successfully\n      expect { subject.add(box_path, box_name, \"1.0\") }.to_not raise_error\n\n      # Add it again, and fail!\n      expect { subject.add(box_path, box_name, \"1.0\") }.\n        to raise_error(Vagrant::Errors::BoxAlreadyExists)\n    end\n\n    it \"should raise an exception and not add the box if the provider doesn't match\" do\n      box_name      = \"foo\"\n      good_provider = :virtualbox\n      bad_provider  = :vmware\n\n      # Create a VirtualBox box file\n      box_path = environment.box2_file(good_provider)\n\n      # Add the box but with an invalid provider, verify we get the proper\n      # error.\n      expect { subject.add(box_path, box_name, \"1.0\", providers: bad_provider) }.\n        to raise_error(Vagrant::Errors::BoxProviderDoesntMatch)\n\n      # Verify the box doesn't exist\n      expect(subject.find(box_name, bad_provider, \"1.0\")).to be_nil\n    end\n\n    it \"should raise an exception if you add an invalid box file\" do\n      # Tar Header information\n      CHECKSUM_OFFSET = 148\n      CHECKSUM_LENGTH = 8\n\n      Tempfile.open(['vagrant-testing', '.tar']) do |f|\n        f.binmode\n\n        # Corrupt the tar by writing over the checksum field\n        f.seek(CHECKSUM_OFFSET)\n        f.write(\"\\0\"*CHECKSUM_LENGTH)\n        f.close\n\n        expect { subject.add(f.path, \"foo\", \"1.0\") }.\n          to raise_error(Vagrant::Errors::BoxUnpackageFailure)\n      end\n    end\n  end\n\n  describe \"#upgrade_v1_1_v1_5\" do\n    let(:boxes_dir) { environment.boxes_dir }\n\n    before do\n      # Create all the various box directories\n      @foo_path    = environment.box2(\"foo\", \"virtualbox\")\n      @vbox_path   = environment.box2(\"precise64\", \"virtualbox\")\n      @vmware_path = environment.box2(\"precise64\", \"vmware\")\n      @v1_path     = environment.box(\"v1box\")\n    end\n\n    it \"upgrades the boxes\" do\n      subject.upgrade_v1_1_v1_5\n\n      # The old paths should not exist anymore\n      expect(@foo_path).to_not exist\n      expect(@vbox_path).to_not exist\n      expect(@vmware_path).to_not exist\n      expect(@v1_path.join(\"box.ovf\")).to_not exist\n\n      # New paths should exist\n      foo_path = boxes_dir.join(\"foo\", \"0\", \"virtualbox\")\n      vbox_path = boxes_dir.join(\"precise64\", \"0\", \"virtualbox\")\n      vmware_path = boxes_dir.join(\"precise64\", \"0\", \"vmware\")\n      v1_path = boxes_dir.join(\"v1box\", \"0\", \"virtualbox\")\n\n      expect(foo_path).to exist\n      expect(vbox_path).to exist\n      expect(vmware_path).to exist\n      expect(v1_path).to exist\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/box_metadata_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\nrequire \"vagrant/box_metadata\"\n\ndescribe Vagrant::BoxMetadata do\n  include_context \"unit\"\n\n  let(:raw) do\n    {\n      name: \"foo\",\n      description: \"bar\",\n      versions: [\n        {\n          version: \"1.0.0\",\n          providers: [\n            { name: \"virtualbox\" },\n            { name: \"vmware\" }\n          ],\n        },\n        {\n          version: \"1.1.5\",\n          providers: [\n            { name: \"virtualbox\" }\n          ]\n        },\n        {\n          version: \"1.1.0\",\n          providers: [\n            { name: \"virtualbox\" },\n            { name: \"vmware\", architecture: \"test-arch\" }\n          ]\n        }\n      ]\n    }.to_json\n  end\n\n  subject { described_class.new(raw) }\n\n  describe '#name' do\n    subject { super().name }\n    it { should eq(\"foo\") }\n  end\n\n  describe '#description' do\n    subject { super().description }\n    it { should eq(\"bar\") }\n  end\n\n  context \"with poorly formatted JSON\" do\n    let(:raw) {\n      {name: \"foo\"}.to_json + \",\"\n    }\n\n    it \"raises an exception\" do\n      expect { subject }.\n        to raise_error(Vagrant::Errors::BoxMetadataMalformed)\n    end\n  end\n\n  context \"with poorly formatted version\" do\n    let(:raw) {\n      {\n        name: \"foo\",\n        versions: [\n          {\n            version: \"I AM NOT VALID\"\n          }\n        ]\n      }.to_json\n    }\n\n    it \"raises an exception\" do\n      expect { subject }.\n        to raise_error(Vagrant::Errors::BoxMetadataMalformedVersion)\n    end\n  end\n\n  describe \"#version\" do\n    it \"matches an exact version\" do\n      result = subject.version(\"1.0.0\")\n      expect(result).to_not be_nil\n      expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n      expect(result.version).to eq(\"1.0.0\")\n    end\n\n    it \"matches a constraint with latest matching version\" do\n      result = subject.version(\">= 1.0\")\n      expect(result).to_not be_nil\n      expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n      expect(result.version).to eq(\"1.1.5\")\n    end\n\n    it \"matches complex constraints\" do\n      result = subject.version(\">= 0.9, ~> 1.0.0\")\n      expect(result).to_not be_nil\n      expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n      expect(result.version).to eq(\"1.0.0\")\n    end\n\n    it \"matches the constraint that has the given provider\" do\n      result = subject.version(\">= 0\", provider: :vmware)\n      expect(result).to_not be_nil\n      expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n      expect(result.version).to eq(\"1.1.0\")\n    end\n  end\n\n  describe \"#versions\" do\n    it \"returns the versions it contained\" do\n      expect(subject.versions).to eq(\n        [\"1.0.0\", \"1.1.0\", \"1.1.5\"])\n    end\n\n    it \"filters versions by matching provider\" do\n      expect(subject.versions(provider: :vmware)).to eq(\n        [\"1.0.0\", \"1.1.0\"])\n    end\n\n    it \"filters versions by architecture\" do\n      expect(subject.versions(architecture: \"test-arch\")).to eq([\"1.1.0\"])\n    end\n\n    it \"filters versions by provider and architecture\" do\n      expect(subject.versions(architecture: \"test-arch\", provider: \"virtualbox\")).to eq([])\n      expect(subject.versions(architecture: \"test-arch\", provider: \"vmware\")).to eq([\"1.1.0\"])\n    end\n\n    it \"filters versions by multiple providers\" do\n      expect(subject.versions(provider: [\"vmware\", \"my-virt\"])).to eq([\"1.0.0\", \"1.1.0\"])\n    end\n  end\n  \n  describe \"#compatible_version_update?\" do \n    let(:raw) do\n      {\n        name: \"foo\",\n        description: \"bar\",\n        versions: [\n          {\n            version: \"1.0.0\",\n            providers: [\n              { name: \"virtualbox\" },\n              { name: \"vmware\" }\n            ],\n          },\n          {\n            version: \"1.1.5\",\n            providers: [\n              { name: \"virtualbox\" }\n            ]\n          },\n          {\n            version: \"1.1.0\",\n            providers: [\n              { name: \"virtualbox\" },\n              { name: \"vmware\" }\n            ]\n          }\n        ]\n      }.to_json\n    end\n\n    it \"is compatible if current version is older than new version\" do\n      expect(subject.compatible_version_update?(\"1.0.0\", \"1.1.0\", provider: \"virtualbox\")).to be true\n      expect(subject.compatible_version_update?(\"1.1.5\", \"1.1.0\", provider: \"virtualbox\")).to be false\n    end\n\n    it \"is compatible if architecture is set and isn't defined in metadata\" do\n      expect(subject.compatible_version_update?(\"1.0.0\", \"1.1.0\", provider: \"virtualbox\", architecture: :auto)).to be true\n    end\n  end\n\n  context \"with architecture\" do\n    let(:raw) do\n      {\n        name: \"foo\",\n        description: \"bar\",\n        versions: [\n          {\n            version: \"1.0.0\",\n            providers: [\n              {\n                name: \"virtualbox\",\n                default_architecture: true,\n                architecture: \"amd64\"\n              },\n              {\n                name: \"virtualbox\",\n                default_architecture: false,\n                architecture: \"arm64\"\n              },\n              {\n                name: \"vmware\",\n                default_architecture: true,\n                architecture: \"arm64\"\n              },\n              {\n                name: \"vmware\",\n                default_architecture: false,\n                architecture: \"amd64\"\n              }\n            ],\n          },\n          {\n            version: \"1.1.5\",\n            providers: [\n              {\n                name: \"virtualbox\",\n                architecture: \"amd64\",\n                default_architecture: true,\n              }\n            ]\n          },\n          {\n            version: \"1.1.6\",\n            providers: [\n              {\n                name: \"virtualbox\",\n                architecture: \"arm64\",\n                default_architecture: true,\n              },\n            ]\n          },\n          {\n            version: \"1.1.0\",\n            providers: [\n              {\n                name: \"virtualbox\",\n                architecture: \"amd64\",\n                default_architecture: true,\n              },\n              {\n                name: \"vmware\",\n                architecture: \"amd64\",\n                default_architecture: true,\n              }\n            ]\n          },\n          {\n            version: \"2.0.0\",\n            providers: [\n              {\n                name: \"vmware\",\n                architecture: \"arm64\",\n                default_architecture: true,\n              }\n            ]\n          }\n        ]\n      }.to_json\n    end\n\n    subject { described_class.new(raw) }\n\n    before { allow(Vagrant::Util::Platform).to receive(:architecture).and_return(\"amd64\") }\n\n    describe \"#version\" do\n      it \"matches an exact version\" do\n        result = subject.version(\"1.0.0\")\n        expect(result).to_not be_nil\n        expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n        expect(result.version).to eq(\"1.0.0\")\n      end\n\n      it \"matches a constraint with latest matching version\" do\n        result = subject.version(\">= 1.0\")\n        expect(result).to_not be_nil\n        expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n        expect(result.version).to eq(\"1.1.5\")\n      end\n\n      it \"matches complex constraints\" do\n        result = subject.version(\">= 0.9, ~> 1.0.0\")\n        expect(result).to_not be_nil\n        expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n        expect(result.version).to eq(\"1.0.0\")\n      end\n\n      context \"with provider filter\" do\n        it \"matches the constraint that has the given provider\" do\n          result = subject.version(\">= 0\", provider: :vmware)\n          expect(result).to_not be_nil\n          expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n          expect(result.version).to eq(\"1.1.0\")\n        end\n\n        it \"matches the exact version that has the given provider\" do\n          result = subject.version(\"1.0.0\", provider: :virtualbox)\n          expect(result).to_not be_nil\n          expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n          expect(result.version).to eq(\"1.0.0\")\n        end\n\n        it \"does not match exact version that has given provider but not host architecture\" do\n          result = subject.version(\"1.1.6\", provider: :virtualbox)\n          expect(result).to be_nil\n        end\n\n        context \"with architecture filter\" do\n          it \"matches the exact version that has provider with host architecture when using :auto\" do\n            result = subject.version(\"1.0.0\", provider: :virtualbox, architecture: :auto)\n            expect(result).to_not be_nil\n            expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n            expect(result.version).to eq(\"1.0.0\")\n          end\n\n          it \"matches the exact version that has provider with defined host architecture\" do\n            result = subject.version(\"1.0.0\", provider: :virtualbox, architecture: \"arm64\")\n            expect(result).to_not be_nil\n            expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n            expect(result.version).to eq(\"1.0.0\")\n          end\n\n          it \"does not match the exact version that has provider with defined host architecture\" do\n            result = subject.version(\"1.0.0\", provider: :virtualbox, architecture: \"ppc64\")\n            expect(result).to be_nil\n          end\n        end\n      end\n\n      context \"with architecture filter\" do\n        it \"matches a constraint that has the detected host architecture\" do\n          result = subject.version(\"> 0\", architecture: :auto)\n          expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n          expect(result.version).to eq(\"1.1.5\")\n        end\n\n        it \"matches a constraint that has the provided architecture\" do\n          result = subject.version(\"> 0\", architecture: \"arm64\")\n          expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n          expect(result.version).to eq(\"2.0.0\")\n        end\n\n        it \"matches exact version that has the provided architecture\" do\n          result = subject.version(\"1.0.0\", architecture: \"arm64\")\n          expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)\n          expect(result.version).to eq(\"1.0.0\")\n        end\n\n        it \"does not match exact version that does not have provided architecture\" do\n          result = subject.version(\"2.0.0\", architecture: \"amd64\")\n          expect(result).to be_nil\n        end\n      end\n    end\n\n    describe \"#versions\" do\n      it \"returns the versions it contained\" do\n        expect(subject.versions).\n          to eq([\"1.0.0\", \"1.1.0\", \"1.1.5\", \"1.1.6\", \"2.0.0\"])\n      end\n\n      context \"with provider filter\" do\n        it \"filters versions\" do\n          expect(subject.versions(provider: :vmware)).\n            to eq([\"1.0.0\", \"1.1.0\", \"2.0.0\"])\n        end\n      end\n\n      context \"with architecture filter\" do\n        it \"filters versions\" do\n          expect(subject.versions(architecture: \"arm64\")).\n            to eq([\"1.0.0\", \"1.1.6\", \"2.0.0\"])\n        end\n\n        it \"returns none when no matching architecture available\" do\n          expect(subject.versions(architecture: \"other\")).\n            to be_empty\n        end\n\n        it \"filters based on host architecture when :auto used\" do\n          expect(subject.versions(architecture: :auto)).\n            to eq(subject.versions(architecture: \"amd64\"))\n        end\n      end\n    end\n\n    describe \"#compatible_version_update?\" do\n      let(:raw) do\n        {\n          name: \"foo\",\n          description: \"bar\",\n          versions: [\n            {\n              version: \"1.0.0\",\n              providers: [\n                {\n                  name: \"vmware\",\n                  default_architecture: true,\n                  architecture: \"arm64\"\n                },\n                {\n                  name: \"docker\",\n                  default_architecture: true,\n                  architecture: \"unknown\"\n                },\n                {\n                  name: \"virtualbox\",\n                  default_architecture: true,\n                  architecture: \"unknown\"\n                },\n                {\n                  name: \"other\",\n                  default_architecture: true,\n                  architecture: \"amd64\"\n                }\n              ]\n            },\n            {\n              version: \"2.0.0\",\n              providers: [\n                {\n                  name: \"vmware\",\n                  architecture: \"arm64\",\n                  default_architecture: true,\n                },\n                {\n                  name: \"docker\",\n                  default_architecture: true,\n                  architecture: \"unknown\"\n                },\n                {\n                  name: \"virtualbox\",\n                  default_architecture: true,\n                  architecture: \"amd64\"\n                },\n                {\n                  name: \"other\",\n                  default_architecture: true,\n                  architecture: \"unknown\"\n                },\n                {\n                  name: \"missing\",\n                  default_architecture: true,\n                  architecture: \"unknown\"\n                }\n              ]\n            }\n          ]\n        }.to_json\n      end\n\n      it \"is compatible if architectures match\" do\n        expect(subject.compatible_version_update?(\"1.0.0\", \"2.0.0\", provider: \"vmware\", architecture: \"arm64\")).to be true\n      end\n\n      it \"is compatible if current arch is unknown, but newer arch matches system\" do\n        expect(subject.compatible_version_update?(\"1.0.0\", \"2.0.0\", provider: \"virtualbox\", architecture: :auto)).to be true\n      end\n\n      it \"is compatible if current architecture is unknown\" do\n        expect(subject.compatible_version_update?(\"1.0.0\", \"2.0.0\", provider: \"docker\", architecture: :auto)).to be true\n      end\n\n      it \"is compatible if current_version is not available from metadata\" do\n        expect(subject.compatible_version_update?(\"1.0.0\", \"2.0.0\", provider: \"missing\", architecture: :auto)).to be true\n      end\n\n      it \"is not compatible if current architecture is defined, but newer architecture is unknown\" do\n        expect(subject.compatible_version_update?(\"1.0.0\", \"2.0.0\", provider: \"other\", architecture: :auto)).to be false\n      end\n      it \"is compatible if current architecture is defined, but newer architecture is unknown, and architecture is set to nil\" do\n        expect(subject.compatible_version_update?(\"1.0.0\", \"2.0.0\", provider: \"other\", architecture: nil)).to be true\n      end\n    end\n  end\nend\n\ndescribe Vagrant::BoxMetadata::Version do\n  let(:raw) { {} }\n\n  subject { described_class.new(raw) }\n\n  before do\n    raw[\"providers\"] = [\n      {\n        \"name\" => \"virtualbox\",\n      },\n      {\n        \"name\" => \"vmware\",\n      }\n    ]\n  end\n\n  describe \"#version\" do\n    it \"is the version in the raw data\" do\n      v = \"1.0\"\n      raw[\"version\"] = v\n      expect(subject.version).to eq(v)\n    end\n  end\n\n  describe \"#provider\" do\n    it \"returns nil if a provider isn't supported\" do\n      expect(subject.provider(\"foo\")).to be_nil\n    end\n\n    it \"returns the provider specified\" do\n      result = subject.provider(\"virtualbox\")\n      expect(result).to_not be_nil\n      expect(result).to be_kind_of(Vagrant::BoxMetadata::Provider)\n    end\n  end\n\n  describe \"#providers\" do\n    it \"returns the providers available\" do\n      expect(subject.providers.sort).to eq(\n        [:virtualbox, :vmware])\n    end\n  end\nend\n\ndescribe Vagrant::BoxMetadata::Provider do\n  let(:raw) { {} }\n\n  subject { described_class.new(raw) }\n\n  describe \"#name\" do\n    it \"is the name specified\" do\n      raw[\"name\"] = \"foo\"\n      expect(subject.name).to eq(\"foo\")\n    end\n  end\n\n  describe \"#url\" do\n    it \"is the URL specified\" do\n      raw[\"url\"] = \"bar\"\n      expect(subject.url).to eq(\"bar\")\n    end\n  end\n\n  describe \"#checksum and #checksum_type\" do\n    it \"is set properly\" do\n      raw[\"checksum\"] = \"foo\"\n      raw[\"checksum_type\"] = \"bar\"\n\n      expect(subject.checksum).to eq(\"foo\")\n      expect(subject.checksum_type).to eq(\"bar\")\n    end\n\n    it \"is nil if not set\" do\n      expect(subject.checksum).to be_nil\n      expect(subject.checksum_type).to be_nil\n    end\n  end\n\n  describe \"architecture\" do\n    it \"is set properly\" do\n      raw[\"architecture\"] = \"test-arch\"\n\n      expect(subject.architecture).to eq(\"test-arch\")\n    end\n\n    it \"is nil if not set\" do\n      expect(subject.architecture).to be_nil\n    end\n  end\n\n  describe \"#architecture_support?\" do\n    it \"is false if architecture is not supported\" do\n      expect(subject.architecture_support?).to be(false)\n    end\n\n    it \"is true if architecture is supported\" do\n      raw[\"default_architecture\"] = false\n\n      expect(subject.architecture_support?).to be(true)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/box_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\nrequire \"pathname\"\nrequire \"stringio\"\nrequire \"tempfile\"\n\nrequire \"vagrant/box_metadata\"\n\ndescribe Vagrant::Box, :skip_windows do\n  include_context \"unit\"\n\n  let(:environment)   { isolated_environment }\n\n  let(:box_collection) { Vagrant::BoxCollection.new(environment.boxes_dir) }\n\n  let(:name)          { \"foo\" }\n  let(:provider)      { :virtualbox }\n  let(:version)       { \"1.0\" }\n  let(:architecture)  { \"test-architecture\" }\n  let(:directory)     { environment.box3(\"foo\", \"1.0\", :virtualbox) }\n  subject             { described_class.new(name, provider, version, directory) }\n\n  describe '#metadata_url' do\n    subject { super().metadata_url }\n    it { should be_nil }\n  end\n\n  it \"provides the name\" do\n    expect(subject.name).to eq(name)\n  end\n\n  it \"provides the provider\" do\n    expect(subject.provider).to eq(provider)\n  end\n\n  it \"provides the directory\" do\n    expect(subject.directory).to eq(directory)\n  end\n\n  it \"provides the metadata associated with a box\" do\n    data = { \"foo\" => \"bar\" }\n\n    # Write the metadata\n    directory.join(\"metadata.json\").open(\"w\") do |f|\n      f.write(JSON.generate(data))\n    end\n\n    # Verify the metadata\n    expect { subject.metadata }.\n        to raise_error(Vagrant::Errors::BoxMetadataMissingRequiredFields)\n  end\n\n  it \"provides the metadata associated with a box\" do\n    data = { \"provider\" => \"bar\" }\n\n    # Write the metadata\n    directory.join(\"metadata.json\").open(\"w\") do |f|\n      f.write(JSON.generate(data))\n    end\n\n    # Verify the metadata\n    expect(subject.metadata).to eq(data)\n  end\n\n  context \"with a metadata URL\" do\n    subject do\n      described_class.new(\n        name, provider, version, directory,\n        metadata_url: \"foo\")\n    end\n\n    describe '#metadata_url' do\n      subject { super().metadata_url }\n      it { should eq(\"foo\") }\n    end\n  end\n\n  context \"with a corrupt metadata file\" do\n    before do\n      directory.join(\"metadata.json\").open(\"w\") do |f|\n        f.write(\"\")\n      end\n    end\n\n    it \"should raise an exception\" do\n      expect { subject }.\n        to raise_error(Vagrant::Errors::BoxMetadataCorrupted)\n    end\n  end\n\n  context \"without a metadata file\" do\n    before :each do\n      directory.join(\"metadata.json\").delete\n    end\n\n    it \"should raise an exception\" do\n      expect { subject }.\n        to raise_error(Vagrant::Errors::BoxMetadataFileNotFound)\n    end\n  end\n\n  context \"#has_update?\" do\n    subject do\n      described_class.new(\n        name, provider, version, directory,\n        metadata_url: \"foo\")\n    end\n\n    it \"raises an exception if no metadata_url is set\" do\n      subject = described_class.new(\n        name, provider, version, directory)\n\n      expect { subject.has_update?(\"> 0\") }.\n        to raise_error(Vagrant::Errors::BoxUpdateNoMetadata)\n    end\n\n    it \"returns nil if there is no update\" do\n      metadata = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW))\n      {\n        \"name\": \"foo\",\n        \"versions\": [\n          { \"version\": \"1.0\" }\n        ]\n      }\n      RAW\n\n      allow(subject).to receive(:load_metadata).and_return(metadata)\n\n      expect(subject.has_update?).to be_nil\n    end\n\n    it \"returns the updated box info if there is an update available\" do\n      metadata = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW))\n      {\n        \"name\": \"foo\",\n        \"versions\": [\n          {\n            \"version\": \"1.0\"\n          },\n          {\n            \"version\": \"1.1\",\n            \"providers\": [\n              {\n                \"name\": \"virtualbox\",\n                \"url\": \"bar\"\n              }\n            ]\n          }\n        ]\n      }\n      RAW\n\n      allow(subject).to receive(:load_metadata).and_return(metadata)\n\n      result = subject.has_update?\n      expect(result).to_not be_nil\n\n      expect(result[0]).to be_kind_of(Vagrant::BoxMetadata)\n      expect(result[1]).to be_kind_of(Vagrant::BoxMetadata::Version)\n      expect(result[2]).to be_kind_of(Vagrant::BoxMetadata::Provider)\n\n      expect(result[0].name).to eq(\"foo\")\n      expect(result[1].version).to eq(\"1.1\")\n      expect(result[2].url).to eq(\"bar\")\n    end\n\n    it \"returns the updated box info within constraints\" do\n      metadata = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW))\n      {\n        \"name\": \"foo\",\n        \"versions\": [\n          {\n            \"version\": \"1.0\"\n          },\n          {\n            \"version\": \"1.1\",\n            \"providers\": [\n              {\n                \"name\": \"virtualbox\",\n                \"url\": \"bar\"\n              }\n            ]\n          },\n          {\n            \"version\": \"1.4\",\n            \"providers\": [\n              {\n                \"name\": \"virtualbox\",\n                \"url\": \"bar\"\n              }\n            ]\n          }\n        ]\n      }\n      RAW\n\n      allow(subject).to receive(:load_metadata).and_return(metadata)\n\n      result = subject.has_update?(\">= 1.1, < 1.4\")\n      expect(result).to_not be_nil\n\n      expect(result[0]).to be_kind_of(Vagrant::BoxMetadata)\n      expect(result[1]).to be_kind_of(Vagrant::BoxMetadata::Version)\n      expect(result[2]).to be_kind_of(Vagrant::BoxMetadata::Provider)\n\n      expect(result[0].name).to eq(\"foo\")\n      expect(result[1].version).to eq(\"1.1\")\n      expect(result[2].url).to eq(\"bar\")\n    end\n\n    context \"with architecture\" do\n      subject do\n        described_class.new(\n          name, provider, version, directory,\n          architecture: architecture,\n          metadata_url: \"foo\"\n        )\n      end\n\n      it \"raises an exception if no metadata_url is set\" do\n        subject = described_class.new(\n          name, provider, version, directory,\n          architecture: architecture,\n        )\n\n        expect { subject.has_update?(\"> 0\") }.\n          to raise_error(Vagrant::Errors::BoxUpdateNoMetadata)\n      end\n\n      it \"returns nil if there is no update\" do\n        metadata = Vagrant::BoxMetadata.new(\n          {\n            name: \"foo\",\n            versions: [ { version: \"1.0\" } ]\n          }.to_json\n        )\n        allow(subject).to receive(:load_metadata).and_return(metadata)\n\n        expect(subject.has_update?).to be_nil\n      end\n\n      it \"returns the updated box info if there is an update available\" do\n        metadata = Vagrant::BoxMetadata.new(\n          {\n            name: \"foo\",\n            versions: [\n              {version: \"1.0\"},\n              {\n                version: \"1.1\",\n                providers: [\n                  {\n                    name: \"virtualbox\",\n                    url: \"bar\",\n                    architecture: architecture,\n                  }\n                ]\n              }\n            ]\n          }.to_json\n        )\n\n        allow(subject).to receive(:load_metadata).and_return(metadata)\n\n        result = subject.has_update?\n        expect(result).to_not be_nil\n\n        expect(result[0]).to be_kind_of(Vagrant::BoxMetadata)\n        expect(result[1]).to be_kind_of(Vagrant::BoxMetadata::Version)\n        expect(result[2]).to be_kind_of(Vagrant::BoxMetadata::Provider)\n\n        expect(result[0].name).to eq(\"foo\")\n        expect(result[1].version).to eq(\"1.1\")\n        expect(result[2].url).to eq(\"bar\")\n      end\n\n      it \"returns nil if update does not support architecture\" do\n        metadata = Vagrant::BoxMetadata.new(\n          {\n            name: \"foo\",\n            versions: [\n              {version: \"1.0\"},\n              {\n                version: \"1.1\",\n                providers: [\n                  {\n                    name: \"virtualbox\",\n                    url: \"bar\",\n                    architecture: \"other-architecture\",\n                  }\n                ]\n              }\n            ]\n          }.to_json\n        )\n\n        allow(subject).to receive(:load_metadata).and_return(metadata)\n\n        result = subject.has_update?\n        expect(result).to be_nil\n      end\n\n      it \"returns the updated box info within constraints\" do\n        metadata = Vagrant::BoxMetadata.new(\n          {\n            name: \"foo\",\n            versions: [\n              {\n                version: \"1.0\",\n              },\n              {\n                version: \"1.1\",\n                providers: [\n                  {\n                    name: \"virtualbox\",\n                    url: \"bar\",\n                    architecture: architecture\n                  },\n                ]\n              },\n              {\n                version: \"1.2\",\n                providers: [\n                  {\n                    name: \"virtualbox\",\n                    url: \"bar\",\n                    architecture: \"other-architecture\",\n                  },\n                ]\n              },\n              {\n                version: \"1.4\",\n                providers: [\n                  {\n                    name: \"virtualbox\",\n                    url: \"bar\",\n                    architecture: architecture\n                  }\n                ]\n              }\n            ]\n          }.to_json\n        )\n\n        allow(subject).to receive(:load_metadata).and_return(metadata)\n\n        result = subject.has_update?(\">= 1.1, < 1.4\")\n        expect(result).to_not be_nil\n\n        expect(result[0]).to be_kind_of(Vagrant::BoxMetadata)\n        expect(result[1]).to be_kind_of(Vagrant::BoxMetadata::Version)\n        expect(result[2]).to be_kind_of(Vagrant::BoxMetadata::Provider)\n\n        expect(result[0].name).to eq(\"foo\")\n        expect(result[1].version).to eq(\"1.1\")\n        expect(result[2].url).to eq(\"bar\")\n      end\n    end\n  end\n\n  context \"#automatic_update_check_allowed?\" do\n    it \"should return true on intial check\" do\n      expect(subject.automatic_update_check_allowed?).to be_truthy\n    end\n\n    it \"should return false on second check\" do\n      expect(subject.automatic_update_check_allowed?).to be_truthy\n      expect(subject.automatic_update_check_allowed?).to be_falsey\n    end\n\n    it \"should use a file to mark last check time\" do\n      expect(FileUtils).to receive(:touch)\n      subject.automatic_update_check_allowed?\n    end\n\n    it \"should return true when time since last check is greater than check interval\" do\n      subject.automatic_update_check_allowed?\n      stub_const(\"Vagrant::Box::BOX_UPDATE_CHECK_INTERVAL\", -1)\n      expect(subject.automatic_update_check_allowed?).to be_truthy\n    end\n  end\n\n  context \"#in_use?\" do\n    let(:index) { [] }\n\n    def new_entry(name, provider, version, architecture=nil)\n      Vagrant::MachineIndex::Entry.new.tap do |entry|\n        entry.extra_data[\"box\"] = {\n          \"name\" => name,\n          \"provider\" => provider,\n          \"version\" => version,\n          \"architecture\" => architecture,\n        }\n      end\n    end\n\n    it \"returns nil if the index has no matching entries\" do\n      index << new_entry(\"foo\", \"bar\", \"1.0\")\n      index << new_entry(\"foo\", \"baz\", \"1.2\")\n\n      expect(subject).to_not be_in_use(index)\n    end\n\n    it \"returns matching entries if they exist\" do\n      matching = new_entry(name, provider.to_s, version)\n      index << new_entry(\"foo\", \"bar\", \"1.0\")\n      index << matching\n      index << new_entry(\"foo\", \"baz\", \"1.2\")\n\n      expect(subject.in_use?(index)).to eq([matching])\n    end\n\n    context \"with architecture information\" do\n      subject { described_class.new(name, provider, version, directory, architecture: architecture) }\n\n      it \"returns nil if the index has no matching entries\" do\n        index << new_entry(\"foo\", \"bar\", \"1.0\", \"amd64\")\n        index << new_entry(\"foo\", \"baz\", \"1.2\", \"arm64\")\n        index << new_entry(name, provider.to_s, version, \"random-arch\")\n\n        expect(subject).to_not be_in_use(index)\n      end\n\n      it \"returns matching entries if they exist\" do\n        matching = new_entry(name, provider.to_s, version, architecture)\n        index << new_entry(\"foo\", \"bar\", \"1.0\", \"amd64\")\n        index << matching\n        index << new_entry(\"foo\", \"baz\", \"1.2\")\n\n        expect(subject.in_use?(index)).to eq([matching])\n      end\n    end\n  end\n\n  context \"#load_metadata\" do\n    let(:metadata_url) do\n      Tempfile.new(\"vagrant-test-box-test\").tap do |f|\n        f.write(<<-RAW)\n        {\n          \"name\": \"foo\",\n          \"description\": \"bar\"\n        }\n        RAW\n        f.close\n      end\n    end\n\n    subject do\n      described_class.new(\n        name, provider, version, directory,\n        metadata_url: metadata_url.path)\n    end\n\n    after do\n      metadata_url.unlink\n    end\n\n    it \"loads the url and returns the data\" do\n      result = subject.load_metadata\n      expect(result.name).to eq(\"foo\")\n      expect(result.description).to eq(\"bar\")\n    end\n\n    it \"raises an error if the download failed\" do\n      dl = double(\"downloader\")\n      allow(Vagrant::Util::Downloader).to receive(:new).and_return(dl)\n      expect(dl).to receive(:download!).and_raise(\n        Vagrant::Errors::DownloaderError.new(message: \"foo\"))\n\n      expect { subject.load_metadata }.\n        to raise_error(Vagrant::Errors::BoxMetadataDownloadError)\n    end\n\n    context \"box has a hook for adding authentication\" do\n\n      let(:hook){ double(\"hook\") }\n\n      subject do\n        described_class.new(\n          name, provider, version, directory,\n          metadata_url: metadata_url.path, hook: hook)\n      end\n\n      it \"add authentication headers to the url\" do\n        expect(hook).to receive(:call).with(:authenticate_box_downloader, any_args)\n        result = subject.load_metadata\n        expect(result.name).to eq(\"foo\")\n        expect(result.description).to eq(\"bar\")\n      end\n    end\n  end\n\n  describe \"destroying\" do\n    it \"should destroy an existing box\" do\n      # Verify that our \"box\" exists\n      expect(directory.exist?).to be\n\n      # Destroy it\n      expect(subject.destroy!).to be\n\n      # Verify that it is \"destroyed\"\n      expect(directory.exist?).not_to be\n    end\n\n    it \"should not error destroying a non-existent box\" do\n      # Get the subject so that it is instantiated\n      box = subject\n\n      # Delete the directory\n      directory.rmtree\n\n      # Destroy it\n      expect(box.destroy!).to be\n    end\n  end\n\n  describe \"repackaging\" do\n    let(:scratch) { Dir.mktmpdir(\"vagrant-test-box-repackaging\") }\n\n    let(:box_output_path) { File.join(scratch, \"package.box\") }\n\n    after do\n      FileUtils.rm_rf(scratch)\n    end\n\n    it \"should repackage the box\", :bsdtar do\n      test_file_contents = \"hello, world!\"\n\n      # Put a file in the box directory to verify it is packaged properly\n      # later.\n      directory.join(\"test_file\").open(\"w\") do |f|\n        f.write(test_file_contents)\n      end\n\n      # Repackage our box to some temporary directory\n      expect(subject.repackage(box_output_path)).to be(true)\n\n      # Let's now add this box again under a different name, and then\n      # verify that we get the proper result back.\n      new_box = box_collection.add(box_output_path, \"foo2\", \"1.0\")\n      expect(new_box.directory.join(\"test_file\").read).to eq(test_file_contents)\n    end\n  end\n\n  describe \"comparison and ordering\" do\n    it \"should be equal if the name, provider, version match\" do\n      a = described_class.new(\"a\", :foo, \"1.0\", directory)\n      b = described_class.new(\"a\", :foo, \"1.0\", directory)\n\n      expect(a).to eq(b)\n    end\n\n    it \"should not be equal if name doesn't match\" do\n      a = described_class.new(\"a\", :foo, \"1.0\", directory)\n      b = described_class.new(\"b\", :foo, \"1.0\", directory)\n\n      expect(a).to_not eq(b)\n    end\n\n    it \"should not be equal if provider doesn't match\" do\n      a = described_class.new(\"a\", :foo, \"1.0\", directory)\n      b = described_class.new(\"a\", :bar, \"1.0\", directory)\n\n      expect(a).to_not eq(b)\n    end\n\n    it \"should not be equal if version doesn't match\" do\n      a = described_class.new(\"a\", :foo, \"1.0\", directory)\n      b = described_class.new(\"a\", :foo, \"1.1\", directory)\n\n      expect(a).to_not eq(b)\n    end\n\n    it \"should sort them in order of name, version, provider\" do\n      a = described_class.new(\"a\", :foo, \"1.0\", directory)\n      b = described_class.new(\"a\", :foo2, \"1.0\", directory)\n      c = described_class.new(\"a\", :foo2, \"1.1\", directory)\n      d = described_class.new(\"b\", :foo2, \"1.0\", directory)\n\n      expect([d, c, a, b].sort).to eq([a, b, c, d])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/bundler_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"tmpdir\"\nrequire_relative \"../base\"\n\nrequire \"vagrant/bundler\"\n\ndescribe Vagrant::Bundler::SolutionFile do\n  let(:plugin_path) { Pathname.new(tmpdir) + \"plugin_file\" }\n  let(:solution_path) { Pathname.new(tmpdir) + \"solution_file\" }\n  let(:tmpdir) { @tmpdir ||= Dir.mktmpdir(\"vagrant-bundler-test\") }\n  let(:subject) {\n    described_class.new(\n      plugin_file: plugin_path,\n      solution_file: solution_path\n    )\n  }\n\n  after do\n    if @tmpdir\n      FileUtils.rm_rf(@tmpdir)\n      @tmpdir = nil\n    end\n  end\n\n  describe \"#initialize\" do\n    context \"file paths\" do\n      context \"with solution_file not provided\" do\n        let(:subject) { described_class.new(plugin_file: plugin_path) }\n\n        it \"should set the plugin_file\" do\n          expect(subject.plugin_file.to_s).to eq(plugin_path.to_s)\n        end\n\n        it \"should set solution path to same directory\" do\n          expect(subject.solution_file.to_s).to eq(plugin_path.to_s + \".sol\")\n        end\n      end\n\n      context \"with custom solution_file provided\" do\n        let(:subject) { described_class.\n            new(plugin_file: plugin_path, solution_file: solution_path) }\n\n        it \"should set the plugin file path\" do\n          expect(subject.plugin_file.to_s).to eq(plugin_path.to_s)\n        end\n\n        it \"should set the solution file path to given value\" do\n          expect(subject.solution_file.to_s).to eq(solution_path.to_s)\n        end\n      end\n    end\n\n    context \"initialization behavior\" do\n      context \"on creation\" do\n        before { expect_any_instance_of(described_class).to receive(:load) }\n\n        it \"should load solution file during initialization\" do\n          subject\n        end\n      end\n\n      it \"should be invalid by default\" do\n        expect(subject.valid?).to be_falsey\n      end\n    end\n  end\n\n  describe \"#dependency_list=\" do\n    it \"should accept a list of Gem::Dependency instances\" do\n      list = [\"dep1\", \"dep2\"].map{ |x| Gem::Dependency.new(x) }\n      subject.dependency_list = list\n      expect(subject.dependency_list.map(&:dependency)).to eq(list)\n    end\n\n    it \"should error if list includes instance not Gem::Dependency\" do\n      list = [\"dep1\", \"dep2\"].map{ |x| Gem::Dependency.new(x) } << :invalid\n      expect{ subject.dependency_list = list }.to raise_error(TypeError)\n    end\n\n    it \"should convert list into resolver dependency request\" do\n      list = [\"dep1\", \"dep2\"].map{ |x| Gem::Dependency.new(x) }\n      subject.dependency_list = list\n      subject.dependency_list.each do |dep|\n        expect(dep).to be_a(Gem::Resolver::DependencyRequest)\n      end\n    end\n\n    it \"should freeze the new dependency list\" do\n      list = [\"dep1\", \"dep2\"].map{ |x| Gem::Dependency.new(x) }\n      subject.dependency_list = list\n      expect(subject.dependency_list).to be_frozen\n    end\n  end\n\n  describe \"#delete!\" do\n    context \"when file does not exist\" do\n      before { subject.solution_file.delete if subject.solution_file.exist? }\n\n      it \"should return false\" do\n        expect(subject.delete!).to be_falsey\n      end\n\n      it \"should not exist\" do\n        subject.delete!\n        expect(subject.solution_file.exist?).to be_falsey\n      end\n    end\n\n    context \"when file does exist\" do\n      before { subject.solution_file.write('x') }\n\n      it \"should return true\" do\n        expect(subject.delete!).to be_truthy\n      end\n\n      it \"should not exist\" do\n        expect(subject.solution_file.exist?).to be_truthy\n        subject.delete!\n        expect(subject.solution_file.exist?).to be_falsey\n      end\n    end\n  end\n\n  describe \"store!\" do\n    context \"when plugin file does not exist\" do\n      before { subject.plugin_file.delete if subject.plugin_file.exist? }\n\n      it \"should return false\" do\n        expect(subject.store!).to be_falsey\n      end\n\n      it \"should not create a solution file\" do\n        subject.store!\n        expect(subject.solution_file.exist?).to be_falsey\n      end\n    end\n\n    context \"when plugin file does exist\" do\n      before { subject.plugin_file.write(\"x\") }\n\n      it \"should return true\" do\n        expect(subject.store!).to be_truthy\n      end\n\n      it \"should create a solution file\" do\n        expect(subject.solution_file.exist?).to be_falsey\n        subject.store!\n        expect(subject.solution_file.exist?).to be_truthy\n      end\n\n      context \"stored file\" do\n        let(:content) {\n          @content ||= JSON.load(subject.solution_file.read)\n        }\n        before { subject.store! }\n        after { @content = nil }\n\n        it \"should store JSON hash\" do\n          expect(content).to be_a(Hash)\n        end\n\n        it \"should include dependencies key as array value\" do\n          expect(content[\"dependencies\"]).to be_a(Array)\n        end\n\n        it \"should include checksum key as string value\" do\n          expect(content[\"checksum\"]).to be_a(String)\n        end\n\n        it \"should include vagrant_version key as string value\" do\n          expect(content[\"vagrant_version\"]).to be_a(String)\n        end\n\n        it \"should include vagrant_version key that matches current version\" do\n          expect(content[\"vagrant_version\"]).to eq(Vagrant::VERSION)\n        end\n      end\n    end\n  end\n\n  describe \"behavior\" do\n    context \"when storing new solution set\" do\n      let(:deps) { [\"dep1\", \"dep2\"].map{ |n| Gem::Dependency.new(n) } }\n\n      context \"when plugin file does not exist\" do\n        before { subject.solution_file.delete if subject.solution_file.exist? }\n\n        it \"should not create a solution file\" do\n          subject.dependency_list = deps\n          subject.store!\n          expect(subject.solution_file.exist?).to be_falsey\n        end\n      end\n\n      context \"when plugin file does exist\" do\n        before { subject.plugin_file.write(\"x\") }\n\n        it \"should create a solution file\" do\n          subject.dependency_list = deps\n          subject.store!\n          expect(subject.solution_file.exist?).to be_truthy\n        end\n\n        it \"should update solution file instance to valid\" do\n          expect(subject.valid?).to be_falsey\n          subject.dependency_list = deps\n          subject.store!\n          expect(subject.valid?).to be_truthy\n        end\n\n        context \"when solution file does exist\" do\n          before do\n            subject.dependency_list = deps\n            subject.store!\n          end\n\n          it \"should be a valid solution\" do\n            subject = described_class.new(\n              plugin_file: plugin_path,\n              solution_file: solution_path\n            )\n            expect(subject.valid?).to be_truthy\n          end\n\n          it \"should have expected dependency list\" do\n            subject = described_class.new(\n              plugin_file: plugin_path,\n              solution_file: solution_path\n            )\n            expect(subject.dependency_list).to eq(deps)\n          end\n\n          context \"when plugin file has been changed\" do\n            before { subject.plugin_file.write(\"xy\") }\n\n            it \"should not be a valid solution\" do\n              subject = described_class.new(\n                plugin_file: plugin_path,\n                solution_file: solution_path\n              )\n              expect(subject.valid?).to be_falsey\n            end\n\n            it \"should have empty dependency list\" do\n              subject = described_class.new(\n                plugin_file: plugin_path,\n                solution_file: solution_path\n              )\n              expect(subject.dependency_list).to be_empty\n            end\n          end\n        end\n      end\n    end\n  end\n\n  describe \"#load\" do\n    let(:plugin_file_exists) { false }\n    let(:solution_file_exists) { false }\n    let(:plugin_file_path) { \"PLUGIN_FILE_PATH\" }\n    let(:solution_file_path) { \"SOLUTION_FILE_PATH\" }\n    let(:plugin_file) { double(\"plugin-file\") }\n    let(:solution_file) { double(\"solution-file\") }\n\n    subject do\n      described_class.new(plugin_file: plugin_file_path, solution_file: solution_file_path)\n    end\n\n    before do\n      allow(Pathname).to receive(:new).with(plugin_file_path).and_return(plugin_file)\n      allow(Pathname).to receive(:new).with(solution_file_path).and_return(solution_file)\n      allow(plugin_file).to receive(:exist?).and_return(plugin_file_exists)\n      allow(solution_file).to receive(:exist?).and_return(solution_file_exists)\n    end\n\n    context \"when plugin file and solution file do not exist\" do\n      it \"should not attempt to read the solution\" do\n        expect_any_instance_of(described_class).not_to receive(:read_solution)\n        subject\n      end\n    end\n\n    context \"when plugin file exists and solution file does not\" do\n      let(:plugin_file_exists) { true }\n\n      it \"should not attempt to read the solution\" do\n        expect_any_instance_of(described_class).not_to receive(:read_solution)\n        subject\n      end\n    end\n\n    context \"when solution file exists and plugin file does not\" do\n      let(:solution_file_exists) { true }\n\n      it \"should not attempt to read the solution\" do\n        expect_any_instance_of(described_class).not_to receive(:read_solution)\n        subject\n      end\n    end\n\n    context \"when solution file and plugin file exist\" do\n      let(:plugin_file_exists) { true }\n      let(:solution_file_exists) { true }\n\n      let(:solution_file_contents) { \"\" }\n\n      before do\n        allow(solution_file).to receive(:read).and_return(solution_file_contents)\n        allow_any_instance_of(described_class).to receive(:plugin_file_checksum).and_return(\"VALID\")\n      end\n\n      context \"when solution file is empty\" do\n        it \"should return false\" do\n          expect(subject.send(:load)).to be_falsey\n        end\n      end\n\n      context \"when solution file contains invalid checksum\" do\n        let(:solution_file_contents) { {checksum: \"INVALID\", vagrant_version: Vagrant::VERSION}.to_json }\n\n        it \"should return false\" do\n          expect(subject.send(:load)).to be_falsey\n        end\n      end\n\n      context \"when solution file contains different Vagrant version\" do\n        let(:solution_file_contents) { {checksum: \"VALID\", vagrant_version: \"0.1\"}.to_json }\n\n        it \"should return false\" do\n          expect(subject.send(:load)).to be_falsey\n        end\n      end\n\n      context \"when solution file contains valid Vagrant version and valid checksum\" do\n        let(:solution_file_contents) {\n          {checksum: \"VALID\", vagrant_version: Vagrant::VERSION, dependencies: file_dependencies}.to_json\n        }\n        let(:file_dependencies) { dependency_list.map{|d| [d.name, d.requirements_list]} }\n        let(:dependency_list) { [] }\n\n        it \"should return true\" do\n          expect(subject.send(:load)).to be_truthy\n        end\n\n        it \"should be valid\" do\n          expect(subject).to be_valid\n        end\n\n        context \"when solution file contains dependency list\" do\n          let(:dependency_list) { [\n            Gem::Dependency.new(\"dep1\", \"> 0\"),\n            Gem::Dependency.new(\"dep2\", \"< 3\")\n          ] }\n\n          it \"should be valid\" do\n            expect(subject).to be_valid\n          end\n\n          it \"should convert list into dependency requests\" do\n            subject.dependency_list.each do |d|\n              expect(d).to be_a(Gem::Resolver::DependencyRequest)\n            end\n          end\n\n          it \"should include defined dependencies\" do\n            expect(subject.dependency_list.first).to eq(dependency_list.first)\n            expect(subject.dependency_list.last).to eq(dependency_list.last)\n          end\n\n          it \"should freeze the dependency list\" do\n            expect(subject.dependency_list).to be_frozen\n          end\n        end\n      end\n    end\n  end\n\n  describe \"#read_solution\" do\n    let(:solution_file_contents) { \"\" }\n    let(:plugin_file_path) { \"PLUGIN_FILE_PATH\" }\n    let(:solution_file_path) { \"SOLUTION_FILE_PATH\" }\n    let(:plugin_file) { double(\"plugin-file\") }\n    let(:solution_file) { double(\"solution-file\") }\n\n    subject do\n      described_class.new(plugin_file: plugin_file_path, solution_file: solution_file_path)\n    end\n\n    before do\n      allow(Pathname).to receive(:new).with(plugin_file_path).and_return(plugin_file)\n      allow(Pathname).to receive(:new).with(solution_file_path).and_return(solution_file)\n      allow(plugin_file).to receive(:exist?).and_return(false)\n      allow(solution_file).to receive(:exist?).and_return(false)\n      allow(solution_file).to receive(:read).and_return(solution_file_contents)\n    end\n\n    it \"should return nil when file contents are empty\" do\n      expect(subject.send(:read_solution)).to be_nil\n    end\n\n    context \"when file contents are hash\" do\n      let(:solution_file_contents) { {checksum: \"VALID\"}.to_json }\n\n      it \"should return a hash\" do\n        expect(subject.send(:read_solution)).to be_a(Hash)\n      end\n\n      it \"should return a hash with indifferent access\" do\n        expect(subject.send(:read_solution)).to be_a(Vagrant::Util::HashWithIndifferentAccess)\n      end\n    end\n\n    context \"when file contents are array\" do\n      let(:solution_file_contents) { [\"test\"].to_json }\n\n      it \"should return a hash\" do\n        expect(subject.send(:read_solution)).to be_a(Hash)\n      end\n\n      it \"should return a hash with indifferent access\" do\n        expect(subject.send(:read_solution)).to be_a(Vagrant::Util::HashWithIndifferentAccess)\n      end\n    end\n\n    context \"when file contents are null\" do\n      let(:solution_file_contents) { \"null\" }\n\n      it \"should return nil\" do\n        expect(subject.send(:read_solution)).to be_nil\n      end\n    end\n\n    context \"when file contents are invalid\" do\n      let(:solution_file_contents) { \"{2dfwef\" }\n\n      it \"should return nil\" do\n        expect(subject.send(:read_solution)).to be_nil\n      end\n    end\n  end\nend\n\ndescribe Vagrant::Bundler do\n  include_context \"unit\"\n\n  let(:iso_env) { isolated_environment }\n  let(:env) { iso_env.create_vagrant_env }\n  let(:tmpdir) { @v_tmpdir ||= Pathname.new(Dir.mktmpdir(\"vagrant-bundler-test\")) }\n\n  before do\n    @tmpdir = Dir.mktmpdir(\"vagrant-bundler-test\")\n    @vh = ENV[\"VAGRANT_HOME\"]\n    ENV[\"VAGRANT_HOME\"] = @tmpdir\n  end\n\n  after do\n    ENV[\"VAGRANT_HOME\"] = @vh\n    FileUtils.rm_rf(@tmpdir)\n    FileUtils.rm_rf(@v_tmpdir) if @v_tmpdir\n  end\n\n  it \"should isolate gem path based on Ruby version\" do\n    expect(subject.plugin_gem_path.to_s).to end_with(RUBY_VERSION)\n  end\n\n  it \"should not have an env_plugin_gem_path by default\" do\n    expect(subject.env_plugin_gem_path).to be_nil\n  end\n\n  describe \"#initialize\" do\n    it \"should automatically set the plugin gem path\" do\n      expect(subject.plugin_gem_path).not_to be_nil\n    end\n\n    it \"should add current ruby version to plugin gem path suffix\" do\n      expect(subject.plugin_gem_path.to_s).to end_with(RUBY_VERSION)\n    end\n\n    it \"should freeze the plugin gem path\" do\n      expect(subject.plugin_gem_path).to be_frozen\n    end\n  end\n\n  describe \"#environment_path=\" do\n    it \"should error if not given Pathname\" do\n      expect { subject.environment_path = :value }.\n        to raise_error(TypeError)\n    end\n\n    context \"when set with Pathname\" do\n      let(:env_path) { Pathname.new(\"/dev/null\") }\n      before { subject.environment_path = env_path }\n\n      it \"should set the environment_data_path\" do\n        expect(subject.environment_data_path).to eq(env_path)\n      end\n\n      it \"should set the env_plugin_gem_path\" do\n        expect(subject.env_plugin_gem_path).not_to be_nil\n      end\n\n      it \"should suffix current ruby version to env_plugin_gem_path\" do\n        expect(subject.env_plugin_gem_path.to_s).to end_with(RUBY_VERSION)\n      end\n\n      it \"should base env_plugin_gem_path on environment_path value\" do\n        expect(subject.env_plugin_gem_path.to_s).to start_with(env_path.to_s)\n      end\n\n      it \"should freeze the env_plugin_gem_path\" do\n        expect(subject.env_plugin_gem_path).to be_frozen\n      end\n    end\n  end\n\n  describe \"#load_solution_file\" do\n    let(:local_opt) { nil }\n    let(:global_opt) { nil }\n    let(:options) { {local: local_opt, global: global_opt} }\n\n    it \"should return nil when local and global options are blank\" do\n      expect(subject.load_solution_file(options)).to be_nil\n    end\n\n    context \"when environment data path is set\" do\n      let(:env_path) { \"/dev/null\" }\n      before { subject.environment_path = Pathname.new(env_path) }\n\n      context \"when local option is set\" do\n        let(:local_opt) { tmpdir + \"local\" }\n\n        it \"should return a SolutionFile instance\" do\n          expect(subject.load_solution_file(options)).to be_a(Vagrant::Bundler::SolutionFile)\n        end\n\n        it \"should be located in the environment data path\" do\n          file = subject.load_solution_file(options)\n          expect(file.solution_file.to_s).to start_with(env_path)\n        end\n\n        it \"should have a local.sol solution file\" do\n          file = subject.load_solution_file(options)\n          expect(file.solution_file.to_s).to end_with(\"local.sol\")\n        end\n\n        it \"should have plugin file set to local value\" do\n          file = subject.load_solution_file(options)\n          expect(file.plugin_file.to_s).to eq(local_opt.to_s)\n        end\n      end\n\n      context \"when global option is set\" do\n        let(:global_opt) { tmpdir + \"global\" }\n\n        it \"should return a SolutionFile instance\" do\n          expect(subject.load_solution_file(options)).to be_a(Vagrant::Bundler::SolutionFile)\n        end\n\n        it \"should be located in the environment data path\" do\n          file = subject.load_solution_file(options)\n          expect(file.solution_file.to_s).to start_with(env_path)\n        end\n\n        it \"should have a global.sol solution file\" do\n          file = subject.load_solution_file(options)\n          expect(file.solution_file.to_s).to end_with(\"global.sol\")\n        end\n\n        it \"should have plugin file set to global value\" do\n          file = subject.load_solution_file(options)\n          expect(file.plugin_file.to_s).to eq(global_opt.to_s)\n        end\n      end\n\n      context \"when local and global option is set\" do\n        let(:global_opt) { tmpdir + \"global\" }\n        let(:local_opt) { tmpdir + \"local\" }\n\n        it \"should return nil\" do\n          expect(subject.load_solution_file(options)).to be_nil\n        end\n      end\n    end\n\n    context \"when environment data path is unset\" do\n      context \"when local option is set\" do\n        let(:local_opt) { tmpdir + \"local\" }\n\n        it \"should return nil\" do\n          expect(subject.load_solution_file(options)).to be_nil\n        end\n      end\n\n      context \"when global option is set\" do\n        let(:global_opt) { tmpdir + \"global\" }\n\n        it \"should return a SolutionFile instance\" do\n          expect(subject.load_solution_file(options)).to be_a(Vagrant::Bundler::SolutionFile)\n        end\n\n        it \"should be located in the vagrant user data path\" do\n          file = subject.load_solution_file(options)\n          expect(file.solution_file.to_s).to start_with(Vagrant.user_data_path.to_s)\n        end\n\n        it \"should have a global.sol solution file\" do\n          file = subject.load_solution_file(options)\n          expect(file.solution_file.to_s).to end_with(\"global.sol\")\n        end\n\n        it \"should have plugin file set to global value\" do\n          file = subject.load_solution_file(options)\n          expect(file.plugin_file.to_s).to eq(global_opt.to_s)\n        end\n      end\n    end\n  end\n\n  describe \"#deinit\" do\n    it \"should provide method for backwards compatibility\" do\n      subject.deinit\n    end\n  end\n\n  describe \"DEFAULT_GEM_SOURCES\" do\n    it \"should list hashicorp gemstore first\" do\n      expect(described_class.const_get(:DEFAULT_GEM_SOURCES).first).to eq(\n        described_class.const_get(:HASHICORP_GEMSTORE))\n    end\n  end\n\n  describe \"#init!\" do\n    context \"Gem.sources\" do\n      before {\n        Gem.sources.clear\n        Gem.sources << \"https://rubygems.org/\" }\n\n      it \"should add hashicorp gem store\" do\n        subject.init!([])\n        expect(Gem.sources).to include(described_class.const_get(:HASHICORP_GEMSTORE))\n      end\n\n      it \"should add hashicorp gem store to start of sources list\" do\n        subject.init!([])\n        expect(Gem.sources.sources.first.uri.to_s).to eq(described_class.const_get(:HASHICORP_GEMSTORE))\n      end\n    end\n\n    context \"multiple specs\" do\n      let(:solution_file) { double('solution_file') }\n      let(:vagrant_set)   { double('vagrant_set') }\n\n      before do\n        allow(subject).to receive(:load_solution_file).and_return(solution_file)\n        allow(subject).to receive(:generate_vagrant_set).and_return(vagrant_set)\n        allow(solution_file).to receive(:valid?).and_return(true)\n      end\n\n      it \"should activate spec of deps already loaded\" do\n        spec = Gem.loaded_specs.first\n        deps = [spec[0]]\n        specs = [spec[1].dup, spec[1].dup]\n        specs[0].version = Gem::Version::new('0.0.1')\n        # make sure haven't accidentally modified both\n        expect(specs[0].version).to_not eq(specs[1].version)\n\n        expect(solution_file).to receive(:dependency_list).and_return(deps)\n        expect(vagrant_set).to receive(:find_all).and_return(specs)\n        expect(subject).to receive(:activate_solution) do |activate_specs|\n          expect(activate_specs.length()).to eq(1)\n          expect(activate_specs[0].full_spec()).to eq(specs[1])\n        end\n        subject.init!([])\n      end\n    end\n  end\n\n  describe \"#install\" do\n    let(:plugins){ {\"my-plugin\" => {\"gem_version\" => \"> 0\"}} }\n\n    it \"should pass plugin information hash to internal install\" do\n      expect(subject).to receive(:internal_install).with(plugins, any_args)\n      subject.install(plugins)\n    end\n\n    it \"should not include any update plugins\" do\n      expect(subject).to receive(:internal_install).with(anything, nil, any_args)\n      subject.install(plugins)\n    end\n\n    it \"should flag local when local is true\" do\n      expect(subject).to receive(:internal_install).with(any_args, env_local: true)\n      subject.install(plugins, true)\n    end\n\n    it \"should not flag local when local is not set\" do\n      expect(subject).to receive(:internal_install).with(any_args, env_local: false)\n      subject.install(plugins)\n    end\n  end\n\n  describe \"#install_local\" do\n    let(:plugin_source){ double(\"plugin_source\", spec: plugin_spec) }\n    let(:plugin_spec){ double(\"plugin_spec\", name: plugin_name, version: plugin_version) }\n    let(:plugin_name){ \"PLUGIN_NAME\" }\n    let(:plugin_version){ \"1.0.0\" }\n    let(:plugin_path){ \"PLUGIN_PATH\" }\n    let(:sources){ \"SOURCES\" }\n\n    before do\n      allow(Gem::Source::SpecificFile).to receive(:new).and_return(plugin_source)\n      allow(subject).to receive(:internal_install)\n    end\n\n    it \"should return plugin gem specification\" do\n      expect(subject.install_local(plugin_path)).to eq(plugin_spec)\n    end\n\n    it \"should set custom sources\" do\n      expect(subject).to receive(:internal_install) do |info, update, opts|\n        expect(info[plugin_name][\"sources\"]).to eq(sources)\n      end\n      subject.install_local(plugin_path, sources: sources)\n    end\n\n    it \"should not set the update parameter\" do\n      expect(subject).to receive(:internal_install) do |info, update, opts|\n        expect(update).to be_nil\n      end\n      subject.install_local(plugin_path)\n    end\n\n    it \"should not set plugin as environment local by default\" do\n      expect(subject).to receive(:internal_install) do |info, update, opts|\n        expect(opts[:env_local]).to be_falsey\n      end\n      subject.install_local(plugin_path)\n    end\n\n    it \"should set if plugin is environment local\" do\n      expect(subject).to receive(:internal_install) do |info, update, opts|\n        expect(opts[:env_local]).to be_truthy\n      end\n      subject.install_local(plugin_path, env_local: true)\n    end\n  end\n\n  describe \"#update\" do\n    let(:plugins){ :plugins }\n    let(:specific){ [] }\n\n    after{ subject.update(plugins, specific) }\n\n    it \"should mark update as true\" do\n      expect(subject).to receive(:internal_install) do |info, update, opts|\n        expect(update).to be_truthy\n      end\n    end\n\n    context \"with specific plugins named\" do\n      let(:specific){ [\"PLUGIN_NAME\"] }\n\n      it \"should set update to specific names\" do\n        expect(subject).to receive(:internal_install) do |info, update, opts|\n          expect(update[:gems]).to eq(specific)\n        end\n      end\n    end\n  end\n\n  describe \"#vagrant_internal_specs\" do\n    let(:vagrant_spec) { double(\"vagrant_spec\", name: \"vagrant\", version: Gem::Version.new(Vagrant::VERSION),\n      activated?: vagrant_spec_activated, activate: nil, runtime_dependencies: vagrant_dep_specs) }\n    let(:spec_list) { [] }\n    let(:spec_dirs) { [] }\n    let(:spec_default_dir) { \"/dev/null\" }\n    let(:dir_spec_list) { [] }\n    let(:vagrant_spec_activated) { true }\n    let(:vagrant_dep_specs) { [] }\n\n    before do\n      allow(Gem::Specification).to receive(:find) { |&b| vagrant_spec if b.call(vagrant_spec) }\n      allow(Gem::Specification).to receive(:find_all).and_return(spec_list)\n      allow(Gem::Specification).to receive(:dirs).and_return(spec_dirs)\n      allow(Gem::Specification).to receive(:default_specifications_dir).and_return(spec_default_dir)\n      allow(Gem::Specification).to receive(:each_spec).and_return(dir_spec_list)\n    end\n\n    it \"should return an empty list\" do\n      expect(subject.send(:vagrant_internal_specs)).to eq([])\n    end\n\n    context \"when vagrant specification is not activated\" do\n      let(:vagrant_spec_activated) { false }\n\n      it \"should activate the specification\" do\n        expect(vagrant_spec).to receive(:activate)\n        subject.send(:vagrant_internal_specs)\n      end\n    end\n\n    context \"when vagrant specification is not found\" do\n      before { allow(Gem::Specification).to receive(:find).and_return(nil) }\n\n      it \"should raise not found error\" do\n        expect { subject.send(:vagrant_internal_specs) }.to raise_error(Vagrant::Errors::SourceSpecNotFound)\n      end\n    end\n\n    context \"when bundler is not defined\" do\n      before { expect(Vagrant).to receive(:in_bundler?).and_return(false) }\n\n      context \"when running inside the installer\" do\n        before { expect(Vagrant).to receive(:in_installer?).and_return(true) }\n\n        it \"should load gem specification directories\" do\n          expect(Gem::Specification).to receive(:dirs).and_return(spec_dirs)\n          subject.send(:vagrant_internal_specs)\n        end\n\n        context \"when checking paths\" do\n          let(:spec_dirs) { [double(\"spec-dir\", start_with?: in_user_dir)] }\n          let(:in_user_dir) { true }\n          let(:user_dir) { double(\"user-dir\") }\n\n          before { allow(Gem).to receive(:user_dir).and_return(user_dir) }\n\n          it \"should check if path is within local user directory\" do\n            expect(spec_dirs.first).to receive(:start_with?).with(user_dir).and_return(false)\n            subject.send(:vagrant_internal_specs)\n          end\n\n          context \"when path is not within user directory\" do\n            let(:in_user_dir) { false }\n\n            it \"should use path when loading specs\" do\n              expect(Gem::Specification).to receive(:each_spec) { |arg| expect(arg).to include(spec_dirs.first) }\n              subject.send(:vagrant_internal_specs)\n            end\n          end\n        end\n      end\n\n      context \"when running outside the installer\" do\n        before { expect(Vagrant).to receive(:in_installer?).and_return(false) }\n\n        it \"should not load gem specification directories\" do\n          expect(Gem::Specification).not_to receive(:dirs)\n          subject.send(:vagrant_internal_specs)\n        end\n      end\n    end\n  end\n\n  describe Vagrant::Bundler::PluginSet do\n    let(:name) { \"test-gem\" }\n    let(:version) { \"1.0.0\" }\n    let(:directory) { @directory ||= Dir.mktmpdir(\"vagrant-bundler-test\") }\n\n    after do\n      FileUtils.rm_rf(@directory) if @directory\n      @directory = nil\n    end\n\n    describe \"#add_vendor_gem\" do\n      context \"when spec file does not exist\" do\n        it \"should raise a not found error\" do\n          expect { subject.add_vendor_gem(name, directory) }.to raise_error(Gem::GemNotFoundException)\n        end\n      end\n\n      context \"when spec file exists\" do\n        before do\n          spec = Gem::Specification.new(name, version)\n          File.write(File.join(directory, \"#{name}.gemspec\"), spec.to_ruby)\n        end\n\n        it \"should load the specification\" do\n          expect(subject.add_vendor_gem(name, directory)).to be_a(Gem::Specification)\n        end\n\n        it \"should set the full path in specification\" do\n          spec = subject.add_vendor_gem(name, directory)\n          expect(spec.full_gem_path).to eq(directory)\n        end\n      end\n    end\n\n    describe \"#find_all\" do\n      let(:request) { Gem::Resolver::DependencyRequest.new(dependency, nil) }\n      let(:dependency) { Gem::Dependency.new(\"test-gem\", requirement) }\n      let(:requirement) { Gem::Requirement.new(version) }\n\n      context \"when specification is not included in set\" do\n        it \"should return empty array\" do\n          expect(subject.find_all(request)).to eq([])\n        end\n      end\n\n      context \"when specification is included in set\" do\n        before do\n          spec = Gem::Specification.new(name, version)\n          File.write(File.join(directory, \"#{name}.gemspec\"), spec.to_ruby)\n          subject.add_vendor_gem(name, directory)\n        end\n\n        it \"should return a vendor specification instance\" do\n          expect(subject.find_all(request).first).to be_a(Gem::Resolver::VendorSpecification)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/capability_host_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\nrequire \"vagrant/capability_host\"\n\ndescribe Vagrant::CapabilityHost do\n  include_context \"capability_helpers\"\n\n  subject do\n    Class.new do\n      extend Vagrant::CapabilityHost\n    end\n  end\n\n  describe \"#initialize_capabilities! and #capability_host_chain\" do\n    it \"raises an error if an explicit host is not found\" do\n      expect { subject.initialize_capabilities!(:foo, {}, {}) }.\n        to raise_error(Vagrant::Errors::CapabilityHostExplicitNotDetected)\n    end\n\n    it \"raises an error if a host can't be detected\" do\n      hosts = {\n        foo: [detect_class(false), nil],\n        bar: [detect_class(false), :foo],\n      }\n\n      expect { subject.initialize_capabilities!(nil, hosts, {}) }.\n        to raise_error(Vagrant::Errors::CapabilityHostNotDetected)\n    end\n\n    it \"passes on extra args to the detect method\" do\n      klass = Class.new do\n        def detect?(*args)\n          raise \"detect: #{args.inspect}\"\n        end\n      end\n\n      hosts = {\n        foo: [klass, nil],\n      }\n\n      expect { subject.initialize_capabilities!(nil, hosts, {}, 1, 2) }.\n        to raise_error(RuntimeError, \"detect: [1, 2]\")\n    end\n\n    it \"detects a basic child\" do\n      hosts = {\n        foo: [detect_class(false), nil],\n        bar: [detect_class(true), nil],\n        baz: [detect_class(false), nil],\n      }\n\n      subject.initialize_capabilities!(nil, hosts, {})\n\n      chain = subject.capability_host_chain\n      expect(chain.length).to eql(1)\n      expect(chain[0][0]).to eql(:bar)\n    end\n\n    it \"detects the host with the most parents (deepest) first\" do\n      hosts = {\n        foo: [detect_class(true), nil],\n        bar: [detect_class(true), :foo],\n        baz: [detect_class(true), :bar],\n        foo2: [detect_class(true), nil],\n        bar2: [detect_class(true), :foo2],\n      }\n\n      subject.initialize_capabilities!(nil, hosts, {})\n\n      chain = subject.capability_host_chain\n      expect(chain.length).to eql(3)\n      expect(chain.map(&:first)).to eql([:baz, :bar, :foo])\n    end\n\n    it \"detects a forced host\" do\n      hosts = {\n        foo: [detect_class(false), nil],\n        bar: [detect_class(false), nil],\n        baz: [detect_class(false), nil],\n      }\n\n      subject.initialize_capabilities!(:bar, hosts, {})\n\n      chain = subject.capability_host_chain\n      expect(chain.length).to eql(1)\n      expect(chain[0][0]).to eql(:bar)\n    end\n  end\n\n  describe \"#capability?\" do\n    before do\n      host  = nil\n      hosts = {\n        foo: [detect_class(true), nil],\n        bar: [detect_class(true), :foo],\n      }\n\n      caps = {\n        foo: { parent: Class.new },\n        bar: { self: Class.new },\n      }\n\n      subject.initialize_capabilities!(host, hosts, caps)\n    end\n\n    it \"does not have a non-existent capability\" do\n      expect(subject.capability?(:foo)).to be(false)\n    end\n\n    it \"has capabilities of itself\" do\n      expect(subject.capability?(:self)).to be(true)\n    end\n\n    it \"has capabilities of parent\" do\n      expect(subject.capability?(:parent)).to be(true)\n    end\n  end\n\n  describe \"capability\" do\n    let(:caps) { {} }\n\n    def init\n      host  = nil\n      hosts = {\n        foo: [detect_class(true), nil],\n        bar: [detect_class(true), :foo],\n      }\n\n      subject.initialize_capabilities!(host, hosts, caps)\n    end\n\n    it \"executes the capability\" do\n      caps[:bar] = { test: cap_instance(:test) }\n      init\n\n      expect { subject.capability(:test) }.\n        to raise_error(RuntimeError, \"cap: test []\")\n    end\n\n    it \"executes the capability with arguments\" do\n      caps[:bar] = { test: cap_instance(:test) }\n      init\n\n      expect { subject.capability(:test, 1) }.\n        to raise_error(RuntimeError, \"cap: test [1]\")\n    end\n\n    it \"raises an exception if the capability doesn't exist\" do\n      init\n\n      expect { subject.capability(:what_is_this_i_dont_even) }.\n        to raise_error(Vagrant::Errors::CapabilityNotFound)\n    end\n\n    it \"raises an exception if the method doesn't exist on the module\" do\n      caps[:bar] = { test_is_corrupt: cap_instance(:test_is_corrupt, corrupt: true) }\n      init\n\n      expect { subject.capability(:test_is_corrupt) }.\n        to raise_error(Vagrant::Errors::CapabilityInvalid)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/cli_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../base\"\n\nrequire \"vagrant/cli\"\nrequire \"vagrant/util\"\n\ndescribe Vagrant::CLI do\n  include_context \"unit\"\n  include_context \"command plugin helpers\"\n\n  let(:commands) { {} }\n  let(:iso_env) { isolated_environment }\n  let(:env)     { iso_env.create_vagrant_env }\n  let(:checkpoint) { double(\"checkpoint\") }\n\n  before do\n    allow(Vagrant.plugin(\"2\").manager).to receive(:commands).and_return(commands)\n    allow(Vagrant::Util::CheckpointClient).to receive(:instance).and_return(checkpoint)\n    allow(checkpoint).to receive(:setup).and_return(checkpoint)\n    allow(checkpoint).to receive(:check)\n    allow(checkpoint).to receive(:display)\n  end\n\n  describe \"#initialize\" do\n    it \"should setup checkpoint\" do\n      expect(checkpoint).to receive(:check)\n      described_class.new([\"destroy\"], env)\n    end\n  end\n\n  describe \"#execute\" do\n    let(:triggers) { double(\"triggers\") }\n\n    it \"invokes help and exits with 1 if invalid command\" do\n      subject = described_class.new([\"i-dont-exist\"], env)\n      expect(subject).to receive(:help).once\n      expect(subject.execute).to eql(1)\n    end\n\n    it \"invokes command and returns its exit status if the command is valid\" do\n      commands[:destroy] = [command_lambda(\"destroy\", 42), {}]\n\n      subject = described_class.new([\"destroy\"], env)\n      expect(subject).not_to receive(:help)\n      expect(subject.execute).to eql(42)\n    end\n\n    it \"returns exit code 1 if interrupted\" do\n      commands[:destroy] = [command_lambda(\"destroy\", 42, exception: Interrupt), {}]\n\n      subject = described_class.new([\"destroy\"], env)\n      expect(subject.execute).to eql(1)\n    end\n\n    it \"displays any checkpoint information\" do\n      commands[:destroy] = [command_lambda(\"destroy\", 42), {}]\n      expect(checkpoint).to receive(:display)\n      described_class.new([\"destroy\"], env).execute\n    end\n\n    it \"fires triggers, if enabled\" do\n      allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).\n        with(\"typed_triggers\").and_return(true)\n      allow(triggers).to receive(:fire)\n      allow(triggers).to receive(:find).and_return([double(\"trigger-result\")])\n\n      commands[:destroy] = [command_lambda(\"destroy\", 42), {}]\n\n      allow(Vagrant::Plugin::V2::Trigger).to receive(:new).and_return(triggers)\n\n      subject = described_class.new([\"destroy\"], env)\n\n      expect(triggers).to receive(:fire).twice\n\n      expect(subject).not_to receive(:help)\n      expect(subject.execute).to eql(42)\n    end\n\n    it \"does not fire triggers if disabled\" do\n      allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).\n        with(\"typed_triggers\").and_return(false)\n\n      commands[:destroy] = [command_lambda(\"destroy\", 42), {}]\n\n      subject = described_class.new([\"destroy\"], env)\n\n      expect(triggers).not_to receive(:fire)\n\n      expect(subject).not_to receive(:help)\n      expect(subject.execute).to eql(42)\n    end\n  end\n\n  describe \"#help\" do\n    subject { described_class.new([], env) }\n\n    it \"includes all primary subcommands\" do\n      commands[:foo] = [command_lambda(\"foo\", 0), { primary: true }]\n      commands[:bar] = [command_lambda(\"bar\", 0), { primary: true }]\n      commands[:baz] = [command_lambda(\"baz\", 0), { primary: false }]\n\n      expect(env.ui).to receive(:info).with(any_args) { |message, opts|\n        expect(message).to include(\"foo\")\n        expect(message).to include(\"bar\")\n        expect(message.include?(\"baz\")).to be(false)\n      }\n\n      subject.help\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/config/loader_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/registry\"\n\ndescribe Vagrant::Config::Loader do\n  include_context \"unit\"\n\n  # This is the current version of configuration for the tests.\n  let(:current_version) { version_order.last }\n\n  # This is just a dummy implementation of a configuration loader which\n  # simply acts on hashes.\n  let(:test_loader) do\n    Class.new(Vagrant::Config::VersionBase) do\n      def self.init\n        {}\n      end\n\n      def self.load(proc)\n        init.tap do |obj|\n          proc.call(obj)\n        end\n      end\n\n      def self.merge(old, new)\n        old.merge(new) {|key, oldval, newval| oldval.concat(newval)}\n      end\n    end\n  end\n\n  let(:versions) do\n    Vagrant::Registry.new.tap do |r|\n      r.register(\"1\") { test_loader }\n    end\n  end\n\n  let(:version_order) { [\"1\"] }\n\n  let(:instance) { described_class.new(versions, version_order) }\n\n  describe \"#set\" do\n    context \"with an object that cannot be inspected\" do\n\n      # This represents the euro symbol in UTF-16LE. pack(\"c*\") returns an ASCII\n      # string and so we have to force the encoding\n      UTF_16LE_STRING_THAT_CANNOT_BE_DOWNCAST_TO_ASCII = [0x20, 0xAC].pack(\"c*\").force_encoding(\"UTF-16LE\")\n\n\n      let(:klass_with_bad_inspect_string) do\n        Class.new do\n          def inspect\n            UTF_16LE_STRING_THAT_CANNOT_BE_DOWNCAST_TO_ASCII\n          end\n        end\n      end\n\n      let(:test_source) {\n        Class.new do\n          def initialize(collaborator)\n            @foo = collaborator.new\n          end\n        end.new(klass_with_bad_inspect_string)\n      }\n\n      it \"does not raise the ascii encoding exception\" do\n        expect {\n          instance.set(:arbitrary, test_source)\n        }.to raise_error(ArgumentError, /Unknown configuration source/)\n      end\n    end\n  end\n\n  describe \"basic loading\" do\n    it \"should ignore non-existent load order keys\" do\n      instance.load([:foo])\n    end\n\n    it \"should load and return the configuration\" do\n      proc = Proc.new do |config|\n        config[:foo] = \"yep\"\n      end\n\n      instance.set(:proc, [[current_version, proc]])\n      config, warnings, errors = instance.load([:proc])\n\n      expect(config[:foo]).to eq(\"yep\")\n      expect(warnings).to eq([])\n      expect(errors).to eq([])\n    end\n\n    it \"should throw a NameError exception if invalid or undefined variable is used\" do\n      vagrantfile = <<-VF\n      Vagrant.configure(\"2\") do |config|\n        config.ssh.port = variable\n      end\n      VF\n\n      instance.set(:foo, temporary_file(vagrantfile))\n\n      expect {\n        instance.load([:foo])\n      }.to raise_error(Vagrant::Errors::VagrantfileNameError, /invalid or undefined variable/)\n    end\n  end\n\n  describe \"finalization\" do\n    it \"should finalize the configuration\" do\n      # Create the finalize method on our loader\n      def test_loader.finalize(obj)\n        obj[:finalized] = true\n        obj\n      end\n\n      # Basic configuration proc\n      proc = lambda do |config|\n        config[:foo] = \"yep\"\n      end\n\n      # Run the actual configuration and assert that we get the proper result\n      instance.set(:proc, [[current_version, proc]])\n      config, _ = instance.load([:proc])\n      expect(config[:foo]).to eq(\"yep\")\n      expect(config[:finalized]).to eq(true)\n    end\n  end\n\n  describe \"upgrading\" do\n    it \"should do an upgrade to the latest version\" do\n      test_loader_v2 = Class.new(test_loader) do\n        def self.upgrade(old)\n          new = old.dup\n          new[:v2] = true\n\n          [new, [], []]\n        end\n      end\n\n      versions.register(\"2\") { test_loader_v2 }\n      version_order << \"2\"\n\n      # Load a version 1 proc, and verify it is upgraded to version 2\n      proc = lambda { |config| config[:foo] = \"yep\" }\n      instance.set(:proc, [[\"1\", proc]])\n      config, _ = instance.load([:proc])\n      expect(config[:foo]).to eq(\"yep\")\n      expect(config[:v2]).to eq(true)\n    end\n\n    it \"should keep track of warnings and errors\" do\n      test_loader_v2 = Class.new(test_loader) do\n        def self.upgrade(old)\n          new = old.dup\n          new[:v2] = true\n\n          [new, [\"foo!\"], [\"bar!\"]]\n        end\n      end\n\n      versions.register(\"2\") { test_loader_v2 }\n      version_order << \"2\"\n\n      # Load a version 1 proc, and verify it is upgraded to version 2\n      proc = lambda { |config| config[:foo] = \"yep\" }\n      instance.set(:proc, [[\"1\", proc]])\n      config, warnings, errors = instance.load([:proc])\n      expect(config[:foo]).to eq(\"yep\")\n      expect(config[:v2]).to eq(true)\n      expect(warnings).to eq([\"foo!\"])\n      expect(errors).to eq([\"bar!\"])\n    end\n  end\n\n  describe \"loading edge cases\" do\n    it \"should only run the same proc once\" do\n      count = 0\n      proc = Proc.new do |config|\n        config[:foo] = \"yep\"\n        count += 1\n      end\n\n      instance.set(:proc, [[current_version, proc]])\n\n      5.times do\n        result, _ = instance.load([:proc])\n\n        # Verify the config result\n        expect(result[:foo]).to eq(\"yep\")\n\n        # Verify the count is only one\n        expect(count).to eq(1)\n      end\n    end\n\n    it \"should discard duplicate configs if :home and :root are the same\" do\n      proc = Proc.new do |config|\n        config[:foo] = [\"yep\"]\n      end\n\n      order = [:root, :home]\n\n      instance.set(:root, [[current_version, proc]])\n      instance.set(:home, [[current_version, proc]])\n\n      result, warnings, errors = instance.load(order)\n\n      # Verify the config result\n      expect(result[:foo]).to eq([\"yep\"])\n      expect(result[:foo].size).to eq(1)\n      expect(warnings).to eq([])\n      expect(errors).to eq([])\n    end\n\n    it \"should only load configuration files once\" do\n      $_config_data = 0\n\n      # We test both setting a file multiple times as well as multiple\n      # loads, since both should not cache the data.\n      file = temporary_file(\"$_config_data += 1\")\n      5.times { instance.set(:file, file) }\n      5.times { instance.load([:file]) }\n\n      expect($_config_data).to eq(1)\n    end\n\n    it \"should not clear the cache if setting to the same value multiple times\" do\n      $_config_data = 0\n\n      file = temporary_file(\"$_config_data += 1\")\n\n      instance.set(:proc, file)\n      5.times { instance.load([:proc]) }\n\n      instance.set(:proc, file)\n      5.times { instance.load([:proc]) }\n\n      expect($_config_data).to eq(1)\n    end\n\n    it \"should raise proper error if there is a syntax error in a Vagrantfile\" do\n      expect { instance.set(:file, temporary_file(\"Vagrant:^Config\")) }.\n        to raise_exception(Vagrant::Errors::VagrantfileSyntaxError)\n    end\n\n    it \"should raise a proper error if there is a problem with the Vagrantfile\" do\n      expect { instance.set(:file, temporary_file(\"foo\")) }.\n        to raise_exception(Vagrant::Errors::VagrantfileLoadError)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/config/v1/dummy_config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Config::V1::DummyConfig do\n  it \"should allow attribute setting\" do\n    expect { subject.foo = :bar }.\n      to_not raise_error\n  end\n\n  it \"should allow method calls that return more DummyConfigs\" do\n    expect(subject.foo).to be_kind_of(described_class)\n  end\n\n  it \"should allow hash access\" do\n    expect { subject[:foo] }.\n      to_not raise_error\n\n    expect(subject[:foo]).to be_kind_of(described_class)\n  end\n\n  it \"should allow setting hash values\" do\n    expect { subject[:foo] = :bar }.\n      to_not raise_error\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/config/v1/loader_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"ostruct\"\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Config::V1::Loader do\n  include_context \"unit\"\n\n  before(:each) do\n    # Force the V1 loader to believe that we are in V1\n    stub_const(\"Vagrant::Config::CURRENT_VERSION\", \"1\")\n  end\n\n  describe \"empty\" do\n    it \"returns an empty configuration object\" do\n      result = described_class.init\n      expect(result).to be_kind_of(Vagrant::Config::V1::Root)\n    end\n\n    it \"returns an object with all configuration keys loaded if V1\" do\n      # Make sure we're version 1\n      stub_const(\"Vagrant::Config::CURRENT_VERSION\", \"1\")\n\n      # Register some config classes.\n      register_plugin(\"1\") do |p|\n        p.config(\"foo\") { OpenStruct }\n        p.config(\"bar\", true) { OpenStruct }\n      end\n\n      # Test that we have all keys\n      result = described_class.init\n      expect(result.foo).to be_kind_of(OpenStruct)\n      expect(result.bar).to be_kind_of(OpenStruct)\n    end\n\n    it \"returns only upgradable config objects if not V1\" do\n      # Make sure we're NOT version 1\n      stub_const(\"Vagrant::Config::CURRENT_VERSION\", \"2\")\n\n      # Register some config classes.\n      register_plugin(\"1\") do |p|\n        p.config(\"foo\") { OpenStruct }\n        p.config(\"bar\", true) { OpenStruct }\n      end\n\n      # Test that we have all keys\n      result = described_class.init\n      expect(result.bar).to be_kind_of(OpenStruct)\n    end\n  end\n\n  describe \"finalizing\" do\n    it \"should call `#finalize` on the configuration object\" do\n      # Register a plugin for our test\n      register_plugin(\"1\") do |plugin|\n        plugin.config \"foo\" do\n          Class.new do\n            attr_accessor :bar\n\n            def finalize!\n              @bar = \"finalized\"\n            end\n          end\n        end\n      end\n\n      # Create the proc we're testing\n      config_proc = Proc.new do |config|\n        config.foo.bar = \"value\"\n      end\n\n      # Test that it works properly\n      config = described_class.load(config_proc)\n      expect(config.foo.bar).to eq(\"value\")\n\n      # Finalize it\n      described_class.finalize(config)\n      expect(config.foo.bar).to eq(\"finalized\")\n    end\n  end\n\n  describe \"loading\" do\n    it \"should configure with all plugin config keys loaded\" do\n      # Register a plugin for our test\n      register_plugin(\"1\") do |plugin|\n        plugin.config(\"foo\") { OpenStruct }\n      end\n\n      # Create the proc we're testing\n      config_proc = Proc.new do |config|\n        config.foo.bar = \"value\"\n      end\n\n      # Test that it works properly\n      config = described_class.load(config_proc)\n      expect(config.foo.bar).to eq(\"value\")\n    end\n  end\n\n  describe \"merging\" do\n    it \"should merge available configuration keys\" do\n      old = Vagrant::Config::V1::Root.new({ foo: Object })\n      new = Vagrant::Config::V1::Root.new({ bar: Object })\n      result = described_class.merge(old, new)\n      expect(result.foo).to be_kind_of(Object)\n      expect(result.bar).to be_kind_of(Object)\n    end\n\n    it \"should merge instantiated objects\" do\n      config_class = Class.new do\n        attr_accessor :value\n      end\n\n      old = Vagrant::Config::V1::Root.new({ foo: config_class })\n      old.foo.value = \"old\"\n\n      new = Vagrant::Config::V1::Root.new({ bar: config_class })\n      new.bar.value = \"new\"\n\n      result = described_class.merge(old, new)\n      expect(result.foo.value).to eq(\"old\")\n      expect(result.bar.value).to eq(\"new\")\n    end\n\n    it \"should merge conflicting classes by calling `merge`\" do\n      config_class = Class.new do\n        attr_accessor :value\n\n        def merge(new)\n          result       = self.class.new\n          result.value = @value + new.value\n          result\n        end\n      end\n\n      old = Vagrant::Config::V1::Root.new({ foo: config_class })\n      old.foo.value = 10\n\n      new = Vagrant::Config::V1::Root.new({ foo: config_class })\n      new.foo.value = 15\n\n      result = described_class.merge(old, new)\n      expect(result.foo.value).to eq(25)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/config/v1/root_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Config::V1::Root do\n  include_context \"unit\"\n\n  it \"should provide access to config objects\" do\n    foo_class = Class.new\n    map       = { foo: foo_class }\n\n    instance  = described_class.new(map)\n    foo       = instance.foo\n    expect(foo).to be_kind_of(foo_class)\n    expect(instance.foo).to eql(foo)\n  end\n\n  it \"can be created with initial state\" do\n    instance = described_class.new({}, { foo: \"bar\" })\n    expect(instance.foo).to eq(\"bar\")\n  end\n\n  it \"should return internal state\" do\n    map      = { \"foo\" => Object, \"bar\" => Object }\n    instance = described_class.new(map)\n    expect(instance.__internal_state).to eq({\n      \"config_map\" => map,\n      \"keys\"       => {},\n      \"missing_key_calls\" => Set.new\n    })\n  end\n\n  it \"should record missing key calls\" do\n    instance = described_class.new({})\n    instance.foo.bar = false\n\n    keys = instance.__internal_state[\"missing_key_calls\"]\n    expect(keys).to be_kind_of(Set)\n    expect(keys.length).to eq(1)\n    expect(keys.include?(\"foo\")).to be\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/config/v2/dummy_config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Config::V2::DummyConfig do\n  it \"should allow attribute setting\" do\n    expect { subject.foo = :bar }.\n      to_not raise_error\n  end\n\n  it \"should allow method calls that return more DummyConfigs\" do\n    expect(subject.foo).to be_kind_of(described_class)\n  end\n\n  it \"should allow hash access\" do\n    expect { subject[:foo] }.\n      to_not raise_error\n\n    expect(subject[:foo]).to be_kind_of(described_class)\n  end\n\n  it \"should allow setting hash values\" do\n    expect { subject[:foo] = :bar }.\n      to_not raise_error\n  end\n\n  it \"should survive being the last arg to a method that captures kwargs without a ruby conversion error\" do\n    arg_capturer = lambda { |*args, **kwargs| }\n    expect {\n      arg_capturer.call(subject)\n    }.to_not raise_error\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/config/v2/loader_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"ostruct\"\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Config::V2::Loader do\n  include_context \"unit\"\n\n  before(:each) do\n    # Force the V2 loader to believe that we are in V2\n    stub_const(\"Vagrant::Config::CURRENT_VERSION\", \"2\")\n  end\n\n  describe \"empty\" do\n    it \"returns an empty configuration object\" do\n      result = described_class.init\n      expect(result).to be_kind_of(Vagrant::Config::V2::Root)\n    end\n  end\n\n  describe \"finalizing\" do\n    it \"should call `#finalize` on the configuration object\" do\n      # Register a plugin for our test\n      register_plugin(\"2\") do |plugin|\n        plugin.config \"foo\" do\n          Class.new(Vagrant.plugin(\"2\", \"config\")) do\n            attr_accessor :bar\n\n            def finalize!\n              @bar = \"finalized\"\n            end\n          end\n        end\n      end\n\n      # Create the proc we're testing\n      config_proc = Proc.new do |config|\n        config.foo.bar = \"value\"\n      end\n\n      # Test that it works properly\n      config = described_class.load(config_proc)\n      expect(config.foo.bar).to eq(\"value\")\n\n      # Finalize it\n      described_class.finalize(config)\n      expect(config.foo.bar).to eq(\"finalized\")\n    end\n  end\n\n  describe \"loading\" do\n    it \"should configure with all plugin config keys loaded\" do\n      # Register a plugin for our test\n      register_plugin(\"2\") do |plugin|\n        plugin.config(\"foo\") { OpenStruct }\n      end\n\n      # Create the proc we're testing\n      config_proc = Proc.new do |config|\n        config.foo.bar = \"value\"\n      end\n\n      # Test that it works properly\n      config = described_class.load(config_proc)\n      expect(config.foo.bar).to eq(\"value\")\n    end\n  end\n\n  describe \"merging\" do\n    it \"should merge available configuration keys\" do\n      old = Vagrant::Config::V2::Root.new({ foo: Object })\n      new = Vagrant::Config::V2::Root.new({ bar: Object })\n      result = described_class.merge(old, new)\n      expect(result.foo).to be_kind_of(Object)\n      expect(result.bar).to be_kind_of(Object)\n    end\n\n    it \"should merge instantiated objects\" do\n      config_class = Class.new do\n        attr_accessor :value\n      end\n\n      old = Vagrant::Config::V2::Root.new({ foo: config_class })\n      old.foo.value = \"old\"\n\n      new = Vagrant::Config::V2::Root.new({ bar: config_class })\n      new.bar.value = \"new\"\n\n      result = described_class.merge(old, new)\n      expect(result.foo.value).to eq(\"old\")\n      expect(result.bar.value).to eq(\"new\")\n    end\n\n    it \"should merge conflicting classes by calling `merge`\" do\n      config_class = Class.new do\n        attr_accessor :value\n\n        def merge(new)\n          result       = self.class.new\n          result.value = @value + new.value\n          result\n        end\n      end\n\n      old = Vagrant::Config::V2::Root.new({ foo: config_class })\n      old.foo.value = 10\n\n      new = Vagrant::Config::V2::Root.new({ foo: config_class })\n      new.foo.value = 15\n\n      result = described_class.merge(old, new)\n      expect(result.foo.value).to eq(25)\n    end\n  end\n\n  describe \"upgrading\" do\n    it \"should continue fine if the key doesn't implement upgrade\" do\n      # Make an old V1 root object\n      old = Vagrant::Config::V1::Root.new({ foo: Class.new })\n\n      # It should work fine\n      expect { result = described_class.upgrade(old) }.to_not raise_error\n    end\n\n    it \"should upgrade the config if it implements the upgrade method\" do\n      # Create the old V1 class that will be upgraded\n      config_class = Class.new do\n        attr_accessor :value\n\n        def upgrade(new)\n          new.foo.value = value * 2\n\n          [[\"foo\"], [\"bar\"]]\n        end\n      end\n\n      # Create the new V2 plugin it is writing to\n      register_plugin(\"2\") do |p|\n        p.config(\"foo\") { OpenStruct }\n      end\n\n      # Test it out!\n      old = Vagrant::Config::V1::Root.new({ foo: config_class })\n      old.foo.value = 5\n\n      data = described_class.upgrade(old)\n      expect(data[0].foo.value).to eq(10)\n      expect(data[1]).to eq([\"foo\"])\n      expect(data[2]).to eq([\"bar\"])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/config/v2/root_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"set\"\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Config::V2::Root do\n  include_context \"unit\"\n\n  it \"should provide access to config objects\" do\n    foo_class = Class.new\n    map       = { foo: foo_class }\n\n    instance  = described_class.new(map)\n    foo       = instance.foo\n    expect(foo).to be_kind_of(foo_class)\n    expect(instance.foo).to eql(foo)\n  end\n\n  it \"record a missing key call if invalid key used\" do\n    instance = described_class.new({})\n    expect { instance.foo }.to_not raise_error\n    expect(instance.__internal_state[\"missing_key_calls\"].include?(\"foo\")).to be\n  end\n\n  it \"returns a dummy config for a missing key\" do\n    instance = described_class.new({})\n    expect { instance.foo.foo = \"bar\" }.to_not raise_error\n  end\n\n  it \"can be created with initial state\" do\n    instance = described_class.new({}, { foo: \"bar\" })\n    expect(instance.foo).to eq(\"bar\")\n  end\n\n  it \"should return internal state\" do\n    map      = { \"foo\" => Object, \"bar\" => Object }\n    instance = described_class.new(map)\n    expect(instance.__internal_state).to eq({\n      \"config_map\"        => map,\n      \"keys\"              => {},\n      \"missing_key_calls\" => Set.new\n    })\n  end\n\n  describe \"#finalize!\" do\n    it \"should call #finalize!\" do\n      foo_class = Class.new(Vagrant.plugin(\"2\", \"config\")) do\n        attr_accessor :foo\n\n        def finalize!\n          @foo = \"SET\"\n        end\n      end\n\n      map = { foo: foo_class }\n      instance = described_class.new(map)\n      instance.finalize!\n\n      expect(instance.foo.foo).to eq(\"SET\")\n    end\n\n    it \"should call #_finalize!\" do\n      klass = Class.new(Vagrant.plugin(\"2\", \"config\"))\n\n      expect_any_instance_of(klass).to receive(:finalize!)\n      expect_any_instance_of(klass).to receive(:_finalize!)\n\n      map = { foo: klass }\n      instance = described_class.new(map)\n      instance.finalize!\n    end\n  end\n\n  describe \"validation\" do\n    let(:instance) do\n      map = { foo: Object, bar: Object }\n      described_class.new(map)\n    end\n\n    it \"should return nil if valid\" do\n      expect(instance.validate({})).to eq({})\n    end\n\n    it \"should return errors if invalid\" do\n      errors = { \"foo\" => [\"errors!\"] }\n      env    = { \"errors\" => errors }\n      foo    = instance.foo\n      def foo.validate(env)\n        env[\"errors\"]\n      end\n\n      expect(instance.validate(env)).to eq(errors)\n    end\n\n    context \"with vms and ignoring provider validations\" do\n      let(:instance) do\n        map = { vm: Object, bar: Object }\n        described_class.new(map)\n      end\n\n      it \"should pass along the ignore_provider flag for ignoring validations\" do\n        errors = { \"vm\" => [\"errors!\"] }\n        env    = { \"errors\" => errors }\n        vm     = instance.vm\n        def vm.validate(env, param)\n          env[\"errors\"]\n        end\n\n        expect(instance.validate({}, true)).to eq({})\n      end\n    end\n\n    it \"should merge errors via array concat if matching keys\" do\n      errors = { \"foo\" => [\"errors!\"] }\n      env    = { \"errors\" => errors }\n      foo    = instance.foo\n      bar    = instance.bar\n      def foo.validate(env)\n        env[\"errors\"]\n      end\n\n      def bar.validate(env)\n        env[\"errors\"].merge({ \"bar\" => [\"bar\"] })\n      end\n\n      expected_errors = {\n        \"foo\" => [\"errors!\", \"errors!\"],\n        \"bar\" => [\"bar\"]\n      }\n\n      expect(instance.validate(env)).to eq(expected_errors)\n    end\n\n    it \"shouldn't count empty keys\" do\n      errors = { \"foo\" => [] }\n      env    = { \"errors\" => errors }\n      foo    = instance.foo\n      def foo.validate(env)\n        env[\"errors\"]\n      end\n\n      expect(instance.validate(env)).to eq({})\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/config/v2/util_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire \"vagrant/config/v2/util\"\n\ndescribe Vagrant::Config::V2::Util do\n  describe \"merging errors\" do\n    it \"should merge matching keys and leave the rest alone\" do\n      first  = { \"one\" => [\"foo\"], \"two\" => [\"two\"] }\n      second = { \"one\" => [\"bar\"], \"three\" => [\"three\"] }\n\n      expected = {\n        \"one\" => [\"foo\", \"bar\"],\n        \"two\" => [\"two\"],\n        \"three\" => [\"three\"]\n      }\n\n      result = described_class.merge_errors(first, second)\n      expect(result).to eq(expected)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\ndescribe Vagrant::Config do\n  it \"should not execute the proc on configuration\" do\n    described_class.run do\n      raise Exception, \"Failure.\"\n    end\n  end\n\n  it \"should capture calls to `Vagrant.configure`\" do\n    receiver = double()\n\n    procs = described_class.capture_configures do\n      Vagrant.configure(\"1\") do\n        receiver.one\n      end\n\n      Vagrant.configure(\"2\") do\n        receiver.two\n      end\n    end\n\n    expect(procs).to be_kind_of(Array)\n    expect(procs.length).to eq(2)\n    expect(procs[0][0]).to eq(\"1\")\n    expect(procs[1][0]).to eq(\"2\")\n\n    # Verify the proper procs were captured\n    expect(receiver).to receive(:one).once.ordered\n    expect(receiver).to receive(:two).once.ordered\n    procs[0][1].call\n    procs[1][1].call\n  end\n\n  it \"should work with integer configurations \"do\n    receiver = double()\n\n    procs = described_class.capture_configures do\n      Vagrant.configure(1) do\n        receiver.one\n      end\n\n      Vagrant.configure(\"2\") do\n        receiver.two\n      end\n    end\n\n    expect(procs).to be_kind_of(Array)\n    expect(procs.length).to eq(2)\n    expect(procs[0][0]).to eq(\"1\")\n    expect(procs[1][0]).to eq(\"2\")\n\n    # Verify the proper procs were captured\n    expect(receiver).to receive(:one).once.ordered\n    expect(receiver).to receive(:two).once.ordered\n    procs[0][1].call\n    procs[1][1].call\n  end\n\n  it \"should capture configuration procs\" do\n    receiver = double()\n\n    procs = described_class.capture_configures do\n      described_class.run do\n        receiver.hello!\n      end\n    end\n\n    # Verify the structure of the result\n    expect(procs).to be_kind_of(Array)\n    expect(procs.length).to eq(1)\n\n    # Verify that the proper proc was captured\n    expect(receiver).to receive(:hello!).once\n    expect(procs[0][0]).to eq(\"1\")\n    procs[0][1].call\n  end\n\n  it \"should capture the proper version\" do\n    procs = described_class.capture_configures do\n      described_class.run(\"1\") {}\n      described_class.run(\"2\") {}\n    end\n\n    # Verify the structure of the result\n    expect(procs).to be_kind_of(Array)\n    expect(procs.length).to eq(2)\n    expect(procs[0][0]).to eq(\"1\")\n    expect(procs[1][0]).to eq(\"2\")\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/environment_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\nrequire \"json\"\nrequire \"pathname\"\nrequire \"tempfile\"\nrequire \"tmpdir\"\n\nrequire \"vagrant/util/file_mode\"\nrequire \"vagrant/util/platform\"\n\ndescribe Vagrant::Environment do\n  include_context \"unit\"\n  include_context \"capability_helpers\"\n\n  let(:env) do\n    isolated_environment.tap do |e|\n      e.box3(\"base\", \"1.0\", :virtualbox)\n      e.vagrantfile <<-VF\n      Vagrant.configure(\"2\") do |config|\n        config.vm.box = \"base\"\n      end\n      VF\n    end\n  end\n\n  let(:instance)  { env.create_vagrant_env }\n  subject { instance }\n\n  describe \"#can_install_provider?\" do\n    let(:plugin_hosts) { {} }\n    let(:plugin_host_caps) { {} }\n\n    before do\n      m = Vagrant.plugin(\"2\").manager\n      allow(m).to receive(:hosts).and_return(plugin_hosts)\n      allow(m).to receive(:host_capabilities).and_return(plugin_host_caps)\n\n      # Detect the host\n      env.vagrantfile <<-VF\n      Vagrant.configure(\"2\") do |config|\n        config.vagrant.host = nil\n      end\n      VF\n\n      # Setup the foo host by default\n      plugin_hosts[:foo] = [detect_class(true), nil]\n    end\n\n    it \"should return whether it can install or not\" do\n      plugin_host_caps[:foo] = { provider_install_foo: Class }\n\n      expect(subject.can_install_provider?(:foo)).to be(true)\n      expect(subject.can_install_provider?(:bar)).to be(false)\n    end\n  end\n\n  describe \"#install_provider\" do\n    let(:host) { double(:host) }\n\n    before do\n      allow(subject).to receive(:host).and_return(host)\n    end\n\n    it \"should install the correct provider\" do\n      expect(host).to receive(:capability).with(:provider_install_foo)\n\n      subject.install_provider(:foo)\n    end\n  end\n\n  describe \"#gems_path\" do\n    it \"is set to Vagrant::Bundler defined path\" do\n      instance = described_class.new\n      expect(instance.gems_path).to eq(Vagrant::Bundler.instance.plugin_gem_path)\n    end\n  end\n\n  describe \"#home_path\" do\n    it \"is set to the home path given\" do\n      Dir.mktmpdir(\"vagrant-test-env-home-path-given\") do |dir|\n        instance = described_class.new(home_path: dir)\n        expect(instance.home_path).to eq(Pathname.new(dir))\n      end\n    end\n\n    it \"is set to the environmental variable VAGRANT_HOME\" do\n      Dir.mktmpdir(\"vagrant-test-env-home-env-var\") do |dir|\n        instance = with_temp_env(\"VAGRANT_HOME\" => dir.to_s) do\n          described_class.new\n        end\n\n        expect(instance.home_path).to eq(Pathname.new(dir))\n      end\n    end\n\n    it \"throws an exception if inaccessible\", skip_windows: true do\n      expect {\n        described_class.new(home_path: \"/\")\n      }.to raise_error(Vagrant::Errors::HomeDirectoryNotAccessible)\n    end\n\n    context \"with setup version file\" do\n      it \"creates a setup version flie\" do\n        path = subject.home_path.join(\"setup_version\")\n        expect(path).to be_file\n        expect(path.read).to eq(Vagrant::Environment::CURRENT_SETUP_VERSION)\n      end\n\n      it \"is okay if it has the current version\" do\n        Dir.mktmpdir(\"vagrant-test-env-current-version\") do |dir|\n          Pathname.new(dir).join(\"setup_version\").open(\"w\") do |f|\n            f.write(Vagrant::Environment::CURRENT_SETUP_VERSION)\n          end\n\n          instance = described_class.new(home_path: dir)\n          path = instance.home_path.join(\"setup_version\")\n          expect(path).to be_file\n          expect(path.read).to eq(Vagrant::Environment::CURRENT_SETUP_VERSION)\n        end\n      end\n\n      it \"raises an exception if the version is newer than ours\" do\n        Dir.mktmpdir(\"vagrant-test-env-newer-version\") do |dir|\n          Pathname.new(dir).join(\"setup_version\").open(\"w\") do |f|\n            f.write(\"100.5\")\n          end\n\n          expect { described_class.new(home_path: dir) }.\n            to raise_error(Vagrant::Errors::HomeDirectoryLaterVersion)\n        end\n      end\n\n      it \"raises an exception if there is an unknown home directory version\" do\n        Dir.mktmpdir(\"vagrant-test-env-unknown-home\") do |dir|\n          Pathname.new(dir).join(\"setup_version\").open(\"w\") do |f|\n            f.write(\"0.7\")\n          end\n\n          expect { described_class.new(home_path: dir) }.\n            to raise_error(Vagrant::Errors::HomeDirectoryUnknownVersion)\n        end\n      end\n    end\n\n    context \"upgrading a v1.1 directory structure\" do\n      let(:env) { isolated_environment }\n\n      before do\n        env.homedir.join(\"setup_version\").open(\"w\") do |f|\n          f.write(\"1.1\")\n        end\n\n        allow_any_instance_of(Vagrant::UI::Silent).\n          to receive(:ask)\n      end\n\n      it \"replaces the setup version with the new version\" do\n        expect(subject.home_path.join(\"setup_version\").read).\n          to eq(Vagrant::Environment::CURRENT_SETUP_VERSION)\n      end\n\n      it \"moves the boxes into the new directory structure\" do\n        # Kind of hacky but avoids two instantiations of BoxCollection\n        allow(Vagrant::Environment).to receive(:boxes)\n          .and_return(double(\"boxes\"))\n\n        collection = double(\"collection\")\n        expect(Vagrant::BoxCollection).to receive(:new).with(\n          env.homedir.join(\"boxes\"), anything).twice.and_return(collection)\n        expect(collection).to receive(:upgrade_v1_1_v1_5).once\n        subject\n      end\n    end\n  end\n\n  describe \"#host\" do\n    let(:plugin_hosts) { {} }\n    let(:plugin_host_caps) { {} }\n\n    before do\n      m = Vagrant.plugin(\"2\").manager\n      allow(m).to receive(:hosts).and_return(plugin_hosts)\n      allow(m).to receive(:host_capabilities).and_return(plugin_host_caps)\n    end\n\n    it \"should default to some host even if there are none\" do\n      env.vagrantfile <<-VF\n      Vagrant.configure(\"2\") do |config|\n        config.vagrant.host = nil\n      end\n      VF\n\n      expect(subject.host).to be\n    end\n\n    it \"should attempt to detect a host if no host is set\" do\n      env.vagrantfile <<-VF\n      Vagrant.configure(\"2\") do |config|\n        config.vagrant.host = nil\n      end\n      VF\n\n      plugin_hosts[:foo] = [detect_class(true), nil]\n      plugin_host_caps[:foo] = { bar: Class }\n\n      result = subject.host\n      expect(result.capability?(:bar)).to be(true)\n    end\n\n    it \"should attempt to detect a host if host is :detect\" do\n      env.vagrantfile <<-VF\n      Vagrant.configure(\"2\") do |config|\n        config.vagrant.host = :detect\n      end\n      VF\n\n      plugin_hosts[:foo] = [detect_class(true), nil]\n      plugin_host_caps[:foo] = { bar: Class }\n\n      result = subject.host\n      expect(result.capability?(:bar)).to be(true)\n    end\n\n    it \"should use an exact host if specified\" do\n      env.vagrantfile <<-VF\n      Vagrant.configure(\"2\") do |config|\n        config.vagrant.host = \"foo\"\n      end\n      VF\n\n      plugin_hosts[:foo] = [detect_class(false), nil]\n      plugin_hosts[:bar] = [detect_class(true), nil]\n      plugin_host_caps[:foo] = { bar: Class }\n\n      result = subject.host\n      expect(result.capability?(:bar)).to be(true)\n    end\n\n    it \"should raise an error if an exact match was specified but not found\" do\n      env.vagrantfile <<-VF\n      Vagrant.configure(\"2\") do |config|\n        config.vagrant.host = \"bar\"\n      end\n      VF\n\n      expect { subject.host }.\n        to raise_error(Vagrant::Errors::HostExplicitNotDetected)\n    end\n  end\n\n  describe \"#lock\" do\n    def lock_count\n      subject.data_dir.\n        children.\n        find_all { |c| c.to_s.end_with?(\"lock\") }.\n        length\n    end\n\n    it \"does nothing if no block is given\" do\n      subject.lock\n    end\n\n    it \"locks the environment\" do\n      another = env.create_vagrant_env\n      raised  = false\n\n      subject.lock do\n        begin\n          another.lock {}\n        rescue Vagrant::Errors::EnvironmentLockedError\n          raised = true\n        end\n      end\n\n      expect(raised).to be(true)\n    end\n\n    it \"allows nested locks on the same environment\" do\n      success = false\n\n      subject.lock do\n        subject.lock do\n          success = true\n        end\n      end\n\n      expect(success).to be(true)\n    end\n\n    it \"cleans up all lock files\" do\n      inner_count = nil\n\n      expect(lock_count).to eq(0)\n      subject.lock do\n        inner_count = lock_count\n      end\n\n      expect(inner_count).to_not be_nil\n      expect(inner_count).to eq(2)\n      expect(lock_count).to eq(1)\n    end\n  end\n\n  describe \"#machine\" do\n    # A helper to register a provider for use in tests.\n    def register_provider(name, config_class=nil, options=nil)\n      provider_cls = Class.new(VagrantTests::DummyProvider)\n\n      register_plugin(\"2\") do |p|\n        p.provider(name, options) { provider_cls }\n\n        if config_class\n          p.config(name, :provider) { config_class }\n        end\n      end\n\n      provider_cls\n    end\n\n    it \"should return a machine object with the correct provider\" do\n      # Create a provider\n      foo_provider = register_provider(\"foo\")\n\n      # Create the configuration\n      isolated_env = isolated_environment do |e|\n        e.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"base\"\n  config.vm.define \"foo\"\nend\nVF\n\n        e.box3(\"base\", \"1.0\", :foo)\n      end\n\n      # Verify that we can get the machine\n      env = isolated_env.create_vagrant_env\n      machine = env.machine(:foo, :foo)\n      expect(machine).to be_kind_of(Vagrant::Machine)\n      expect(machine.name).to eq(:foo)\n      expect(machine.provider).to be_kind_of(foo_provider)\n      expect(machine.provider_config).to be_nil\n    end\n\n    it \"should return a machine object with the machine configuration\" do\n      # Create a provider\n      foo_config = Class.new(Vagrant.plugin(\"2\", :config)) do\n        attr_accessor :value\n      end\n\n      foo_provider = register_provider(\"foo\", foo_config)\n\n      # Create the configuration\n      isolated_env = isolated_environment do |e|\n        e.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"base\"\n  config.vm.define \"foo\"\n\n  config.vm.provider :foo do |fooconfig|\n    fooconfig.value = 100\n  end\nend\nVF\n\n        e.box3(\"base\", \"1.0\", :foo)\n      end\n\n      # Verify that we can get the machine\n      env = isolated_env.create_vagrant_env\n      machine = env.machine(:foo, :foo)\n      expect(machine).to be_kind_of(Vagrant::Machine)\n      expect(machine.name).to eq(:foo)\n      expect(machine.provider).to be_kind_of(foo_provider)\n      expect(machine.provider_config.value).to eq(100)\n    end\n\n    it \"should cache the machine objects by name and provider\" do\n      # Create a provider\n      foo_provider = register_provider(\"foo\")\n      bar_provider = register_provider(\"bar\")\n\n      # Create the configuration\n      isolated_env = isolated_environment do |e|\n        e.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"base\"\n  config.vm.define \"vm1\"\n  config.vm.define \"vm2\"\nend\nVF\n\n        e.box3(\"base\", \"1.0\", :foo)\n        e.box3(\"base\", \"1.0\", :bar)\n      end\n\n      env = isolated_env.create_vagrant_env\n      vm1_foo = env.machine(:vm1, :foo)\n      vm1_bar = env.machine(:vm1, :bar)\n      vm2_foo = env.machine(:vm2, :foo)\n\n      expect(vm1_foo).to eql(env.machine(:vm1, :foo))\n      expect(vm1_bar).to eql(env.machine(:vm1, :bar))\n      expect(vm1_foo).not_to eql(vm1_bar)\n      expect(vm2_foo).to eql(env.machine(:vm2, :foo))\n    end\n\n    it \"should load a machine without a box\" do\n      register_provider(\"foo\")\n\n      environment = isolated_environment do |env|\n        env.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"i-dont-exist\"\nend\nVF\n      end\n\n      env = environment.create_vagrant_env\n      machine = env.machine(:default, :foo)\n      expect(machine.box).to be_nil\n    end\n\n    it \"should load the machine configuration\" do\n      register_provider(\"foo\")\n\n      environment = isolated_environment do |env|\n        env.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.ssh.port = 1\n  config.vm.box = \"base\"\n\n  config.vm.define \"vm1\" do |inner|\n    inner.ssh.port = 100\n  end\nend\nVF\n\n        env.box3(\"base\", \"1.0\", :foo)\n      end\n\n      env = environment.create_vagrant_env\n      machine = env.machine(:vm1, :foo)\n      expect(machine.config.ssh.port).to eq(100)\n      expect(machine.config.vm.box).to eq(\"base\")\n    end\n\n    it \"should load the box configuration for a box\" do\n      register_provider(\"foo\")\n\n      environment = isolated_environment do |env|\n        env.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"base\"\nend\nVF\n\n        env.box3(\"base\", \"1.0\", :foo, vagrantfile: <<-VF)\nVagrant.configure(\"2\") do |config|\n  config.ssh.port = 100\nend\nVF\n      end\n\n      env = environment.create_vagrant_env\n      machine = env.machine(:default, :foo)\n      expect(machine.config.ssh.port).to eq(100)\n    end\n\n    it \"should load the box configuration for a box and custom Vagrantfile name\" do\n      register_provider(\"foo\")\n\n      environment = isolated_environment do |env|\n        env.file(\"some_other_name\", <<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"base\"\nend\nVF\n\n        env.box3(\"base\", \"1.0\", :foo, vagrantfile: <<-VF)\nVagrant.configure(\"2\") do |config|\n  config.ssh.port = 100\nend\nVF\n      end\n\n      env = with_temp_env(\"VAGRANT_VAGRANTFILE\" => \"some_other_name\") do\n        environment.create_vagrant_env\n      end\n\n      machine = env.machine(:default, :foo)\n      expect(machine.config.ssh.port).to eq(100)\n    end\n\n    it \"should load the box configuration for other formats for a box\" do\n      register_provider(\"foo\", nil, box_format: \"bar\")\n\n      environment = isolated_environment do |env|\n        env.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"base\"\nend\nVF\n\n        env.box3(\"base\", \"1.0\", :bar, vagrantfile: <<-VF)\nVagrant.configure(\"2\") do |config|\n  config.ssh.port = 100\nend\nVF\n      end\n\n      env = environment.create_vagrant_env\n      machine = env.machine(:default, :foo)\n      expect(machine.config.ssh.port).to eq(100)\n    end\n\n    it \"prefer sooner formats when multiple box formats are available\" do\n      register_provider(\"foo\", nil, box_format: [\"fA\", \"fB\"])\n\n      environment = isolated_environment do |env|\n        env.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"base\"\nend\nVF\n\n        env.box3(\"base\", \"1.0\", :fA, vagrantfile: <<-VF)\nVagrant.configure(\"2\") do |config|\n  config.ssh.port = 100\nend\nVF\n\n        env.box3(\"base\", \"1.0\", :fB, vagrantfile: <<-VF)\nVagrant.configure(\"2\") do |config|\n  config.ssh.port = 200\nend\nVF\n      end\n\n      env = environment.create_vagrant_env\n      machine = env.machine(:default, :foo)\n      expect(machine.config.ssh.port).to eq(100)\n    end\n\n    it \"should load the proper version of a box\" do\n      register_provider(\"foo\")\n\n      environment = isolated_environment do |env|\n        env.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"base\"\n  config.vm.box_version = \"~> 1.2\"\nend\nVF\n\n        env.box3(\"base\", \"1.0\", :foo, vagrantfile: <<-VF)\nVagrant.configure(\"2\") do |config|\n  config.ssh.port = 100\nend\nVF\n\n        env.box3(\"base\", \"1.5\", :foo, vagrantfile: <<-VF)\nVagrant.configure(\"2\") do |config|\n  config.ssh.port = 200\nend\nVF\n      end\n\n      env = environment.create_vagrant_env\n      machine = env.machine(:default, :foo)\n      expect(machine.config.ssh.port).to eq(200)\n    end\n\n    it \"should load the provider override if set\" do\n      register_provider(\"bar\")\n      register_provider(\"foo\")\n\n      isolated_env = isolated_environment do |e|\n        e.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"foo\"\n\n  config.vm.provider :foo do |_, c|\n    c.vm.box = \"bar\"\n  end\nend\nVF\n      end\n\n      env = isolated_env.create_vagrant_env\n      foo_vm = env.machine(:default, :foo)\n      bar_vm = env.machine(:default, :bar)\n      expect(foo_vm.config.vm.box).to eq(\"bar\")\n      expect(bar_vm.config.vm.box).to eq(\"foo\")\n    end\n\n    it \"should reload the cache if refresh is set\" do\n      # Create a provider\n      foo_provider = register_provider(\"foo\")\n\n      # Create the configuration\n      isolated_env = isolated_environment do |e|\n        e.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"base\"\nend\nVF\n\n        e.box3(\"base\", \"1.0\", :foo)\n      end\n\n      env = isolated_env.create_vagrant_env\n      vm1 = env.machine(:default, :foo)\n      vm2 = env.machine(:default, :foo, true)\n      vm3 = env.machine(:default, :foo)\n\n      expect(vm1).not_to eql(vm2)\n      expect(vm2).to eql(vm3)\n    end\n\n    it \"should raise an error if the VM is not found\" do\n      expect { instance.machine(\"i-definitely-dont-exist\", :virtualbox) }.\n        to raise_error(Vagrant::Errors::MachineNotFound)\n    end\n\n    it \"should raise an error if the provider is not found\" do\n      expect { instance.machine(:default, :lol_no) }.\n        to raise_error(Vagrant::Errors::ProviderNotFound)\n    end\n  end\n\n  describe \"#machine_index\" do\n    it \"returns a machine index\" do\n      expect(subject.machine_index).to be_kind_of(Vagrant::MachineIndex)\n    end\n\n    it \"caches the result\" do\n      result = subject.machine_index\n      expect(subject.machine_index).to equal(result)\n    end\n\n    it \"uses a directory within the home directory by default\" do\n      klass = double(\"machine_index\")\n      stub_const(\"Vagrant::MachineIndex\", klass)\n      # Need to stub the mappers here since the Vagrant::MachineIndex module is getting\n      # stubbed as a double. This is causing the mapper definition to fail on account\n      # of the Input to the mapper not being a module. The mapper has no impact on this \n      # test otherwise.\n      stub_mapper = Class.new {}\n      stub_const(\"VagrantPlugins::CommandServe::Mappers\", stub_mapper)\n\n      expect(klass).to receive(:new).with(any_args) do |path|\n        expect(path.to_s.start_with?(subject.home_path.to_s)).to be(true)\n        true\n      end\n\n      subject.machine_index\n    end\n  end\n\n  describe \"active machines\" do\n    it \"should be empty if there is no root path\" do\n      Dir.mktmpdir(\"vagrant-test-env-no-root-path\") do |temp_dir|\n        instance = described_class.new(cwd: temp_dir)\n        expect(instance.active_machines).to be_empty\n      end\n    end\n\n    it \"should be empty if the machines folder doesn't exist\" do\n      folder = instance.local_data_path.join(\"machines\")\n      expect(folder).not_to be_exist\n\n      expect(instance.active_machines).to be_empty\n    end\n\n    it \"should return the name and provider of active machines\" do\n      machines = instance.local_data_path.join(\"machines\")\n\n      # Valid machine, with \"foo\" and virtualbox\n      machine_foo = machines.join(\"foo/virtualbox\")\n      machine_foo.mkpath\n      machine_foo.join(\"id\").open(\"w+\") { |f| f.write(\"\") }\n\n      # Invalid machine (no ID)\n      machine_bar = machines.join(\"bar/virtualbox\")\n      machine_bar.mkpath\n\n      expect(instance.active_machines).to eq([[:foo, :virtualbox]])\n    end\n  end\n\n  describe \"batching\" do\n    let(:batch) do\n      double(\"batch\") do |b|\n        allow(b).to receive(:run)\n      end\n    end\n\n    context \"without the disabling env var\" do\n      it \"should run without disabling parallelization\" do\n        with_temp_env(\"VAGRANT_NO_PARALLEL\" => nil) do\n          expect(Vagrant::BatchAction).to receive(:new).with(true).and_return(batch)\n          expect(batch).to receive(:run)\n\n          instance.batch {}\n        end\n      end\n\n      it \"should run with disabling parallelization if explicit\" do\n        with_temp_env(\"VAGRANT_NO_PARALLEL\" => nil) do\n          expect(Vagrant::BatchAction).to receive(:new).with(false).and_return(batch)\n          expect(batch).to receive(:run)\n\n          instance.batch(false) {}\n        end\n      end\n    end\n\n    context \"with the disabling env var\" do\n      it \"should run with disabling parallelization\" do\n        with_temp_env(\"VAGRANT_NO_PARALLEL\" => \"yes\") do\n          expect(Vagrant::BatchAction).to receive(:new).with(false).and_return(batch)\n          expect(batch).to receive(:run)\n\n          instance.batch {}\n        end\n      end\n    end\n  end\n\n  describe \"current working directory\" do\n    it \"is the cwd by default\" do\n      Dir.mktmpdir(\"vagrant-test-env-cwd-default\") do |temp_dir|\n        Dir.chdir(temp_dir) do\n          with_temp_env(\"VAGRANT_CWD\" => nil) do\n            expect(described_class.new.cwd).to eq(Pathname.new(Dir.pwd))\n          end\n        end\n      end\n    end\n\n    it \"is set to the cwd given\" do\n      Dir.mktmpdir(\"vagrant-test-env-set-cwd\") do |directory|\n        instance = described_class.new(cwd: directory)\n        expect(instance.cwd).to eq(Pathname.new(directory))\n      end\n    end\n\n    it \"is set to the environmental variable VAGRANT_CWD\" do\n      Dir.mktmpdir(\"vagrant-test-env-set-vagrant-cwd\") do |directory|\n        instance = with_temp_env(\"VAGRANT_CWD\" => directory) do\n          described_class.new\n        end\n\n        expect(instance.cwd).to eq(Pathname.new(directory))\n      end\n    end\n\n    it \"raises an exception if the CWD doesn't exist\" do\n      expect { described_class.new(cwd: \"doesntexist\") }.\n        to raise_error(Vagrant::Errors::EnvironmentNonExistentCWD)\n    end\n  end\n\n  describe \"default provider\" do\n    let(:plugin_providers) { {} }\n\n    before do\n      m = Vagrant.plugin(\"2\").manager\n      allow(m).to receive(:providers).and_return(plugin_providers)\n      allow_any_instance_of(described_class).to receive(:process_configured_plugins)\n    end\n\n    it \"is the highest matching usable provider\" do\n      plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }]\n      plugin_providers[:baz] = [provider_usable_class(true), { priority: 2 }]\n      plugin_providers[:boom] = [provider_usable_class(true), { priority: 3 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => nil,\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect(subject.default_provider).to eq(:bar)\n      end\n    end\n\n    it \"is the highest matching usable provider that is defaultable\" do\n      plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }]\n      plugin_providers[:bar] = [\n        provider_usable_class(true), { defaultable: false, priority: 7 }]\n      plugin_providers[:baz] = [provider_usable_class(true), { priority: 2 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => nil,\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect(subject.default_provider).to eq(:foo)\n      end\n    end\n\n    it \"is the highest matching usable provider that isn't excluded\" do\n      plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }]\n      plugin_providers[:baz] = [provider_usable_class(true), { priority: 2 }]\n      plugin_providers[:boom] = [provider_usable_class(true), { priority: 3 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => nil,\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect(subject.default_provider(exclude: [:bar, :foo])).to eq(:boom)\n      end\n    end\n\n    it \"is the default provider set if usable\" do\n      plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }]\n      plugin_providers[:baz] = [provider_usable_class(true), { priority: 2 }]\n      plugin_providers[:boom] = [provider_usable_class(true), { priority: 3 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => \"baz\",\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect(subject.default_provider).to eq(:baz)\n      end\n    end\n\n    it \"is the default provider set even if unusable\" do\n      plugin_providers[:baz] = [provider_usable_class(false), { priority: 5 }]\n      plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => \"baz\",\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect(subject.default_provider).to eq(:baz)\n      end\n    end\n\n    it \"is the usable despite default if not forced\" do\n      plugin_providers[:baz] = [provider_usable_class(false), { priority: 5 }]\n      plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => \"baz\",\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect(subject.default_provider(force_default: false)).to eq(:bar)\n      end\n    end\n\n    it \"prefers the default even if not forced\" do\n      plugin_providers[:baz] = [provider_usable_class(true), { priority: 5 }]\n      plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => \"baz\",\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect(subject.default_provider(force_default: false)).to eq(:baz)\n      end\n    end\n\n    it \"uses the first usable provider that isn't the default if excluded\" do\n      plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }]\n      plugin_providers[:baz] = [provider_usable_class(true), { priority: 8 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => \"baz\",\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect(subject.default_provider(\n          exclude: [:baz], force_default: false)).to eq(:bar)\n      end\n    end\n\n    it \"raise an error if nothing else is usable\" do\n      plugin_providers[:foo] = [provider_usable_class(false), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(false), { priority: 5 }]\n      plugin_providers[:baz] = [provider_usable_class(false), { priority: 5 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => nil,\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect { subject.default_provider }.to raise_error(\n          Vagrant::Errors::NoDefaultProvider)\n      end\n    end\n\n    it \"is the provider in the Vagrantfile that is preferred and usable\" do\n      subject.vagrantfile.config.vm.provider \"foo\"\n      subject.vagrantfile.config.vm.provider \"bar\"\n      subject.vagrantfile.config.vm.finalize!\n\n      plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }]\n      plugin_providers[:baz] = [provider_usable_class(true), { priority: 2 }]\n      plugin_providers[:boom] = [provider_usable_class(true), { priority: 3 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => nil,\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => 'baz,bar') do\n        expect(subject.default_provider).to eq(:bar)\n      end\n    end\n\n    it \"is the provider in the Vagrantfile that is usable\" do\n      subject.vagrantfile.config.vm.provider \"foo\"\n      subject.vagrantfile.config.vm.provider \"bar\"\n      subject.vagrantfile.config.vm.finalize!\n\n      plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }]\n      plugin_providers[:baz] = [provider_usable_class(true), { priority: 2 }]\n      plugin_providers[:boom] = [provider_usable_class(true), { priority: 3 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => nil,\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect(subject.default_provider).to eq(:foo)\n      end\n    end\n\n    it \"is the provider in the Vagrantfile that is usable even if only one specified (1)\" do\n      subject.vagrantfile.config.vm.provider \"foo\"\n      subject.vagrantfile.config.vm.finalize!\n\n      plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => nil,\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect(subject.default_provider).to eq(:foo)\n      end\n    end\n\n    it \"is the provider in the Vagrantfile that is usable even if only one specified (2)\" do\n      subject.vagrantfile.config.vm.provider \"bar\"\n      subject.vagrantfile.config.vm.finalize!\n\n      plugin_providers[:foo] = [provider_usable_class(true), { priority: 7 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 5 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => nil,\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect(subject.default_provider).to eq(:bar)\n      end\n    end\n\n    it \"is the preferred usable provider outside the Vagrantfile\" do\n      subject.vagrantfile.config.vm.provider \"foo\"\n      subject.vagrantfile.config.vm.finalize!\n\n      plugin_providers[:foo] = [provider_usable_class(false), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }]\n      plugin_providers[:baz] = [provider_usable_class(true), { priority: 2 }]\n      plugin_providers[:boom] = [provider_usable_class(true), { priority: 3 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => nil,\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => 'boom,baz') do\n        expect(subject.default_provider).to eq(:boom)\n      end\n    end\n\n    it \"is the highest usable provider outside the Vagrantfile\" do\n      subject.vagrantfile.config.vm.provider \"foo\"\n      subject.vagrantfile.config.vm.finalize!\n\n      plugin_providers[:foo] = [provider_usable_class(false), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }]\n      plugin_providers[:baz] = [provider_usable_class(true), { priority: 2 }]\n      plugin_providers[:boom] = [provider_usable_class(true), { priority: 3 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => nil,\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect(subject.default_provider).to eq(:bar)\n      end\n    end\n\n    it \"is the provider in the Vagrantfile that is usable for a machine\" do\n      subject.vagrantfile.config.vm.provider \"foo\"\n      subject.vagrantfile.config.vm.define \"sub\" do |v|\n        v.vm.provider \"bar\"\n      end\n      subject.vagrantfile.config.vm.finalize!\n\n      plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }]\n      plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }]\n      plugin_providers[:baz] = [provider_usable_class(true), { priority: 2 }]\n      plugin_providers[:boom] = [provider_usable_class(true), { priority: 3 }]\n\n      with_temp_env(\"VAGRANT_DEFAULT_PROVIDER\" => nil,\n                    \"VAGRANT_PREFERRED_PROVIDERS\" => nil) do\n        expect(subject.default_provider(machine: :sub)).to eq(:bar)\n      end\n    end\n  end\n\n  describe \"local data path\" do\n    it \"is set to the proper default\" do\n      default = instance.root_path.join(described_class::DEFAULT_LOCAL_DATA)\n      expect(instance.local_data_path).to eq(default)\n    end\n\n    it \"is expanded relative to the cwd\" do\n      Dir.mktmpdir(\"vagrant-test-env-relative-cwd\") do |temp_dir|\n        Dir.chdir(temp_dir) do\n          instance = described_class.new(local_data_path: \"foo\")\n          expect(instance.local_data_path).to eq(instance.cwd.join(\"foo\"))\n        end\n      end\n    end\n\n    it \"is set to the given value\" do\n      Dir.mktmpdir(\"vagrant-test-env-set-given\") do |dir|\n        instance = described_class.new(local_data_path: dir)\n        expect(instance.local_data_path.to_s).to eq(dir)\n      end\n    end\n\n    context \"with environmental variable VAGRANT_DOTFILE_PATH set to the empty string\" do\n      it \"is set to the default, from the work directory\" do\n        with_temp_env(\"VAGRANT_DOTFILE_PATH\" => \"\") do\n          instance = env.create_vagrant_env\n          expect(instance.cwd).to eq(env.workdir)\n          expect(instance.local_data_path.to_s).to eq(File.join(env.workdir, \".vagrant\"))\n        end\n      end\n\n      it \"is set to the default, from a sub-directory of the work directory\" do\n        Dir.mktmpdir(\"sub-directory\", env.workdir) do |temp_dir|\n          with_temp_env(\"VAGRANT_DOTFILE_PATH\" => \"\") do\n            instance = env.create_vagrant_env(cwd: temp_dir)\n            expect(instance.cwd.to_s).to eq(temp_dir)\n            expect(instance.local_data_path.to_s).to eq(File.join(env.workdir, \".vagrant\"))\n          end\n        end\n      end\n    end\n\n    context \"with environmental variable VAGRANT_DOTFILE_PATH set to an absolute path\" do\n      it \"is set to VAGRANT_DOTFILE_PATH from the work directory\" do\n        Dir.mktmpdir(\"sub-directory\", env.workdir) do |temp_dir|\n          dotfile_path = File.join(temp_dir, \".vagrant-custom\")\n\n          with_temp_env(\"VAGRANT_DOTFILE_PATH\" => dotfile_path) do\n            instance = env.create_vagrant_env\n            expect(instance.cwd).to eq(env.workdir)\n            expect(instance.local_data_path.to_s).to eq(dotfile_path)\n          end\n        end\n      end\n\n      it \"is set to VAGRANT_DOTFILE_PATH from a sub-directory of the work directory\" do\n        Dir.mktmpdir(\"sub-directory\", env.workdir) do |temp_dir|\n          dotfile_path = File.join(temp_dir, \".vagrant-custom\")\n\n          with_temp_env(\"VAGRANT_DOTFILE_PATH\" => dotfile_path) do\n            instance = env.create_vagrant_env(cwd: temp_dir)\n            expect(instance.cwd.to_s).to eq(temp_dir)\n            expect(instance.local_data_path.to_s).to eq(dotfile_path)\n          end\n        end\n      end\n    end\n\n    context \"with environmental variable VAGRANT_DOTFILE_PATH set to a relative path\" do\n      it \"is set relative to the the work directory, from the work directory\" do\n        Dir.mktmpdir(\"sub-directory\", env.workdir) do |temp_dir|\n          with_temp_env(\"VAGRANT_DOTFILE_PATH\" => \".vagrant-custom\") do\n            instance = env.create_vagrant_env\n            expect(instance.cwd).to eq(env.workdir)\n            expect(instance.local_data_path.to_s).to eq(File.join(env.workdir, \".vagrant-custom\"))\n          end\n        end\n      end\n\n      it \"is set relative to the the work directory, from a sub-directory of the work directory\" do\n        Dir.mktmpdir(\"sub-directory\", env.workdir) do |temp_dir|\n          with_temp_env(\"VAGRANT_DOTFILE_PATH\" => \".vagrant-custom\") do\n            instance = env.create_vagrant_env(cwd: temp_dir)\n            expect(instance.cwd.to_s).to eq(temp_dir)\n            expect(instance.local_data_path.to_s).to eq(File.join(env.workdir, \".vagrant-custom\"))\n          end\n        end\n      end\n\n      it \"is set to the empty string when there is no valid work directory\" do\n        Dir.mktmpdir(\"out-of-tree-directory\") do |temp_dir|\n          with_temp_env(\"VAGRANT_DOTFILE_PATH\" => \".vagrant-custom\") do\n            instance = env.create_vagrant_env(cwd: temp_dir)\n            expect(instance.cwd.to_s).to eq(temp_dir)\n            expect(instance.local_data_path.to_s).to eq(\"\")\n          end\n        end\n      end\n    end\n\n    context \"with environmental variable VAGRANT_DOTFILE_PATH set with tilde\" do\n      it \"is set relative to the user's home directory\" do\n        with_temp_env(\"VAGRANT_DOTFILE_PATH\" => \"~/.vagrant\") do\n          instance = env.create_vagrant_env\n          expect(instance.cwd).to eq(env.workdir)\n          expect(instance.local_data_path.to_s).to eq(File.join(Dir.home, \".vagrant\"))\n        end\n      end\n    end\n\n    describe \"upgrading V1 dotfiles\" do\n      let(:v1_dotfile_tempfile) do\n        Tempfile.new(\"vagrant-upgrade-dotfile\").tap do |f|\n          f.close\n        end\n      end\n\n      let(:v1_dotfile)          { Pathname.new(v1_dotfile_tempfile.path) }\n      let(:local_data_path)     { v1_dotfile_tempfile.path }\n      let(:instance) { described_class.new(local_data_path: local_data_path) }\n\n      after do\n        FileUtils.rm_rf(local_data_path)\n      end\n\n      it \"should be fine if dotfile is empty\" do\n        v1_dotfile.open(\"w+\") do |f|\n          f.write(\"\")\n        end\n\n        expect { instance }.to_not raise_error\n      end\n\n      it \"should upgrade all active VMs\" do\n        active_vms = {\n          \"foo\" => \"foo_id\",\n          \"bar\" => \"bar_id\"\n        }\n\n        v1_dotfile.open(\"w+\") do |f|\n          f.write(JSON.dump({\n            \"active\" => active_vms\n          }))\n        end\n\n        expect { instance }.to_not raise_error\n\n        local_data_pathname = Pathname.new(local_data_path)\n        foo_id_file = local_data_pathname.join(\"machines/foo/virtualbox/id\")\n        expect(foo_id_file).to be_file\n        expect(foo_id_file.read).to eq(\"foo_id\")\n\n        bar_id_file = local_data_pathname.join(\"machines/bar/virtualbox/id\")\n        expect(bar_id_file).to be_file\n        expect(bar_id_file.read).to eq(\"bar_id\")\n      end\n\n      it \"should raise an error if invalid JSON\" do\n        v1_dotfile.open(\"w+\") do |f|\n          f.write(\"bad\")\n        end\n\n        expect { instance }.\n          to raise_error(Vagrant::Errors::DotfileUpgradeJSONError)\n      end\n    end\n  end\n\n  describe \"copying the private SSH key\" do\n    it \"copies the SSH key into the home directory\" do\n      env = isolated_environment\n      instance = described_class.new(home_path: env.homedir)\n\n      pk = env.homedir.join(\"insecure_private_key\")\n      expect(pk).to be_exist\n\n      if !Vagrant::Util::Platform.windows?\n        expect(Vagrant::Util::FileMode.from_octal(pk.stat.mode)).to eq(\"600\")\n      end\n    end\n  end\n\n  it \"has a box collection pointed to the proper directory\" do\n    collection = instance.boxes\n    expect(collection).to be_kind_of(Vagrant::BoxCollection)\n    expect(collection.directory).to eq(instance.boxes_path)\n\n    # Reach into some internal state here but not sure how else\n    # to test this at the moment.\n    expect(collection.instance_variable_get(:@hook)).\n      to eq(instance.method(:hook))\n  end\n\n  describe \"action runner\" do\n    it \"has an action runner\" do\n      expect(instance.action_runner).to be_kind_of(Vagrant::Action::Runner)\n    end\n\n    it \"has a `ui` in the globals\" do\n      result = nil\n      callable = lambda { |env| result = env[:ui] }\n\n      instance.action_runner.run(callable)\n      expect(result).to eql(instance.ui)\n    end\n  end\n\n  describe \"#pushes\" do\n    it \"returns the pushes from the Vagrantfile config\" do\n      environment = isolated_environment do |env|\n        env.vagrantfile(<<-VF.gsub(/^ {10}/, ''))\n          Vagrant.configure(\"2\") do |config|\n            config.push.define \"noop\"\n          end\n        VF\n      end\n\n      env = environment.create_vagrant_env\n      expect(env.pushes).to eq([:noop])\n    end\n  end\n\n  describe \"#push\" do\n    let(:push_class) do\n      Class.new(Vagrant.plugin(\"2\", :push)) do\n        def self.pushed?\n          !!class_variable_get(:@@pushed)\n        end\n\n        def push\n          self.class.class_variable_set(:@@pushed, true)\n        end\n      end\n    end\n\n    it \"raises an exception when the push does not exist\" do\n      expect { instance.push(\"lolwatbacon\") }\n        .to raise_error(Vagrant::Errors::PushStrategyNotDefined)\n    end\n\n    it \"raises an exception if the strategy does not exist\" do\n      environment = isolated_environment do |env|\n        env.vagrantfile(<<-VF.gsub(/^ {10}/, ''))\n          Vagrant.configure(\"2\") do |config|\n            config.push.define \"lolwatbacon\"\n          end\n        VF\n      end\n\n      env = environment.create_vagrant_env\n      expect { env.push(\"lolwatbacon\") }\n        .to raise_error(Vagrant::Errors::PushStrategyNotLoaded)\n    end\n\n    it \"executes the push action\" do\n      register_plugin(\"2\") do |plugin|\n        plugin.name \"foo\"\n\n        plugin.push(:foo) do\n          push_class\n        end\n      end\n\n      environment = isolated_environment do |env|\n        env.vagrantfile(<<-VF.gsub(/^ {10}/, ''))\n          Vagrant.configure(\"2\") do |config|\n            config.push.define \"foo\"\n          end\n        VF\n      end\n\n      env = environment.create_vagrant_env\n      env.push(\"foo\")\n      expect(push_class.pushed?).to be(true)\n    end\n  end\n\n  describe \"#hook\" do\n    it \"should call the action runner with the proper hook\" do\n      hook_name = :foo\n\n      expect(instance.action_runner).to receive(:run).with(any_args) { |callable, env|\n        expect(env[:action_name]).to eq(hook_name)\n      }\n\n      instance.hook(hook_name)\n    end\n\n    it \"should return the result of the action runner run\" do\n      expect(instance.action_runner).to receive(:run).and_return(:foo)\n\n      expect(instance.hook(:bar)).to eq(:foo)\n    end\n\n    it \"should allow passing in a custom action runner\" do\n      expect(instance.action_runner).not_to receive(:run)\n      other_runner = double(\"runner\")\n      expect(other_runner).to receive(:run).and_return(:foo)\n\n      expect(instance.hook(:bar, runner: other_runner)).to eq(:foo)\n    end\n\n    it \"should allow passing in custom data\" do\n      expect(instance.action_runner).to receive(:run).with(any_args) { |callable, env|\n        expect(env[:foo]).to eq(:bar)\n      }\n\n      instance.hook(:foo, foo: :bar)\n    end\n\n    it \"should allow passing a custom callable\" do\n      expect(instance.action_runner).to receive(:run).with(any_args) { |callable, env|\n        expect(callable).to eq(:what)\n      }\n\n      instance.hook(:foo, callable: :what)\n    end\n  end\n\n  describe \"primary machine name\" do\n    it \"should be the only machine if not a multi-machine environment\" do\n      expect(instance.primary_machine_name).to eq(instance.machine_names.first)\n    end\n\n    it \"should be the machine marked as the primary\" do\n      environment = isolated_environment do |env|\n        env.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"base\"\n  config.vm.define :foo\n  config.vm.define :bar, primary: true\nend\nVF\n\n        env.box3(\"base\", \"1.0\", :virtualbox)\n      end\n\n      env = environment.create_vagrant_env\n      expect(env.primary_machine_name).to eq(:bar)\n    end\n\n    it \"should be nil if no primary is specified in a multi-machine environment\" do\n      environment = isolated_environment do |env|\n        env.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"base\"\n  config.vm.define :foo\n  config.vm.define :bar\nend\nVF\n\n        env.box3(\"base\", \"1.0\", :virtualbox)\n      end\n\n      env = environment.create_vagrant_env\n      expect(env.primary_machine_name).to be_nil\n    end\n  end\n\n  describe \"loading configuration\" do\n    it \"should load global configuration\" do\n      environment = isolated_environment do |env|\n        env.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.ssh.port = 200\nend\nVF\n      end\n\n      env = environment.create_vagrant_env\n      expect(env.vagrantfile.config.ssh.port).to eq(200)\n    end\n\n    it \"should load from a custom Vagrantfile\" do\n      environment = isolated_environment do |env|\n        env.file(\"non_standard_name\", <<-VF)\nVagrant.configure(\"2\") do |config|\n  config.ssh.port = 200\nend\nVF\n      end\n\n      env = environment.create_vagrant_env(vagrantfile_name: \"non_standard_name\")\n      expect(env.vagrantfile.config.ssh.port).to eq(200)\n    end\n\n    it \"should load from a custom Vagrantfile specified by env var\" do\n      environment = isolated_environment do |env|\n        env.file(\"some_other_name\", <<-VF)\nVagrant.configure(\"2\") do |config|\n  config.ssh.port = 400\nend\nVF\n      end\n\n      env = with_temp_env(\"VAGRANT_VAGRANTFILE\" => \"some_other_name\") do\n        environment.create_vagrant_env\n      end\n\n      expect(env.vagrantfile.config.ssh.port).to eq(400)\n    end\n  end\n\n  describe \"ui\" do\n    it \"should be a silent UI by default\" do\n      expect(described_class.new.ui).to be_kind_of(Vagrant::UI::Silent)\n    end\n\n    it \"should be a UI given in the constructor\" do\n      # Create a custom UI for our test\n      class CustomUI < Vagrant::UI::Interface; end\n\n      instance = described_class.new(ui_class: CustomUI)\n      expect(instance.ui).to be_kind_of(CustomUI)\n    end\n  end\n\n  describe \"#unload\" do\n    it \"should run the unload hook\" do\n      expect(instance).to receive(:hook).with(:environment_unload).once\n      instance.unload\n    end\n  end\n\n  describe \"getting machine names\" do\n    it \"should return the default machine if no multi-VM is used\" do\n      # Create the config\n      isolated_env = isolated_environment do |e|\n        e.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\nend\nVF\n      end\n\n      env = isolated_env.create_vagrant_env\n      expect(env.machine_names).to eq([:default])\n    end\n\n    it \"should return the machine names in order\" do\n      # Create the config\n      isolated_env = isolated_environment do |e|\n        e.vagrantfile(<<-VF)\nVagrant.configure(\"2\") do |config|\n  config.vm.define \"foo\"\n  config.vm.define \"bar\"\nend\nVF\n      end\n\n      env = isolated_env.create_vagrant_env\n      expect(env.machine_names).to eq([:foo, :bar])\n    end\n  end\n\n  describe \"guess_provider\" do\n    before { allow_any_instance_of(described_class).to receive(:process_configured_plugins) }\n\n    it \"should return the default provider by default\" do\n      expect(subject).to receive(:default_provider).and_return(\"default_provider\")\n      expect(subject.send(:guess_provider)).to eq(\"default_provider\")\n    end\n\n    context \"when provider is defined via command line argument\" do\n      before { stub_const(\"ARGV\", argv) }\n\n      context \"when provider is given as single argument\" do\n        let(:argv) { [\"--provider=single_arg\"] }\n\n        it \"should return the provider name\" do\n          expect(subject.send(:guess_provider)).to eq(:single_arg)\n        end\n      end\n\n      context \"when provider is given as two arguments\" do\n        let(:argv) { [\"--provider\", \"double_arg\"] }\n\n        it \"should return the provider name\" do\n          expect(subject.send(:guess_provider)).to eq(:double_arg)\n        end\n      end\n    end\n\n    context \"when no default provider is available\" do\n      before {\n        expect(subject).to receive(:default_provider).\n          and_raise(Vagrant::Errors::NoDefaultProvider) }\n\n      it \"should return a nil value\" do\n        expect(subject.send(:guess_provider)).to be_nil\n      end\n    end\n  end\n\n  describe \"#find_configured_plugins\" do\n    before do\n      allow_any_instance_of(described_class).to receive(:guess_provider).and_return(:dummy)\n      allow_any_instance_of(described_class).to receive(:process_configured_plugins)\n    end\n\n    it \"should find no plugins when no plugins are configured\" do\n      expect(subject.send(:find_configured_plugins)).to be_empty\n    end\n\n    context \"when plugins are defined in the Vagrantfile\" do\n      before do\n        env.vagrantfile <<-VF\n          Vagrant.configure(\"2\") do |config|\n            config.vagrant.plugins = \"vagrant-plugin\"\n          end\n          VF\n      end\n\n      it \"should return the vagrant-plugin\" do\n        expect(subject.send(:find_configured_plugins).keys).to include(\"vagrant-plugin\")\n      end\n    end\n\n    context \"when plugins are defined in the Vagrantfile of a box\" do\n      before do\n        env.box3(\"foo\", \"1.0\", :dummy, vagrantfile: <<-VF)\n          Vagrant.configure(\"2\") do |config|\n            config.vagrant.plugins = \"vagrant-plugin\"\n          end\n        VF\n        env.vagrantfile <<-VF\n          Vagrant.configure(\"2\") do |config|\n            config.vm.box = \"foo\"\n          end\n        VF\n      end\n\n      it \"should return the vagrant-plugin\" do\n        expect(subject.send(:find_configured_plugins).keys).to include(\"vagrant-plugin\")\n      end\n    end\n\n    context \"when the box does not match the provider\" do\n      before do\n        env.box3(\"foo\", \"1.0\", :other, vagrantfile: <<-VF)\n          Vagrant.configure(\"2\") do |config|\n            config.vagrant.plugins = \"vagrant-plugin\"\n          end\n        VF\n        env.vagrantfile <<-VF\n          Vagrant.configure(\"2\") do |config|\n            config.vm.box = \"foo\"\n          end\n        VF\n      end\n\n      it \"should not return the vagrant-plugin\" do\n        expect(subject.send(:find_configured_plugins).keys).not_to include(\"vagrant-plugin\")\n      end\n    end\n  end\n\n  describe \"#process_configured_plugins\" do\n    let(:env) do\n      isolated_environment.tap do |e|\n        e.box3(\"base\", \"1.0\", :virtualbox)\n        e.vagrantfile(vagrantfile)\n      end\n    end\n\n    let(:vagrantfile) do\n      'Vagrant.configure(\"2\"){ |config| config.vm.box = \"base\" }'\n    end\n\n    let(:plugin_manager) {\n      double(\"plugin_manager\", installed_plugins: installed_plugins, local_file: local_file)\n    }\n\n    let(:installed_plugins) { {} }\n    let(:local_file) { double(\"local_file\", installed_plugins: local_installed_plugins) }\n    let(:local_installed_plugins) { {} }\n\n    before do\n      allow(Vagrant::Plugin::Manager).to receive(:instance).and_return(plugin_manager)\n      allow(plugin_manager).to receive(:globalize!)\n      allow(plugin_manager).to receive(:localize!)\n      allow(plugin_manager).to receive(:load_plugins)\n    end\n\n    context \"when local data directory does not exist\" do\n      let(:local_file) { nil }\n\n      it \"should properly return empty result\" do\n        expect(instance.send(:process_configured_plugins)).to be_empty\n      end\n    end\n\n    context \"plugins are disabled\" do\n      before{ allow(Vagrant).to receive(:plugins_enabled?).and_return(false) }\n\n      it \"should return empty result\" do\n        expect(instance.send(:process_configured_plugins)).to be_nil\n      end\n    end\n\n    context \"when vagrant is invalid\" do\n      let(:vagrantfile) { 'Vagrant.configure(\"2\"){ |config| config.vagrant.bad_key = true }' }\n\n      it \"should raise a configuration error\" do\n        expect { instance.send(:process_configured_plugins) }.to raise_error(Vagrant::Errors::ConfigInvalid)\n      end\n    end\n\n    context \"with local plugins defined\" do\n      let(:vagrantfile) { 'Vagrant.configure(\"2\"){ |config| config.vagrant.plugins = \"vagrant\" }' }\n      let(:installed_plugins) { {\"vagrant\" => true} }\n\n      context \"with plugin already installed\" do\n\n        it \"should not attempt to install a plugin\" do\n          expect(plugin_manager).not_to receive(:install_plugin)\n          expect(instance.send(:process_configured_plugins)).to eq(local_installed_plugins)\n        end\n      end\n\n      context \"without plugin installed\" do\n\n        before { allow(instance).to receive(:exit) }\n\n        it \"should prompt user before installation\" do\n          expect(instance.ui).to receive(:ask).and_return(\"n\")\n          expect(plugin_manager).to receive(:installed_plugins).and_return({})\n          expect { instance.send(:process_configured_plugins) }.to raise_error(Vagrant::Errors::PluginMissingLocalError)\n        end\n\n        it \"should install plugin\" do\n          expect(instance.ui).to receive(:ask).and_return(\"y\")\n          expect(plugin_manager).to receive(:installed_plugins).and_return({})\n          expect(plugin_manager).to receive(:install_plugin).and_return(double(\"spec\", \"name\" => \"vagrant\", \"version\" => \"1\"))\n          instance.send(:process_configured_plugins)\n        end\n\n        it \"should exit after install\" do\n          expect(instance.ui).to receive(:ask).and_return(\"y\")\n          expect(plugin_manager).to receive(:installed_plugins).and_return({})\n          expect(plugin_manager).to receive(:install_plugin).and_return(double(\"spec\", \"name\" => \"vagrant\", \"version\" => \"1\"))\n          expect(instance).to receive(:exit)\n          instance.send(:process_configured_plugins)\n        end\n      end\n    end\n  end\n\n  describe \"#setup_local_data_path\" do\n    before do\n      allow(FileUtils).to receive(:mkdir_p).and_call_original\n      allow(FileUtils).to receive(:cp).and_call_original\n    end\n\n    it \"should create an rgloader path\" do\n      expect(FileUtils).to receive(:mkdir_p).with(/(?!home)rgloader/)\n      instance\n    end\n\n    it \"should write the rgloader file\" do\n      expect(FileUtils).to receive(:cp).with(anything, /(?!home)rgloader.*rb$/)\n      instance\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/errors_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\ndescribe Vagrant::Errors::VagrantError do\n  describe \"subclass with error key\" do\n    let(:klass) do\n      Class.new(described_class) do\n        error_key(\"test_key\")\n      end\n    end\n\n    subject { klass.new }\n\n    it \"should use the translation for the message\" do\n      expect(subject.to_s).to eq(\"test value\")\n    end\n\n    describe '#status_code' do\n      subject { super().status_code }\n      it { should eq(1) }\n    end\n  end\n\n  describe \"passing error key through options\" do\n    subject { described_class.new(_key: \"test_key\") }\n\n    it \"should use the translation for the message\" do\n      expect(subject.to_s).to eq(\"test value\")\n    end\n  end\n\n  describe \"subclass with error message\" do\n    let(:klass) do\n      Class.new(described_class) do\n        error_message(\"foo\")\n      end\n    end\n\n    subject { klass.new(data: \"yep\") }\n\n    it \"should use the translation for the message\" do\n      expect(subject.to_s).to eq(\"foo\")\n    end\n\n    it \"should expose translation keys to the user\" do\n      expect(subject.extra_data.length).to eql(1)\n      expect(subject.extra_data).to have_key(:data)\n      expect(subject.extra_data[:data]).to eql(\"yep\")\n    end\n\n    it \"should use a symbol initializer as a key\" do\n      subject = klass.new(:test_key)\n      expect(subject.extra_data).to be_empty\n      expect(subject.to_s).to eql(\"test value\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/guest_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\ndescribe Vagrant::Guest do\n  include_context \"capability_helpers\"\n\n  let(:capabilities) { {} }\n  let(:guests)  { {} }\n  let(:machine) do\n    double(\"machine\").tap do |m|\n      allow(m).to receive(:inspect).and_return(\"machine\")\n      allow(m).to receive(:config).and_return(double(\"config\"))\n      allow(m.config).to receive(:vm).and_return(double(\"vm_config\"))\n      allow(m.config.vm).to receive(:guest).and_return(nil)\n    end\n  end\n\n  subject { described_class.new(machine, guests, capabilities) }\n\n  describe \"#capability\" do\n    before(:each) do\n      guests[:foo] = [detect_class(true), nil]\n      capabilities[:foo] = {\n        foo_cap: cap_instance(:foo_cap),\n        corrupt_cap: cap_instance(:corrupt_cap, corrupt: true),\n      }\n\n      subject.detect!\n    end\n\n    it \"executes existing capabilities\" do\n      expect { subject.capability(:foo_cap) }.\n        to raise_error(RuntimeError, \"cap: foo_cap [machine]\")\n    end\n\n    it \"raises user-friendly error for non-existing caps\" do\n      expect { subject.capability(:what_cap) }.\n        to raise_error(Vagrant::Errors::GuestCapabilityNotFound)\n    end\n\n    it \"raises a user-friendly error for corrupt caps\" do\n      expect { subject.capability(:corrupt_cap) }.\n        to raise_error(Vagrant::Errors::GuestCapabilityInvalid)\n    end\n  end\n\n  describe \"#detect!\" do\n    it \"auto-detects if no explicit guest name given\" do\n      allow(machine.config.vm).to receive(:guest).and_return(nil)\n      expect(subject).to receive(:initialize_capabilities!).\n        with(nil, guests, capabilities, machine)\n\n      subject.detect!\n    end\n\n    it \"uses the explicit guest name if specified\" do\n      allow(machine.config.vm).to receive(:guest).and_return(:foo)\n      expect(subject).to receive(:initialize_capabilities!).\n        with(:foo, guests, capabilities, machine)\n\n      subject.detect!\n    end\n\n    it \"raises a user-friendly error if specified guest doesn't exist\" do\n      allow(machine.config.vm).to receive(:guest).and_return(:foo)\n\n      expect { subject.detect! }.\n        to raise_error(Vagrant::Errors::GuestExplicitNotDetected)\n    end\n\n    it \"raises a user-friendly error if auto-detected guest not found\" do\n      expect { subject.detect! }.\n        to raise_error(Vagrant::Errors::GuestNotDetected)\n    end\n  end\n\n  describe \"#name\" do\n    it \"should be the name of the detected guest\" do\n      guests[:foo] = [detect_class(true), nil]\n      guests[:bar] = [detect_class(false), nil]\n\n      subject.detect!\n      expect(subject.name).to eql(:foo)\n    end\n  end\n\n  describe \"#ready?\" do\n    before(:each) do\n      guests[:foo] = [detect_class(true), nil]\n    end\n\n    it \"should not be ready by default\" do\n      expect(subject.ready?).not_to be\n    end\n\n    it \"should be ready after detecting\" do\n      subject.detect!\n      expect(subject.ready?).to be\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/host_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\ndescribe Vagrant::Host do\n  include_context \"capability_helpers\"\n\n  let(:capabilities) { {} }\n  let(:hosts)  { {} }\n  let(:env) { Object.new }\n\n  it \"initializes the capabilities\" do\n    expect_any_instance_of(described_class).to receive(:initialize_capabilities!).\n      with(:foo, hosts, capabilities, env)\n\n    described_class.new(:foo, hosts, capabilities, env)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/machine_index_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\nrequire \"pathname\"\nrequire \"tempfile\"\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\nrequire \"vagrant/machine_index\"\n\ndescribe Vagrant::MachineIndex do\n  include_context \"unit\"\n\n  let(:data_dir) { Pathname.new(Dir.mktmpdir(\"vagrant-test-machine-index-data-dir\")) }\n  let(:entry_klass) { Vagrant::MachineIndex::Entry }\n\n  let(:new_entry) do\n    entry_klass.new.tap do |e|\n      e.name = \"foo\"\n      e.vagrantfile_path = \"/bar\"\n    end\n  end\n\n  subject { described_class.new(data_dir) }\n\n  after do\n    FileUtils.rm_rf(data_dir)\n  end\n\n  it \"raises an exception if the data file is corrupt\" do\n    data_dir.join(\"index\").open(\"w\") do |f|\n      f.write(JSON.dump({}))\n    end\n\n    expect { subject }.\n      to raise_error(Vagrant::Errors::CorruptMachineIndex)\n  end\n\n  it \"raises an exception if the JSON is invalid\" do\n    data_dir.join(\"index\").open(\"w\") do |f|\n      f.write(\"foo\")\n    end\n\n    expect { subject }.\n      to raise_error(Vagrant::Errors::CorruptMachineIndex)\n  end\n\n  describe \"#each\" do\n    before do\n      5.times do |i|\n        e = entry_klass.new\n        e.name = \"entry-#{i}\"\n        e.vagrantfile_path = \"/foo\"\n        subject.release(subject.set(e))\n      end\n    end\n\n    it \"should iterate over all the elements\" do\n      items = []\n\n      subject = described_class.new(data_dir)\n      subject.each do |entry|\n        items << entry.name\n      end\n\n      items.sort!\n      expect(items).to eq([\n        \"entry-0\",\n        \"entry-1\",\n        \"entry-2\",\n        \"entry-3\",\n        \"entry-4\",\n      ])\n    end\n  end\n\n  describe \"#get and #release\" do\n    before do\n      data = {\n        \"version\" => 1,\n        \"machines\" => {\n          \"bar\" => {\n            \"name\" => \"default\",\n            \"provider\" => \"vmware\",\n            \"local_data_path\" => \"/foo\",\n            \"vagrantfile_path\" => \"/foo/bar/baz\",\n            \"state\" => \"running\",\n            \"updated_at\" => \"foo\",\n          },\n          \"baz\" => {\n            \"name\" => \"default\",\n            \"provider\" => \"vmware\",\n            \"vagrantfile_path\" => \"/foo/bar/baz\",\n            \"state\" => \"running\",\n            \"updated_at\" => \"foo\",\n            \"extra_data\" => {\n              \"foo\" => \"bar\",\n            },\n          },\n        }\n      }\n\n      data_dir.join(\"index\").open(\"w\") do |f|\n        f.write(JSON.dump(data))\n      end\n    end\n\n    it \"returns nil if the machine doesn't exist\" do\n      expect(subject.get(\"foo\")).to be_nil\n      expect(subject.get(nil)).to be_nil\n    end\n\n    it \"returns nil if the machine doesn't exist (is an empty string)\" do\n      expect(subject.get(\"\")).to be_nil\n      expect(subject.get(nil)).to be_nil\n    end\n\n    it \"returns a valid entry if the machine exists\" do\n      result = subject.get(\"bar\")\n\n      expect(result.id).to eq(\"bar\")\n      expect(result.name).to eq(\"default\")\n      expect(result.provider).to eq(\"vmware\")\n      expect(result.local_data_path).to eq(Pathname.new(\"/foo\"))\n      expect(result.vagrantfile_path).to eq(Pathname.new(\"/foo/bar/baz\"))\n      expect(result.state).to eq(\"running\")\n      expect(result.updated_at).to eq(\"foo\")\n      expect(result.extra_data).to eq({})\n    end\n\n    it \"returns a valid entry with extra data\" do\n      result = subject.get(\"baz\")\n      expect(result.id).to eq(\"baz\")\n      expect(result.extra_data).to eq({\n        \"foo\" => \"bar\",\n      })\n    end\n\n    it \"returns a valid entry by unique prefix\" do\n      result = subject.get(\"b\")\n\n      expect(result).to_not be_nil\n      expect(result.id).to eq(\"bar\")\n    end\n\n    it \"should include? by prefix\" do\n      expect(subject.include?(\"b\")).to be(true)\n    end\n\n    it \"should return false if given nil input\" do\n      expect(subject.include?(nil)).to be(false)\n    end\n\n    it \"locks the entry so subsequent gets fail\" do\n      result = subject.get(\"bar\")\n      expect(result).to_not be_nil\n\n      expect { subject.get(\"bar\") }.\n        to raise_error(Vagrant::Errors::MachineLocked)\n    end\n\n    it \"can unlock a machine\" do\n      result = subject.get(\"bar\")\n      expect(result).to_not be_nil\n      subject.release(result)\n\n      result = subject.get(\"bar\")\n      expect(result).to_not be_nil\n    end\n  end\n\n  describe \"#include\" do\n    it \"should not include non-existent things\" do\n      expect(subject.include?(\"foo\")).to be(false)\n    end\n\n    it \"should include created entries\" do\n      result = subject.set(new_entry)\n      expect(result.id).to_not be_empty\n      subject.release(result)\n\n      subject = described_class.new(data_dir)\n      expect(subject.include?(result.id)).to be(true)\n    end\n  end\n\n  describe \"#set and #get and #delete\" do\n    it \"adds a new entry\" do\n      result = subject.set(new_entry)\n      expect(result.id).to_not be_empty\n\n      # It should be locked\n      expect { subject.get(result.id) }.\n        to raise_error(Vagrant::Errors::MachineLocked)\n\n      # Get it froma new class and check the results\n      subject.release(result)\n      subject = described_class.new(data_dir)\n      entry   = subject.get(result.id)\n      expect(entry).to_not be_nil\n      expect(entry.name).to eq(\"foo\")\n\n      # TODO: test that updated_at is set\n    end\n\n    it \"can delete an entry\" do\n      result = subject.set(new_entry)\n      expect(result.id).to_not be_empty\n      subject.delete(result)\n\n      # Get it from a new class and check the results\n      subject = described_class.new(data_dir)\n      entry   = subject.get(result.id)\n      expect(entry).to be_nil\n    end\n\n    it \"can delete an entry that doesn't exist\" do\n      e = entry_klass.new\n      expect(subject.delete(e)).to be(true)\n    end\n\n    it \"updates an existing entry\" do\n      entry = entry_klass.new\n      entry.name = \"foo\"\n      entry.vagrantfile_path = \"/bar\"\n\n      result = subject.set(entry)\n      expect(result.id).to_not be_empty\n\n      result.name = \"bar\"\n      result.extra_data[\"foo\"] = \"bar\"\n\n      nextresult = subject.set(result)\n      expect(nextresult.id).to eq(result.id)\n\n      # Release it so we can test the contents\n      subject.release(nextresult)\n\n      # Get it froma new class and check the results\n      subject = described_class.new(data_dir)\n      entry   = subject.get(result.id)\n      expect(entry).to_not be_nil\n      expect(entry.name).to eq(\"bar\")\n      expect(entry.extra_data).to eq({\n        \"foo\" => \"bar\",\n      })\n    end\n\n    it \"updates an existing directory if the name, provider, and path are the same\" do\n      entry = entry_klass.new\n      entry.name = \"foo\"\n      entry.provider = \"bar\"\n      entry.vagrantfile_path = \"/bar\"\n      entry.state = \"foo\"\n\n      result = subject.set(entry)\n      expect(result.id).to_not be_empty\n\n      # Release it so we can modify it\n      subject.release(result)\n\n      entry2 = entry_klass.new\n      entry2.name = entry.name\n      entry2.provider = entry.provider\n      entry2.vagrantfile_path = entry.vagrantfile_path\n      entry2.state = \"bar\"\n      expect(entry2.id).to be_nil\n\n      nextresult = subject.set(entry2)\n      expect(nextresult.id).to eq(result.id)\n\n      # Release it so we can test the contents\n      subject.release(nextresult)\n\n      # Get it from a new class and check the results\n      subject = described_class.new(data_dir)\n      entry   = subject.get(result.id)\n      expect(entry).to_not be_nil\n      expect(entry.name).to eq(entry2.name)\n      expect(entry.state).to eq(entry2.state)\n    end\n  end\n\n  describe \"#recover\" do\n    it \"recovers an entry if not in the index\" do\n      result = subject.recover(new_entry)\n      expect(result.id).to_not be_empty\n      expect { subject.get(result.id) }.to raise_error(Vagrant::Errors::MachineLocked)\n    end\n\n    it \"returns an entry if in the index\" do\n      test_entry = entry_klass.new()\n      entry = subject.set(test_entry)\n      subject.release(entry)\n\n      new_test_entry = entry_klass.new(id=entry.id, {})\n      result = subject.recover(new_test_entry)\n\n      expect(result.id).to eq(entry.id)\n    end\n  end\nend\n\ndescribe Vagrant::MachineIndex::Entry do\n  include_context \"unit\"\n\n  let(:env) {\n    iso_env = isolated_environment\n    iso_env.vagrantfile(vagrantfile)\n    iso_env.create_vagrant_env\n  }\n\n  let(:vagrantfile) { \"\" }\n\n  describe \"#valid?\" do\n    let(:machine) { env.machine(:default, :dummy) }\n\n    subject do\n      described_class.new.tap do |e|\n        e.name = \"default\"\n        e.provider = \"dummy\"\n        e.vagrantfile_path = env.root_path\n      end\n    end\n\n    it \"should be valid with a valid entry\" do\n      machine.id = \"foo\"\n      expect(subject).to be_valid(env.home_path)\n    end\n\n    it \"should be invalid if no Vagrantfile path is set\" do\n      subject.vagrantfile_path = nil\n      expect(subject).to_not be_valid(env.home_path)\n    end\n\n    it \"should be invalid if the Vagrantfile path does not exist\" do\n      subject.vagrantfile_path = Pathname.new(\"/i/should/not/exist\")\n      expect(subject).to_not be_valid(env.home_path)\n    end\n\n    it \"should be invalid if the machine is inactive\" do\n      machine.id = nil\n      expect(subject).to_not be_valid(env.home_path)\n    end\n\n    it \"should be invalid if machine is not created\" do\n      machine.id = \"foo\"\n      machine.provider.state = Vagrant::MachineState::NOT_CREATED_ID\n      expect(subject).to_not be_valid(env.home_path)\n    end\n\n    context \"with another active machine\" do\n      let(:vagrantfile) do\n        <<-VF\n        Vagrant.configure(\"2\") do |config|\n          config.vm.define \"web\"\n          config.vm.define \"db\"\n        end\n        VF\n      end\n\n      it \"should be invalid if the wrong machine is active only\" do\n        m = env.machine(:web, :dummy)\n        m.id = \"foo\"\n\n        subject.name = \"db\"\n        expect(subject).to_not be_valid(env.home_path)\n      end\n\n      it \"should be valid if the correct machine is active\" do\n        env.machine(:web, :dummy).id = \"foo\"\n        env.machine(:db, :dummy).id = \"foo\"\n\n        subject.name = \"db\"\n        expect(subject).to be_valid(env.home_path)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/machine_state_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\ndescribe Vagrant::MachineState do\n  include_context \"unit\"\n\n  let(:id) { :some_state }\n  let(:short) { \"foo\" }\n  let(:long) { \"I am a longer foo\" }\n\n  it \"should give access to the id\" do\n    instance = described_class.new(id, short, long)\n    expect(instance.id).to eq(id)\n  end\n\n  it \"should give access to the short description\" do\n    instance = described_class.new(id, short, long)\n    expect(instance.short_description).to eq(short)\n  end\n\n  it \"should give access to the long description\" do\n    instance = described_class.new(id, short, long)\n    expect(instance.long_description).to eq(long)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/machine_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"pathname\"\nrequire \"tmpdir\"\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\nrequire \"vagrant/util/platform\"\n\ndescribe Vagrant::Machine do\n  include_context \"unit\"\n\n  let(:name)     { \"foo\" }\n  let(:provider) { new_provider_mock }\n  let(:provider_cls) do\n    obj = double(\"provider_cls\")\n    allow(obj).to receive(:new).and_return(provider)\n    obj\n  end\n  let(:provider_config) { Object.new }\n  let(:provider_name) { :test }\n  let(:provider_options) { {} }\n  let(:base)     { false }\n  let(:box) do\n    double(\"box\").tap do |b|\n      allow(b).to receive(:name).and_return(\"foo\")\n      allow(b).to receive(:provider).and_return(:dummy)\n      allow(b).to receive(:architecture)\n      allow(b).to receive(:version).and_return(\"1.0\")\n    end\n  end\n\n  let(:config)   { env.vagrantfile.config }\n  let(:data_dir) { Pathname.new(Dir.mktmpdir(\"vagrant-machine-data-dir\")) }\n  let(:env)      do\n    # We need to create a Vagrantfile so that this test environment\n    # has a proper root path\n    test_env.vagrantfile(\"\")\n\n    # Create the Vagrant::Environment instance\n    test_env.create_vagrant_env\n  end\n\n  let(:test_env) { isolated_environment }\n\n  let(:instance) { new_instance }\n\n  after do\n    FileUtils.rm_rf(data_dir) if data_dir\n  end\n\n  subject { instance }\n\n  def new_provider_mock\n    double(\"provider\").tap do |obj|\n      allow(obj).to receive(:_initialize)\n        .with(provider_name, anything).and_return(nil)\n      allow(obj).to receive(:machine_id_changed).and_return(nil)\n      allow(obj).to receive(:state).and_return(Vagrant::MachineState.new(\n        :created, \"\", \"\"))\n    end\n  end\n\n  # Returns a new instance with the test data\n  def new_instance\n    described_class.new(name, provider_name, provider_cls, provider_config,\n                        provider_options, config, data_dir, box,\n                        env, env.vagrantfile, base)\n  end\n\n  describe \"initialization\" do\n    it \"should set the ID to nil if the state is not created\" do\n      subject.id = \"foo\"\n\n      allow(provider).to receive(:state).and_return(Vagrant::MachineState.new(\n        Vagrant::MachineState::NOT_CREATED_ID, \"short\", \"long\"))\n\n      subject = new_instance\n      expect(subject.state.id).to eq(Vagrant::MachineState::NOT_CREATED_ID)\n      expect(subject.id).to be_nil\n    end\n\n    context \"setting up triggers\" do\n      before do\n        expect(provider).to receive(:_initialize) do |*args|\n          machine = args.last\n          @trigger_instance = machine.instance_variable_get(:@triggers)\n          true\n        end\n      end\n\n      it \"should initialize the trigger object\" do\n        subject = new_instance\n        expect(subject.instance_variable_get(:@triggers))\n          .to be_a(Vagrant::Plugin::V2::Trigger)\n        expect(subject.instance_variable_get(:@triggers))\n          .to eq(@trigger_instance)\n      end\n    end\n\n    describe \"as a base\" do\n      let(:base) { true}\n\n      it \"should not insert key\" do\n        subject = new_instance\n        expect(subject.config.ssh.insert_key).to be(false)\n      end\n    end\n\n    describe \"communicator loading\" do\n      it \"doesn't eager load SSH\" do\n        config.vm.communicator = :ssh\n\n        klass = Vagrant.plugin(\"2\").manager.communicators[:ssh]\n        expect(klass).to_not receive(:new)\n\n        subject\n      end\n\n      it \"eager loads WinRM\" do\n        config.vm.communicator = :winrm\n\n        klass    = Vagrant.plugin(\"2\").manager.communicators[:winrm]\n        instance = double(\"instance\")\n        expect(klass).to receive(:new).and_return(instance)\n\n        subject\n      end\n    end\n\n    describe \"provider initialization\" do\n      # This is a helper that generates a test for provider initialization.\n      # This is a separate helper method because it takes a block that can\n      # be used to have additional tests on the received machine.\n      #\n      # @yield [machine] Yields the machine that the provider initialization\n      #   method received so you can run additional tests on it.\n      def provider_init_test(instance=nil)\n        received_machine = nil\n\n        if !instance\n          instance = new_provider_mock\n        end\n\n        provider_cls = double(\"provider_cls\")\n        expect(provider_cls).to receive(:new) { |machine|\n          # Store this for later so we can verify that it is the\n          # one we expected to receive.\n          received_machine = machine\n\n          # Sanity check\n          expect(machine).to be\n\n          # Yield our machine if we want to do additional tests\n          yield machine if block_given?\n          true\n        }.and_return(instance)\n\n        # Initialize a new machine and verify that we properly receive\n        # the machine we expect.\n        instance = described_class.new(name, provider_name, provider_cls, provider_config,\n                                       provider_options, config, data_dir, box,\n                                       env, env.vagrantfile)\n        expect(received_machine).to eql(instance)\n      end\n\n      it \"should initialize with the machine object\" do\n        # Just run the blank test\n        provider_init_test\n      end\n\n      it \"should have the machine name setup\" do\n        provider_init_test do |machine|\n          expect(machine.name).to eq(name)\n        end\n      end\n\n      it \"should have the machine configuration\" do\n        provider_init_test do |machine|\n          expect(machine.config).to eql(config)\n        end\n      end\n\n      it \"should have the box\" do\n        provider_init_test do |machine|\n          expect(machine.box).to eql(box)\n        end\n      end\n\n      it \"should have the environment\" do\n        provider_init_test do |machine|\n          expect(machine.env).to eql(env)\n        end\n      end\n\n      it \"should have the vagrantfile\" do\n        provider_init_test do |machine|\n          expect(machine.vagrantfile).to equal(env.vagrantfile)\n        end\n      end\n\n      it \"should have access to the ID\" do\n        # Stub this because #id= calls it.\n        allow(provider).to receive(:machine_id_changed)\n\n        # Set the ID on the previous instance so that it is persisted\n        instance.id = \"foo\"\n\n        provider_init_test do |machine|\n          expect(machine.id).to eq(\"foo\")\n        end\n      end\n\n      it \"should NOT have access to the provider\" do\n        provider_init_test do |machine|\n          expect(machine.provider).to be_nil\n        end\n      end\n\n      it \"should initialize the capabilities\" do\n        instance = new_provider_mock\n        expect(instance).to receive(:_initialize).with(any_args) { |p, m|\n          expect(p).to eq(provider_name)\n          expect(m.name).to eq(name)\n          true\n        }\n\n        provider_init_test(instance)\n      end\n    end\n  end\n\n  describe \"attributes\" do\n    describe '#name' do\n      subject { super().name }\n      it             { should eq(name) }\n    end\n\n    describe '#config' do\n      subject { super().config }\n      it           { should eql(config) }\n    end\n\n    describe '#box' do\n      subject { super().box }\n      it              { should eql(box) }\n    end\n\n    describe '#env' do\n      subject { super().env }\n      it              { should eql(env) }\n    end\n\n    describe '#provider' do\n      subject { super().provider }\n      it         { should eql(provider) }\n    end\n\n    describe '#provider_config' do\n      subject { super().provider_config }\n      it  { should eql(provider_config) }\n    end\n\n    describe '#provider_options' do\n      subject { super().provider_options }\n      it { should eq(provider_options) }\n    end\n  end\n\n  describe \"#action\" do\n    before do\n      allow(Vagrant::Plugin::Manager.instance).to receive(:installed_plugins).and_return({})\n    end\n\n    it \"should be able to run an action that exists\" do\n      action_name = :destroy\n      called      = false\n      callable    = lambda { |_env| called = true }\n\n      expect(provider).to receive(:action).with(action_name).and_return(callable)\n      instance.action(action_name)\n      expect(called).to be\n    end\n\n    it \"should provide the machine in the environment\" do\n      action_name = :destroy\n      machine     = nil\n      callable    = lambda { |env| machine = env[:machine] }\n\n      allow(provider).to receive(:action).with(action_name).and_return(callable)\n      instance.action(action_name)\n\n      expect(machine).to eql(instance)\n    end\n\n    it \"should pass any extra options to the environment\" do\n      action_name = :destroy\n      foo         = nil\n      callable    = lambda { |env| foo = env[:foo] }\n\n      allow(provider).to receive(:action).with(action_name).and_return(callable)\n      instance.action(action_name, foo: :bar)\n\n      expect(foo).to eq(:bar)\n    end\n\n    it \"should pass any extra options to the environment as strings\" do\n      action_name = :destroy\n      foo         = nil\n      callable    = lambda { |env| foo = env[\"foo\"] }\n\n      allow(provider).to receive(:action).with(action_name).and_return(callable)\n      instance.action(action_name, \"foo\" => :bar)\n\n      expect(foo).to eq(:bar)\n    end\n\n    it \"should return the environment as a result\" do\n      action_name = :destroy\n      callable    = lambda { |env| env[:result] = \"FOO\" }\n\n      allow(provider).to receive(:action).with(action_name).and_return(callable)\n      result = instance.action(action_name)\n\n      expect(result[:result]).to eq(\"FOO\")\n    end\n\n    it \"should raise an exception if the action is not implemented\" do\n      action_name = :destroy\n\n      allow(provider).to receive(:action).with(action_name).and_return(nil)\n\n      expect { instance.action(action_name) }.\n        to raise_error(Vagrant::Errors::UnimplementedProviderAction)\n    end\n\n    it 'should not warn if the machines cwd has not changed' do\n      initial_action_name  = :destroy\n      second_action_name  = :reload\n      callable     = lambda { |_env| }\n\n      allow(provider).to receive(:action).with(initial_action_name).and_return(callable)\n      allow(provider).to receive(:action).with(second_action_name).and_return(callable)\n      allow(subject.ui).to receive(:warn)\n\n      instance.action(initial_action_name)\n      expect(subject.ui).to_not have_received(:warn)\n\n      instance.action(second_action_name)\n      expect(subject.ui).to_not have_received(:warn)\n    end\n\n    it 'should warn if the machine was last run under a different directory' do\n      action_name  = :destroy\n      callable     = lambda { |_env| }\n      original_cwd = env.cwd.to_s\n\n      allow(provider).to receive(:action).with(action_name).and_return(callable)\n      allow(subject.ui).to receive(:warn)\n\n      instance.action(action_name)\n\n      expect(subject.ui).to_not have_received(:warn)\n\n      # Whenever the machine is run on a different directory, the user is warned\n      allow(env).to receive(:root_path).and_return('/a/new/path')\n      instance.action(action_name)\n\n      expect(subject.ui).to have_received(:warn) do |warn_msg|\n        expect(warn_msg).to include(original_cwd)\n        expect(warn_msg).to include('/a/new/path')\n      end\n    end\n\n    it 'should not warn if dirs are same but different cases' do\n      action_name  = :destroy\n      callable     = lambda { |_env| }\n      original_cwd = env.cwd.to_s\n\n      allow(provider).to receive(:action).with(action_name).and_return(callable)\n      allow(subject.ui).to receive(:warn)\n\n      instance.action(action_name)\n\n      expect(subject.ui).to_not have_received(:warn)\n\n      # In cygwin or other windows shell, it might have a path like\n      # c:/path and C:/path\n      # which are the same.\n      allow(env).to receive(:root_path).and_return(original_cwd.upcase)\n      expect(subject.ui).to_not have_received(:warn)\n      instance.action(action_name)\n    end\n\n    context \"if in a subdir\" do\n      let (:data_dir) { env.cwd }\n\n      it 'should not warn if vagrant is run in subdirectory' do\n        action_name  = :destroy\n        callable     = lambda { |_env| }\n        original_cwd = env.cwd.to_s\n\n        allow(provider).to receive(:action).with(action_name).and_return(callable)\n        allow(subject.ui).to receive(:warn)\n\n        instance.action(action_name)\n\n        expect(subject.ui).to_not have_received(:warn)\n        # mock out cwd to be subdir and ensure no warn is printed\n        allow(env).to receive(:cwd).and_return(\"#{original_cwd}/a/new/path\")\n\n        instance.action(action_name)\n        expect(subject.ui).to_not have_received(:warn)\n      end\n    end\n  end\n\n  describe \"#action_raw\" do\n    let(:callable) {lambda { |e|\n      e[:called] = true\n      @env = e\n    }}\n\n    before do\n      @env = {}\n    end\n\n    it \"should run the callable with the proper env\" do\n      subject.action_raw(:foo, callable)\n\n      expect(@env[:called]).to be(true)\n      expect(@env[:action_name]).to eq(:machine_action_foo)\n      expect(@env[:machine]).to equal(subject)\n      expect(@env[:machine_action]).to eq(:foo)\n      expect(@env[:ui]).to equal(subject.ui)\n    end\n\n    it \"should return the environment as a result\" do\n      result = subject.action_raw(:foo, callable)\n      expect(result).to equal(@env)\n    end\n\n    it \"should merge in any extra env\" do\n      subject.action_raw(:bar, callable, foo: :bar)\n\n      expect(@env[:called]).to be(true)\n      expect(@env[:foo]).to eq(:bar)\n    end\n  end\n\n  describe \"#communicate\" do\n    it \"should return the SSH communicator by default\" do\n      expect(subject.communicate).\n        to be_kind_of(VagrantPlugins::CommunicatorSSH::Communicator)\n    end\n\n    it \"should return the specified communicator if given\" do\n      subject.config.vm.communicator = :winrm\n      expect(subject.communicate).\n        to be_kind_of(VagrantPlugins::CommunicatorWinRM::Communicator)\n    end\n\n    it \"should memoize the result\" do\n      obj = subject.communicate\n      expect(subject.communicate).to equal(obj)\n    end\n\n    it \"raises an exception if an invalid communicator is given\" do\n      subject.config.vm.communicator = :foo\n      expect { subject.communicate }.\n        to raise_error(Vagrant::Errors::CommunicatorNotFound)\n    end\n  end\n\n  describe \"guest implementation\" do\n    let(:communicator) do\n      result = double(\"communicator\")\n      allow(result).to receive(:ready?).and_return(true)\n      allow(result).to receive(:test).and_return(false)\n      result\n    end\n\n    before(:each) do\n      test_guest = Class.new(Vagrant.plugin(\"2\", :guest)) do\n        def detect?(machine)\n          true\n        end\n      end\n\n      register_plugin do |p|\n        p.guest(:test) { test_guest }\n      end\n\n      allow(instance).to receive(:communicate).and_return(communicator)\n    end\n\n    it \"should raise an exception if communication is not ready\" do\n      expect(communicator).to receive(:ready?).and_return(false)\n\n      expect { instance.guest }.\n        to raise_error(Vagrant::Errors::MachineGuestNotReady)\n    end\n\n    it \"should return the configured guest\" do\n      result = instance.guest\n      expect(result).to be_kind_of(Vagrant::Guest)\n      expect(result).to be_ready\n      expect(result.capability_host_chain[0][0]).to eql(:test)\n    end\n  end\n\n  describe \"setting the ID\" do\n    before(:each) do\n      allow(provider).to receive(:machine_id_changed)\n    end\n\n    it \"should not have an ID by default\" do\n      expect(instance.id).to be_nil\n    end\n\n    it \"should set an ID\" do\n      instance.id = \"bar\"\n      expect(instance.id).to eq(\"bar\")\n    end\n\n    it \"should notify the machine that the ID changed\" do\n      expect(provider).to receive(:machine_id_changed).once\n\n      instance.id = \"bar\"\n    end\n\n    it \"should persist the ID\" do\n      instance.id = \"foo\"\n      expect(new_instance.id).to eq(\"foo\")\n    end\n\n    it \"should delete the ID\" do\n      instance.id = \"foo\"\n\n      second = new_instance\n      expect(second.id).to eq(\"foo\")\n      second.id = nil\n      expect(second.id).to be_nil\n\n      third = new_instance\n      expect(third.id).to be_nil\n    end\n\n    it \"should set the UID that created the machine\" do\n      instance.id = \"foo\"\n\n      second = new_instance\n      expect(second.uid).to eq(Process.uid.to_s)\n    end\n\n    it \"should delete the UID when the id is nil\" do\n      instance.id = \"foo\"\n      instance.id = nil\n\n      second = new_instance\n      expect(second.uid).to be_nil\n    end\n  end\n\n  describe \"#index_uuid\" do\n    before(:each) do\n      allow(provider).to receive(:machine_id_changed)\n    end\n\n    it \"should not have an index UUID by default\" do\n      expect(subject.index_uuid).to be_nil\n    end\n\n    it \"is set one when setting an ID\" do\n      # Stub the message we want\n      allow(provider).to receive(:state).and_return(Vagrant::MachineState.new(\n        :preparing, \"preparing\", \"preparing\"))\n\n      # Setup the box information\n      box = double(\"box\")\n      allow(box).to receive(:name).and_return(\"foo\")\n      allow(box).to receive(:provider).and_return(:bar)\n      allow(box).to receive(:architecture)\n      allow(box).to receive(:version).and_return(\"1.2.3\")\n      subject.box = box\n\n      subject.id = \"foo\"\n\n      uuid = subject.index_uuid\n      expect(uuid).to_not be_nil\n      expect(new_instance.index_uuid).to eq(uuid)\n\n      # Test the entry itself\n      entry = env.machine_index.get(uuid)\n      expect(entry.name).to eq(subject.name)\n      expect(entry.provider).to eq(subject.provider_name.to_s)\n      expect(entry.state).to eq(\"preparing\")\n      expect(entry.vagrantfile_path).to eq(env.root_path)\n      expect(entry.vagrantfile_name).to eq(env.vagrantfile_name)\n      expect(entry.extra_data[\"box\"]).to eq({\n        \"name\"     => box.name,\n        \"provider\" => box.provider.to_s,\n        \"architecture\" => nil,\n        \"version\"  => box.version,\n      })\n      env.machine_index.release(entry)\n    end\n\n    it \"deletes the UUID when setting to nil\" do\n      subject.id = \"foo\"\n      uuid = subject.index_uuid\n\n      subject.id = nil\n      expect(subject.index_uuid).to be_nil\n      expect(env.machine_index.get(uuid)).to be_nil\n    end\n  end\n\n  describe \"#reload\" do\n    before do\n      allow(provider).to receive(:machine_id_changed)\n      subject.id = \"foo\"\n    end\n\n    it \"should read the ID\" do\n      expect(provider).to_not receive(:machine_id_changed)\n\n      subject.reload\n\n      expect(subject.id).to eq(\"foo\")\n    end\n\n    it \"should read the updated ID\" do\n      new_instance.id = \"bar\"\n\n      expect(provider).to receive(:machine_id_changed)\n\n      subject.reload\n\n      expect(subject.id).to eq(\"bar\")\n    end\n  end\n\n  describe \"#ssh_info\" do\n\n    describe \"with the provider returning nil\" do\n      it \"should return nil if the provider returns nil\" do\n        expect(provider).to receive(:ssh_info).and_return(nil)\n        expect(instance.ssh_info).to be_nil\n      end\n    end\n\n    describe \"with the provider returning data\" do\n      let(:provider_ssh_info) { {} }\n      let(:ssh_klass) { Vagrant::Util::SSH }\n\n      before(:each) do\n        allow(provider).to receive(:ssh_info).and_return(provider_ssh_info)\n        # Stub the check_key_permissions method so that even if we test incorrectly,\n        # no side effects actually happen.\n        allow(ssh_klass).to receive(:check_key_permissions)\n      end\n\n      [:host, :port, :username].each do |type|\n        it \"should return the provider data if not configured in Vagrantfile\" do\n          provider_ssh_info[type] = \"foo\"\n          instance.config.ssh.send(\"#{type}=\", nil)\n\n          expect(instance.ssh_info[type]).to eq(\"foo\")\n        end\n\n        it \"should return the Vagrantfile value if provider data not given\" do\n          provider_ssh_info[type] = nil\n          instance.config.ssh.send(\"#{type}=\", \"bar\")\n\n          expect(instance.ssh_info[type]).to eq(\"bar\")\n        end\n\n        it \"should use the default if no override and no provider\" do\n          provider_ssh_info[type] = nil\n          instance.config.ssh.send(\"#{type}=\", nil)\n          instance.config.ssh.default.send(\"#{type}=\", \"foo\")\n\n          expect(instance.ssh_info[type]).to eq(\"foo\")\n        end\n\n        it \"should use the override if set even with a provider\" do\n          provider_ssh_info[type] = \"baz\"\n          instance.config.ssh.send(\"#{type}=\", \"bar\")\n          instance.config.ssh.default.send(\"#{type}=\", \"foo\")\n\n          expect(instance.ssh_info[type]).to eq(\"bar\")\n        end\n      end\n\n      it \"should set the configured forward agent settings\" do\n        provider_ssh_info[:forward_agent] = true\n        instance.config.ssh.forward_agent = false\n\n        expect(instance.ssh_info[:forward_agent]).to eq(false)\n      end\n\n      it \"should set the configured forward X11 settings\" do\n        provider_ssh_info[:forward_x11] = true\n        instance.config.ssh.forward_x11 = false\n\n        expect(instance.ssh_info[:forward_x11]).to eq(false)\n      end\n\n      it \"should return the provider private key if given\" do\n        provider_ssh_info[:private_key_path] = \"/foo\"\n\n        expect(instance.ssh_info[:private_key_path]).to eq([File.expand_path(\"/foo\", env.root_path)])\n      end\n\n      it \"should return the configured SSH key path if set\" do\n        provider_ssh_info[:private_key_path] = nil\n        instance.config.ssh.private_key_path = \"/bar\"\n\n        expect(instance.ssh_info[:private_key_path]).to eq([File.expand_path(\"/bar\", env.root_path)])\n      end\n\n      it \"should return the array of SSH keys if set\" do\n        provider_ssh_info[:private_key_path] = nil\n        instance.config.ssh.private_key_path = [\"/foo\", \"/bar\"]\n\n        expect(instance.ssh_info[:private_key_path]).to eq([\n          File.expand_path(\"/foo\", env.root_path),\n          File.expand_path(\"/bar\", env.root_path),\n        ])\n      end\n\n      it \"should check and try to fix the permissions of the default private key file\" do\n        provider_ssh_info[:private_key_path] = nil\n        instance.config.ssh.private_key_path = nil\n\n        instance.env.default_private_key_paths.each do |key_path|\n          expect(ssh_klass).to receive(:check_key_permissions).once.with(Pathname.new(key_path.to_s))\n        end\n        instance.ssh_info\n      end\n\n      it \"should check and try to fix the permissions of given private key files\" do\n        provider_ssh_info[:private_key_path] = nil\n        # Use __FILE__ to provide an existing file\n        instance.config.ssh.private_key_path = [File.expand_path(__FILE__), File.expand_path(__FILE__)]\n\n        expect(ssh_klass).to receive(:check_key_permissions).twice.with(Pathname.new(File.expand_path(__FILE__)))\n        instance.ssh_info\n      end\n\n      it \"should not check the permissions of a private key file that does not exist\" do\n        provider_ssh_info[:private_key_path] = \"/foo\"\n\n        expect(ssh_klass).to_not receive(:check_key_permissions)\n        instance.ssh_info\n      end\n\n      context \"expanding path relative to the root path\" do\n        it \"should with the provider key path\" do\n          provider_ssh_info[:private_key_path] = \"~/foo\"\n\n          expect(instance.ssh_info[:private_key_path]).to eq(\n            [File.expand_path(\"~/foo\", env.root_path)]\n          )\n        end\n\n        it \"should with the config private key path\" do\n          provider_ssh_info[:private_key_path] = nil\n          instance.config.ssh.private_key_path = \"~/bar\"\n\n          expect(instance.ssh_info[:private_key_path]).to eq(\n            [File.expand_path(\"~/bar\", env.root_path)]\n          )\n        end\n      end\n\n      it \"should return the default private key path if provider and config doesn't have one\" do\n        provider_ssh_info[:private_key_path] = nil\n        instance.config.ssh.private_key_path = nil\n\n        expect(instance.ssh_info[:private_key_path]).to eq(\n          instance.env.default_private_key_paths\n        )\n      end\n\n      it \"should return the default private key path with keys_only = false\" do\n        provider_ssh_info[:private_key_path] = nil\n        instance.config.ssh.private_key_path = nil\n        instance.config.ssh.keys_only = false\n\n        expect(instance.ssh_info[:private_key_path]).to eq(\n          instance.env.default_private_key_paths\n        )\n      end\n\n      it \"should not set any default private keys if a password is specified\" do\n        provider_ssh_info[:private_key_path] = nil\n        instance.config.ssh.private_key_path = nil\n        instance.config.ssh.password = \"\"\n\n        expect(instance.ssh_info[:private_key_path]).to be_empty\n        expect(instance.ssh_info[:password]).to eql(\"\")\n      end\n\n      it \"should return the private key in the data dir above all else\" do\n        provider_ssh_info[:private_key_path] = nil\n        instance.config.ssh.private_key_path = nil\n        instance.config.ssh.password = \"\"\n\n        instance.data_dir.join(\"private_key\").open(\"w+\") do |f|\n          f.write(\"hey\")\n        end\n\n        expect(instance.ssh_info[:private_key_path]).to eql(\n          [instance.data_dir.join(\"private_key\").to_s])\n        expect(instance.ssh_info[:password]).to eql(\"\")\n      end\n\n      it \"should return the private key in the Vagrantfile if the data dir exists\" do\n        path = \"/foo\"\n        path = \"C:/foo\" if Vagrant::Util::Platform.windows?\n\n        provider_ssh_info[:private_key_path] = nil\n        instance.config.ssh.private_key_path = path\n\n        instance.data_dir.join(\"private_key\").open(\"w+\") do |f|\n          f.write(\"hey\")\n        end\n\n        expect(instance.ssh_info[:private_key_path]).to eql([path])\n      end\n\n      it \"should return the remote_user when set\" do\n        instance.config.ssh.remote_user = \"remote-user\"\n        expect(instance.ssh_info[:remote_user]).to eq(\"remote-user\")\n      end\n\n      it \"should return the config when set\" do\n        instance.config.ssh.config = \"/path/to/ssh_config\"\n        expect(instance.ssh_info[:config]).to eq(\"/path/to/ssh_config\")\n      end\n\n      it \"should return the default connect_timeout\" do\n        expect(instance.ssh_info[:connect_timeout]).\n          to eq(VagrantPlugins::Kernel_V2::SSHConnectConfig::DEFAULT_SSH_CONNECT_TIMEOUT)\n      end\n\n      it \"should return the connect_timeout when set\" do\n        instance.config.ssh.connect_timeout = 2\n        expect(instance.ssh_info[:connect_timeout]).to eq(2)\n      end\n\n      context \"with no data dir\" do\n        let(:base)     { true }\n        let(:data_dir) { nil }\n\n        it \"returns nil as the private key path\" do\n          provider_ssh_info[:private_key_path] = nil\n          instance.config.ssh.private_key_path = nil\n          instance.config.ssh.password = \"\"\n\n          expect(instance.ssh_info[:private_key_path]).to be_empty\n          expect(instance.ssh_info[:password]).to eql(\"\")\n        end\n      end\n\n      context \"with custom ssh_info\" do\n        it \"keys_only should be default\" do\n          expect(instance.ssh_info[:keys_only]).to be(true)\n        end\n        it \"verify_host_key should be default\" do\n          expect(instance.ssh_info[:verify_host_key]).to be(:never)\n        end\n        it \"extra_args should be nil\" do\n          expect(instance.ssh_info[:extra_args]).to be(nil)\n        end\n        it \"extra_args should be set\" do\n          instance.config.ssh.extra_args = [\"-L\", \"127.1.2.7:8008:127.1.2.7:8008\"]\n          expect(instance.ssh_info[:extra_args]).to eq([\"-L\", \"127.1.2.7:8008:127.1.2.7:8008\"])\n        end\n        it \"extra_args should be set as an array\" do\n          instance.config.ssh.extra_args = \"-6\"\n          expect(instance.ssh_info[:extra_args]).to eq(\"-6\")\n        end\n        it \"keys_only should be overridden\" do\n          instance.config.ssh.keys_only = false\n          expect(instance.ssh_info[:keys_only]).to be(false)\n        end\n        it \"verify_host_key should be overridden\" do\n          instance.config.ssh.verify_host_key = true\n          expect(instance.ssh_info[:verify_host_key]).to be(true)\n        end\n      end\n    end\n  end\n\n  describe \"#state\" do\n    it \"should query state from the provider\" do\n      state = Vagrant::MachineState.new(:id, \"short\", \"long\")\n\n      allow(provider).to receive(:state).and_return(state)\n      expect(instance.state.id).to eq(:id)\n    end\n\n    it \"should raise an exception if a MachineState is not returned\" do\n      expect(provider).to receive(:state).and_return(:old_school)\n      expect { instance.state }.\n        to raise_error(Vagrant::Errors::MachineStateInvalid)\n    end\n\n    it \"should save the state with the index\" do\n      allow(provider).to receive(:machine_id_changed)\n      subject.id = \"foo\"\n\n      state = Vagrant::MachineState.new(:id, \"short\", \"long\")\n      expect(provider).to receive(:state).and_return(state)\n\n      subject.state\n\n      entry = env.machine_index.get(subject.index_uuid)\n      expect(entry).to_not be_nil\n      expect(entry.state).to eq(\"short\")\n      env.machine_index.release(entry)\n    end\n  end\n\n  describe \"#recover_machine\" do\n    it \"does not recover a machine already in the index\" do\n      subject.id = \"foo\"\n      expected_entry = env.machine_index.get(subject.index_uuid)\n      env.machine_index.release(expected_entry)\n\n      entry = subject.recover_machine(:running)\n      expect(entry.id).to eq(expected_entry.id)\n      # Ensure entry is not locked\n      env.machine_index.get(subject.index_uuid)\n    end\n\n    it \"recovers a machine\" do\n      instance = new_instance\n      instance.id = \"foo\"\n\n      entry = instance.recover_machine(:running)\n      expect(entry.id).to eq(instance.index_uuid)\n      # Ensure entry is not locked\n      env.machine_index.get(entry.id)\n      env.machine_index.release(entry)\n\n      query_entry = env.machine_index.get(instance.index_uuid)\n      expect(entry.id).to eq(query_entry.id)\n    end\n  end\n\n  describe \"#with_ui\" do\n    it \"temporarily changes the UI\" do\n      ui = Object.new\n      changed_ui = nil\n\n      subject.with_ui(ui) do\n        changed_ui = subject.ui\n      end\n\n      expect(changed_ui).to equal(ui)\n      expect(subject.ui).to_not equal(ui)\n    end\n  end\n\n  describe \"#reload\" do\n    context \"when ID is unset and id file does not exist\" do\n      it \"should remain nil\" do\n        expect(subject.id).to be_nil\n        instance.reload\n        expect(subject.id).to be_nil\n      end\n    end\n\n    context \"when id file is set\" do\n      let(:id_content) { \"test-machine-id\" }\n\n      before do\n        id_file = subject.data_dir.join(\"id\")\n        File.write(id_file.to_s, id_content)\n      end\n\n      it \"should update the machine id\" do\n        expect(subject.id).to be_nil\n        instance.reload\n        expect(subject.id).to eq(id_content)\n      end\n\n      it \"should notify of the id change when provider is set\" do\n        expect(provider).to receive(:machine_id_changed)\n        instance.reload\n      end\n\n      context \"when id file content includes whitespace\" do\n        let(:id_content) { \"   test-machine-id\\n\" }\n\n        it \"should remove all whitespace\" do\n          instance.reload\n          expect(instance.id).to eq(\"test-machine-id\")\n        end\n      end\n\n      context \"when id file content is all whitespace\" do\n        let(:id_content) { \"\\0\\0\\0\\0\\0\\0\" }\n\n        it \"should not update the id\" do\n          expect(instance.id).to be_nil\n          instance.reload\n          expect(instance.id).to be_nil\n        end\n      end\n\n      context \"when id is already set to value in id file\" do\n        it \"should not notify of id change\" do\n          instance.instance_variable_set(:@id, id_content)\n          expect(provider).not_to receive(:machine_id_changed)\n          instance.reload\n          expect(instance.id).to eq(id_content)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/manager_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\nrequire \"pathname\"\n\nrequire \"vagrant/plugin\"\nrequire \"vagrant/plugin/manager\"\nrequire \"vagrant/plugin/state_file\"\nrequire \"vagrant/util/deep_merge\"\nrequire File.expand_path(\"../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::Manager do\n  include_context \"unit\"\n\n  let(:path) do\n    Pathname.new(Dir::Tmpname.create(\"vagrant-test-plugin-manager\") {})\n  end\n\n  let(:bundler) { double(\"bundler\") }\n\n  after do\n    path.unlink if path.file?\n  end\n\n  before do\n    allow(Vagrant::Bundler).to receive(:instance).and_return(bundler)\n  end\n\n  subject { described_class.new(path) }\n\n  describe \"#globalize!\" do\n    let(:plugins) { double(\"plugins\") }\n\n    before do\n      allow(subject).to receive(:bundler_init)\n      allow(subject).to receive(:installed_plugins).and_return(plugins)\n    end\n\n    it \"should init bundler with installed plugins\" do\n      expect(subject).to receive(:bundler_init).with(plugins, anything)\n      subject.globalize!\n    end\n\n    it \"should return installed plugins\" do\n      expect(subject.globalize!).to eq(plugins)\n    end\n  end\n\n  describe \"#localize!\" do\n    let(:env) { double(\"env\", local_data_path: local_data_path) }\n    let(:local_data_path) { double(\"local_data_path\") }\n    let(:plugins) { double(\"plugins\") }\n    let(:state_file) { double(\"state_file\", path: double(\"state_file_path\"), installed_plugins: plugins) }\n\n    before do\n      allow(Vagrant::Plugin::StateFile).to receive(:new).and_return(state_file)\n      allow(bundler).to receive(:environment_path=)\n      allow(local_data_path).to receive(:join).and_return(local_data_path) if local_data_path\n      allow(subject).to receive(:bundler_init)\n    end\n\n    context \"without local data path defined\" do\n      let(:local_data_path) { nil }\n\n      it \"should not do any initialization\" do\n        expect(subject).not_to receive(:bundler_init)\n        subject.localize!(env)\n      end\n\n      it \"should return nil\" do\n        expect(subject.localize!(env)).to be_nil\n      end\n    end\n\n    it \"should run bundler initialization\" do\n      expect(subject).to receive(:bundler_init).with(plugins, anything)\n      subject.localize!(env)\n    end\n\n    it \"should return plugins\" do\n      expect(subject.localize!(env)).to eq(plugins)\n    end\n  end\n\n  describe \"#ready?\" do\n    let(:plugins) { double(\"plugins\") }\n    let(:env) { double(\"env\", local_data_path: nil) }\n\n    before do\n      allow(subject).to receive(:bundler_init)\n    end\n\n    it \"should be false by default\" do\n      expect(subject.ready?).to be_falsey\n    end\n\n    it \"should be false when only globalize! has been called\" do\n      subject.globalize!\n      expect(subject.ready?).to be_falsey\n    end\n\n    it \"should be false when only localize! has been called\" do\n      subject.localize!(env)\n      expect(subject.ready?).to be_falsey\n    end\n\n    it \"should be true when both localize! and globalize! have been called\" do\n      subject.globalize!\n      subject.localize!(env)\n      expect(subject.ready?).to be_truthy\n    end\n  end\n\n  describe \"#bundler_init\" do\n    let(:plugins) { {\"plugin_name\" => {}} }\n\n    before do\n      allow(Vagrant).to receive(:plugins_init?).and_return(true)\n      allow(bundler).to receive(:init!)\n    end\n\n    it \"should init the bundler instance with plugins\" do\n      expect(bundler).to receive(:init!).with(plugins, any_args)\n      subject.bundler_init(plugins)\n    end\n\n    it \"should return nil\" do\n      expect(subject.bundler_init(plugins)).to be_nil\n    end\n\n    context \"with plugin init disabled\" do\n      before { expect(Vagrant).to receive(:plugins_init?).and_return(false) }\n\n      it \"should return nil\" do\n        expect(subject.bundler_init(plugins)).to be_nil\n      end\n\n      it \"should not init the bundler instance\" do\n        expect(bundler).not_to receive(:init!).with(plugins)\n        subject.bundler_init(plugins)\n      end\n    end\n  end\n\n  describe \"#plugin_installed?\" do\n    let(:ready) { true }\n    let(:specs) { [] }\n\n    before do\n      allow(subject).to receive(:ready?).and_return(ready)\n      allow(subject).to receive(:installed_specs).and_return(specs)\n    end\n\n    context \"when manager is ready\" do\n      it \"should return false when plugin is not found\" do\n        expect(subject.plugin_installed?(\"vagrant-plugin\")).to be_falsey\n      end\n\n      context \"when plugin is installed\" do\n        let(:specs) { [Gem::Specification.new(\"vagrant-plugin\", \"1.2.3\")] }\n\n        it \"should return true\" do\n          expect(subject.plugin_installed?(\"vagrant-plugin\")).to be_truthy\n        end\n\n        it \"should return true when version matches installed version\" do\n          expect(subject.plugin_installed?(\"vagrant-plugin\", \"1.2.3\")).to be_truthy\n        end\n\n        it \"should return true when version requirement is satisified by version\" do\n          expect(subject.plugin_installed?(\"vagrant-plugin\", \"> 1.0\")).to be_truthy\n        end\n\n        it \"should return false when version requirement is not satisified by version\" do\n          expect(subject.plugin_installed?(\"vagrant-plugin\", \"2.0\")).to be_falsey\n        end\n      end\n    end\n\n    context \"when manager is not ready\" do\n      let(:ready) { false }\n      let(:plugins) { {} }\n      before { allow(subject).to receive(:installed_plugins).and_return(plugins) }\n\n      it \"should check installed plugin data\" do\n        expect(subject).to receive(:installed_plugins).and_return(plugins)\n        subject.plugin_installed?(\"vagrant-plugin\")\n      end\n\n      it \"should return false when plugin is not found\" do\n        expect(subject.plugin_installed?(\"vagrant-plugin\")).to be_falsey\n      end\n\n      context \"when plugin is installed\" do\n        let(:plugins) { {\"vagrant-plugin\" => {\"installed_gem_version\" => \"1.2.3\"}} }\n\n        it \"should return true\" do\n          expect(subject.plugin_installed?(\"vagrant-plugin\")).to be_truthy\n        end\n\n        it \"should return true when version matches installed version\" do\n          expect(subject.plugin_installed?(\"vagrant-plugin\", \"1.2.3\")).to be_truthy\n        end\n\n        it \"should return true when version requirement is satisified by version\" do\n          expect(subject.plugin_installed?(\"vagrant-plugin\", \"> 1.0\")).to be_truthy\n        end\n\n        it \"should return false when version requirement is not satisified by version\" do\n          expect(subject.plugin_installed?(\"vagrant-plugin\", \"2.0\")).to be_falsey\n        end\n      end\n    end\n  end\n\n  describe \"#install_plugin\" do\n    it \"installs the plugin and adds it to the state file\" do\n      specs = Array.new(5) { Gem::Specification.new }\n      specs[3].name = \"foo\"\n      expect(bundler).to receive(:install).once.with(any_args) { |plugins, local|\n        expect(plugins).to have_key(\"foo\")\n        expect(local).to be_falsey\n      }.and_return(specs)\n      expect(bundler).to receive(:clean)\n\n      result = subject.install_plugin(\"foo\")\n\n      # It should return the spec of the installed plugin\n      expect(result).to eql(specs[3])\n\n      # It should've added the plugin to the state\n      expect(subject.installed_plugins).to have_key(\"foo\")\n    end\n\n    it \"masks GemNotFound with our error\" do\n      expect(bundler).to receive(:install).and_raise(Gem::GemNotFoundException)\n\n      expect { subject.install_plugin(\"foo\") }.\n        to raise_error(Vagrant::Errors::PluginGemNotFound)\n    end\n\n    it \"masks bundler errors with our own error\" do\n      expect(bundler).to receive(:install).and_raise(Gem::InstallError)\n\n      expect { subject.install_plugin(\"foo\") }.\n        to raise_error(Vagrant::Errors::BundlerError)\n    end\n\n    it \"can install a local gem\" do\n      name    = \"foo.gem\"\n      version = \"1.0\"\n\n      local_spec = Gem::Specification.new\n      local_spec.name = \"bar\"\n      local_spec.version = version\n\n      expect(bundler).to receive(:install_local).with(name, {}).\n        ordered.and_return(local_spec)\n\n      expect(bundler).not_to receive(:install)\n      expect(bundler).to receive(:clean)\n\n      subject.install_plugin(name)\n\n      plugins = subject.installed_plugins\n      expect(plugins).to have_key(\"bar\")\n      expect(plugins[\"bar\"][\"gem_version\"]).to eql(\"1.0\")\n    end\n\n    context \"with existing activation\" do\n      let(:value) { double(\"value\") }\n\n      before do\n        expect(bundler).to receive(:install).and_return([])\n        allow(bundler).to receive(:clean)\n      end\n\n      it \"should locate existing activation if available\" do\n        expect(Gem::Specification).to receive(:find).and_return(value)\n        expect(subject.install_plugin(\"foo\")).to eq(value)\n      end\n\n      it \"should raise an error if no activation is located\" do\n        expect(Gem::Specification).to receive(:find).and_return(nil)\n        expect { subject.install_plugin(\"foo\") }.to raise_error(Vagrant::Errors::PluginInstallFailed)\n      end\n    end\n\n    describe \"installation options\" do\n      let(:specs) do\n        specs = Array.new(5) { Gem::Specification.new }\n        specs[3].name = \"foo\"\n        specs\n      end\n\n      before do\n        allow(bundler).to receive(:install).and_return(specs)\n      end\n\n      it \"installs a version with constraints\" do\n        expect(bundler).to receive(:install).once.with(any_args) { |plugins, local|\n          expect(plugins).to have_key(\"foo\")\n          expect(plugins[\"foo\"][\"gem_version\"]).to eql(\">= 0.1.0\")\n          expect(local).to be_falsey\n        }.and_return(specs)\n        expect(bundler).to receive(:clean)\n\n        subject.install_plugin(\"foo\", version: \">= 0.1.0\")\n\n        plugins = subject.installed_plugins\n        expect(plugins).to have_key(\"foo\")\n        expect(plugins[\"foo\"][\"gem_version\"]).to eql(\">= 0.1.0\")\n      end\n\n      it \"installs with an exact version but doesn't constrain\" do\n        expect(bundler).to receive(:install).once.with(any_args) { |plugins, local|\n          expect(plugins).to have_key(\"foo\")\n          expect(plugins[\"foo\"][\"gem_version\"]).to eql(\"0.1.0\")\n          expect(local).to be_falsey\n        }.and_return(specs)\n        expect(bundler).to receive(:clean)\n\n        subject.install_plugin(\"foo\", version: \"0.1.0\")\n\n        plugins = subject.installed_plugins\n        expect(plugins).to have_key(\"foo\")\n        expect(plugins[\"foo\"][\"gem_version\"]).to eql(\"0.1.0\")\n      end\n    end\n  end\n\n  describe \"#uninstall_plugin\" do\n    it \"removes the plugin from the state\" do\n      sf = Vagrant::Plugin::StateFile.new(path)\n      sf.add_plugin(\"foo\")\n\n      # Sanity\n      expect(subject.installed_plugins).to have_key(\"foo\")\n\n      # Test\n      expect(bundler).to receive(:clean).once.with({})\n\n      # Remove it\n      subject.uninstall_plugin(\"foo\")\n      expect(subject.installed_plugins).to_not have_key(\"foo\")\n    end\n\n    it \"masks bundler errors with our own error\" do\n      sf = Vagrant::Plugin::StateFile.new(path)\n      sf.add_plugin(\"foo\")\n      expect(bundler).to receive(:clean).and_raise(Gem::InstallError)\n\n      expect { subject.uninstall_plugin(\"foo\") }.\n        to raise_error(Vagrant::Errors::BundlerError)\n    end\n\n    context \"with a system file\" do\n      let(:systems_path) { temporary_file }\n\n      before do\n        systems_path.unlink\n\n        allow(described_class).to receive(:system_plugins_file).and_return(systems_path)\n\n        sf = Vagrant::Plugin::StateFile.new(systems_path)\n        sf.add_plugin(\"foo\", version: \"0.2.0\")\n        sf.add_plugin(\"bar\")\n      end\n\n      it \"uninstalls the user plugin if it exists\" do\n        sf = Vagrant::Plugin::StateFile.new(path)\n        sf.add_plugin(\"bar\")\n\n        # Test\n        expect(bundler).to receive(:clean).once.with(anything)\n\n        # Remove it\n        subject.uninstall_plugin(\"bar\")\n\n        plugins = subject.installed_plugins\n        expect(plugins[\"foo\"][\"system\"]).to be(true)\n      end\n\n      it \"raises an error if uninstalling a system gem\" do\n        expect { subject.uninstall_plugin(\"bar\") }.\n          to raise_error(Vagrant::Errors::PluginUninstallSystem)\n      end\n    end\n  end\n\n  describe \"#update_plugins\" do\n    it \"masks bundler errors with our own error\" do\n      expect(bundler).to receive(:update).and_raise(Gem::InstallError)\n\n      expect { subject.update_plugins([]) }.\n        to raise_error(Vagrant::Errors::BundlerError)\n    end\n  end\n\n  context \"without state\" do\n    describe \"#installed_plugins\" do\n      it \"is empty initially\" do\n        expect(subject.installed_plugins).to be_empty\n      end\n    end\n  end\n\n  context \"with state\" do\n    before do\n      sf = Vagrant::Plugin::StateFile.new(path)\n      sf.add_plugin(\"foo\", version: \"0.1.0\")\n    end\n\n    describe \"#installed_plugins\" do\n      it \"has the plugins\" do\n        plugins = subject.installed_plugins\n        expect(plugins.length).to eql(1)\n        expect(plugins).to have_key(\"foo\")\n      end\n    end\n\n    describe \"#installed_specs\" do\n      it \"has the plugins\" do\n        # We just add \"i18n\" because it is a dependency of Vagrant and\n        # we know it will be there.\n        sf = Vagrant::Plugin::StateFile.new(path)\n        sf.add_plugin(\"i18n\")\n\n        specs = subject.installed_specs\n        expect(specs.length).to eql(1)\n        expect(specs.first.name).to eql(\"i18n\")\n      end\n    end\n\n    context \"with system plugins\" do\n      let(:systems_path) { temporary_file }\n\n      before do\n        systems_path.unlink\n\n        allow(described_class).to receive(:system_plugins_file).and_return(systems_path)\n\n        sf = Vagrant::Plugin::StateFile.new(systems_path)\n        sf.add_plugin(\"foo\", version: \"0.2.0\")\n        sf.add_plugin(\"bar\")\n      end\n\n      describe \"#installed_plugins\" do\n        it \"has the plugins\" do\n          plugins = subject.installed_plugins\n          expect(plugins.length).to eql(2)\n          expect(plugins).to have_key(\"foo\")\n          expect(plugins[\"foo\"][\"gem_version\"]).to eq(\"0.1.0\")\n          expect(plugins[\"foo\"][\"system\"]).to be_truthy\n          expect(plugins).to have_key(\"bar\")\n          expect(plugins[\"bar\"][\"system\"]).to be(true)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/state_file_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"json\"\nrequire \"pathname\"\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::StateFile do\n  let(:path) do\n    Pathname.new(Dir::Tmpname.create(\"vagrant-test-statefile\") {})\n  end\n\n  after do\n    path.unlink if path.file?\n  end\n\n  subject { described_class.new(path) }\n\n  context \"new usage\" do\n    it \"should have no plugins without saving some\" do\n      expect(subject.installed_plugins).to be_empty\n    end\n\n    it \"should have plugins when saving\" do\n      subject.add_plugin(\"foo\")\n\n      instance = described_class.new(path)\n      plugins = instance.installed_plugins\n      expect(plugins.length).to eql(1)\n      expect(plugins[\"foo\"]).to eql({\n        \"ruby_version\"          => RUBY_VERSION,\n        \"vagrant_version\"       => Vagrant::VERSION,\n        \"gem_version\"           => \"\",\n        \"require\"               => \"\",\n        \"sources\"               => [],\n        \"installed_gem_version\" => nil,\n        \"env_local\"             => false,\n      })\n    end\n\n    it \"should check for plugins\" do\n      expect(subject.has_plugin?(\"foo\")).to be(false)\n      subject.add_plugin(\"foo\")\n      expect(subject.has_plugin?(\"foo\")).to be(true)\n    end\n\n    it \"should remove plugins\" do\n      subject.add_plugin(\"foo\")\n      subject.remove_plugin(\"foo\")\n\n      instance = described_class.new(path)\n      expect(instance.installed_plugins).to be_empty\n    end\n\n    it \"should store plugins uniquely\" do\n      subject.add_plugin(\"foo\")\n      subject.add_plugin(\"foo\")\n\n      instance = described_class.new(path)\n      expect(instance.installed_plugins.keys).to eql([\"foo\"])\n    end\n\n    it \"should store metadata\" do\n      subject.add_plugin(\"foo\", version: \"1.2.3\")\n      expect(subject.installed_plugins[\"foo\"][\"gem_version\"]).to eql(\"1.2.3\")\n    end\n\n    describe \"sources\" do\n      it \"should have no sources\" do\n        expect(subject.sources).to be_empty\n      end\n\n      it \"should add sources\" do\n        subject.add_source(\"foo\")\n        expect(subject.sources).to eql([\"foo\"])\n      end\n\n      it \"should de-dup sources\" do\n        subject.add_source(\"foo\")\n        subject.add_source(\"foo\")\n        expect(subject.sources).to eql([\"foo\"])\n      end\n\n      it \"can remove sources\" do\n        subject.add_source(\"foo\")\n        subject.remove_source(\"foo\")\n        expect(subject.sources).to be_empty\n      end\n    end\n  end\n\n  context \"with an old-style file\" do\n    before do\n      data = {\n        \"installed\" => [\"foo\"],\n      }\n\n      path.open(\"w+\") do |f|\n        f.write(JSON.dump(data))\n      end\n    end\n\n    it \"should have the right installed plugins\" do\n      plugins = subject.installed_plugins\n      expect(plugins.keys).to eql([\"foo\"])\n      expect(plugins[\"foo\"][\"ruby_version\"]).to eql(\"0\")\n      expect(plugins[\"foo\"][\"vagrant_version\"]).to eql(\"0\")\n    end\n  end\n\n  context \"with parse errors\" do\n    before do\n      path.open(\"w+\") do |f|\n        f.write(\"I'm not json\")\n      end\n    end\n\n    it \"should raise a VagrantError\" do\n      expect { subject }.\n        to raise_error(Vagrant::Errors::PluginStateFileParseError)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v1/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\nrequire 'optparse'\n\ndescribe Vagrant::Plugin::V1::Command do\n  describe \"parsing options\" do\n    let(:klass) do\n      Class.new(described_class) do\n        # Make the method public since it is normally protected\n        public :parse_options\n      end\n    end\n\n    it \"returns the remaining arguments\" do\n      options = {}\n      opts = OptionParser.new do |opts|\n        opts.on(\"-f\") do |f|\n          options[:f] = f\n        end\n      end\n\n      result = klass.new([\"-f\", \"foo\"], nil).parse_options(opts)\n\n      # Check the results\n      expect(options[:f]).to be\n      expect(result).to eq([\"foo\"])\n    end\n\n    it \"creates an option parser if none is given\" do\n      result = klass.new([\"foo\"], nil).parse_options(nil)\n      expect(result).to eq([\"foo\"])\n    end\n\n    [\"-h\", \"--help\"].each do |help_string|\n      it \"returns nil and prints the help if '#{help_string}' is given\" do\n        instance = klass.new([help_string], nil)\n        expect(instance).to receive(:safe_puts)\n        expect(instance.parse_options(OptionParser.new)).to be_nil\n      end\n    end\n\n    it \"raises an error if invalid options are given\" do\n      instance = klass.new([\"-f\"], nil)\n      expect { instance.parse_options(OptionParser.new) }.\n        to raise_error(Vagrant::Errors::CLIInvalidOptions)\n    end\n  end\n\n  describe \"target VMs\" do\n    let(:klass) do\n      Class.new(described_class) do\n        # Make the method public since it is normally protected\n        public :with_target_vms\n      end\n    end\n\n    let(:environment) do\n      env = double(\"environment\")\n      allow(env).to receive(:root_path).and_return(\"foo\")\n      env\n    end\n\n    let(:instance)    { klass.new([], environment) }\n\n    it \"should raise an exception if a root_path is not available\" do\n      allow(environment).to receive(:root_path).and_return(nil)\n\n      expect { instance.with_target_vms }.\n        to raise_error(Vagrant::Errors::NoEnvironmentError)\n    end\n\n    it \"should yield every VM in order is no name is given\" do\n      foo_vm = double(\"foo\")\n      allow(foo_vm).to receive(:name).and_return(\"foo\")\n\n      bar_vm = double(\"bar\")\n      allow(bar_vm).to receive(:name).and_return(\"bar\")\n\n      allow(environment).to receive(:multivm?).and_return(true)\n      allow(environment).to receive(:vms).and_return({ \"foo\" => foo_vm, \"bar\" => bar_vm })\n      allow(environment).to receive(:vms_ordered).and_return([foo_vm, bar_vm])\n\n      vms = []\n      instance.with_target_vms do |vm|\n        vms << vm\n      end\n\n      expect(vms).to eq([foo_vm, bar_vm])\n    end\n\n    it \"raises an exception if the named VM doesn't exist\" do\n      allow(environment).to receive(:multivm?).and_return(true)\n      allow(environment).to receive(:vms).and_return({})\n\n      expect { instance.with_target_vms(\"foo\") }.\n        to raise_error(Vagrant::Errors::VMNotFoundError)\n    end\n\n    it \"yields the given VM if a name is given\" do\n      foo_vm = double(\"foo\")\n      allow(foo_vm).to receive(:name).and_return(:foo)\n\n      allow(environment).to receive(:multivm?).and_return(true)\n      allow(environment).to receive(:vms).and_return({ foo: foo_vm, bar: nil })\n\n      vms = []\n      instance.with_target_vms(\"foo\") { |vm| vms << vm }\n      expect(vms).to eq([foo_vm])\n    end\n  end\n\n  describe \"splitting the main and subcommand args\" do\n    let(:instance) do\n      Class.new(described_class) do\n        # Make the method public since it is normally protected\n        public :split_main_and_subcommand\n      end.new(nil, nil)\n    end\n\n    it \"should work when given all 3 parts\" do\n      result = instance.split_main_and_subcommand([\"-v\", \"status\", \"-h\", \"-v\"])\n      expect(result).to eq([[\"-v\"], \"status\", [\"-h\", \"-v\"]])\n    end\n\n    it \"should work when given only a subcommand and args\" do\n      result = instance.split_main_and_subcommand([\"status\", \"-h\"])\n      expect(result).to eq([[], \"status\", [\"-h\"]])\n    end\n\n    it \"should work when given only main flags\" do\n      result = instance.split_main_and_subcommand([\"-v\", \"-h\"])\n      expect(result).to eq([[\"-v\", \"-h\"], nil, []])\n    end\n\n    it \"should work when given only a subcommand\" do\n      result = instance.split_main_and_subcommand([\"status\"])\n      expect(result).to eq([[], \"status\", []])\n    end\n\n    it \"works when there are other non-flag args after the subcommand\" do\n      result = instance.split_main_and_subcommand([\"-v\", \"box\", \"add\", \"-h\"])\n      expect(result).to eq([[\"-v\"], \"box\", [\"add\", \"-h\"]])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v1/communicator_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::V1::Communicator do\n  let(:machine)  { Object.new }\n\n  it \"should not match by default\" do\n    expect(described_class.match?(machine)).not_to be\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v1/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::V1::Config do\n  include_context \"unit\"\n\n  let(:foo_class) do\n    Class.new(described_class) do\n      attr_accessor :one\n      attr_accessor :two\n    end\n  end\n\n  it \"has an UNSET_VALUE constant\" do\n    value = described_class.const_get(\"UNSET_VALUE\")\n    expect(value).to be_kind_of Object\n    expect(value).to eql(described_class.const_get(\"UNSET_VALUE\"))\n  end\n\n  describe \"merging\" do\n    it \"should merge by default by simply copying each instance variable\" do\n      one = foo_class.new\n      one.one = 2\n      one.two = 1\n\n      two = foo_class.new\n      two.two = 5\n\n      result = one.merge(two)\n      expect(result.one).to eq(2)\n      expect(result.two).to eq(5)\n    end\n\n    it \"doesn't merge values that start with a double underscore\" do\n      one = foo_class.new\n      one.one = 1\n      one.two = 1\n      one.instance_variable_set(:@__bar, \"one\")\n\n      two = foo_class.new\n      two.two = 2\n      two.instance_variable_set(:@__bar, \"two\")\n\n      # Merge and verify\n      result = one.merge(two)\n      expect(result.one).to eq(1)\n      expect(result.two).to eq(2)\n      expect(result.instance_variable_get(:@__bar)).to be_nil\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v1/host_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::V1::Host do\n  # No tests.\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v1/manager_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::V1::Manager do\n  include_context \"unit\"\n\n  let(:instance) { described_class.new }\n\n  def plugin\n    p = Class.new(Vagrant.plugin(\"1\"))\n    yield p\n    p\n  end\n\n  it \"should enumerate registered communicator classes\" do\n    pA = plugin do |p|\n      p.communicator(\"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.communicator(\"bar\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.communicators.length).to eq(2)\n    expect(instance.communicators[:foo]).to eq(\"bar\")\n    expect(instance.communicators[:bar]).to eq(\"baz\")\n  end\n\n  it \"should enumerate registered configuration classes\" do\n    pA = plugin do |p|\n      p.config(\"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.config(\"bar\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.config.length).to eq(2)\n    expect(instance.config[:foo]).to eq(\"bar\")\n    expect(instance.config[:bar]).to eq(\"baz\")\n  end\n\n  it \"should enumerate registered upgrade safe config classes\" do\n    pA = plugin do |p|\n      p.config(\"foo\", true) { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.config(\"bar\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.config_upgrade_safe.length).to eq(1)\n    expect(instance.config_upgrade_safe[:foo]).to eq(\"bar\")\n  end\n\n  it \"should enumerate registered guest classes\" do\n    pA = plugin do |p|\n      p.guest(\"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.guest(\"bar\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.guests.length).to eq(2)\n    expect(instance.guests[:foo]).to eq(\"bar\")\n    expect(instance.guests[:bar]).to eq(\"baz\")\n  end\n\n  it \"should enumerate registered host classes\" do\n    pA = plugin do |p|\n      p.host(\"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.host(\"bar\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.hosts.length).to eq(2)\n    expect(instance.hosts[:foo]).to eq(\"bar\")\n    expect(instance.hosts[:bar]).to eq(\"baz\")\n  end\n\n  it \"should enumerate registered provider classes\" do\n    pA = plugin do |p|\n      p.provider(\"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.provider(\"bar\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.providers.length).to eq(2)\n    expect(instance.providers[:foo]).to eq(\"bar\")\n    expect(instance.providers[:bar]).to eq(\"baz\")\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v1/plugin_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::V1::Plugin do\n  after(:each) do\n    # We want to make sure that the registered plugins remains empty\n    # after each test.\n    described_class.manager.reset!\n  end\n\n  it \"should be able to set and get the name\" do\n    plugin = Class.new(described_class) do\n      name \"foo\"\n    end\n\n    expect(plugin.name).to eq(\"foo\")\n  end\n\n  it \"should be able to set and get the description\" do\n    plugin = Class.new(described_class) do\n      description \"bar\"\n    end\n\n    expect(plugin.description).to eq(\"bar\")\n  end\n\n  describe \"action hooks\" do\n    it \"should register action hooks\" do\n      plugin = Class.new(described_class) do\n        action_hook(\"foo\") { \"bar\" }\n      end\n\n      hooks = plugin.action_hook(\"foo\")\n      expect(hooks.length).to eq(1)\n      expect(hooks[0].call).to eq(\"bar\")\n    end\n  end\n\n  describe \"commands\" do\n    it \"should register command classes\" do\n      plugin = Class.new(described_class) do\n        command(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.command[:foo]).to eq(\"bar\")\n    end\n\n    [\"spaces bad\", \"sym^bols\"].each do |bad|\n      it \"should not allow bad command name: #{bad}\" do\n        plugin = Class.new(described_class)\n\n        expect { plugin.command(bad) {} }.\n          to raise_error(Vagrant::Plugin::V1::InvalidCommandName)\n      end\n    end\n\n    it \"should lazily register command classes\" do\n      # Below would raise an error if the value of the command class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n        command(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the command key that\n      # a proper error is raised.\n      expect {\n        plugin.command[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"communicators\" do\n    it \"should register communicator classes\" do\n      plugin = Class.new(described_class) do\n        communicator(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.communicator[:foo]).to eq(\"bar\")\n    end\n\n    it \"should lazily register communicator classes\" do\n      # Below would raise an error if the value of the class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n        communicator(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the configuration key that\n      # a proper error is raised.\n      expect {\n        plugin.communicator[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"configuration\" do\n    it \"should register configuration classes\" do\n      plugin = Class.new(described_class) do\n        config(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.config[:foo]).to eq(\"bar\")\n    end\n\n    it \"should lazily register configuration classes\" do\n      # Below would raise an error if the value of the config class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n        config(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the configuration key that\n      # a proper error is raised.\n      expect {\n        plugin.config[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"guests\" do\n    it \"should register guest classes\" do\n      plugin = Class.new(described_class) do\n        guest(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.guest[:foo]).to eq(\"bar\")\n    end\n\n    it \"should lazily register guest classes\" do\n      # Below would raise an error if the value of the guest class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n          guest(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the guest key that\n      # a proper error is raised.\n      expect {\n        plugin.guest[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"hosts\" do\n    it \"should register host classes\" do\n      plugin = Class.new(described_class) do\n        host(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.host[:foo]).to eq(\"bar\")\n    end\n\n    it \"should lazily register host classes\" do\n      # Below would raise an error if the value of the host class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n          host(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the host key that\n      # a proper error is raised.\n      expect {\n        plugin.host[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"providers\" do\n    it \"should register provider classes\" do\n      plugin = Class.new(described_class) do\n        provider(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.provider[:foo]).to eq(\"bar\")\n    end\n\n    it \"should lazily register provider classes\" do\n      # Below would raise an error if the value of the config class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n          provider(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the configuration key that\n      # a proper error is raised.\n      expect {\n        plugin.provider[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"provisioners\" do\n    it \"should register provisioner classes\" do\n      plugin = Class.new(described_class) do\n        provisioner(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.provisioner[:foo]).to eq(\"bar\")\n    end\n\n    it \"should lazily register provisioner classes\" do\n      # Below would raise an error if the value of the config class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n          provisioner(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the configuration key that\n      # a proper error is raised.\n      expect {\n        plugin.provisioner[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"plugin registration\" do\n    let(:manager) { described_class.manager }\n\n    it \"should have no registered plugins\" do\n      expect(manager.registered).to be_empty\n    end\n\n    it \"should register a plugin when a name is set\" do\n      plugin = Class.new(described_class) do\n        name \"foo\"\n      end\n\n      expect(manager.registered).to eq([plugin])\n    end\n\n    it \"should register a plugin only once\" do\n      plugin = Class.new(described_class) do\n        name \"foo\"\n        name \"bar\"\n      end\n\n      expect(manager.registered).to eq([plugin])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v1/provider_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::V1::Provider do\n  let(:machine)  { Object.new }\n  let(:instance) { described_class.new(machine) }\n\n  it \"should return nil by default for actions\" do\n    expect(instance.action(:whatever)).to be_nil\n  end\n\n  it \"should return nil by default for ssh info\" do\n    expect(instance.ssh_info).to be_nil\n  end\n\n  it \"should return nil by default for state\" do\n    expect(instance.state).to be_nil\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v2/command_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\nrequire 'optparse'\nrequire 'vagrant/machine_index'\n\ndescribe Vagrant::Plugin::V2::Command do\n  include_context \"unit\"\n\n  describe \"parsing options\" do\n    let(:klass) do\n      Class.new(described_class) do\n        # Make the method public since it is normally protected\n        public :parse_options\n      end\n    end\n\n    it \"returns the remaining arguments\" do\n      options = {}\n      opts = OptionParser.new do |o|\n        o.on(\"-f\") do |f|\n          options[:f] = f\n        end\n      end\n\n      result = klass.new([\"-f\", \"foo\"], nil).parse_options(opts)\n\n      # Check the results\n      expect(options[:f]).to be\n      expect(result).to eq([\"foo\"])\n    end\n\n    it \"creates an option parser if none is given\" do\n      result = klass.new([\"foo\"], nil).parse_options(nil)\n      expect(result).to eq([\"foo\"])\n    end\n\n    [\"-h\", \"--help\"].each do |help_string|\n      it \"returns nil and prints the help if '#{help_string}' is given\" do\n        instance = klass.new([help_string], nil)\n        expect(instance).to receive(:safe_puts)\n        expect(instance.parse_options(OptionParser.new)).to be_nil\n      end\n    end\n\n    it \"raises an error if invalid options are given\" do\n      instance = klass.new([\"-f\"], nil)\n      expect { instance.parse_options(OptionParser.new) }.\n        to raise_error(Vagrant::Errors::CLIInvalidOptions)\n    end\n\n    it \"raises an error if ambiguous options are given\" do\n      instance = klass.new([\"-provision\"], nil)\n      expect { instance.parse_options(OptionParser.new) }.\n        to raise_error(Vagrant::Errors::CLIInvalidOptions)\n    end\n\n    it \"raises an error if options without a value are given\" do\n      opts = OptionParser.new do |o|\n        o.on(\"--provision-with x,y,z\", Array, \"Example\") { |f| }\n      end\n\n\n      instance = klass.new([\"--provision-with\"], nil)\n      expect { instance.parse_options(opts) }.\n        to raise_error(Vagrant::Errors::CLIInvalidOptions)\n    end\n  end\n\n  describe \"target VMs\" do\n    let(:klass) do\n      Class.new(described_class) do\n        # Make the method public since it is normally protected\n        public :with_target_vms\n      end\n    end\n\n    let(:environment) do\n      # We have to create a Vagrantfile so there is a root path\n      test_iso_env.vagrantfile(\"\")\n      test_iso_env.create_vagrant_env\n    end\n    let(:test_iso_env) { isolated_environment }\n\n    let(:instance)    { klass.new([], environment) }\n\n    subject { instance }\n\n    it \"should raise an exception if a root_path is not available\" do\n      allow(environment).to receive(:root_path).and_return(nil)\n\n      expect { instance.with_target_vms }.\n        to raise_error(Vagrant::Errors::NoEnvironmentError)\n    end\n\n    it \"should yield every VM in order if no name is given\" do\n      foo_vm = double(\"foo\")\n      allow(foo_vm).to receive(:name).and_return(\"foo\")\n      allow(foo_vm).to receive(:provider).and_return(:foobarbaz)\n      allow(foo_vm).to receive(:ui).and_return(Vagrant::UI::Silent.new)\n      allow(foo_vm).to receive(:state).and_return(nil)\n\n      bar_vm = double(\"bar\")\n      allow(bar_vm).to receive(:name).and_return(\"bar\")\n      allow(bar_vm).to receive(:provider).and_return(:foobarbaz)\n      allow(bar_vm).to receive(:ui).and_return(Vagrant::UI::Silent.new)\n      allow(bar_vm).to receive(:state).and_return(nil)\n\n      allow(environment).to receive(:machine_names).and_return([:foo, :bar])\n      allow(environment).to receive(:machine).with(:foo, environment.default_provider).and_return(foo_vm)\n      allow(environment).to receive(:machine).with(:bar, environment.default_provider).and_return(bar_vm)\n\n      vms = []\n      instance.with_target_vms do |vm|\n        vms << vm\n      end\n\n      expect(vms).to eq([foo_vm, bar_vm])\n    end\n\n    it \"raises an exception if the named VM doesn't exist\" do\n      allow(environment).to receive(:machine_names).and_return([:default])\n      allow(environment).to receive(:machine).with(:foo, anything).and_return(nil)\n\n      expect { instance.with_target_vms(\"foo\") }.\n        to raise_error(Vagrant::Errors::VMNotFoundError)\n    end\n\n    it \"yields the given VM if a name is given\" do\n      foo_vm = double(\"foo\")\n      allow(foo_vm).to receive(:name).and_return(\"foo\")\n      allow(foo_vm).to receive(:provider).and_return(:foobarbaz)\n      allow(foo_vm).to receive(:ui).and_return(Vagrant::UI::Silent.new)\n      allow(foo_vm).to receive(:state).and_return(nil)\n\n      allow(environment).to receive(:machine).with(:foo, environment.default_provider).and_return(foo_vm)\n\n      vms = []\n      instance.with_target_vms(\"foo\") { |vm| vms << vm }\n      expect(vms).to eq([foo_vm])\n    end\n\n    it \"calls state after yielding the vm to update the machine index\" do\n      foo_vm = double(\"foo\")\n      allow(foo_vm).to receive(:name).and_return(\"foo\")\n      allow(foo_vm).to receive(:provider).and_return(:foobarbaz)\n      allow(foo_vm).to receive(:ui).and_return(Vagrant::UI::Silent.new)\n      allow(foo_vm).to receive(:state).and_return(nil)\n\n      allow(environment).to receive(:machine).with(:foo, environment.default_provider).and_return(foo_vm)\n\n      vms = []\n      expect(foo_vm).to receive(:state)\n      instance.with_target_vms(\"foo\") { |vm| vms << vm }\n    end\n\n    it \"does not recover the vm if it has no uuid\" do\n      foo_vm = double(\"foo\")\n      provider = :foobarbaz\n      state_id = :some_state\n      allow(foo_vm).to receive(:name).and_return(\"foo\")\n      allow(foo_vm).to receive(:provider).and_return(provider)\n      allow(foo_vm).to receive(:ui).and_return(Vagrant::UI::Silent.new)\n      allow(foo_vm).to receive(:state).and_return(double(\"state\", id: state_id))\n      allow(foo_vm).to receive(:index_uuid).and_return(nil)\n      allow(environment).to receive(:machine).with(:foo, provider).and_return(foo_vm)\n      \n      expect(foo_vm).not_to receive(:recover_machine).with(state_id)\n      vms = []\n      instance.with_target_vms(\"foo\", provider: provider) { |vm| vms << vm }\n      expect(vms).to eq([foo_vm])\n    end\n\n    it \"recovers the vm\" do\n      foo_vm = double(\"foo\")\n      provider = :foobarbaz\n      state_id = :some_state\n      allow(foo_vm).to receive(:name).and_return(\"foo\")\n      allow(foo_vm).to receive(:provider).and_return(provider)\n      allow(foo_vm).to receive(:ui).and_return(Vagrant::UI::Silent.new)\n      allow(foo_vm).to receive(:state).and_return(double(\"state\", id: state_id))\n      allow(foo_vm).to receive(:index_uuid).and_return(\"someuuid\")\n      allow(environment).to receive(:machine).with(:foo, provider).and_return(foo_vm)\n      \n      expect(foo_vm).to receive(:recover_machine).with(state_id)\n      vms = []\n      instance.with_target_vms(\"foo\", provider: provider) { |vm| vms << vm }\n      expect(vms).to eq([foo_vm])\n    end\n\n    it \"yields the given VM with proper provider if given\" do\n      foo_vm = double(\"foo\")\n      provider = :foobarbaz\n\n      allow(foo_vm).to receive(:name).and_return(\"foo\")\n      allow(foo_vm).to receive(:provider).and_return(provider)\n      allow(foo_vm).to receive(:ui).and_return(Vagrant::UI::Silent.new)\n      allow(foo_vm).to receive(:state).and_return(nil)\n      allow(environment).to receive(:machine).with(:foo, provider).and_return(foo_vm)\n\n      vms = []\n      instance.with_target_vms(\"foo\", provider: provider) { |vm| vms << vm }\n      expect(vms).to eq([foo_vm])\n    end\n\n    it \"should raise an exception if an active machine exists with a different provider\" do\n      name = :foo\n\n      allow(environment).to receive(:active_machines).and_return([[name, :vmware]])\n      expect { instance.with_target_vms(name.to_s, provider: :foo) }.\n        to raise_error Vagrant::Errors::ActiveMachineWithDifferentProvider\n    end\n\n    it \"should default to the active machine provider if no explicit provider requested\" do\n      name = :foo\n      provider = :vmware\n      vmware_vm = double(\"vmware_vm\")\n\n      allow(environment).to receive(:active_machines).and_return([[name, provider]])\n      allow(environment).to receive(:machine).with(name, provider).and_return(vmware_vm)\n      allow(vmware_vm).to receive(:name).and_return(name)\n      allow(vmware_vm).to receive(:provider).and_return(provider)\n      allow(vmware_vm).to receive(:ui).and_return(Vagrant::UI::Silent.new)\n      allow(vmware_vm).to receive(:state).and_return(nil)\n\n      vms = []\n      instance.with_target_vms(name.to_s) { |vm| vms << vm }\n      expect(vms).to eq([vmware_vm])\n    end\n\n    it \"should use the explicit provider if it maches the active machine\" do\n      name = :foo\n      provider = :vmware\n      vmware_vm = double(\"vmware_vm\")\n\n      allow(environment).to receive(:active_machines).and_return([[name, provider]])\n      allow(environment).to receive(:machine).with(name, provider).and_return(vmware_vm)\n      allow(vmware_vm).to receive(:name).and_return(name)\n      allow(vmware_vm).to receive(:provider).and_return(provider)\n      allow(vmware_vm).to receive(:ui).and_return(Vagrant::UI::Silent.new)\n      allow(vmware_vm).to receive(:state).and_return(nil)\n\n      vms = []\n      instance.with_target_vms(name.to_s, provider: provider) { |vm| vms << vm }\n      expect(vms).to eq([vmware_vm])\n    end\n\n    it \"should use the default provider if none is given and none are active\" do\n      name = :foo\n      machine = double(\"machine\")\n\n      allow(environment).to receive(:machine).with(name, environment.default_provider).and_return(machine)\n      allow(machine).to receive(:name).and_return(name)\n      allow(machine).to receive(:provider).and_return(environment.default_provider)\n      allow(machine).to receive(:ui).and_return(Vagrant::UI::Silent.new)\n      allow(machine).to receive(:state).and_return(nil)\n\n      results = []\n      instance.with_target_vms(name.to_s) { |m| results << m }\n      expect(results).to eq([machine])\n    end\n\n    it \"should use the primary machine with the active provider\" do\n      name = :foo\n      provider = :vmware\n      vmware_vm = double(\"vmware_vm\")\n\n      allow(environment).to receive(:active_machines).and_return([[name, provider]])\n      allow(environment).to receive(:machine).with(name, provider).and_return(vmware_vm)\n      allow(environment).to receive(:machine_names).and_return([])\n      allow(environment).to receive(:primary_machine_name).and_return(name)\n      allow(vmware_vm).to receive(:name).and_return(name)\n      allow(vmware_vm).to receive(:provider).and_return(provider)\n      allow(vmware_vm).to receive(:ui).and_return(Vagrant::UI::Silent.new)\n      allow(vmware_vm).to receive(:state).and_return(nil)\n\n      vms = []\n      instance.with_target_vms(nil, single_target: true) { |vm| vms << vm }\n      expect(vms).to eq([vmware_vm])\n    end\n\n    it \"should use the primary machine with the default provider\" do\n      name = :foo\n      machine = double(\"machine\")\n\n      allow(environment).to receive(:active_machines).and_return([])\n      allow(environment).to receive(:machine).with(name, environment.default_provider).and_return(machine)\n      allow(environment).to receive(:machine_names).and_return([])\n      allow(environment).to receive(:primary_machine_name).and_return(name)\n      allow(machine).to receive(:name).and_return(name)\n      allow(machine).to receive(:provider).and_return(environment.default_provider)\n      allow(machine).to receive(:ui).and_return(Vagrant::UI::Silent.new)\n      allow(machine).to receive(:state).and_return(nil)\n\n      vms = []\n      instance.with_target_vms(nil, single_target: true) { |vm| vms << machine }\n      expect(vms).to eq([machine])\n    end\n\n    it \"should yield machines from another environment\" do\n      iso_env       = isolated_environment\n      iso_env.vagrantfile(\"\")\n      other_env     = iso_env.create_vagrant_env(\n        home_path: environment.home_path)\n      other_machine = other_env.machine(\n        other_env.machine_names[0], other_env.default_provider)\n\n      # Set an ID on it so that it is \"created\" in the index\n      other_machine.id = \"foo\"\n\n      # Make sure we don't have a root path, to test\n      allow(environment).to receive(:root_path).and_return(nil)\n\n      results = []\n      subject.with_target_vms(other_machine.index_uuid) { |m| results << m }\n\n      expect(results.length).to eq(1)\n      expect(results[0].id).to eq(other_machine.id)\n    end\n\n    it \"should yield machines from another environment\" do\n      iso_env       = isolated_environment\n      iso_env.vagrantfile(\"\")\n      other_env     = iso_env.create_vagrant_env(\n        home_path: environment.home_path)\n      other_machine = other_env.machine(\n        other_env.machine_names[0], other_env.default_provider)\n\n      # Set an ID on it so that it is \"created\" in the index\n      other_machine.id = \"foo\"\n\n      # Grab the uuid so we know what it is\n      index_uuid = other_machine.index_uuid\n\n      # Remove the working directory\n      FileUtils.rm_rf(iso_env.workdir)\n\n      # Make sure we don't have a root path, to test\n      allow(environment).to receive(:root_path).and_return(nil)\n\n      # Run the command\n      expect {\n          subject.with_target_vms(index_uuid) { |*args| }\n      }.to raise_error(Vagrant::Errors::EnvironmentNonExistentCWD)\n\n      # Verify that it no longer exists in the index\n      expect(other_env.machine_index.get(index_uuid)).to be_nil\n    end\n  end\n\n  describe \"splitting the main and subcommand args\" do\n    let(:instance) do\n      Class.new(described_class) do\n        # Make the method public since it is normally protected\n        public :split_main_and_subcommand\n      end.new(nil, nil)\n    end\n\n    it \"should work when given all 3 parts\" do\n      result = instance.split_main_and_subcommand([\"-v\", \"status\", \"-h\", \"-v\"])\n      expect(result).to eq([[\"-v\"], \"status\", [\"-h\", \"-v\"]])\n    end\n\n    it \"should work when given only a subcommand and args\" do\n      result = instance.split_main_and_subcommand([\"status\", \"-h\"])\n      expect(result).to eq([[], \"status\", [\"-h\"]])\n    end\n\n    it \"should work when given only main flags\" do\n      result = instance.split_main_and_subcommand([\"-v\", \"-h\"])\n      expect(result).to eq([[\"-v\", \"-h\"], nil, []])\n    end\n\n    it \"should work when given only a subcommand\" do\n      result = instance.split_main_and_subcommand([\"status\"])\n      expect(result).to eq([[], \"status\", []])\n    end\n\n    it \"works when there are other non-flag args after the subcommand\" do\n      result = instance.split_main_and_subcommand([\"-v\", \"box\", \"add\", \"-h\"])\n      expect(result).to eq([[\"-v\"], \"box\", [\"add\", \"-h\"]])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v2/communicator_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::V2::Communicator do\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v2/components_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\nrequire \"vagrant/registry\"\n\ndescribe Vagrant::Plugin::V2::Components do\n  subject { described_class.new }\n\n  it \"should have synced folders\" do\n    expect(subject.synced_folders).to be_kind_of(Vagrant::Registry)\n  end\n\n  describe \"configs\" do\n    it \"should have configs\" do\n      expect(subject.configs).to be_kind_of(Hash)\n    end\n\n    it \"should default the values to registries\" do\n      expect(subject.configs[:i_probably_dont_exist]).to be_kind_of(Vagrant::Registry)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v2/config_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::V2::Config do\n  include_context \"unit\"\n\n  let(:foo_class) do\n    Class.new(described_class) do\n      attr_accessor :one\n      attr_accessor :two\n    end\n  end\n\n  let(:unset_value) { described_class.const_get(\"UNSET_VALUE\") }\n\n  subject { foo_class.new }\n\n  describe \"#merge\" do\n    it \"should merge by default by simply copying each instance variable\" do\n      one = foo_class.new\n      one.one = 2\n      one.two = 1\n\n      two = foo_class.new\n      two.two = 5\n\n      result = one.merge(two)\n      expect(result.one).to eq(2)\n      expect(result.two).to eq(5)\n    end\n\n    it \"prefers any set value over an UNSET_VALUE\" do\n      one = foo_class.new\n      one.one = 1\n      one.two = 2\n\n      two = foo_class.new\n      two.one = unset_value\n      two.two = 5\n\n      result = one.merge(two)\n      expect(result.one).to eq(1)\n      expect(result.two).to eq(5)\n    end\n\n    it \"doesn't merge values that start with a double underscore\" do\n      one = foo_class.new\n      one.one = 1\n      one.two = 1\n      one.instance_variable_set(:@__bar, \"one\")\n\n      two = foo_class.new\n      two.two = 2\n      two.instance_variable_set(:@__bar, \"two\")\n\n      # Merge and verify\n      result = one.merge(two)\n      expect(result.one).to eq(1)\n      expect(result.two).to eq(2)\n      expect(result.instance_variable_get(:@__bar)).to be_nil\n    end\n  end\n\n  describe \"#method_missing\" do\n    it \"returns a DummyConfig object\" do\n      expect(subject.i_should_not_exist).\n        to be_kind_of(Vagrant::Config::V2::DummyConfig)\n    end\n\n    it \"raises an error if finalized (internally)\" do\n      subject._finalize!\n\n      expect { subject.i_should_not_exist }.\n        to raise_error(NoMethodError)\n    end\n\n    it \"should survive being the last arg to a method that captures kwargs without a ruby conversion error\" do\n      arg_capturer = lambda { |*args, **kwargs| }\n      expect {\n        arg_capturer.call(subject)\n      }.to_not raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v2/host_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::V2::Host do\n  # No tests.\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v2/manager_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::V2::Manager do\n  include_context \"unit\"\n\n  let(:instance) { described_class.new }\n\n  def plugin\n    p = Class.new(Vagrant.plugin(\"2\"))\n    yield p\n    p\n  end\n\n  describe \"#generate_hook_keys\" do\n    it \"should return array with one value\" do\n      expect(subject.generate_hook_keys(:test_value)).to eq([\"test_value\"])\n    end\n\n    it \"should return array with two values when key is camel cased\" do\n      result = subject.generate_hook_keys(\"TestValue\")\n      expect(result.size).to eq(2)\n      expect(result).to include(\"TestValue\")\n      expect(result).to include(\"test_value\")\n    end\n\n    it \"should handle class/module value\" do\n      result = subject.generate_hook_keys(Vagrant)\n      expect(result.size).to eq(2)\n      expect(result).to include(\"Vagrant\")\n      expect(result).to include(\"vagrant\")\n    end\n\n    it \"should handle namespaced value\" do\n      result = subject.generate_hook_keys(Vagrant::Plugin)\n      expect(result.size).to eq(4)\n      expect(result).to include(\"Vagrant::Plugin\")\n      expect(result).to include(\"Plugin\")\n      expect(result).to include(\"vagrant_plugin\")\n      expect(result).to include(\"plugin\")\n    end\n  end\n\n  describe \"#find_action_hooks\" do\n    let(:hook_name) { \"Vagrant::Plugin\" }\n\n    before do\n      h_name = hook_name\n      pA = plugin do |p|\n        p.action_hook(:test, h_name) { \"hook_called\" }\n      end\n      subject.register(pA)\n    end\n\n    it \"should find hook with full namespace\" do\n      hooks = subject.find_action_hooks(Vagrant::Plugin)\n      expect(hooks).not_to be_empty\n      expect(hooks.first.call).to eq(\"hook_called\")\n    end\n\n    it \"should not find hook with short class name\" do\n      hooks = subject.find_action_hooks(\"Plugin\")\n      expect(hooks).to be_empty\n    end\n\n    it \"should not find hook with full snake cased name\" do\n      hooks = subject.find_action_hooks(:vagrant_plugin)\n      expect(hooks).to be_empty\n    end\n\n    it \"should not find hook with short snake cased name\" do\n      hooks = subject.find_action_hooks(\"plugin\")\n      expect(hooks).to be_empty\n    end\n\n    context \"when hook uses full snake cased name\" do\n      let(:hook_name) { :vagrant_plugin }\n\n      it \"should find hook with full namespace\" do\n        hooks = subject.find_action_hooks(Vagrant::Plugin)\n        expect(hooks).not_to be_empty\n        expect(hooks.first.call).to eq(\"hook_called\")\n      end\n\n      it \"should find hook with full snake cased name\" do\n        hooks = subject.find_action_hooks(:vagrant_plugin)\n        expect(hooks).not_to be_empty\n        expect(hooks.first.call).to eq(\"hook_called\")\n      end\n\n      it \"should not find hook with short class name\" do\n        hooks = subject.find_action_hooks(\"Plugin\")\n        expect(hooks).to be_empty\n      end\n\n      it \"should not find hook with short snake cased name\" do\n        hooks = subject.find_action_hooks(\"plugin\")\n        expect(hooks).to be_empty\n      end\n    end\n\n    context \"when hook uses short snake cased name\" do\n      let(:hook_name) { :plugin }\n\n      it \"should find hook with full namespace\" do\n        hooks = subject.find_action_hooks(Vagrant::Plugin)\n        expect(hooks).not_to be_empty\n        expect(hooks.first.call).to eq(\"hook_called\")\n      end\n\n      it \"should find hook with short class name\" do\n        hooks = subject.find_action_hooks(\"Plugin\")\n        expect(hooks).not_to be_empty\n        expect(hooks.first.call).to eq(\"hook_called\")\n      end\n\n      it \"should find hook with short snake cased name\" do\n        hooks = subject.find_action_hooks(\"plugin\")\n        expect(hooks).not_to be_empty\n        expect(hooks.first.call).to eq(\"hook_called\")\n      end\n\n      it \"should not find hook with full snake cased name\" do\n        hooks = subject.find_action_hooks(:vagrant_plugin)\n        expect(hooks).to be_empty\n      end\n    end\n\n  end\n\n  describe \"#action_hooks\" do\n    it \"should contain globally registered hooks\" do\n      pA = plugin do |p|\n        p.action_hook(\"foo\") { \"bar\" }\n      end\n\n      pB = plugin do |p|\n        p.action_hook(\"bar\") { \"baz\" }\n      end\n\n      instance.register(pA)\n      instance.register(pB)\n\n      result = instance.action_hooks(nil)\n      expect(result.length).to eq(2)\n      expect(result[0].call).to eq(\"bar\")\n      expect(result[1].call).to eq(\"baz\")\n    end\n\n    it \"should contain specific hooks with globally registered hooks\" do\n      pA = plugin do |p|\n        p.action_hook(\"foo\") { \"bar\" }\n        p.action_hook(\"foo\", :foo) { \"bar_foo\" }\n        p.action_hook(\"foo\", :bar) { \"bar_bar\" }\n      end\n\n      pB = plugin do |p|\n        p.action_hook(\"bar\") { \"baz\" }\n      end\n\n      instance.register(pA)\n      instance.register(pB)\n\n      result = instance.action_hooks(:foo)\n      expect(result.length).to eq(3)\n      expect(result[0].call).to eq(\"bar\")\n      expect(result[1].call).to eq(\"bar_foo\")\n      expect(result[2].call).to eq(\"baz\")\n    end\n  end\n\n  it \"should enumerate registered communicator classes\" do\n    pA = plugin do |p|\n      p.communicator(\"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.communicator(\"bar\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.communicators.to_hash.length).to eq(2)\n    expect(instance.communicators[:foo]).to eq(\"bar\")\n    expect(instance.communicators[:bar]).to eq(\"baz\")\n  end\n\n  it \"should enumerate registered configuration classes\" do\n    pA = plugin do |p|\n      p.config(\"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.config(\"bar\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.config.to_hash.length).to eq(2)\n    expect(instance.config[:foo]).to eq(\"bar\")\n    expect(instance.config[:bar]).to eq(\"baz\")\n  end\n\n  it \"should enumerate registered guest classes\" do\n    pA = plugin do |p|\n      p.guest(\"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.guest(\"bar\", \"foo\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.guests.to_hash.length).to eq(2)\n    expect(instance.guests[:foo]).to eq([\"bar\", nil])\n    expect(instance.guests[:bar]).to eq([\"baz\", :foo])\n  end\n\n  it \"should enumerate registered guest capabilities\" do\n    pA = plugin do |p|\n      p.guest_capability(\"foo\", \"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.guest_capability(\"bar\", \"foo\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.guest_capabilities.length).to eq(2)\n    expect(instance.guest_capabilities[:foo][:foo]).to eq(\"bar\")\n    expect(instance.guest_capabilities[:bar][:foo]).to eq(\"baz\")\n  end\n\n  it \"should enumerate registered host classes\" do\n    pA = plugin do |p|\n      p.host(\"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.host(\"bar\", \"foo\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.hosts.to_hash.length).to eq(2)\n    expect(instance.hosts[:foo]).to eq([\"bar\", nil])\n    expect(instance.hosts[:bar]).to eq([\"baz\", :foo])\n  end\n\n  it \"should enumerate registered host capabilities\" do\n    pA = plugin do |p|\n      p.host_capability(\"foo\", \"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.host_capability(\"bar\", \"foo\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.host_capabilities.length).to eq(2)\n    expect(instance.host_capabilities[:foo][:foo]).to eq(\"bar\")\n    expect(instance.host_capabilities[:bar][:foo]).to eq(\"baz\")\n  end\n\n  it \"should enumerate registered provider classes\" do\n    pA = plugin do |p|\n      p.provider(\"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.provider(\"bar\", foo: \"bar\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.providers.to_hash.length).to eq(2)\n    expect(instance.providers[:foo]).to eq([\"bar\", { priority: 5 }])\n    expect(instance.providers[:bar]).to eq([\"baz\", { foo: \"bar\", priority: 5 }])\n  end\n\n  it \"provides the collection of registered provider configs\" do\n    pA = plugin do |p|\n      p.config(\"foo\", :provider) { \"foo\" }\n    end\n\n    pB = plugin do |p|\n      p.config(\"bar\", :provider) { \"bar\" }\n      p.config(\"baz\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.provider_configs.to_hash.length).to eq(2)\n    expect(instance.provider_configs[:foo]).to eq(\"foo\")\n    expect(instance.provider_configs[:bar]).to eq(\"bar\")\n  end\n\n  it \"should enumerate registered push classes\" do\n    pA = plugin do |p|\n      p.push(\"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.push(\"bar\", foo: \"bar\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.pushes.to_hash.length).to eq(2)\n    expect(instance.pushes[:foo]).to eq([\"bar\", nil])\n    expect(instance.pushes[:bar]).to eq([\"baz\", { foo: \"bar\" }])\n  end\n\n  it \"provides the collection of registered push configs\" do\n    pA = plugin do |p|\n      p.config(\"foo\", :push) { \"foo\" }\n    end\n\n    pB = plugin do |p|\n      p.config(\"bar\", :push) { \"bar\" }\n      p.config(\"baz\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.push_configs.to_hash.length).to eq(2)\n    expect(instance.push_configs[:foo]).to eq(\"foo\")\n    expect(instance.push_configs[:bar]).to eq(\"bar\")\n  end\n\n\n  it \"should enumerate all registered synced folder implementations\" do\n    pA = plugin do |p|\n      p.synced_folder(\"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.synced_folder(\"bar\", 50) { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.synced_folders.to_hash.length).to eq(2)\n    expect(instance.synced_folders[:foo]).to eq([\"bar\", 10])\n    expect(instance.synced_folders[:bar]).to eq([\"baz\", 50])\n  end\n\n  it \"should enumerate registered synced_folder_capabilities classes\" do\n    pA = plugin do |p|\n      p.synced_folder_capability(\"foo\", \"foo\") { \"bar\" }\n    end\n\n    pB = plugin do |p|\n      p.synced_folder_capability(\"bar\", \"bar\") { \"baz\" }\n    end\n\n    instance.register(pA)\n    instance.register(pB)\n\n    expect(instance.synced_folder_capabilities.to_hash.length).to eq(2)\n    expect(instance.synced_folder_capabilities[:foo][:foo]).to eq(\"bar\")\n    expect(instance.synced_folder_capabilities[:bar][:bar]).to eq(\"baz\")\n  end\n\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v2/plugin_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::V2::Plugin do\n  before do\n    allow(described_class).to receive(:manager)\n      .and_return(Vagrant::Plugin::V2::Manager.new)\n  end\n\n  it \"should be able to set and get the name\" do\n    plugin = Class.new(described_class) do\n      name \"foo\"\n    end\n\n    expect(plugin.name).to eq(\"foo\")\n  end\n\n  it \"should be able to set and get the description\" do\n    plugin = Class.new(described_class) do\n      description \"bar\"\n    end\n\n    expect(plugin.description).to eq(\"bar\")\n  end\n\n  describe \"action hooks\" do\n    it \"should register on all actions by default\" do\n      plugin = Class.new(described_class) do\n        action_hook(\"foo\") { \"bar\" }\n      end\n\n      hooks_registry = plugin.components.action_hooks\n      hooks = hooks_registry[described_class.const_get(\"ALL_ACTIONS\")]\n      expect(hooks.length).to eq(1)\n      expect(hooks[0].call).to eq(\"bar\")\n    end\n\n    it \"should register for a specific action by default\" do\n      plugin = Class.new(described_class) do\n        action_hook(\"foo\", :bar) { \"bar\" }\n      end\n\n      hooks_registry = plugin.components.action_hooks\n      hooks = hooks_registry[:bar]\n      expect(hooks.length).to eq(1)\n      expect(hooks[0].call).to eq(\"bar\")\n    end\n  end\n\n  describe \"commands\" do\n    it \"should register command classes\" do\n      plugin = Class.new(described_class) do\n        command(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.components.commands.keys).to be_include(:foo)\n      expect(plugin.components.commands[:foo][0].call).to eql(\"bar\")\n    end\n\n    it \"should register command classes with options\" do\n      plugin = Class.new(described_class) do\n        command(\"foo\", opt: :bar) { \"bar\" }\n      end\n\n      expect(plugin.components.commands.keys).to be_include(:foo)\n      expect(plugin.components.commands[:foo][0].call).to eql(\"bar\")\n      expect(plugin.components.commands[:foo][1][:opt]).to eql(:bar)\n    end\n\n    it \"should register commands as primary by default\" do\n      plugin = Class.new(described_class) do\n        command(\"foo\") { \"bar\" }\n        command(\"bar\", primary: false) { \"bar\" }\n      end\n\n      expect(plugin.components.commands[:foo][1][:primary]).to be(true)\n      expect(plugin.components.commands[:bar][1][:primary]).to be(false)\n    end\n\n    [\"spaces bad\", \"sym^bols\"].each do |bad|\n      it \"should not allow bad command name: #{bad}\" do\n        plugin = Class.new(described_class)\n\n        expect { plugin.command(bad) {} }.\n          to raise_error(Vagrant::Plugin::V2::InvalidCommandName)\n      end\n    end\n\n    it \"should lazily register command classes\" do\n      # Below would raise an error if the value of the command class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n        command(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the command key that\n      # a proper error is raised.\n      expect {\n        plugin.components.commands[:foo][0].call\n      }.to raise_error(StandardError, \"FAIL!\")\n    end\n  end\n\n  describe \"communicators\" do\n    it \"should register communicator classes\" do\n      plugin = Class.new(described_class) do\n        communicator(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.communicator[:foo]).to eq(\"bar\")\n    end\n\n    it \"should lazily register communicator classes\" do\n      # Below would raise an error if the value of the class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n        communicator(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the configuration key that\n      # a proper error is raised.\n      expect {\n        plugin.communicator[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"configuration\" do\n    it \"should register configuration classes\" do\n      plugin = Class.new(described_class) do\n        config(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.components.configs[:top][:foo]).to eq(\"bar\")\n    end\n\n    it \"should lazily register configuration classes\" do\n      # Below would raise an error if the value of the config class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n        config(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the configuration key that\n      # a proper error is raised.\n      expect {\n        plugin.components.configs[:top][:foo]\n      }.to raise_error(StandardError)\n    end\n\n    it \"should register configuration classes for providers\" do\n      plugin = Class.new(described_class) do\n        config(\"foo\", :provider) { \"bar\" }\n      end\n\n      expect(plugin.components.configs[:provider][:foo]).to eq(\"bar\")\n    end\n  end\n\n  describe \"guests\" do\n    it \"should register guest classes\" do\n      plugin = Class.new(described_class) do\n        guest(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.components.guests[:foo]).to eq([\"bar\", nil])\n    end\n\n    it \"should lazily register guest classes\" do\n      # Below would raise an error if the value of the guest class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n          guest(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the guest key that\n      # a proper error is raised.\n      expect {\n        plugin.guest[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"guest capabilities\" do\n    it \"should register guest capabilities\" do\n      plugin = Class.new(described_class) do\n        guest_capability(\"foo\", \"bar\") { \"baz\" }\n      end\n\n      expect(plugin.components.guest_capabilities[:foo][:bar]).to eq(\"baz\")\n    end\n  end\n\n  describe \"hosts\" do\n    it \"should register host classes\" do\n      plugin = Class.new(described_class) do\n        host(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.components.hosts[:foo]).to eq([\"bar\", nil])\n    end\n\n    it \"should lazily register host classes\" do\n      # Below would raise an error if the value of the host class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n          host(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the host key that\n      # a proper error is raised.\n      expect {\n        plugin.host[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"host capabilities\" do\n    it \"should register host capabilities\" do\n      plugin = Class.new(described_class) do\n        host_capability(\"foo\", \"bar\") { \"baz\" }\n      end\n\n      expect(plugin.components.host_capabilities[:foo][:bar]).to eq(\"baz\")\n    end\n  end\n\n  describe \"providers\" do\n    it \"should register provider classes\" do\n      plugin = Class.new(described_class) do\n        provider(\"foo\") { \"bar\" }\n      end\n\n      result = plugin.components.providers[:foo]\n      expect(result[0]).to eq(\"bar\")\n      expect(result[1][:priority]).to eq(5)\n    end\n\n    it \"should register provider classes with options\" do\n      plugin = Class.new(described_class) do\n        provider(\"foo\", foo: \"yep\") { \"bar\" }\n      end\n\n      result = plugin.components.providers[:foo]\n      expect(result[0]).to eq(\"bar\")\n      expect(result[1][:priority]).to eq(5)\n      expect(result[1][:foo]).to eq(\"yep\")\n    end\n\n    it \"should lazily register provider classes\" do\n      # Below would raise an error if the value of the config class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n          provider(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the configuration key that\n      # a proper error is raised.\n      expect {\n        plugin.components.providers[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"provider capabilities\" do\n    it \"should register host capabilities\" do\n      plugin = Class.new(described_class) do\n        provider_capability(\"foo\", \"bar\") { \"baz\" }\n      end\n\n      expect(plugin.components.provider_capabilities[:foo][:bar]).to eq(\"baz\")\n    end\n  end\n\n  describe \"provisioners\" do\n    it \"should register provisioner classes\" do\n      plugin = Class.new(described_class) do\n        provisioner(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.provisioner[:foo]).to eq(\"bar\")\n    end\n\n    it \"should lazily register provisioner classes\" do\n      # Below would raise an error if the value of the config class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n          provisioner(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the configuration key that\n      # a proper error is raised.\n      expect {\n        plugin.provisioner[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"pushes\" do\n    it \"should register implementations\" do\n      plugin = Class.new(described_class) do\n        push(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.components.pushes[:foo]).to eq([\"bar\", nil])\n    end\n\n    it \"should be able to specify priorities\" do\n      plugin = Class.new(described_class) do\n        push(\"foo\", bar: 1) { \"bar\" }\n      end\n\n      expect(plugin.components.pushes[:foo]).to eq([\"bar\", bar: 1])\n    end\n\n    it \"should lazily register implementations\" do\n      # Below would raise an error if the value of the config class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n          push(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the configuration key that\n      # a proper error is raised.\n      expect {\n        plugin.components.pushes[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"synced folders\" do\n    it \"should register implementations\" do\n      plugin = Class.new(described_class) do\n        synced_folder(\"foo\") { \"bar\" }\n      end\n\n      expect(plugin.components.synced_folders[:foo]).to eq([\"bar\", 10])\n    end\n\n    it \"should be able to specify priorities\" do\n      plugin = Class.new(described_class) do\n        synced_folder(\"foo\", 50) { \"bar\" }\n      end\n\n      expect(plugin.components.synced_folders[:foo]).to eq([\"bar\", 50])\n    end\n\n    it \"should lazily register implementations\" do\n      # Below would raise an error if the value of the config class was\n      # evaluated immediately. By asserting that this does not raise an\n      # error, we verify that the value is actually lazily loaded\n      plugin = nil\n      expect {\n        plugin = Class.new(described_class) do\n          synced_folder(\"foo\") { raise StandardError, \"FAIL!\" }\n        end\n      }.to_not raise_error\n\n      # Now verify when we actually get the configuration key that\n      # a proper error is raised.\n      expect {\n        plugin.components.synced_folders[:foo]\n      }.to raise_error(StandardError)\n    end\n  end\n\n  describe \"plugin registration\" do\n    let(:manager) { described_class.manager }\n\n    it \"should have no registered plugins\" do\n      expect(manager.registered).to be_empty\n    end\n\n    it \"should register a plugin when a name is set\" do\n      plugin = Class.new(described_class) do\n        name \"foo\"\n      end\n\n      expect(manager.registered).to eq([plugin])\n    end\n\n    it \"should register a plugin only once\" do\n      plugin = Class.new(described_class) do\n        name \"foo\"\n        name \"bar\"\n      end\n\n      expect(manager.registered).to eq([plugin])\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v2/provider_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::V2::Provider do\n  include_context \"unit\"\n\n  let(:machine)  { Object.new }\n  let(:instance) { described_class.new(machine) }\n\n  subject { instance }\n\n  it \"should be usable by default\" do\n    expect(described_class).to be_usable\n  end\n\n  it \"should be installed by default\" do\n    expect(described_class).to be_installed\n  end\n\n  it \"should return nil by default for actions\" do\n    expect(instance.action(:whatever)).to be_nil\n  end\n\n  it \"should return nil by default for ssh info\" do\n    expect(instance.ssh_info).to be_nil\n  end\n\n  it \"should return nil by default for state\" do\n    expect(instance.state).to be_nil\n  end\n\n  context \"capabilities\" do\n    before do\n      register_plugin(\"2\") do |p|\n        p.provider_capability(\"bar\", \"foo\") {}\n\n        p.provider_capability(\"foo\", \"bar\") do\n          Class.new do\n            def self.bar(machine)\n              raise \"bar #{machine.id}\"\n            end\n          end\n        end\n      end\n\n      allow(machine).to receive(:id).and_return(\"YEAH\")\n\n      instance._initialize(\"foo\", machine)\n    end\n\n    it \"can execute capabilities\" do\n      expect(subject.capability?(:foo)).to be(false)\n      expect(subject.capability?(:bar)).to be(true)\n\n      expect { subject.capability(:bar) }.\n        to raise_error(\"bar YEAH\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v2/synced_folder_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\n\ndescribe Vagrant::Plugin::V2::SyncedFolder::Collection do\n  include_context \"unit\"\n\n  let(:folders) { described_class[\n    :nfs=>\n      {\"/other\"=>\n        {:type=>:nfs, :guestpath=>\"/other\", :hostpath=>\"/other\", :disabled=>false, :__vagrantfile=>true, plugin:\"someclass\"},\n       \"/tests\"=>\n        {:type=>:nfs, :guestpath=>\"/tests\", :hostpath=>\"/tests\", :disabled=>false, :__vagrantfile=>true, plugin:\"someclass\"}},\n    :virtualbox=>\n      {\"/vagrant\"=>\n        {:guestpath=>\"/vagrant\", :hostpath=>\"/vagrant\", :disabled=>false, :__vagrantfile=>true, plugin:\"someotherclass\"}}\n  ]}\n  \n  describe \"#types\" do\n    it \"gets all the types of synced folders\" do \n      expect(folders.types).to eq([:nfs, :virtualbox])\n    end\n  end\n\n  describe \"#type\" do\n    it \"returns the plugin for a type\" do \n      expect(folders.type(:nfs)).to eq(\"someclass\")\n      expect(folders.type(:virtualbox)).to eq(\"someotherclass\")\n    end\n  end\n\n  describe \"to_h\" do\n    it \"removed plugin key\" do \n      original_folders = folders\n      folders_h = folders.to_h\n      folders_h.values.each do |v|\n        v.values.each do |w|\n          expect(w).not_to include(:plugin)\n        end\n      end\n      original_folders.values.each do |v|\n        v.values.each do |w|\n          expect(w).to include(:plugin)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/plugin/v2/trigger_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../../base\", __FILE__)\nrequire Vagrant.source_root.join(\"plugins/kernel_v2/config/trigger\")\n\ndescribe Vagrant::Plugin::V2::Trigger do\n  include_context \"unit\"\n\n  let(:iso_env) do\n    # We have to create a Vagrantfile so there is a root path\n    isolated_environment.tap do |env|\n      env.vagrantfile(\"\")\n    end\n  end\n  let(:iso_vagrant_env) { iso_env.create_vagrant_env }\n  let(:state) { double(\"state\", id: :running) }\n  let(:machine) do\n    iso_vagrant_env.machine(iso_vagrant_env.machine_names[0], :dummy).tap do |m|\n      allow(m).to receive(:state).and_return(state)\n    end\n  end\n  let(:ui) { Vagrant::UI::Silent.new }\n  let(:env) { {\n    machine: machine,\n    ui: ui,\n  } }\n\n  let(:triggers) {\n    @triggers ||= VagrantPlugins::Kernel_V2::TriggerConfig.new.tap do |triggers|\n      triggers.before(:up, hash_block)\n      triggers.before(:destroy, hash_block)\n      triggers.before(:halt, hash_block_two)\n      triggers.after(:up, hash_block)\n      triggers.after(:destroy, hash_block)\n      triggers.finalize!\n    end\n  }\n  let(:hash_block) { {info: \"hi\", run: {inline: \"echo 'hi'\"}} }\n  let(:hash_block_two) { {warn: \"WARNING!!\", run_remote: {inline: \"echo 'hi'\"}} }\n\n  let(:subject) { described_class.new(env, triggers, machine, ui) }\n\n  describe \"#fire\" do\n    it \"raises an error if an improper stage is given\" do\n      expect{ subject.fire(:up, :not_real, \"guest\", :action) }.\n       to raise_error(Vagrant::Errors::TriggersNoStageGiven)\n    end\n\n    it \"does not fire triggers if community plugin is detected\" do\n      allow(subject).to receive(:community_plugin_detected?).and_return(true)\n\n      expect(subject).not_to receive(:execute)\n      subject.fire(:up, :before, \"guest\", :action)\n    end\n\n    it \"does fire triggers if community plugin is not detected\" do\n      allow(subject).to receive(:community_plugin_detected?).and_return(false)\n\n      expect(subject).to receive(:execute)\n      subject.fire(:up, :before, \"guest\", :action)\n    end\n  end\n\n  describe \"#find\" do\n    it \"raises an error if an improper stage is given\" do\n      expect { subject.find(:up, :not_real, \"guest\", :action) }.\n        to raise_error(Vagrant::Errors::TriggersNoStageGiven)\n    end\n\n    it \"returns empty array when no triggers are found\" do\n      expect(subject.find(:halt, :after, \"guest\", :action)).to be_empty\n    end\n\n    it \"returns items in array when triggers are found\" do\n      expect(subject.find(:halt, :before, \"guest\", :action)).not_to be_empty\n    end\n\n    it \"returns the execpted number of items in the array when triggers are found\" do\n      expect(subject.find(:halt, :before, \"guest\", :action).count).to eq(1)\n    end\n\n    it \"filters all found triggers\" do\n      expect(subject).to receive(:filter_triggers)\n      subject.find(:halt, :before, \"guest\", :action)\n    end\n\n    it \"should not attempt to match hook name with non-hook type\" do\n      expect(subject).not_to receive(:matched_hook?)\n      subject.find(:halt, :before, \"guest\", :action)\n    end\n\n    context \"with :all special value\" do\n      let(:triggers) { VagrantPlugins::Kernel_V2::TriggerConfig.new }\n      let(:ignores) { [] }\n\n      before do\n        triggers.before(:all, hash_block.merge(ignore: ignores))\n        triggers.after(:all, hash_block.merge(ignore: ignores))\n        triggers.finalize!\n      end\n\n      [:destroy, :halt, :provision, :reload, :resume, :suspend, :up].each do |supported_action|\n        it \"should locate trigger when before #{supported_action} action is requested\" do\n          expect(subject.find(supported_action, :before, \"guest\", :action, all: true)).not_to be_empty\n        end\n\n        it \"should locate trigger when after #{supported_action} action is requested\" do\n          expect(subject.find(supported_action, :after, \"guest\", :action, all: true)).not_to be_empty\n        end\n      end\n\n      context \"with ignores\" do\n        let(:ignores) { [:halt, :up] }\n\n        it \"should not locate trigger when before command is ignored\" do\n          expect(subject.find(:up, :before, \"guest\", :action, all: true)).to be_empty\n        end\n\n        it \"should not locate trigger when after command is ignored\" do\n          expect(subject.find(:halt, :after, \"guest\", :action, all: true)).to be_empty\n        end\n\n        it \"should locate trigger when before command is not ignored\" do\n          expect(subject.find(:provision, :before, \"guest\", :action, all: true)).not_to be_empty\n        end\n\n        it \"should locate trigger when after command is not ignored\" do\n          expect(subject.find(:provision, :after, \"guest\", :action, all: true)).not_to be_empty\n        end\n      end\n    end\n\n    context \"with hook type\" do\n      before do\n        triggers.before(:environment_load, hash_block.merge(type: :hook))\n        triggers.before(Vagrant::Action::Builtin::SyncedFolders, hash_block.merge(type: :hook))\n        triggers.finalize!\n      end\n\n      it \"returns empty array when no triggers are found\" do\n        expect(subject.find(:environment_unload, :before, \"guest\", :hook)).to be_empty\n      end\n\n      it \"returns items in array when triggers are found\" do\n        expect(subject.find(:environment_load, :before, \"guest\", :hook).size).to eq(1)\n      end\n\n      it \"should locate hook trigger using class constant\" do\n        expect(subject.find(Vagrant::Action::Builtin::SyncedFolders, :before, \"guest\", :hook)).\n          not_to be_empty\n      end\n\n      it \"should locate hook trigger using string\" do\n        expect(subject.find(\"environment_load\", :before, \"guest\", :hook)).not_to be_empty\n      end\n\n      it \"should locate hook trigger using full converted name\" do\n        expect(subject.find(:vagrant_action_builtin_synced_folders, :before, \"guest\", :hook)).\n          not_to be_empty\n      end\n\n      it \"should locate hook trigger using partial suffix converted name\" do\n        expect(subject.find(:builtin_synced_folders, :before, \"guest\", :hook)).\n          not_to be_empty\n      end\n\n      it \"should not locate hook trigger using partial prefix converted name\" do\n        expect(subject.find(:vagrant_action, :before, \"guest\", :hook)).\n          to be_empty\n      end\n    end\n  end\n\n  describe \"#filter_triggers\" do\n    it \"returns all triggers if no constraints\" do\n      before_triggers = triggers.before_triggers\n      filtered_triggers = subject.send(:filter_triggers, before_triggers, \"guest\", :action)\n      expect(filtered_triggers).to eq(before_triggers)\n    end\n\n    it \"filters a trigger if it doesn't match guest_name\" do\n      trigger_config = {info: \"no\", only_on: \"notrealguest\"}\n      triggers.after(:up, trigger_config)\n      triggers.finalize!\n\n      after_triggers = triggers.after_triggers\n      expect(after_triggers.size).to eq(3)\n      subject.send(:filter_triggers, after_triggers, :ubuntu, :action)\n      expect(after_triggers.size).to eq(2)\n    end\n\n    it \"keeps a trigger that has a restraint that matches guest name\" do\n      trigger_config = {info: \"no\", only_on: /guest/}\n      triggers.after(:up, trigger_config)\n      triggers.finalize!\n\n      after_triggers = triggers.after_triggers\n      expect(after_triggers.size).to eq(3)\n      subject.send(:filter_triggers, after_triggers, \"ubuntu-guest\", :action)\n      expect(after_triggers.size).to eq(3)\n    end\n\n    it \"keeps a trigger that has multiple restraints that matches guest name\" do\n      trigger_config = {info: \"no\", only_on: [\"debian\", /guest/]}\n      triggers.after(:up, trigger_config)\n      triggers.finalize!\n\n      after_triggers = triggers.after_triggers\n      expect(after_triggers.size).to eq(3)\n      subject.send(:filter_triggers, after_triggers, \"ubuntu-guest\", :action)\n      expect(after_triggers.size).to eq(3)\n    end\n  end\n\n  describe \"#execute\" do\n    it \"calls the corresponding trigger methods if options set\" do\n      expect(subject).to receive(:info).twice\n      expect(subject).to receive(:warn).once\n      expect(subject).to receive(:run).twice\n      expect(subject).to receive(:run_remote).once\n      subject.send(:execute, triggers.before_triggers)\n    end\n  end\n\n  describe \"#info\" do\n    let(:message) { \"Printing some info\" }\n\n    it \"prints messages at INFO\" do\n      expect(ui).to receive(:info).with(message).and_call_original\n\n      subject.send(:info, message)\n    end\n  end\n\n  describe \"#warn\" do\n    let(:message) { \"Printing some warnings\" }\n\n    it \"prints messages at WARN\" do\n      expect(ui).to receive(:warn).with(message).and_call_original\n\n      subject.send(:warn, message)\n    end\n  end\n\n  describe \"#run\" do\n    let(:trigger_run) { VagrantPlugins::Kernel_V2::TriggerConfig.new }\n    let(:shell_block) { {info: \"hi\", run: {inline: \"echo 'hi'\", env: {\"KEY\"=>\"VALUE\"}}} }\n    let(:shell_block_exit_codes) {\n      {info: \"hi\", run: {inline: \"echo 'hi'\", env: {\"KEY\"=>\"VALUE\"}},\n       exit_codes: [0,50]} }\n    let(:path_block) { {warn: \"bye\",\n                         run: {path: \"path/to the/script.sh\", args: \"HELLO\", env: {\"KEY\"=>\"VALUE\"}},\n                         on_error: :continue} }\n\n    let(:path_block_ps1) { {warn: \"bye\",\n                         run: {path: \"script.ps1\", args: [\"HELLO\", \"THERE\"], env: {\"KEY\"=>\"VALUE\"}},\n                         on_error: :continue} }\n\n    let(:exit_code) { 0 }\n    let(:options) { {:notify=>[:stdout, :stderr]} }\n\n    let(:subprocess_result) do\n      double(\"subprocess_result\").tap do |result|\n        allow(result).to receive(:exit_code).and_return(exit_code)\n        allow(result).to receive(:stderr).and_return(\"\")\n      end\n    end\n\n    let(:subprocess_result_failure) do\n      double(\"subprocess_result_failure\").tap do |result|\n        allow(result).to receive(:exit_code).and_return(1)\n        allow(result).to receive(:stderr).and_return(\"\")\n      end\n    end\n\n    let(:subprocess_result_custom) do\n      double(\"subprocess_result_custom\").tap do |result|\n        allow(result).to receive(:exit_code).and_return(50)\n        allow(result).to receive(:stderr).and_return(\"\")\n      end\n    end\n\n    before do\n      trigger_run.after(:up, shell_block)\n      trigger_run.after(:up, shell_block_exit_codes)\n      trigger_run.before(:destroy, path_block)\n      trigger_run.before(:destroy, path_block_ps1)\n      trigger_run.finalize!\n    end\n\n    it \"executes an inline script with powershell if windows\" do\n      allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n      allow(Vagrant::Util::PowerShell).to receive(:execute_inline).\n        and_yield(:stderr, \"some output\").\n        and_return(subprocess_result)\n\n      trigger = trigger_run.after_triggers.first\n      shell_config = trigger.run\n      on_error = trigger.on_error\n      exit_codes = trigger.exit_codes\n\n      expect(Vagrant::Util::PowerShell).to receive(:execute_inline).\n        with(\"echo 'hi'\", options)\n      subject.send(:run, shell_config, on_error, exit_codes)\n    end\n\n    it \"executes a path script with powershell if windows\" do\n      allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n      allow(Vagrant::Util::PowerShell).to receive(:execute).\n        and_yield(:stdout, \"more output\").\n        and_return(subprocess_result)\n      allow(env).to receive(:root_path).and_return(\"/vagrant/home\")\n\n      trigger = trigger_run.before_triggers[1]\n      shell_config = trigger.run\n      on_error = trigger.on_error\n      exit_codes = trigger.exit_codes\n\n      expect(Vagrant::Util::PowerShell).to receive(:execute).\n        with(\"/vagrant/home/script.ps1\", \"HELLO\", \"THERE\", options)\n      subject.send(:run, shell_config, on_error, exit_codes)\n    end\n\n    it \"executes an inline script\" do\n      allow(Vagrant::Util::Subprocess).to receive(:execute).\n        and_return(subprocess_result)\n\n      trigger = trigger_run.after_triggers.first\n      shell_config = trigger.run\n      on_error = trigger.on_error\n      exit_codes = trigger.exit_codes\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute).\n        with(\"echo\", \"hi\", options)\n      subject.send(:run, shell_config, on_error, exit_codes)\n    end\n\n    it \"executes a path script\" do\n      allow(Vagrant::Util::Subprocess).to receive(:execute).\n        and_return(subprocess_result)\n      allow(env).to receive(:root_path).and_return(\"/vagrant/home\")\n      allow(FileUtils).to receive(:chmod).and_return(true)\n\n      trigger = trigger_run.before_triggers.first\n      shell_config = trigger.run\n      on_error = trigger.on_error\n      exit_codes = trigger.exit_codes\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute).\n        with(\"/vagrant/home/path/to the/script.sh\", \"HELLO\", options)\n      subject.send(:run, shell_config, on_error, exit_codes)\n    end\n\n    it \"continues on error\" do\n      allow(Vagrant::Util::Subprocess).to receive(:execute).\n        and_raise(\"Fail!\")\n      allow(env).to receive(:root_path).and_return(\"/vagrant/home\")\n      allow(FileUtils).to receive(:chmod).and_return(true)\n\n      trigger = trigger_run.before_triggers.first\n      shell_config = trigger.run\n      on_error = trigger.on_error\n      exit_codes = trigger.exit_codes\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute).\n        with(\"/vagrant/home/path/to the/script.sh\", \"HELLO\", options)\n      subject.send(:run, shell_config, on_error, exit_codes)\n    end\n\n    it \"halts on error\" do\n      allow(Vagrant::Util::Subprocess).to receive(:execute).\n        and_raise(\"Fail!\")\n\n      trigger = trigger_run.after_triggers.first\n      shell_config = trigger.run\n      on_error = trigger.on_error\n      exit_codes = trigger.exit_codes\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute).\n        with(\"echo\", \"hi\", options)\n      expect { subject.send(:run, shell_config, on_error, exit_codes) }.to raise_error(\"Fail!\")\n    end\n\n    it \"allows for acceptable exit codes\" do\n      allow(Vagrant::Util::Subprocess).to receive(:execute).\n        and_return(subprocess_result_custom)\n\n      trigger = trigger_run.after_triggers[1]\n      shell_config = trigger.run\n      on_error = trigger.on_error\n      exit_codes = trigger.exit_codes\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute).\n        with(\"echo\", \"hi\", options)\n      subject.send(:run, shell_config, on_error, exit_codes)\n    end\n\n    it \"exits if given a bad exit code\" do\n      allow(Vagrant::Util::Subprocess).to receive(:execute).\n        and_return(subprocess_result_custom)\n\n      trigger = trigger_run.after_triggers.first\n      shell_config = trigger.run\n      on_error = trigger.on_error\n      exit_codes = trigger.exit_codes\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute).\n        with(\"echo\", \"hi\", options)\n      expect { subject.send(:run, shell_config, on_error, exit_codes) }.to raise_error(Vagrant::Errors::TriggersBadExitCodes)\n    end\n  end\n\n  describe \"#run_remote\" do\n    let (:trigger_run) { VagrantPlugins::Kernel_V2::TriggerConfig.new }\n    let (:shell_block) { {info: \"hi\", run_remote: {inline: \"echo 'hi'\", env: {\"KEY\"=>\"VALUE\"}}} }\n    let (:path_block) { {warn: \"bye\",\n                         run_remote: {path: \"script.sh\", env: {\"KEY\"=>\"VALUE\"}},\n                         on_error: :continue} }\n    let(:provision) { double(\"provision\") }\n\n    before do\n      trigger_run.after(:up, shell_block)\n      trigger_run.before(:destroy, path_block)\n      trigger_run.finalize!\n    end\n\n    context \"with no machine existing\" do\n      let(:machine) { nil }\n\n      it \"raises an error and halts if guest does not exist\" do\n        trigger = trigger_run.after_triggers.first\n        shell_config = trigger.run_remote\n        on_error = trigger.on_error\n        exit_codes = trigger.exit_codes\n\n        expect { subject.send(:run_remote, shell_config, on_error, exit_codes) }.\n          to raise_error(Vagrant::Errors::TriggersGuestNotExist)\n      end\n\n      it \"continues on if guest does not exist but is configured to continue on error\" do\n        trigger = trigger_run.before_triggers.first\n        shell_config = trigger.run_remote\n        on_error = trigger.on_error\n        exit_codes = trigger.exit_codes\n\n        subject.send(:run_remote, shell_config, on_error, exit_codes)\n      end\n    end\n\n    it \"raises an error and halts if guest is not running\" do\n      allow(machine.state).to receive(:id).and_return(:not_running)\n\n      trigger = trigger_run.after_triggers.first\n      shell_config = trigger.run_remote\n      on_error = trigger.on_error\n      exit_codes = trigger.exit_codes\n\n      expect { subject.send(:run_remote, shell_config, on_error, exit_codes) }.\n        to raise_error(Vagrant::Errors::TriggersGuestNotRunning)\n    end\n\n    it \"continues on if guest is not running but is configured to continue on error\" do\n      allow(machine.state).to receive(:id).and_return(:not_running)\n\n      allow(env).to receive(:root_path).and_return(\"/vagrant/home\")\n      allow(FileUtils).to receive(:chmod).and_return(true)\n\n      trigger = trigger_run.before_triggers.first\n      shell_config = trigger.run_remote\n      on_error = trigger.on_error\n      exit_codes = trigger.exit_codes\n\n      subject.send(:run_remote, shell_config, on_error, exit_codes)\n    end\n\n    it \"calls the provision function on the shell provisioner\" do\n      allow(machine.state).to receive(:id).and_return(:running)\n      allow(provision).to receive(:provision).and_return(\"Provision!\")\n      allow(VagrantPlugins::Shell::Provisioner).to receive(:new).\n        and_return(provision)\n\n      trigger = trigger_run.after_triggers.first\n      shell_config = trigger.run_remote\n      on_error = trigger.on_error\n      exit_codes = trigger.exit_codes\n\n      subject.send(:run_remote, shell_config, on_error, exit_codes)\n    end\n\n    it \"continues on if provision fails\" do\n      allow(machine.state).to receive(:id).and_return(:running)\n      allow(provision).to receive(:provision).and_raise(\"Nope!\")\n      allow(VagrantPlugins::Shell::Provisioner).to receive(:new).\n        and_return(provision)\n\n      trigger = trigger_run.before_triggers.first\n      shell_config = trigger.run_remote\n      on_error = trigger.on_error\n      exit_codes = trigger.exit_codes\n\n      subject.send(:run_remote, shell_config, on_error, exit_codes)\n    end\n\n    it \"fails if it encounters an error\" do\n      allow(machine.state).to receive(:id).and_return(:running)\n      allow(provision).to receive(:provision).and_raise(\"Nope!\")\n      allow(VagrantPlugins::Shell::Provisioner).to receive(:new).\n        and_return(provision)\n\n      trigger = trigger_run.after_triggers.first\n      shell_config = trigger.run_remote\n      on_error = trigger.on_error\n      exit_codes = trigger.exit_codes\n\n      expect { subject.send(:run_remote, shell_config, on_error, exit_codes) }.\n        to raise_error(\"Nope!\")\n    end\n  end\n\n  describe \"#trigger_abort\" do\n    it \"system exits when called\" do\n      allow(Process).to receive(:exit!).and_return(true)\n\n      expect(Process).to receive(:exit!).with(3)\n      subject.send(:trigger_abort, 3)\n    end\n\n    context \"when running in parallel\" do\n      let(:thread) {\n        @t ||= Thread.new do\n          Thread.current[:batch_parallel_action] = true\n          Thread.stop\n          subject.send(:trigger_abort, exit_code)\n        end\n      }\n      let(:exit_code) { 22 }\n\n      before do\n        expect(Process).not_to receive(:exit!)\n        sleep(0.1) until thread.stop?\n      end\n\n      after { @t = nil }\n\n      it \"should terminate the thread\" do\n        expect(thread).to receive(:terminate).and_call_original\n        thread.wakeup\n        thread.join(1) while thread.alive?\n      end\n\n      it \"should set the exit code into the thread data\" do\n        expect(thread).to receive(:terminate).and_call_original\n        thread.wakeup\n        thread.join(1) while thread.alive?\n        expect(thread[:exit_code]).to eq(exit_code)\n      end\n    end\n  end\n\n  describe \"#ruby\" do\n    let(:trigger_run) { VagrantPlugins::Kernel_V2::TriggerConfig.new }\n    let(:block) { proc{var = 1+1} }\n    let(:ruby_trigger) { {info: \"hi\", ruby: block} }\n\n    before do\n      trigger_run.after(:up, ruby_trigger)\n      trigger_run.finalize!\n    end\n\n    it \"executes a ruby block\" do\n      expect(block).to receive(:call)\n      subject.send(:execute_ruby, block)\n    end\n  end\n\n  describe \"#nameify\" do\n    it \"should return empty string when object\" do\n      expect(subject.send(:nameify, \"\")).to eq(\"\")\n    end\n\n    it \"should return name of class\" do\n      expect(subject.send(:nameify, String)).to eq(\"String\")\n    end\n\n    it \"should return empty string when class has no name\" do\n      expect(subject.send(:nameify, Class.new)).to eq(\"\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/registry_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\ndescribe Vagrant::Registry do\n  let(:instance) { described_class.new }\n\n  it \"should return nil for nonexistent items\" do\n    expect(instance.get(\"foo\")).to be_nil\n  end\n\n  it \"should register a simple key/value\" do\n    instance.register(\"foo\") { \"value\" }\n    expect(instance.get(\"foo\")).to eq(\"value\")\n  end\n\n  it \"should register an item without calling the block yet\" do\n    expect do\n      instance.register(\"foo\") do\n        raise Exception, \"BOOM!\"\n      end\n    end.to_not raise_error\n  end\n\n  it \"should raise an error if no block is given\" do\n    expect { instance.register(\"foo\") }.\n      to raise_error(ArgumentError)\n  end\n\n  it \"should call and return the result of a block when asking for the item\" do\n    object = Object.new\n    instance.register(\"foo\") do\n      object\n    end\n\n    expect(instance.get(\"foo\")).to eql(object)\n  end\n\n  it \"should be able to get the item with []\" do\n    object = Object.new\n    instance.register(\"foo\") { object }\n\n    expect(instance[\"foo\"]).to eql(object)\n  end\n\n  it \"should be able to get keys with #keys\" do\n    instance.register(\"foo\") { \"bar\" }\n    instance.register(\"baz\") { raise \"BOOM\" }\n\n    expect(instance.keys.sort).to eq([ 'baz', 'foo' ])\n  end\n\n  it \"should cache the result of the item so they can be modified\" do\n    # Make the proc generate a NEW array each time\n    instance.register(\"foo\") { [] }\n\n    # Test that modifying the result modifies the actual cached\n    # value. This verifies we're caching.\n    expect(instance.get(\"foo\")).to eq([])\n    instance.get(\"foo\") << \"value\"\n    expect(instance.get(\"foo\")).to eq([\"value\"])\n  end\n\n  it \"should be able to check if a key exists\" do\n    instance.register(\"foo\") { \"bar\" }\n    expect(instance).to have_key(\"foo\")\n    expect(instance).not_to have_key(\"bar\")\n  end\n\n  it \"should be enumerable\" do\n    instance.register(\"foo\") { \"foovalue\" }\n    instance.register(\"bar\") { \"barvalue\" }\n\n    keys   = []\n    values = []\n    instance.each do |key, value|\n      keys << key\n      values << value\n    end\n\n    expect(keys.sort).to eq([\"bar\", \"foo\"])\n    expect(values.sort).to eq([\"barvalue\", \"foovalue\"])\n  end\n\n  it \"should be able to convert to a hash\" do\n    instance.register(\"foo\") { \"foovalue\" }\n    instance.register(\"bar\") { \"barvalue\" }\n\n    result = instance.to_hash\n    expect(result).to be_a(Hash)\n    expect(result[\"foo\"]).to eq(\"foovalue\")\n    expect(result[\"bar\"]).to eq(\"barvalue\")\n  end\n\n  describe \"#length\" do\n    it \"should return 0 when the registry is empty\" do\n      expect(instance.length).to eq(0)\n    end\n\n    it \"should return the number of items in the registry\" do\n      instance.register(\"foo\") { }\n      instance.register(\"bar\") { }\n\n      expect(instance.length).to eq(2)\n    end\n  end\n\n  describe \"#size\" do\n    it \"should be an alias to #length\" do\n      size = described_class.instance_method(:size)\n      length = described_class.instance_method(:length)\n\n      expect(size).to eq(length)\n    end\n  end\n\n  describe \"#empty\" do\n    it \"should return true when the registry is empty\" do\n      expect(instance.empty?).to be(true)\n    end\n\n    it \"should return false when there is at least one element\" do\n      instance.register(\"foo\") { }\n      expect(instance.empty?).to be(false)\n    end\n  end\n\n  describe \"merging\" do\n    it \"should merge in another registry\" do\n      one = described_class.new\n      two = described_class.new\n\n      one.register(\"foo\") { raise \"BOOM!\" }\n      two.register(\"bar\") { raise \"BAM!\" }\n\n      three = one.merge(two)\n      expect { three[\"foo\"] }.to raise_error(\"BOOM!\")\n      expect { three[\"bar\"] }.to raise_error(\"BAM!\")\n    end\n\n    it \"should NOT merge in the cache\" do\n      one = described_class.new\n      two = described_class.new\n\n      one.register(\"foo\") { [] }\n      one[\"foo\"] << 1\n\n      two.register(\"bar\") { [] }\n      two[\"bar\"] << 2\n\n      three = one.merge(two)\n      expect(three[\"foo\"]).to eq([])\n      expect(three[\"bar\"]).to eq([])\n    end\n  end\n\n  describe \"merge!\" do\n    it \"should merge into self\" do\n      one = described_class.new\n      two = described_class.new\n\n      one.register(\"foo\") { \"foo\" }\n      two.register(\"bar\") { \"bar\" }\n\n      one.merge!(two)\n      expect(one[\"foo\"]).to eq(\"foo\")\n      expect(one[\"bar\"]).to eq(\"bar\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/shared_helpers_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\nrequire \"vagrant/shared_helpers\"\nrequire \"vagrant/util/platform\"\n\ndescribe Vagrant do\n  include_context \"unit\"\n\n  subject { described_class }\n\n  describe \".global_lock\" do\n    it \"yields to the block\" do\n      result = subject.global_lock { 42 }\n      expect(result).to eq(42)\n    end\n  end\n\n  describe \".in_installer?\" do\n    it \"is not if env is not set\" do\n      with_temp_env(\"VAGRANT_INSTALLER_ENV\" => nil) do\n        expect(subject.in_installer?).to be(false)\n      end\n    end\n\n    it \"is if env is set\" do\n      with_temp_env(\"VAGRANT_INSTALLER_ENV\" => \"/foo\") do\n        expect(subject.in_installer?).to be(true)\n      end\n    end\n  end\n\n  describe \".installer_embedded_dir\" do\n    it \"returns nil if not in an installer\" do\n      allow(Vagrant).to receive(:in_installer?).and_return(false)\n      expect(subject.installer_embedded_dir).to be_nil\n    end\n\n    it \"returns the set directory\" do\n      allow(Vagrant).to receive(:in_installer?).and_return(true)\n\n      with_temp_env(\"VAGRANT_INSTALLER_EMBEDDED_DIR\" => \"/foo\") do\n        expect(subject.installer_embedded_dir).to eq(\"/foo\")\n      end\n    end\n  end\n\n  describe \".plugins_enabled?\" do\n    it \"returns true if the env is not set\" do\n      with_temp_env(\"VAGRANT_NO_PLUGINS\" => nil) do\n        expect(subject.plugins_enabled?).to be(true)\n      end\n    end\n\n    it \"returns false if the env is set\" do\n      with_temp_env(\"VAGRANT_NO_PLUGINS\" => \"1\") do\n        expect(subject.plugins_enabled?).to be(false)\n      end\n    end\n  end\n\n  describe \".server_url\" do\n    it \"defaults to the default value\" do\n      with_temp_env(\"VAGRANT_SERVER_URL\" => nil) do\n        expect(subject.server_url).to eq(\n          Vagrant::DEFAULT_SERVER_URL)\n      end\n    end\n\n    it \"defaults if the string is empty\" do\n      with_temp_env(\"VAGRANT_SERVER_URL\" => \"\") do\n        expect(subject.server_url).to eq(\n          Vagrant::DEFAULT_SERVER_URL)\n      end\n    end\n\n    it \"is the VAGRANT_SERVER_URL value\" do\n      with_temp_env(\"VAGRANT_SERVER_URL\" => \"foo\") do\n        expect(subject.server_url).to eq(\"foo\")\n      end\n    end\n\n    it \"is the VAGRANT_SERVER_URL value if the server url is configured\" do\n      with_temp_env(\"VAGRANT_SERVER_URL\" => \"foo\") do\n        expect(subject.server_url('bar')).to eq(\"foo\")\n      end\n    end\n\n    it \"is the configured server url if VAGRANT_SERVER_URL is not set\" do\n      with_temp_env(\"VAGRANT_SERVER_URL\" => nil) do\n        expect(subject.server_url(\"bar\")).to eq(\"bar\")\n      end\n    end\n  end\n\n  describe \".user_data_path\" do\n    around do |example|\n      env = {\n        \"USERPROFILE\" => nil,\n        \"VAGRANT_HOME\" => nil,\n      }\n      with_temp_env(env) { example.run }\n    end\n\n    it \"defaults to ~/.vagrant.d\" do\n      expect(subject.user_data_path).to eql(Pathname.new(\"~/.vagrant.d\").expand_path)\n    end\n\n    it \"is VAGRANT_HOME if set\" do\n      with_temp_env(\"VAGRANT_HOME\" => \"/foo\") do\n        expected = Pathname.new(\"/foo\").expand_path\n        expect(subject.user_data_path).to eql(expected)\n      end\n    end\n\n    it \"is USERPROFILE/.vagrant.d if set\" do\n      with_temp_env(\"USERPROFILE\" => \"/bar\") do\n        expected = Pathname.new(\"/bar/.vagrant.d\").expand_path\n        expect(subject.user_data_path).to eql(expected)\n      end\n    end\n\n    it \"prefers VAGRANT_HOME over USERPROFILE if both are set\" do\n      env = {\n        \"USERPROFILE\" => \"/bar\",\n        \"VAGRANT_HOME\" => \"/foo\",\n      }\n\n      with_temp_env(env) do\n        expected = Pathname.new(\"/foo\").expand_path\n        expect(subject.user_data_path).to eql(expected)\n      end\n    end\n  end\n\n  describe \".prerelease?\" do\n    it \"should return true when Vagrant version is development\" do\n      stub_const(\"Vagrant::VERSION\", \"1.0.0.dev\")\n      expect(subject.prerelease?).to be(true)\n    end\n\n    it \"should return false when Vagrant version is release\" do\n      stub_const(\"Vagrant::VERSION\", \"1.0.0\")\n      expect(subject.prerelease?).to be(false)\n    end\n  end\n\n  describe \".allow_prerelease_dependencies?\" do\n    context \"with environment variable set\" do\n      before { allow(ENV).to receive(:[]).with(\"VAGRANT_ALLOW_PRERELEASE\").and_return(\"1\") }\n\n      it \"should return true\" do\n        expect(subject.allow_prerelease_dependencies?).to be(true)\n      end\n    end\n\n    context \"with environment variable unset\" do\n      before { allow(ENV).to receive(:[]).with(\"VAGRANT_ALLOW_PRERELEASE\").and_return(nil) }\n\n      it \"should return false\" do\n        expect(subject.allow_prerelease_dependencies?).to be(false)\n      end\n    end\n  end\n\n  describe \".enable_resolv_replace\" do\n    it \"should not attempt to require resolv-replace by default\" do\n      expect(subject).not_to receive(:require).with(\"resolv-replace\")\n      subject.enable_resolv_replace\n    end\n\n    it \"should require resolv-replace when VAGRANT_ENABLE_RESOLV_REPLACE is set\" do\n      expect(subject).to receive(:require).with(\"resolv-replace\")\n      with_temp_env(\"VAGRANT_ENABLE_RESOLV_REPLACE\" => \"1\"){ subject.enable_resolv_replace }\n    end\n\n    it \"should not require resolv-replace when VAGRANT_DISABLE_RESOLV_REPLACE is set\" do\n      expect(subject).not_to receive(:require).with(\"resolv-replace\")\n      with_temp_env(\"VAGRANT_ENABLE_RESOLV_REPLACE\" => \"1\", \"VAGRANT_DISABLE_RESOLV_REPLACE\" => \"1\") do\n        subject.enable_resolv_replace\n      end\n    end\n  end\n\n  describe \".global_logger\" do\n    after{ subject.global_logger = nil }\n\n    it \"should return a logger when none have been provided\" do\n      expect(subject.global_logger).not_to be_nil\n    end\n\n    it \"should return previously set logger\" do\n      logger = double(\"logger\")\n      expect(subject.global_logger = logger).to eq(logger)\n      expect(subject.global_logger).to eq(logger)\n    end\n  end\n\n  describe \".add_default_cli_options\" do\n    it \"should raise a type error when no provided with proc\" do\n      expect { subject.add_default_cli_options(true) }.\n        to raise_error(TypeError)\n    end\n\n    it \"should raise an argument error when proc does not accept argument\" do\n      expect { subject.add_default_cli_options(proc{}) }.\n        to raise_error(ArgumentError)\n    end\n\n    it \"should accept a proc type argument\" do\n      expect(subject.add_default_cli_options(proc{|o|})).to be_nil\n    end\n  end\n\n  describe \".default_cli_options\" do\n    it \"should return array of items\" do\n      expect(subject.default_cli_options).to be_a(Array)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/ui_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\ndescribe Vagrant::UI::Basic do\n  context \"in general\" do\n    it \"outputs within the a new thread\" do\n      current = Thread.current.object_id\n\n      expect(subject).to receive(:safe_puts).with(any_args) { |*args|\n        expect(Thread.current.object_id).to_not eq(current)\n        true\n      }\n\n      subject.output(\"foo\")\n    end\n\n    it \"outputs using `puts` by default\" do\n      expect(subject).to receive(:safe_puts).with(any_args) { |message, opts|\n        expect(opts[:printer]).to eq(:puts)\n        true\n      }\n\n      subject.output(\"foo\")\n    end\n\n    it \"outputs using `print` if new_line is false\" do\n      expect(subject).to receive(:safe_puts).with(any_args) { |message, opts|\n        expect(opts[:printer]).to eq(:print)\n        true\n      }\n\n      subject.output(\"foo\", new_line: false)\n    end\n\n    it \"outputs using `print` if new_line is false\" do\n      expect(subject).to receive(:safe_puts).with(any_args) { |message, opts|\n        expect(opts[:printer]).to eq(:print)\n        true\n      }\n\n      subject.output(\"foo\", new_line: false)\n    end\n\n    it \"outputs to the assigned stdout\" do\n      stdout = StringIO.new\n      subject.stdout = stdout\n\n      expect(subject).to receive(:safe_puts).with(any_args) { |message, opts|\n        expect(opts[:io]).to be(stdout)\n        true\n      }\n\n      subject.output(\"foo\")\n    end\n\n    it \"outputs to stdout by default\" do\n      expect(subject.stdout).to be($stdout)\n    end\n\n    it \"outputs to the assigned stderr for errors\" do\n      stderr = StringIO.new\n      subject.stderr = stderr\n\n      expect(subject).to receive(:safe_puts).with(any_args) { |message, opts|\n        expect(opts[:io]).to be(stderr)\n        true\n      }\n\n      subject.error(\"foo\")\n    end\n\n    it \"outputs to stderr for errors by default\" do\n      expect(subject.stderr).to be($stderr)\n    end\n  end\n\n  context \"#color?\" do\n    it \"returns false\" do\n      expect(subject.color?).to be(false)\n    end\n  end\n\n  context \"#detail\" do\n    it \"outputs details\" do\n      expect(subject).to receive(:safe_puts).with(any_args) { |message, opts|\n        expect(message).to eq(\"foo\")\n        true\n      }\n\n      subject.detail(\"foo\")\n    end\n\n    it \"doesn't output details if disabled\" do\n      expect(subject).to receive(:safe_puts).never\n\n      subject.opts[:hide_detail] = true\n      subject.detail(\"foo\")\n    end\n  end\n\n  context \"with sensitive data\" do\n    let(:password){ \"my-birthday\" }\n    let(:output){ \"You're password is: #{password}\" }\n\n    before{ Vagrant::Util::CredentialScrubber.sensitive(password) }\n\n    it \"should remove sensitive information from the output\" do\n      expect(subject).to receive(:safe_puts).with(any_args) do |message, opts|\n        expect(message).not_to include(password)\n      end\n      subject.detail(output)\n    end\n  end\n\n  context \"#rewriting\" do\n    it \"does output progress\" do\n      expect { |b| subject.rewriting(&b) }.to yield_control\n    end\n  end\nend\n\ndescribe Vagrant::UI::NonInteractive do\n  describe \"#ask\" do\n    it \"raises an exception\" do\n      expect{subject.ask(\"foo\")}.to raise_error(Vagrant::Errors::UIExpectsTTY)\n    end\n  end\n\n  describe \"#report_progress\" do\n    it \"does not output progress\" do\n      expect(subject).to_not receive(:info)\n      subject.report_progress(1, 1)\n    end\n  end\n\n  describe \"#rewriting\" do\n    it \"does not output progress\" do\n      expect { |b| subject.rewriting(&b) }.to_not yield_control\n    end\n  end\nend\n\ndescribe Vagrant::UI::Colored do\n  include_context \"unit\"\n\n  describe \"#color?\" do\n    it \"returns true\" do\n      expect(subject.color?).to be(true)\n    end\n  end\n\n  describe \"#detail\" do\n    it \"colors output nothing by default\" do\n      expect(subject).to receive(:safe_puts).with(\"\\033[0mfoo\\033[0m\", anything)\n      subject.detail(\"foo\")\n    end\n\n    it \"does not bold by default with a color\" do\n      expect(subject).to receive(:safe_puts).with(any_args) { |message, *args|\n        expect(message).to start_with(\"\\033[0;31m\")\n        expect(message).to end_with(\"\\033[0m\")\n      }\n\n      subject.detail(\"foo\", color: :red)\n    end\n  end\n\n  describe \"#error\" do\n    it \"colors red\" do\n      expect(subject).to receive(:safe_puts).with(any_args) { |message, *args|\n        expect(message).to start_with(\"\\033[0;31m\")\n        expect(message).to end_with(\"\\033[0m\")\n      }\n\n      subject.error(\"foo\")\n    end\n  end\n\n  describe \"#output\" do\n    it \"colors output nothing by default, no bold\" do\n      expect(subject).to receive(:safe_puts).with(\"\\033[0mfoo\\033[0m\", anything)\n      subject.output(\"foo\")\n    end\n\n    it \"doesn't use a color if default color\" do\n      expect(subject).to receive(:safe_puts).with(\"\\033[0mfoo\\033[0m\", anything)\n      subject.output(\"foo\", color: :default)\n    end\n\n    it \"bolds output without color if specified\" do\n      expect(subject).to receive(:safe_puts).with(\"\\033[1mfoo\\033[0m\", anything)\n      subject.output(\"foo\", bold: true)\n    end\n\n    it \"colors output to color specified in global opts\" do\n      subject.opts[:color] = :red\n\n      expect(subject).to receive(:safe_puts).with(any_args) { |message, *args|\n        expect(message).to start_with(\"\\033[0;31m\")\n        expect(message).to end_with(\"\\033[0m\")\n      }\n\n      subject.output(\"foo\")\n    end\n\n    it \"colors output to specified color over global opts\" do\n      subject.opts[:color] = :red\n\n      expect(subject).to receive(:safe_puts).with(any_args) { |message, *args|\n        expect(message).to start_with(\"\\033[0;32m\")\n        expect(message).to end_with(\"\\033[0m\")\n      }\n\n      subject.output(\"foo\", color: :green)\n    end\n\n    it \"bolds the output if specified\" do\n      subject.opts[:color] = :red\n\n      expect(subject).to receive(:safe_puts).with(any_args) { |message, *args|\n        expect(message).to start_with(\"\\033[1;31m\")\n        expect(message).to end_with(\"\\033[0m\")\n      }\n\n      subject.output(\"foo\", bold: true)\n    end\n  end\n\n  describe \"#success\" do\n    it \"colors green\" do\n      expect(subject).to receive(:safe_puts).with(any_args) { |message, *args|\n        expect(message).to start_with(\"\\033[0;32m\")\n        expect(message).to end_with(\"\\033[0m\")\n      }\n\n      subject.success(\"foo\")\n    end\n  end\n\n  describe \"#warn\" do\n    it \"colors yellow\" do\n      expect(subject).to receive(:safe_puts).with(any_args) { |message, *args|\n        expect(message).to start_with(\"\\033[0;33m\")\n        expect(message).to end_with(\"\\033[0m\")\n      }\n\n      subject.warn(\"foo\")\n    end\n  end\nend\n\ndescribe Vagrant::UI::MachineReadable do\n  describe \"#ask\" do\n    it \"raises an exception\" do\n      expect { subject.ask(\"foo\") }.\n        to raise_error(Vagrant::Errors::UIExpectsTTY)\n    end\n  end\n\n  [:detail, :warn, :error, :info, :output, :success].each do |method|\n    describe \"##{method}\" do\n      it \"outputs UI type to the machine-readable output\" do\n        expect(subject).to receive(:safe_puts).with(any_args) { |message|\n          parts = message.split(\",\")\n          expect(parts.length).to eq(5)\n          expect(parts[1]).to eq(\"\")\n          expect(parts[2]).to eq(\"ui\")\n          expect(parts[3]).to eq(method.to_s)\n          expect(parts[4]).to eq(\"foo\")\n          true\n        }\n\n        subject.send(method, \"foo\")\n      end\n    end\n  end\n\n  describe \"#machine\" do\n    it \"is formatted properly\" do\n      expect(subject).to receive(:safe_puts).with(any_args) { |message|\n        parts = message.split(\",\")\n        expect(parts.length).to eq(5)\n        expect(parts[1]).to eq(\"\")\n        expect(parts[2]).to eq(\"type\")\n        expect(parts[3]).to eq(\"data\")\n        expect(parts[4]).to eq(\"another\")\n        true\n      }\n\n      subject.machine(:type, \"data\", \"another\")\n    end\n\n    it \"includes a target if given\" do\n      expect(subject).to receive(:safe_puts).with(any_args) { |message|\n        parts = message.split(\",\")\n        expect(parts.length).to eq(4)\n        expect(parts[1]).to eq(\"boom\")\n        expect(parts[2]).to eq(\"type\")\n        expect(parts[3]).to eq(\"data\")\n        true\n      }\n\n      subject.machine(:type, \"data\", target: \"boom\")\n    end\n\n    it \"replaces commas\" do\n      expect(subject).to receive(:safe_puts).with(any_args) { |message|\n        parts = message.split(\",\")\n        expect(parts.length).to eq(4)\n        expect(parts[3]).to eq(\"foo%!(VAGRANT_COMMA)bar\")\n        true\n      }\n\n      subject.machine(:type, \"foo,bar\")\n    end\n\n    it \"replaces newlines\" do\n      expect(subject).to receive(:safe_puts).with(any_args) { |message|\n        parts = message.split(\",\")\n        expect(parts.length).to eq(4)\n        expect(parts[3]).to eq(\"foo\\\\nbar\\\\r\")\n        true\n      }\n\n      subject.machine(:type, \"foo\\nbar\\r\")\n    end\n\n    # This is for a bug where JSON parses are frozen and an\n    # exception was being raised.\n    it \"works properly with frozen string arguments\" do\n      expect(subject).to receive(:safe_puts).with(any_args) { |message|\n        parts = message.split(\",\")\n        expect(parts.length).to eq(4)\n        expect(parts[3]).to eq(\"foo\\\\nbar\\\\r\")\n        true\n      }\n\n      subject.machine(:type, \"foo\\nbar\\r\".freeze)\n    end\n  end\nend\n\ndescribe Vagrant::UI::Prefixed do\n  let(:prefix) { \"foo\" }\n  let(:ui)     { Vagrant::UI::Basic.new }\n\n  subject { described_class.new(ui, prefix) }\n\n  describe \"#initialize_copy\" do\n    it \"duplicates the underlying ui too\" do\n      another = subject.dup\n      expect(another.opts).to_not equal(subject.opts)\n    end\n  end\n\n  describe \"#ask\" do\n    it \"does not request bolding\" do\n      expect(ui).to receive(:ask).with(\"    #{prefix}: foo\", bold: false, target: prefix)\n      subject.ask(\"foo\")\n    end\n  end\n\n  describe \"#detail\" do\n    it \"prefixes with spaces and the message\" do\n      expect(ui).to receive(:safe_puts).with(\"    #{prefix}: foo\", anything)\n      subject.detail(\"foo\")\n    end\n\n    it \"prefixes every line\" do\n      expect(ui).to receive(:detail).with(\n        \"    #{prefix}: foo\\n    #{prefix}: bar\", bold: false, target: prefix)\n      subject.detail(\"foo\\nbar\")\n    end\n\n    it \"doesn't prefix if requested\" do\n      expect(ui).to receive(:detail).with(\"foo\", prefix: false, bold: false, target: prefix)\n      subject.detail(\"foo\", prefix: false)\n    end\n  end\n\n  describe \"#machine\" do\n    it \"sets the target option\" do\n      expect(ui).to receive(:machine).with(:foo, { target: prefix })\n      subject.machine(:foo)\n    end\n\n    it \"preserves existing options\" do\n      expect(ui).to receive(:machine).with(:foo, :bar, { foo: :bar, target: prefix })\n      subject.machine(:foo, :bar, foo: :bar)\n    end\n  end\n\n  describe \"#opts\" do\n    it \"is the parent's opts\" do\n      allow(ui).to receive(:opts).and_return(Object.new)\n      expect(subject.opts).to be(ui.opts)\n    end\n  end\n\n  describe \"#output\" do\n    it \"prefixes with an arrow and the message\" do\n      expect(ui).to receive(:output).with(\"==> #{prefix}: foo\", anything)\n      subject.output(\"foo\")\n    end\n\n    it \"prefixes with spaces if requested\" do\n      expect(ui).to receive(:output).with(\"    #{prefix}: foo\", anything)\n      subject.output(\"foo\", prefix_spaces: true)\n    end\n\n    it \"prefixes every line\" do\n      expect(ui).to receive(:output).with(\"==> #{prefix}: foo\\n==> #{prefix}: bar\", anything)\n      subject.output(\"foo\\nbar\")\n    end\n\n    it \"doesn't prefix if requested\" do\n      expect(ui).to receive(:output).with(\"foo\", prefix: false, bold: true, target: prefix)\n      subject.output(\"foo\", prefix: false)\n    end\n\n    it \"requests bolding\" do\n      expect(ui).to receive(:output).with(\"==> #{prefix}: foo\", bold: true, target: prefix)\n      subject.output(\"foo\")\n    end\n\n    it \"does not request bolding if class-level disabled\" do\n      ui.opts[:bold] = false\n      expect(ui).to receive(:output).with(\"==> #{prefix}: foo\", target: prefix)\n      subject.output(\"foo\")\n    end\n\n    it \"prefixes with another prefix if requested\" do\n      expect(ui).to receive(:output).with(\"==> bar: foo\", anything)\n      subject.output(\"foo\", target: \"bar\")\n    end\n  end\n\n  describe \"#format_message\" do\n    it \"should return the same number of new lines as given\" do\n      [\"no new line\", \"one\\nnew line\", \"two\\nnew lines\\n\", \"three\\nnew lines\\n\\n\"].each do |msg|\n        expect(subject.format_message(:detail, msg).count(\"\\n\")).to eq(msg.count(\"\\n\"))\n      end\n    end\n\n    it \"should properly format a blank message\" do\n      expect(subject.format_message(:detail, \"\", target: \"default\", prefix: true)).\n        to match(/\\s+default:\\s+/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/ansi_escape_code_remover_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/ansi_escape_code_remover\"\n\ndescribe Vagrant::Util::ANSIEscapeCodeRemover do\n  let(:klass) do\n    Class.new do\n      extend Vagrant::Util::ANSIEscapeCodeRemover\n    end\n  end\n\n  it \"should remove ANSI escape codes\" do\n    expect(klass.remove_ansi_escape_codes(\"\\e[Hyo\")).to eq(\"yo\")\n  end\nend\n\n"
  },
  {
    "path": "test/unit/vagrant/util/caps_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/caps\"\n\ndescribe Vagrant::Util::Caps do\n  describe \"BuildISO\" do\n\n    class TestSubject\n      extend Vagrant::Util::Caps::BuildISO\n      BUILD_ISO_CMD = \"test\".freeze\n    end\n\n    let(:subject) { TestSubject }\n    let(:env) { double(\"env\") }\n\n    describe \".build_iso\" do\n      let(:file_destination) { Pathname.new(\"/woo/out.iso\") }\n\n      before do \n        allow(file_destination).to receive(:exists?).and_return(false)\n        allow(FileUtils).to receive(:mkdir_p)\n      end\n\n      it \"should run command\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"test\", \"cmd\").and_return(double(exit_code: 0))\n        subject.build_iso([\"test\", \"cmd\"], \"/src/dir\", file_destination)\n      end\n\n      it \"raise an error if command fails\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"test\", \"cmd\").and_return(double(exit_code: 1, stdout: \"oh no\", stderr: \"oh no\"))\n        expect{ subject.build_iso([\"test\", \"cmd\"], \"/src/dir\", file_destination) }.to raise_error(Vagrant::Errors::ISOBuildFailed)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/checkpoint_client_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/checkpoint_client\"\n\ndescribe Vagrant::Util::CheckpointClient do\n  include_context \"unit\"\n\n  let(:iso_env) { isolated_environment }\n  let(:env)     { iso_env.create_vagrant_env }\n  let(:result) { {} }\n\n  subject{ Vagrant::Util::CheckpointClient.instance }\n\n  after{ subject.reset! }\n\n  before do\n    allow(subject).to receive(:result).and_return(result)\n  end\n\n  it \"should not be enabled by default\" do\n    expect(subject.enabled).to be(false)\n  end\n\n  describe \"#setup\" do\n    let(:environment){ {} }\n    before{ with_temp_env(environment){ subject.setup(env) } }\n\n    it \"should enable after setup\" do\n      expect(subject.enabled).to be(true)\n    end\n\n    it \"should generate required paths\" do\n      expect(subject.files).not_to be_empty\n    end\n\n    context \"with VAGRANT_CHECKPOINT_DISABLE set\" do\n      let(:environment){ {\"VAGRANT_CHECKPOINT_DISABLE\" => \"1\"} }\n\n      it \"should not be enabled after setup\" do\n        expect(subject.enabled).to be(false)\n      end\n    end\n  end\n\n  describe \"#check\" do\n    context \"without #setup\" do\n      it \"should not start the check\" do\n        expect(Thread).not_to receive(:new)\n        subject.check\n      end\n    end\n\n    context \"with setup\" do\n      before{ subject.setup(env) }\n\n      it \"should start the check\" do\n        expect(Thread).to receive(:new)\n        subject.check\n      end\n\n      it \"should call checkpoint\" do\n        expect(Thread).to receive(:new).and_yield\n        expect(Checkpoint).to receive(:check)\n        subject.check\n      end\n    end\n  end\n\n  describe \"#display\" do\n    it \"should only display once\" do\n      expect(subject).to receive(:version_check).once\n      expect(subject).to receive(:alerts_check).once\n\n      2.times{ subject.display }\n    end\n\n    it \"should not display cached information\" do\n      expect(subject).to receive(:result).and_return(\"cached\" => true).at_least(:once)\n      expect(subject).not_to receive(:version_check)\n      expect(subject).not_to receive(:alerts_check)\n\n      subject.display\n    end\n  end\n\n  describe \"#alerts_check\" do\n    let(:critical){\n      [{\"level\" => \"critical\", \"message\" => \"critical message\",\n        \"url\" => \"http://example.com\", \"date\" => Time.now.to_i}]\n    }\n    let(:warn){\n      [{\"level\" => \"warn\", \"message\" => \"warn message\",\n        \"url\" => \"http://example.com\", \"date\" => Time.now.to_i}]\n    }\n    let(:info){\n      [{\"level\" => \"info\", \"message\" => \"info message\",\n        \"url\" => \"http://example.com\", \"date\" => Time.now.to_i}]\n    }\n\n    before{ subject.setup(env) }\n\n    context \"with no alerts\" do\n      it \"should not display alerts\" do\n        expect(env.ui).not_to receive(:info)\n        subject.alerts_check\n      end\n    end\n\n    context \"with critical alerts\" do\n      let(:result) { {\"alerts\" => critical} }\n\n      it \"should display critical alert\" do\n        expect(env.ui).to receive(:error)\n        subject.alerts_check\n      end\n    end\n\n    context \"with warn alerts\" do\n      let(:result) { {\"alerts\" => warn} }\n\n      it \"should display warn alerts\" do\n        expect(env.ui).to receive(:warn)\n        subject.alerts_check\n      end\n    end\n\n    context \"with info alerts\" do\n      let(:result) { {\"alerts\" => info} }\n\n      it \"should display info alerts\" do\n        expect(env.ui).to receive(:info).at_least(:once)\n        subject.alerts_check\n      end\n    end\n\n    context \"with mixed alerts\" do\n      let(:result) { {\"alerts\" => info + warn + critical} }\n\n      it \"should display all alert types\" do\n        expect(env.ui).to receive(:info).at_least(:once)\n        expect(env.ui).to receive(:warn).at_least(:once)\n        expect(env.ui).to receive(:error).at_least(:once)\n\n        subject.alerts_check\n      end\n    end\n  end\n\n  describe \"#version_check\" do\n    before{ subject.setup(env) }\n\n    let(:new_version){ Gem::Version.new(Vagrant::VERSION).bump.to_s }\n    let(:old_version){ Gem::Version.new(\"1.0.0\") }\n\n    context \"latest version is same as current version\" do\n      let(:result) { {\"current_version\" => Vagrant::VERSION } }\n\n      it \"should not display upgrade information\" do\n        expect(env.ui).not_to receive(:info)\n        subject.version_check\n      end\n    end\n\n    context \"latest version is older than current version\" do\n      let(:result) { {\"current_version\" => old_version} }\n\n      it \"should not display upgrade information\" do\n        expect(env.ui).not_to receive(:info)\n        subject.version_check\n      end\n    end\n\n    context \"latest version is newer than current version\" do\n      let(:result) { {\"current_version\" => new_version} }\n\n      it \"should display upgrade information\" do\n        expect(env.ui).to receive(:info).at_least(:once)\n        subject.version_check\n      end\n\n      it \"should display upgrade information on error channel\" do\n        expect(env.ui).to receive(:info).with(any_args, hash_including(channel: :error)).at_least(:once)\n        subject.version_check\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/command_deprecation_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/command_deprecation\"\n\ndescribe Vagrant::Util do\n  include_context \"unit\"\n\n  let(:app){ lambda{|env|} }\n  let(:argv){[]}\n  let(:env){ {ui: Vagrant::UI::Silent.new} }\n\n  let(:command_class) do\n    Class.new(Vagrant.plugin(\"2\", :command)) do\n      def self.synopsis\n        \"base synopsis\"\n      end\n      def self.name\n        \"VagrantPlugins::CommandTest::Command\"\n      end\n      def execute\n        @env[:ui].info(\"COMMAND CONTENT\")\n        0\n      end\n    end\n  end\n\n  let(:command){ command_class.new(app, env) }\n\n  describe Vagrant::Util::CommandDeprecation do\n    before{ command_class.include(Vagrant::Util::CommandDeprecation) }\n\n    it \"should add deprecation warning to synopsis\" do\n      expect(command_class.synopsis).to include('[DEPRECATED]')\n      command.class.synopsis\n    end\n\n    it \"should add deprecation warning to #execute\" do\n      expect(env[:ui]).to receive(:warn).with(/DEPRECATION WARNING/)\n      command.execute\n    end\n\n    it \"should execute original command\" do\n      expect(env[:ui]).to receive(:info).with(\"COMMAND CONTENT\")\n      command.execute\n    end\n\n    it \"should return with a 0 value\" do\n      expect(command.execute).to eq(0)\n    end\n\n    context \"with custom name defined\" do\n      before do\n        command_class.class_eval do\n          def deprecation_command_name\n            \"custom-name\"\n          end\n        end\n      end\n\n      it \"should use custom name within warning message\" do\n        expect(env[:ui]).to receive(:warn).with(/custom-name/)\n        command.execute\n      end\n    end\n\n    context \"with deprecated subcommand\" do\n      let(:command_class) do\n        Class.new(Vagrant.plugin(\"2\", :command)) do\n          def self.name\n            \"VagrantPlugins::CommandTest::Command::Action\"\n          end\n          def execute\n            @env[:ui].info(\"COMMAND CONTENT\")\n            0\n          end\n        end\n      end\n\n      it \"should not modify empty synopsis\" do\n        expect(command_class.synopsis.to_s).to be_empty\n      end\n\n      it \"should extract command name and subname\" do\n        expect(command.deprecation_command_name).to eq(\"test action\")\n      end\n    end\n  end\n\n  describe Vagrant::Util::CommandDeprecation::Complete do\n    before{ command_class.include(Vagrant::Util::CommandDeprecation::Complete) }\n\n    it \"should add deprecation warning to synopsis\" do\n      expect(command_class.synopsis).to include('[DEPRECATED]')\n      command.class.synopsis\n    end\n\n    it \"should raise a deprecation error when executed\" do\n      expect{ command.execute }.to raise_error(Vagrant::Errors::CommandDeprecated)\n    end\n\n    it \"should not run original command\" do\n      expect(env[:ui]).not_to receive(:info).with(\"COMMAND CONTENT\")\n      expect{ command.execute }.to raise_error(Vagrant::Errors::CommandDeprecated)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/credential_scrubber_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/credential_scrubber\"\n\ndescribe Vagrant::Util::CredentialScrubber do\n  subject{ Vagrant::Util::CredentialScrubber }\n\n  after{ subject.reset! }\n\n  describe \".url_scrubber\" do\n    let(:user){ \"vagrant-user\" }\n    let(:password){ \"vagrant-pass\" }\n    let(:url){ \"http://#{user}:#{password}@example.com\" }\n\n    it \"should remove user credentials from URL\" do\n      result = subject.url_scrubber(url)\n      expect(result).not_to include(user)\n      expect(result).not_to include(password)\n    end\n  end\n\n  describe \".sensitive\" do\n    it \"should return a nil value\" do\n      expect(subject.sensitive(\"value\")).to be_nil\n    end\n\n    it \"should add value to list of strings\" do\n      subject.sensitive(\"value\")\n      expect(subject.sensitive_strings).to include(\"value\")\n    end\n\n    it \"should remove duplicates\" do\n      subject.sensitive(\"value\")\n      subject.sensitive(\"value\")\n      expect(subject.sensitive_strings.count(\"value\")).to eq(1)\n    end\n\n    it \"should not add an empty string\" do\n      subject.sensitive(\"\")\n      expect(subject.sensitive_strings).to be_empty\n    end\n\n    it \"should type cast input to string\" do\n      subject.sensitive(2)\n      expect(subject.sensitive_strings.first).to eq(\"2\")\n    end\n  end\n\n  describe \".unsensitive\" do\n    it \"should return a nil value\" do\n      expect(subject.unsensitive(\"value\")).to be_nil\n    end\n\n    it \"should remove value from list\" do\n      subject.sensitive(\"value\")\n      expect(subject.sensitive_strings).to include(\"value\")\n      subject.unsensitive(\"value\")\n      expect(subject.sensitive_strings).not_to include(\"value\")\n    end\n  end\n\n  describe \".sensitive_strings\" do\n    it \"should always return the same array\" do\n      expect(subject.sensitive_strings).to be(subject.sensitive_strings)\n    end\n  end\n\n  describe \".desensitize\" do\n    let(:to_scrub){ [] }\n    let(:string){ \"a line of text with my-birthday and my-cats-birthday embedded\" }\n    before{ to_scrub.each{|s| subject.sensitive(s) }}\n\n    context \"with no sensitive strings registered\" do\n      it \"should not modify the string\" do\n        expect(subject.desensitize(string)).to eq(string)\n      end\n    end\n\n    context \"with single value registered\" do\n      let(:to_scrub){ [\"my-birthday\"] }\n\n      it \"should remove the registered value\" do\n        expect(subject.desensitize(string)).not_to include(to_scrub.first)\n      end\n    end\n\n    context \"with multiple values registered\" do\n      let(:to_scrub){ [\"my-birthday\", \"my-cats-birthday\"] }\n\n      it \"should remove all registered values\" do\n        result = subject.desensitize(string)\n        to_scrub.each do |registered_value|\n          expect(result).not_to include(registered_value)\n        end\n      end\n    end\n\n    context \"with sensitive words that are part of non-sensitive words\" do\n      let(:to_scrub){ [\"a\"] }\n     \n      it \"should not remove parts of words\" do\n        result = subject.desensitize(string)\n        to_scrub.each do |registered_value|\n          expect(result).not_to match(/(\\W|^)#{registered_value}(\\W|$)/)\n        end\n        expect(result).to include(\"my-birthday\")\n        expect(result).to include(\"my-cats-birthday\")\n      end\n    end\n\n    context \"with sensitive words that are part of non-sensitive words\" do\n      let(:to_scrub){ [\"avery@strange/string^indeed!\"] }\n      let(:string){ \"a line of text with avery@strange/string^indeed! my-birthday and my-cats-birthday embedded\" }\n\n      it \"should work for strings with escape characters\" do\n        result = subject.desensitize(string)\n        to_scrub.each do |registered_value|\n          expect(result).not_to include(registered_value)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/curl_helper_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/curl_helper\"\n\ndescribe Vagrant::Util::CurlHelper do\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/deep_merge_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire 'vagrant/util/deep_merge'\n\ndescribe Vagrant::Util::DeepMerge do\n  it \"should deep merge hashes\" do\n    original = {\n      \"foo\" => {\n        \"bar\" => \"baz\",\n      },\n      \"bar\" => \"blah\",\n    }\n\n    other = {\n      \"foo\" => {\n        \"bar\" => \"new\",\n      },\n    }\n\n    result = described_class.deep_merge(original, other)\n    expect(result).to_not equal(original)\n    expect(result).to eq({\n      \"foo\" => {\n        \"bar\" => \"new\",\n      },\n      \"bar\" => \"blah\",\n    })\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/directory_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\nrequire \"vagrant/util/directory\"\nrequire \"time\"\n\ndescribe Vagrant::Util::Directory do\n  include_context \"unit\"\n  \n  let(:subject){ Vagrant::Util::Directory }\n  \n  describe \".directory_changed?\" do\n\n    it \"should return false if the threshold time is larger the all mtimes\" do\n      t = Time.new(\"3008\", \"09\", \"09\")\n      expect(subject.directory_changed?(Dir.getwd, t)).to eq(false)\n    end\n\n    it \"should return true if the threshold time is less than any mtimes\" do\n      t = Time.new(\"1990\", \"06\", \"06\")\n      expect(subject.directory_changed?(Dir.getwd, t)).to eq(true)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/downloader_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/downloader\"\n\ndescribe Vagrant::Util::Downloader do\n  let(:source) { \"foo\" }\n  let(:destination) { \"bar\" }\n  let(:exit_code) { 0 }\n  let(:options) { {} }\n\n  let(:subprocess_result) do\n    double(\"subprocess_result\").tap do |result|\n      allow(result).to receive(:exit_code).and_return(exit_code)\n      allow(result).to receive(:stderr).and_return(\"\")\n    end\n  end\n\n  subject { described_class.new(source, destination, options) }\n\n  before :each do\n    allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(subprocess_result)\n    allow(Vagrant).to receive(:in_installer?).and_return(false)\n  end\n\n  describe \"USER_AGENT\" do\n    it \"should not include a trailing space\" do\n      expect(described_class.const_get(:USER_AGENT)).not_to end_with(\" \")\n    end\n  end\n\n  describe \"#download!\" do\n    let(:curl_options) {\n      [\"-q\", \"--fail\", \"--location\", \"--max-redirs\", \"10\",\n       \"--verbose\", \"--user-agent\", described_class::USER_AGENT,\n       \"--output\", destination, source, {}]\n    }\n\n    context \"on Windows\" do\n      before do\n        allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n      end\n\n      it \"should use best effort for ssl revocation check by default\" do\n        expect(subject).to receive(:execute_curl) do |opts, *_|\n          expect(opts).to include(\"--ssl-revoke-best-effort\")\n        end\n        subject.download!\n      end\n\n      context \"when ssl revoke best effort is disabled\" do\n        let(:options) { {disable_ssl_revoke_best_effort: true} }\n\n        it \"should not use best effort for ssl revocation check\" do\n          expect(subject).to receive(:execute_curl) do |opts, _|\n            expect(opts).not_to include(\"--ssl-revoke-best-effort\")\n          end\n\n          subject.download!\n        end\n      end\n    end\n\n    context \"with UI\" do\n      let(:ui) { Vagrant::UI::Silent.new }\n      let(:options) { {ui: ui} }\n      let(:source) { \"http://example.org/vagrant.box\" }\n      let(:redirect) { nil }\n      let(:progress_data) { \"Location: #{redirect}\" }\n\n      after do\n        expect(subject).to receive(:execute_curl) do |*_, &data_proc|\n          expect(data_proc).not_to be_nil\n          data_proc.call(:stderr, progress_data)\n        end\n        subject.download!\n      end\n\n      context \"with Location header at same host\" do\n        let(:redirect) { \"http://example.org/other-vagrant.box\" }\n\n        it \"should not output redirection information\" do\n          expect(ui).not_to receive(:detail)\n        end\n      end\n\n      context \"with Location header at different host\" do\n        let(:redirect) { \"http://example.com/vagrant.box\" }\n\n        it \"should output redirection information\" do\n          expect(ui).to receive(:detail).with(/example.com/).and_call_original\n        end\n      end\n\n      context \"with Location header at different subdomain\" do\n        let(:redirect) { \"http://downloads.example.org/vagrant.box\" }\n\n        it \"should output redirection information\" do\n          expect(ui).to receive(:detail).with(/downloads.example.org/).and_call_original\n        end\n      end\n\n      context \"with custom header including Location name\" do\n        let(:custom_redirect) { \"http://example.com/vagrant.box\" }\n        let(:progress_data) { \"X-Custom-Location: #{custom_redirect}\" }\n\n        it \"should not output redirection information\" do\n          expect(ui).not_to receive(:detail)\n        end\n\n        context \"with Location header at different host\" do\n          let(:redirect) { \"http://downloads.example.com/vagrant.box\" }\n          let(:progress_data) { \"X-Custom-Location: #{custom_redirect}\\nLocation: #{redirect}\" }\n\n          it \"should output redirection information\" do\n            expect(ui).to receive(:detail).with(/downloads.example.com/).and_call_original\n          end\n        end\n      end\n    end\n\n    context \"with a good exit status\" do\n      let(:exit_code) { 0 }\n\n      it \"downloads the file and returns true\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).\n          with(\"curl\", *curl_options).\n          and_return(subprocess_result)\n\n        expect(subject.download!).to be\n      end\n    end\n\n    context \"with a bad exit status\" do\n      let(:exit_code) { 1 }\n      let(:subprocess_result_416) do\n        double(\"subprocess_result\").tap do |result|\n          allow(result).to receive(:exit_code).and_return(exit_code)\n          allow(result).to receive(:stderr).and_return(\"curl: (416) The download is fine\")\n        end\n      end\n\n      it \"continues on if a 416 was received\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).\n          with(\"curl\", *curl_options).\n          and_return(subprocess_result_416)\n\n        expect(subject.download!).to be(true)\n      end\n\n      it \"raises an exception\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).\n          with(\"curl\", *curl_options).\n          and_return(subprocess_result)\n\n        expect { subject.download! }.\n          to raise_error(Vagrant::Errors::DownloaderError)\n      end\n    end\n\n    context \"with a username and password\" do\n      it \"downloads the file with the proper flags\" do\n        original_source = source\n        source  = \"http://foo:bar@example.com/box.box\"\n        subject = described_class.new(source, destination)\n\n        i = curl_options.index(original_source)\n        curl_options[i] = \"http://example.com/box.box\"\n\n        i = curl_options.index(\"--output\")\n        curl_options.insert(i, \"foo:bar\")\n        curl_options.insert(i, \"-u\")\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).\n          with(\"curl\", *curl_options).\n          and_return(subprocess_result)\n\n        expect(subject.download!).to be(true)\n      end\n    end\n\n    context \"with an urlescaped username and password\" do\n      it \"downloads the file with unescaped credentials\" do\n        original_source = source\n        source  = \"http://fo%5Eo:b%40r@example.com/box.box\"\n        subject = described_class.new(source, destination)\n\n        i = curl_options.index(original_source)\n        curl_options[i] = \"http://example.com/box.box\"\n\n        i = curl_options.index(\"--output\")\n        curl_options.insert(i, \"fo^o:b@r\")\n        curl_options.insert(i, \"-u\")\n\n        expect(Vagrant::Util::Subprocess).to receive(:execute).\n          with(\"curl\", *curl_options).\n          and_return(subprocess_result)\n\n        expect(subject.download!).to be(true)\n      end\n    end\n\n    context \"with checksum\" do\n      let(:checksum_expected_value){ 'MD5_CHECKSUM_VALUE' }\n      let(:checksum_invalid_value){ 'INVALID_VALUE' }\n      let(:filechecksum) { double(\"filechecksum\", checksum: checksum_value) }\n      let(:checksum_value) { double(\"checksum_value\") }\n\n      before { allow(FileChecksum).to receive(:new).with(any_args).and_return(filechecksum) }\n\n      [Digest::MD5, Digest::SHA1, Digest::SHA256, Digest::SHA384, Digest::SHA512].each do |klass|\n        short_name = klass.to_s.split(\"::\").last.downcase\n\n        context \"using #{short_name} digest\" do\n          subject { described_class.new(source, destination, short_name.to_sym => checksum_expected_value) }\n\n          context \"that matches expected value\" do\n            let(:checksum_value) { checksum_expected_value }\n\n            it \"should not raise an exception\" do\n              expect(subject.download!).to be(true)\n            end\n          end\n\n          context \"that does not match expected value\" do\n            let(:checksum_value) { checksum_invalid_value }\n\n            it \"should raise an exception\" do\n              expect{ subject.download! }.to raise_error(Vagrant::Errors::DownloaderChecksumError)\n            end\n          end\n        end\n      end\n\n      context \"using both md5 and sha1 digests\" do\n        context \"that both match expected values\" do\n          let(:checksum_value) { checksum_expected_value }\n\n          subject { described_class.new(source, destination, md5: checksum_expected_value, sha1: checksum_expected_value) }\n\n          it \"should not raise an exception\" do\n            expect(subject.download!).to be(true)\n          end\n        end\n\n        context \"that only sha1 matches expected value\" do\n          subject { described_class.new(source, destination, md5: checksum_expected_value, sha1: checksum_expected_value) }\n\n          let(:valid_checksum) { double(\"valid_checksum\", checksum: checksum_expected_value) }\n          let(:invalid_checksum) { double(\"invalid_checksum\", checksum: checksum_invalid_value) }\n\n          before do\n            allow(FileChecksum).to receive(:new).with(anything, :sha1).and_return(valid_checksum)\n            allow(FileChecksum).to receive(:new).with(anything, :md5).and_return(invalid_checksum)\n          end\n\n          it \"should raise an exception\" do\n            expect{ subject.download! }.to raise_error(Vagrant::Errors::DownloaderChecksumError)\n          end\n        end\n\n        context \"that only md5 matches expected value\" do\n          subject { described_class.new(source, destination, md5: checksum_expected_value, sha1: checksum_expected_value) }\n\n          let(:valid_checksum) { double(\"valid_checksum\", checksum: checksum_expected_value) }\n          let(:invalid_checksum) { double(\"invalid_checksum\", checksum: checksum_invalid_value) }\n\n          before do\n            allow(FileChecksum).to receive(:new).with(anything, :md5).and_return(valid_checksum)\n            allow(FileChecksum).to receive(:new).with(anything, :sha1).and_return(invalid_checksum)\n          end\n\n          it \"should raise an exception\" do\n            expect{ subject.download! }.to raise_error(Vagrant::Errors::DownloaderChecksumError)\n          end\n        end\n\n        context \"that none match expected value\" do\n          let(:checksum_value) { checksum_expected_value }\n          subject { described_class.new(source, destination, md5: checksum_invalid_value, sha1: checksum_invalid_value) }\n\n          it \"should raise an exception\" do\n            expect{ subject.download! }.to raise_error(Vagrant::Errors::DownloaderChecksumError)\n          end\n        end\n      end\n\n      context \"when extra download options specified\" do\n        let(:options) { {:box_extra_download_options => [\"--test\", \"arbitrary\"]} }\n        subject { described_class.new(source, destination, options) }\n\n        it \"inserts the extra download options\" do\n          i = curl_options.index(\"--output\")\n          curl_options.insert(i, \"arbitrary\")\n          curl_options.insert(i, \"--test\")\n          expect(Vagrant::Util::Subprocess).to receive(:execute).\n            with(\"curl\", *curl_options).\n            and_return(subprocess_result)\n\n        expect(subject.download!).to be(true)\n        end\n      end\n    end\n  end\n\n  describe \"#head\" do\n    let(:curl_options) {\n      [\"-q\", \"-I\", \"--fail\", \"--location\", \"--max-redirs\", \"10\",\n       \"--verbose\", \"--user-agent\", described_class::USER_AGENT,\n       source, {}]\n    }\n\n    it \"returns the output\" do\n      allow(subprocess_result).to receive(:stdout).and_return(\"foo\")\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute).\n        with(\"curl\", *curl_options).and_return(subprocess_result)\n\n      expect(subject.head).to eq(\"foo\")\n    end\n  end\n\n  describe \"#options\" do\n    describe \"CURL_CA_BUNDLE\" do\n      let(:ca_bundle){ \"CUSTOM_CA_BUNDLE\" }\n\n      context \"when running within the installer\" do\n        before do\n          allow(Vagrant).to receive(:in_installer?).and_return(true)\n          allow(ENV).to receive(:[]).with(\"CURL_CA_BUNDLE\").and_return(ca_bundle)\n        end\n\n        it \"should set custom CURL_CA_BUNDLE in subprocess ENV\" do\n          _, subprocess_opts = subject.send(:options)\n          expect(subprocess_opts[:env]).not_to be_nil\n          expect(subprocess_opts[:env][\"CURL_CA_BUNDLE\"]).to eql(ca_bundle)\n        end\n      end\n\n      context \"when not running within the installer\" do\n        before{ allow(Vagrant).to receive(:installer?).and_return(false) }\n\n        it \"should not set custom CURL_CA_BUNDLE in subprocess ENV\" do\n          _, subprocess_opts = subject.send(:options)\n          expect(subprocess_opts[:env]).to be_nil\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/env_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire 'vagrant/util/env'\n\ndescribe Vagrant::Util::Env do\n  context \"with valid environment variables\" do\n    before do\n      ENV[\"VAGRANT_TEST\"] = \"1\"\n    end\n\n    after do\n      ENV.delete(\"VAGRANT_TEST\")\n    end\n\n    it \"should execute block with original environment variables\" do\n      Vagrant::Util::Env.with_original_env do\n        expect(ENV[\"VAGRANT_TEST\"]).to be_nil\n      end\n    end\n\n    it \"should replace environment variables after executing block\" do\n      Vagrant::Util::Env.with_original_env do\n        expect(ENV[\"VAGRANT_TEST\"]).to be_nil\n      end\n      expect(ENV[\"VAGRANT_TEST\"]).to eq(\"1\")\n    end\n  end\n\n  context \"with invalid environment variables\" do\n    it \"should not attempt to restore invalid environment variable\" do\n      invalid_vars = ENV.to_hash.merge(\"VAGRANT_OLD_ENV_\" => \"INVALID\")\n      mock = expect(ENV).to receive(:each)\n      invalid_vars.each do |k,v|\n        mock.and_yield(k, v)\n      end\n      expect do\n        Vagrant::Util::Env.with_original_env do\n          expect(ENV[\"VAGRANT_TEST\"]).to be_nil\n        end\n      end.not_to raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/experimental_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/experimental\"\n\ndescribe Vagrant::Util::Experimental do\n  include_context \"unit\"\n  before(:all) { described_class.reset! }\n  after(:each) { described_class.reset! }\n  subject { described_class }\n\n  describe \"#enabled?\" do\n    it \"returns true if enabled with '1'\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"1\")\n      expect(subject.enabled?).to eq(true)\n    end\n\n    it \"returns true if enabled with a list of features\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"list,of,features\")\n      expect(subject.enabled?).to eq(true)\n    end\n\n    it \"returns false if disabled\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"0\")\n      expect(subject.enabled?).to eq(false)\n    end\n\n    it \"returns false if not set\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(nil)\n      expect(subject.enabled?).to eq(false)\n    end\n  end\n\n  describe \"#global_enabled?\" do\n    it \"returns true if enabled with '1'\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"1\")\n      expect(subject.global_enabled?).to eq(true)\n    end\n\n    it \"returns false if enabled with a partial list of features\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"list,of,features\")\n      expect(subject.global_enabled?).to eq(false)\n    end\n\n    it \"returns false if disabled\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"0\")\n      expect(subject.global_enabled?).to eq(false)\n    end\n\n    it \"returns false if not set\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(nil)\n      expect(subject.global_enabled?).to eq(false)\n    end\n  end\n\n  describe \"#feature_enabled?\" do\n    it \"returns true if flag set to 1\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"1\")\n      expect(subject.feature_enabled?(\"anything\")).to eq(true)\n    end\n\n    it \"returns true if flag contains feature requested\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"secret_feature\")\n      expect(subject.feature_enabled?(\"secret_feature\")).to eq(true)\n    end\n\n    it \"returns true if flag contains feature requested and the request is a symbol\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"secret_feature\")\n      expect(subject.feature_enabled?(:secret_feature)).to eq(true)\n    end\n\n    it \"returns true if flag contains feature requested with other features 'enabled'\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"secret_feature,other_secret\")\n      expect(subject.feature_enabled?(\"secret_feature\")).to eq(true)\n    end\n\n    it \"returns false if flag is set but does not contain feature requested\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"fake_feature\")\n      expect(subject.feature_enabled?(\"secret_feature\")).to eq(false)\n    end\n\n    it \"returns false if flag set to 0\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"0\")\n      expect(subject.feature_enabled?(\"anything\")).to eq(false)\n    end\n\n    it \"returns false if flag is not set\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(nil)\n      expect(subject.feature_enabled?(\"anything\")).to eq(false)\n    end\n  end\n\n  describe \"#features_requested\" do\n    it \"returns an array of requested features\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"secret_feature,other_secret\")\n      expect(subject.features_requested).to eq([\"secret_feature\",\"other_secret\"])\n    end\n  end\n\n  describe \"#guard_with\" do\n    it \"does not execute the block if the feature is not requested\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(nil)\n      expect{|b| subject.guard_with(\"secret_feature\", &b) }.not_to yield_control\n    end\n\n    it \"executes the block if the feature is valid and requested\" do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_EXPERIMENTAL\").and_return(\"secret_feature,other_secret\")\n      expect{|b| subject.guard_with(\"secret_feature\", &b) }.to yield_control\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/file_checksum_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\nrequire 'digest/md5'\nrequire 'digest/sha1'\n\nrequire 'vagrant/util/file_checksum'\n\ndescribe FileChecksum do\n  include_context \"unit\"\n\n  let(:environment) { isolated_environment }\n\n  it \"should return a valid checksum for a file\" do\n    file = environment.workdir.join(\"file\")\n    file.open(\"w+\") { |f| f.write(\"HELLO!\") }\n\n    # Check multiple digests\n    instance = described_class.new(file, Digest::MD5)\n    expect(instance.checksum).to eq(\"9ac96c64417b5976a58839eceaa77956\")\n\n    instance = described_class.new(file, Digest::SHA1)\n    expect(instance.checksum).to eq(\"264b207c7913e461c43d0f63d2512f4017af4755\")\n  end\n\n  it \"should support initialize with class or string\" do\n    file = environment.workdir.join(\"file\")\n    file.open(\"w+\") { |f| f.write(\"HELLO!\") }\n\n    %w(md5 sha1 sha256 sha384 sha512).each do |type|\n      klass = Digest.const_get(type.upcase)\n      t_i = described_class.new(file, type)\n      k_i = described_class.new(file, klass)\n      expect(t_i.checksum).to eq(k_i.checksum)\n    end\n  end\n\n  context \"with an invalid digest\" do\n    let(:fake_digest) { :fake_digest }\n\n    it \"should raise an exception if the box has an invalid checksum type\" do\n      file = environment.workdir.join(\"file\")\n      file.open(\"w+\") { |f| f.write(\"HELLO!\") }\n\n      expect{ described_class.new(file, fake_digest) }.to raise_error(Vagrant::Errors::BoxChecksumInvalidType)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/file_mutex_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\nrequire 'vagrant/util/file_mutex'\n\ndescribe Vagrant::Util::FileMutex do\n  include_context \"unit\"\n\n  let(:temp_dir) { Dir.mktmpdir(\"vagrant-test-util-mutex_test\") }\n  let(:mutex_path) { File.join(temp_dir, \"test.lock\") }\n  let(:subject) { described_class.new(mutex_path) }\n\n  after do\n    FileUtils.rm_rf(temp_dir)\n  end\n\n  it \"should create a lock file\" do\n    subject.lock\n    expect(File).to exist(mutex_path)\n  end\n\n  it \"should create and delete lock file\" do\n    subject.lock\n    expect(File).to exist(mutex_path)\n    subject.unlock\n    expect(File).to_not exist(mutex_path)\n  end\n\n  it \"should not raise an error if the lock file does not exist\" do\n    subject.unlock\n    expect(File).to_not exist(mutex_path)\n  end\n\n  it \"should run a function with a lock\" do\n    subject.with_lock { true }\n    expect(File).to_not exist(mutex_path)\n  end\n\n  it \"should fail running a function when locked\" do\n    # create a lock\n    subject.lock\n    # create a new lock that will run a function\n    instance = described_class.new(mutex_path)\n    # lock should persist for multiple runs\n    expect {instance.with_lock { true }}.\n      to raise_error(Vagrant::Errors::VagrantLocked)\n    expect {instance.with_lock { true }}.\n      to raise_error(Vagrant::Errors::VagrantLocked)\n    # mutex should exist until its unlocked\n    expect(File).to exist(mutex_path)\n    subject.unlock\n    expect(File).to_not exist(mutex_path)\n  end\n\n  it \"should fail running a function within a locked\" do\n    # create a new lock that will run a function\n    instance = described_class.new(mutex_path)\n    expect {\n      subject.with_lock { instance.with_lock{true} }\n    }.to raise_error(Vagrant::Errors::VagrantLocked)\n    expect(File).to_not exist(mutex_path)\n  end\n\n  it \"should delete the lock even when the function fails\" do\n    expect {\n      subject.with_lock { raise Vagrant::Errors::VagrantError.new }\n    }.to raise_error(Vagrant::Errors::VagrantError)\n    expect(File).to_not exist(mutex_path)\n  end\n\n  it \"should unlock file before deletion\" do\n    lock_file = double(:lock_file)\n    allow(subject).to receive(:lock_file).and_return(lock_file)\n    allow(lock_file).to receive(:flock).and_return(true)\n\n    expect(lock_file).to receive(:flock).with(File::LOCK_UN)\n    expect(lock_file).to receive(:close)\n\n    subject.with_lock { true }\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/guest_hosts_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\nrequire 'vagrant/util/guest_hosts'\n\ndescribe \"Vagrant::Util::GuestHosts\" do\n  include_context \"unit\"\n\n  let(:machine) { double(\"machine\") }\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n  end\n\n  describe \"Linux\" do\n    subject{ Class.new { extend Vagrant::Util::GuestHosts::Linux } }\n    \n    it \"can add replace hostname\" do\n      subject.replace_host(comm, \"test.end\", \"192.186.4.2\")\n      expect(comm.received_commands[0]).to match(/sed -i '\\/test.end\\/d' \\/etc\\/hosts/)\n    end\n\n    it \"can add hostname to loopback interface\" do\n      subject.add_hostname_to_loopback_interface(comm, \"test.end\", 4)\n      expect(comm.received_commands[0]).to match(/for i in 1 2 3 4; do/)\n      expect(comm.received_commands[0]).to match(/echo \\\"127.0.\\${i}.1 test.end test\\\" >> \\/etc\\/hosts/)\n    end\n  end\n\n  describe \"BSD\" do\n    subject{ Class.new { extend Vagrant::Util::GuestHosts::BSD } }\n\n    it \"can add replace hostname\" do\n      subject.replace_host(comm, \"test.end\", \"192.186.4.2\")\n      expect(comm.received_commands[0]).to match(/sed -i.bak '\\/test.end\\/d' \\/etc\\/hosts/)\n    end\n\n    it \"can add hostname to loopback interface\" do\n      subject.add_hostname_to_loopback_interface(comm, \"test.end\", 4)\n      expect(comm.received_commands[0]).to match(/for i in 1 2 3 4; do/)\n      expect(comm.received_commands[0]).to match(/echo \\\"127.0.\\${i}.1 test.end test\\\" >> \\/etc\\/hosts/)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/guest_inspection_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/guest_inspection\"\n\ndescribe Vagrant::Util::GuestInspection::Linux do\n  include_context \"unit\"\n\n  let(:comm) { double(\"comm\") }\n\n  subject{ Class.new { extend Vagrant::Util::GuestInspection::Linux } }\n\n  describe \"#systemd?\" do\n    it \"should execute the command with sudo\" do\n      expect(comm).to receive(:test).with(/ps/, {sudo: true}).and_return(true)\n      expect(subject.systemd?(comm)).to be(true)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/guest_networks_spec.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/guest_networks\"\n\ndescribe Vagrant::Util::GuestNetworks::Linux do\n  include_context \"unit\"\n\n  subject { Class.new { extend Vagrant::Util::GuestNetworks::Linux } }\n\n  let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }\n  let(:config) { double(\"config\", vm: vm) }\n  let(:guest) { double(\"guest\") }\n  let(:machine) { double(\"machine\", guest: guest, config: config) }\n  let(:networks){ [[:public_network, network_1], [:private_network, network_2]] }\n  let(:vm){ double(\"vm\", networks: networks) }\n  let(:interfaces) { [\"eth1\", \"eth2\", \"eth3\"] }\n\n  before do\n    allow(machine).to receive(:communicate).and_return(comm)\n    allow(guest).to receive(:capability).with(:network_interfaces).and_return(interfaces)\n  end\n\n  after do\n    comm.verify_expectations!\n  end\n\n  let(:network_1) do\n    {\n      interface: 0,\n      type: \"dhcp\",\n    }\n  end\n\n  let(:network_2) do\n    {\n      interface: 1,\n      type: \"static\",\n      ip: \"33.33.33.10\",\n      netmask: \"255.255.0.0\",\n      gateway: \"33.33.0.1\",\n    }\n  end\n\n  let(:network_3) do\n    {\n      interface: 2,\n      type: \"static\",\n      ip: \"33.33.33.11\",\n      netmask: \"255.255.0.0\",\n      gateway: \"33.33.0.1\",\n    }\n  end\n  describe \"#configure_network_manager\" do\n    it \"should fetch mac address for devices\" do\n      subject.configure_network_manager(machine, [network_1, network_2, network_3])\n\n      expect(comm.received_commands).to include(%r{/net/eth1/address})\n      expect(comm.received_commands).to include(%r{/net/eth2/address})\n      expect(comm.received_commands).to include(%r{/net/eth3/address})\n    end\n\n    it \"should change ownership of files\" do\n      subject.configure_network_manager(machine, [network_1, network_2, network_3])\n\n      expect(comm.received_commands).to include(%r{chown root:root '/tmp/vagrant.*eth1.*'})\n      expect(comm.received_commands).to include(%r{chown root:root '/tmp/vagrant.*eth2.*'})\n      expect(comm.received_commands).to include(%r{chown root:root '/tmp/vagrant.*eth3.*'})\n    end\n\n    it \"should change mode of files\" do\n      subject.configure_network_manager(machine, [network_1, network_2, network_3])\n\n      expect(comm.received_commands).to include(%r{chmod 0600 '/tmp/vagrant.*eth1.*'})\n      expect(comm.received_commands).to include(%r{chmod 0600 '/tmp/vagrant.*eth2.*'})\n      expect(comm.received_commands).to include(%r{chmod 0600 '/tmp/vagrant.*eth3.*'})\n    end\n\n    it \"should move configuration files\" do\n      subject.configure_network_manager(machine, [network_1, network_2, network_3])\n\n      expect(comm.received_commands).to include(%r{mv '/tmp/vagrant.*eth1.*' '/etc/NetworkManager/system-connections/eth1.nmconnection'})\n      expect(comm.received_commands).to include(%r{mv '/tmp/vagrant.*eth2.*' '/etc/NetworkManager/system-connections/eth2.nmconnection'})\n      expect(comm.received_commands).to include(%r{mv '/tmp/vagrant.*eth3.*' '/etc/NetworkManager/system-connections/eth3.nmconnection'})\n    end\n\n    it \"should move configuration files\" do\n      subject.configure_network_manager(machine, [network_1, network_2, network_3])\n\n      expect(comm.received_commands).to include(%r{mv '/tmp/vagrant.*eth1.*' '/etc/NetworkManager/system-connections/eth1.nmconnection'})\n      expect(comm.received_commands).to include(%r{mv '/tmp/vagrant.*eth2.*' '/etc/NetworkManager/system-connections/eth2.nmconnection'})\n      expect(comm.received_commands).to include(%r{mv '/tmp/vagrant.*eth3.*' '/etc/NetworkManager/system-connections/eth3.nmconnection'})\n    end\n\n    it \"should move load new configuration files\" do\n      subject.configure_network_manager(machine, [network_1, network_2, network_3])\n\n      expect(comm.received_commands).to include(\"nmcli c load '/etc/NetworkManager/system-connections/eth1.nmconnection'\")\n      expect(comm.received_commands).to include(\"nmcli c load '/etc/NetworkManager/system-connections/eth2.nmconnection'\")\n      expect(comm.received_commands).to include(\"nmcli c load '/etc/NetworkManager/system-connections/eth3.nmconnection'\")\n    end\n\n    it \"should connect new devices\" do\n      subject.configure_network_manager(machine, [network_1, network_2, network_3])\n\n      expect(comm.received_commands).to include(\"nmcli d connect 'eth1'\")\n      expect(comm.received_commands).to include(\"nmcli d connect 'eth2'\")\n      expect(comm.received_commands).to include(\"nmcli d connect 'eth3'\")\n    end\n\n    context \"network configuration file\" do\n      let(:networks){ [[:public_network, network_1], [:private_network, network_2], [:private_network, network_3]] }\n\n      let(:tempfile) { double(\"tempfile\") }\n\n      before do\n        allow(tempfile).to receive(:binmode)\n        allow(tempfile).to receive(:write)\n        allow(tempfile).to receive(:fsync)\n        allow(tempfile).to receive(:close)\n        allow(tempfile).to receive(:path)\n        allow(Tempfile).to receive(:open).and_yield(tempfile)\n      end\n\n      it \"should generate two configuration files\" do\n        expect(Tempfile).to receive(:open).twice\n        subject.configure_network_manager(machine, [network_1, network_2])\n      end\n\n      it \"should generate three configuration files\" do\n        expect(Tempfile).to receive(:open).thrice\n        subject.configure_network_manager(machine, [network_1, network_2, network_3])\n      end\n\n      it \"should generate configuration with network_2 IP address\" do\n        expect(tempfile).to receive(:write).with(/#{Regexp.escape(network_2[:ip])}/)\n        subject.configure_network_manager(machine, [network_1, network_2, network_3])\n      end\n\n      it \"should generate configuration with network_3 IP address\" do\n        expect(tempfile).to receive(:write).with(/#{Regexp.escape(network_3[:ip])}/)\n        subject.configure_network_manager(machine, [network_1, network_2, network_3])\n      end\n    end\n  end\n\n  describe \"#get_current_devices\" do\n    it \"should return a hash of current devices\" do\n      expect(comm).to receive(:execute).with(\"nmcli -t c show\").and_yield(:stderr, \"\").and_yield(:stdout, \"1:eth1:ethernet:eth1\\n2:eth2:ethernet:eth2\\n3:eth3:ethernet:eth3\\n\")\n      result = subject.get_current_devices(comm)\n      expect(result).to eq({\"eth1\" => \"eth1\", \"eth2\" => \"eth2\", \"eth3\" => \"eth3\"})\n    end\n\n    it \"should return an empty hash if no devices are found\" do\n      expect(comm).to receive(:execute).with(\"nmcli -t c show\").and_yield(:stderr, \"some error\").and_yield(:stdout, \"\")\n      result = subject.get_current_devices(comm)\n      expect(result).to eq({})\n    end\n\n    it \"should ignore empty lines in the output\" do\n      expect(comm).to receive(:execute).with(\"nmcli -t c show\").and_yield(:stderr, \"\").and_yield(:stdout, \"1:eth1:ethernet:eth1\\n\\n2:eth2:ethernet:eth2\\n\\n3:eth3:ethernet:eth3\\n\")\n      result = subject.get_current_devices(comm)\n      expect(result).to eq({\"eth1\" => \"eth1\", \"eth2\" => \"eth2\", \"eth3\" => \"eth3\"})\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/hash_with_indifferent_access_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/hash_with_indifferent_access\"\n\ndescribe Vagrant::Util::HashWithIndifferentAccess do\n  let(:instance) { described_class.new }\n\n  it \"is a Hash\" do\n    expect(instance).to be_kind_of(Hash)\n  end\n\n  it \"allows indifferent access when setting with a string\" do\n    instance[\"foo\"] = \"bar\"\n    expect(instance[:foo]).to eq(\"bar\")\n  end\n\n  it \"allows indifferent access when setting with a symbol\" do\n    instance[:foo] = \"bar\"\n    expect(instance[\"foo\"]).to eq(\"bar\")\n  end\n\n  it \"allows indifferent key lookup\" do\n    instance[\"foo\"] = \"bar\"\n    expect(instance.key?(:foo)).to be\n    expect(instance.key?(:foo)).to be\n    expect(instance.include?(:foo)).to be\n    expect(instance.member?(:foo)).to be\n  end\n\n  it \"allows for defaults to be passed in via an initializer block\" do\n    instance = described_class.new do |h,k|\n      h[k] = \"foo\"\n    end\n\n    expect(instance[:foo]).to eq(\"foo\")\n    expect(instance[\"bar\"]).to eq(\"foo\")\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/install_cli_autocomplete_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire 'vagrant/util/install_cli_autocomplete'\nrequire 'fileutils'\n\ndescribe Vagrant::Util::InstallZSHShellConfig do\n\n  let(:home) { \"#{Dir.tmpdir}/not-home\" }\n  let(:target_file) { \"#{home}/.zshrc\" }\n\n  subject { described_class.new() }\n\n  describe \"#shell_installed\" do\n    it \"should return path to config file if exists\" do\n      allow(File).to receive(:exist?).with(target_file).and_return(true)\n      expect(subject.shell_installed(home)).to eq(target_file) \n    end\n\n    it \"should return nil if config file does not exists\" do\n      FileUtils.rm_rf(target_file)\n      expect(subject.shell_installed(home)).to eq(nil) \n    end\n  end\n\n  describe \"#is_installed\" do\n    it \"returns false if autocompletion not already installed\" do\n      allow(File).to receive(:foreach).with(target_file).and_yield(\"nothing\")\n      expect(subject.is_installed(target_file)).to eq(false)\n    end\n\n    it \"returns true if autocompletion is already installed\" do\n      allow(File).to receive(:foreach).with(target_file).and_yield(subject.prepend_string)\n      expect(subject.is_installed(target_file)).to eq(true)\n    end\n  end\n\n  describe \"#install\" do\n    it \"installs autocomplete\" do\n      allow(File).to receive(:exist?).with(target_file).and_return(true)\n      allow(File).to receive(:foreach).with(target_file).and_yield(\"nothing\")\n      expect(File).to receive(:open).with(target_file, \"a\")\n      subject.install(home)\n    end\n  end\nend\n\ndescribe Vagrant::Util::InstallCLIAutocomplete do\n\n  let(:zshrc_path) { \"/path/to/.zshrc\" }\n  let(:bashrc_path) { \"/path/to/.bash_profile\" }\n\n  subject { described_class }\n\n  describe \".install\" do\n    it \"installs requested shells\" do\n      allow_any_instance_of(Vagrant::Util::InstallZSHShellConfig).to receive(:install).and_return(zshrc_path)\n      expect(subject.install([\"zsh\"])).to eq([zshrc_path])\n    end\n\n    it \"installs all shells by default\" do\n      allow_any_instance_of(Vagrant::Util::InstallZSHShellConfig).to receive(:install).and_return(zshrc_path)\n      allow_any_instance_of(Vagrant::Util::InstallBashShellConfig).to receive(:install).and_return(bashrc_path)\n      expect(subject.install()).to eq([zshrc_path, bashrc_path])\n    end\n\n    it \"does not install unsupported shells\" do\n      expect{ subject.install([\"oops\"]) }.to raise_error(ArgumentError)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/io_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n# -*- coding: utf-8 -*-\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire 'vagrant/util/io'\n\ndescribe Vagrant::Util::IO do\n  describe \".read_until_block\" do\n    let(:io) { double(\"io\") }\n\n    before do\n      # Ensure that we don't get stuck in a loop\n      allow(io).to receive(:read_nonblock).and_raise(EOFError)\n      allow(io).to receive(:readpartial).and_raise(EOFError)\n    end\n\n    context \"on non-Windows system\" do\n      before { allow(Vagrant::Util::Platform).to receive(:windows?).\n          and_return(false) }\n\n      it \"should use a non-blocking read\" do\n        expect(io).to receive(:read_nonblock).and_return(\"\")\n        described_class.read_until_block(io)\n      end\n\n      it \"should receive data until breakable event\" do\n        expect(io).to receive(:read_nonblock).and_return(\"one\")\n        expect(io).to receive(:read_nonblock).and_return(\"two\")\n        expect(io).to receive(:read_nonblock).and_return(\"three\")\n        data = described_class.read_until_block(io)\n        expect(data).to eq(\"onetwothree\")\n      end\n\n      context \"with breakable errors\" do\n        [EOFError, Errno::EAGAIN, IO::EAGAINWaitReadable, IO::EINPROGRESSWaitReadable, IO::EWOULDBLOCKWaitReadable].each do |err_class|\n          it \"should break without error on #{err_class}\" do\n            expect(io).to receive(:read_nonblock).and_raise(err_class)\n            expect(described_class.read_until_block(io)).to be_empty\n          end\n        end\n      end\n\n      context \"with non-breakable errors\" do\n        it \"should raise the error\" do\n          expect(io).to receive(:read_nonblock).and_raise(StandardError)\n          expect { described_class.read_until_block(io) }.to raise_error(StandardError)\n        end\n      end\n    end\n\n    context \"on Windows system\" do\n      before do\n        allow(Vagrant::Util::Platform).to receive(:windows?).\n          and_return(true)\n        allow(IO).to receive(:select).with([io], any_args).\n          and_return([io])\n        allow(io).to receive(:empty?).and_return(false)\n      end\n\n      it \"should use select\" do\n        expect(IO).to receive(:select).with([io], any_args)\n        described_class.read_until_block(io)\n      end\n\n      it \"should receive data until breakable event\" do\n        expect(io).to receive(:readpartial).and_return(\"one\")\n        expect(io).to receive(:readpartial).and_return(\"two\")\n        expect(io).to receive(:readpartial).and_return(\"three\")\n        data = described_class.read_until_block(io)\n        expect(data).to eq(\"onetwothree\")\n      end\n\n      context \"with breakable errors\" do\n        [EOFError, Errno::EAGAIN, IO::EAGAINWaitReadable, IO::EINPROGRESSWaitReadable,\n          IO::EWOULDBLOCKWaitReadable].each do |err_class|\n          it \"should break without error on #{err_class}\" do\n            expect(io).to receive(:readpartial).and_raise(err_class)\n            expect(described_class.read_until_block(io)).to be_empty\n          end\n        end\n      end\n\n      context \"with non-breakable errors\" do\n        it \"should raise the error\" do\n          expect(io).to receive(:readpartial).and_raise(StandardError)\n          expect { described_class.read_until_block(io) }.to raise_error(StandardError)\n        end\n      end\n\n      context \"encoding\" do\n        let(:output) { \"output\".force_encoding(\"ASCII-8BIT\") }\n\n        before { expect(io).to receive(:readpartial).and_return(output) }\n\n        it \"should encode output to UTF-8\" do\n          expect(described_class.read_until_block(io).encoding.name).to eq(\"UTF-8\")\n        end\n\n        context \"when output includes characters with undefined conversion\" do\n          let(:output) { \"output\\xFF\".force_encoding(\"ASCII-8BIT\") }\n\n          before { expect(Encoding).to receive(:default_external).\n              and_return(Encoding.find(\"ASCII-8BIT\")) }\n\n          it \"should return data with invalid characters replaced\" do\n            expect(described_class.read_until_block(io)).to include(\"�\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/ipv4_interfaces_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/ipv4_interfaces\"\n\ndescribe Vagrant::Util::IPv4Interfaces do\n  subject { described_class }\n\n  describe \"#ipv4_interfaces\" do\n    let(:name) { double(\"name\") }\n    let(:address) { double(\"address\") }\n\n    let(:ipv4_ifaddr) do\n      double(\"ipv4_ifaddr\").tap do |ifaddr|\n        allow(ifaddr).to receive(:name).and_return(name)\n        allow(ifaddr).to receive_message_chain(:addr, :ipv4?).and_return(true)\n        allow(ifaddr).to receive_message_chain(:addr, :ip_address).and_return(address)\n      end\n    end\n\n    let(:ipv6_ifaddr) do\n      double(\"ipv6_ifaddr\").tap do |ifaddr|\n        allow(ifaddr).to receive(:name)\n        allow(ifaddr).to receive_message_chain(:addr, :ipv4?).and_return(false)\n      end\n    end\n\n    let(:ifaddrs) { [ ipv4_ifaddr, ipv6_ifaddr ] }\n\n    before do\n      allow(Socket).to receive(:getifaddrs).and_return(ifaddrs)\n    end\n\n    it \"returns a list of IPv4 interfaces with their names and addresses\" do\n      expect(subject.ipv4_interfaces).to eq([ [name, address] ])\n    end\n\n    context \"with nil interface address\" do\n      let(:nil_ifaddr) { double(\"nil_ifaddr\", addr: nil ) }\n      let(:ifaddrs) { [ ipv4_ifaddr, ipv6_ifaddr, nil_ifaddr ] }\n\n      it \"filters out nil addr info\" do\n        expect(subject.ipv4_interfaces).to eq([ [name, address] ])\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "test/unit/vagrant/util/is_port_open_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"socket\"\n\nrequire \"vagrant/util/is_port_open\"\n\ndescribe Vagrant::Util::IsPortOpen do\n  subject { described_class }\n\n  let(:open_port)   { 52811 }\n  let(:closed_port) { 52811 }\n\n  it \"should report open ports\" do\n    # Start a thread which listens on a port\n    thr = Thread.new do\n      server = TCPServer.new(open_port)\n      Thread.current[:running] = true\n\n      # Wait until we're told to die\n      Thread.current[:die]     = false\n      while !Thread.current[:die]\n        Thread.pass\n      end\n\n      # Die!\n      server.close\n    end\n\n    # Wait until the server is running\n    while !thr[:running]\n      Thread.pass\n    end\n\n    # Verify that we report the port is open\n    expect(subject.is_port_open?(\"127.0.0.1\", open_port)).to be\n\n    # Kill the thread\n    thr[:die] = true\n    thr.join\n  end\n\n  it \"should report closed ports\" do\n    # This CAN fail, since port 52811 might actually be in use, but I'm\n    # not sure what to do except choose some random port and hope for the\n    # best, really.\n    expect(subject.is_port_open?(\"127.0.0.1\", closed_port)).not_to be\n  end\n\n  it \"should handle connection refused\" do\n    expect(Socket).to receive(:tcp).with(\"0.0.0.0\", closed_port, any_args).and_raise(Errno::ECONNREFUSED)\n    expect(subject.is_port_open?(\"0.0.0.0\", closed_port)).to be(false)\n  end\n\n  it \"should raise an error if cannot assign requested address\" do\n    expect(Socket).to receive(:tcp).with(\"0.0.0.0\", open_port, any_args).and_raise(Errno::EADDRNOTAVAIL)\n    expect { subject.is_port_open?(\"0.0.0.0\", open_port) }.to raise_error(Errno::EADDRNOTAVAIL)\n  end\n\n  it \"should treat operation already in progress as unavailable\" do\n    expect(Socket).to receive(:tcp).with(\"0.0.0.0\", closed_port, any_args).and_raise(Errno::EALREADY)\n    expect(subject.is_port_open?(\"0.0.0.0\", closed_port)).to be(false)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/keypair_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire \"openssl\"\nrequire \"ed25519\"\nrequire \"net/ssh\"\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/keypair\"\n\ndescribe Vagrant::Util::Keypair do\n  describe Vagrant::Util::Keypair::Rsa do\n    describe \".create\" do\n      it \"generates a usable keypair with no password\" do\n        # I don't know how to validate the final return value yet...\n        pubkey, privkey, _ = described_class.create\n\n        pubkey  = OpenSSL::PKey::RSA.new(pubkey)\n        privkey = OpenSSL::PKey::RSA.new(privkey)\n\n        encrypted = pubkey.public_encrypt(\"foo\")\n        decrypted = privkey.private_decrypt(encrypted)\n\n        expect(decrypted).to eq(\"foo\")\n      end\n\n      it \"generates a keypair that requires a password\" do\n        pubkey, privkey, _ = described_class.create(\"password\")\n\n        pubkey  = OpenSSL::PKey::RSA.new(pubkey)\n        privkey = OpenSSL::PKey::RSA.new(privkey, \"password\")\n\n        encrypted = pubkey.public_encrypt(\"foo\")\n        decrypted = privkey.private_decrypt(encrypted)\n\n        expect(decrypted).to eq(\"foo\")\n      end\n    end\n  end\n\n  describe Vagrant::Util::Keypair::Ed25519 do\n    describe \".create\" do\n      it \"generates a usable keypair with no password\" do\n        pubkey, ossh_privkey, _ = described_class.create\n\n\n        privkey = Net::SSH::Authentication::ED25519::PrivKey.read(ossh_privkey, \"\").sign_key\n        pubkey = Ed25519::VerifyKey.new(pubkey)\n\n        message = \"vagrant test\"\n        signature = privkey.sign(message)\n        expect(pubkey.verify(signature, message)).to be_truthy\n      end\n\n      it \"does not generate a keypair that requires a password\" do\n        expect {\n          described_class.create(\"my password\")\n        }.to raise_error(NotImplementedError)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/line_buffer_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\nrequire \"vagrant/util/line_buffer\"\n\ndescribe Vagrant::Util::LineBuffer do\n  it \"should raise error when no callback is provided\" do\n    expect { subject }.to raise_error(ArgumentError)\n  end\n\n  context \"with block defined\" do\n    let(:block) { proc{ |l| output << l } }\n    let(:output) { [] }\n    let(:partial) { \"this is part of a line. \" }\n    let(:line) { \"this is a full line\\n\" }\n\n    subject { described_class.new(&block) }\n\n    it \"should not raise an error when callback is provided\" do\n      expect { subject }.not_to raise_error\n    end\n\n    describe \"#<<\" do\n      it \"should add line to the output\" do\n        subject << line\n        expect(output).to eq([line.rstrip])\n      end\n\n      it \"should not add partial line to output\" do\n        subject << partial\n        expect(output).to be_empty\n      end\n\n      it \"should add partial line to output once full line is given\" do\n        subject << partial\n        expect(output).to be_empty\n        subject << line\n        expect(output).to eq([partial + line.rstrip])\n      end\n\n      it \"should add line once it has surpassed max line length\" do\n        overflow = \"a\" * (described_class.const_get(:MAX_LINE_LENGTH) + 1)\n        subject << overflow\n        expect(output).to eq([overflow])\n      end\n    end\n\n    describe \"#close\" do\n      it \"should output any partial data left in buffer\" do\n        subject << partial\n        expect(output).to be_empty\n        subject.close\n        expect(output).to eq([partial])\n      end\n\n      it \"should not be writable after closing\" do\n        subject.close\n        expect { subject << partial }.to raise_error(FrozenError)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/line_endings_helper_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/line_ending_helpers\"\n\ndescribe Vagrant::Util::LineEndingHelpers do\n  let(:klass) do\n    Class.new do\n      extend Vagrant::Util::LineEndingHelpers\n    end\n  end\n\n  it \"should convert DOS to unix-style line endings\" do\n    expect(klass.dos_to_unix(\"foo\\r\\nbar\\r\\n\")).to eq(\"foo\\nbar\\n\")\n  end\nend\n\n"
  },
  {
    "path": "test/unit/vagrant/util/map_command_options_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire 'vagrant/util/map_command_options'\n\ndescribe Vagrant::Util::MapCommandOptions do\n\n  subject { described_class }\n\n  it \"should convert a map to a list of command options\" do\n    [\n      [{a: \"opt1\", b: true, c: \"opt3\"}, \"--\", [\"--a\", \"opt1\", \"--b\", \"--c\", \"opt3\"]],\n      [{a: \"opt1\", b: false, c: \"opt3\"}, \"-\", [\"-a\", \"opt1\", \"-c\", \"opt3\"]],\n      [{a: \"opt1\", b: 1}, \"--\", [\"--a\", \"opt1\"]],\n      [{a: 1, b: 1}, \"--\", []],\n      [{}, \"--\", []],\n      [nil, nil, []]\n    ].each do |map, cmd_flag, expected_output|\n      expect(subject.map_to_command_options(map, cmd_flag)).to eq(expected_output)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/mime_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire 'vagrant/util/mime'\nrequire 'mime/types'\n\ndescribe Vagrant::Util::Mime::Multipart do\n\n  let(:mime) { subject }\n  let(:time) { 603907018 }\n  let(:secure_random) { \"123qwe\" }\n\n  before do \n    allow(Time).to receive(:now).and_return(time)\n    allow(SecureRandom).to receive(:alphanumeric).and_return(secure_random)\n  end\n\n  it \"can add headers\" do\n    mime.headers[\"Mime-Version\"] = \"1.0\"\n    expected_string = \"Content-ID: <#{time}@#{secure_random}.local>\nContent-Type: multipart/mixed; boundary=Boundary_#{secure_random}\nMime-Version: 1.0\n\n--Boundary_#{secure_random}\n\"\n    expect(mime.to_s).to eq(expected_string)\n  end\n\n  it \"can add content\" do\n    mime.add(\"something\")\n    expected_string = \"Content-ID: <#{time}@#{secure_random}.local>\nContent-Type: multipart/mixed; boundary=Boundary_#{secure_random}\n\n--Boundary_#{secure_random}\nsomething\n--Boundary_#{secure_random}\n\"\n    expect(mime.to_s).to eq(expected_string)\n  end\n\n  it \"can add Vagrant::Util::Mime::Entity content\" do\n    mime.add(Vagrant::Util::Mime::Entity.new(\"something\", \"text/cloud-config\"))\n    expected_string = \"Content-ID: <#{time}@#{secure_random}.local>\nContent-Type: multipart/mixed; boundary=Boundary_#{secure_random}\n\n--Boundary_#{secure_random}\nContent-ID: <#{time}@#{secure_random}.local>\nContent-Type: text/cloud-config\n\nsomething\n--Boundary_#{secure_random}\n\"\n    expect(mime.to_s).to eq(expected_string)\n  end\nend\n\ndescribe Vagrant::Util::Mime::Entity do\n\n  let(:time) { 603907018 }\n  let(:secure_random) { \"123qwe\" }\n\n  before do \n    allow(Time).to receive(:now).and_return(time)\n    allow(SecureRandom).to receive(:alphanumeric).and_return(secure_random)\n  end\n\n  it \"registers the content type\" do \n    described_class.new(\"something\", \"text/cloud-config\")\n    expect(MIME::Types).to include(\"text/cloud-config\")\n  end\n\n  it \"outputs as a string\" do\n    entity = described_class.new(\"something\", \"text/cloud-config\")\n    expected_string = \"Content-ID: <#{time}@#{secure_random}.local>\nContent-Type: text/cloud-config\n\nsomething\"\n    expect(entity.to_s).to eq(expected_string)\n  end\n\n  it \"can set disposition\" do\n    entity = described_class.new(\"something\", \"text/cloud-config\")\n    entity.disposition = \"attachment; filename='path.sh'\"\n    expected_string = \"Content-ID: <#{time}@#{secure_random}.local>\nContent-Type: text/cloud-config\nContent-Disposition: attachment; filename='path.sh'\n\nsomething\"\n    expect(entity.to_s).to eq(expected_string)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/network_ip_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/network_ip\"\n\ndescribe Vagrant::Util::NetworkIP do\n  let(:klass) do\n    Class.new do\n      include Vagrant::Util::NetworkIP\n    end\n  end\n\n  subject { klass.new }\n\n  describe \"#network_address\" do\n    it \"calculates it properly\" do\n      expect(subject.network_address(\"192.168.2.234\", \"255.255.255.0\")).to eq(\"192.168.2.0\")\n    end\n\n    it \"calculates it properly with integer submask\" do\n      expect(subject.network_address(\"192.168.2.234\", \"24\")).to eq(\"192.168.2.0\")\n    end\n\n    it \"calculates it properly with integer submask\" do\n      expect(subject.network_address(\"192.168.2.234\", 24)).to eq(\"192.168.2.0\")\n    end\n\n    it \"calculates it properly for IPv6\" do\n      expect(subject.network_address(\"fde4:8dba:82e1::c4\", \"64\")).to eq(\"fde4:8dba:82e1::\")\n    end\n\n    it \"calculates it properly for IPv6\" do\n      expect(subject.network_address(\"fde4:8dba:82e1::c4\", 64)).to eq(\"fde4:8dba:82e1::\")\n    end\n\n    it \"calculates it properly for IPv6 for string mask\" do\n      expect(subject.network_address(\"fde4:8dba:82e1::c4\", \"ffff:ffff:ffff:ffff::\")).to eq(\"fde4:8dba:82e1::\")\n    end\n\n    it \"recovers from invalid netmask\" do\n      # The mask function will produce an error for ruby >= 2.5\n      # If using a version of ruby that produces and error, then\n      # test to ensure `subject.network_address` produces expected\n      # results.\n      begin\n        IPAddr.new(\"192.168.2.234\").mask(\"1.2.3.4\")\n      rescue IPAddr::InvalidPrefixError\n        expect(subject.network_address(\"192.168.2.234\", \"1.2.3.4\")).to eq(\"192.168.2.0\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/numeric_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/numeric\"\n\ndescribe Vagrant::Util::Numeric do\n  include_context \"unit\"\n  before(:each) { described_class.reset! }\n  subject { described_class }\n\n  describe \"#string_to_bytes\" do\n    it \"converts a string to the proper bytes\" do\n      bytes = subject.string_to_bytes(\"10KB\")\n      expect(bytes).to eq(10240)\n    end\n\n    it \"returns nil if the given string is the wrong format\" do\n      bytes = subject.string_to_bytes(\"10 Kilobytes\")\n      expect(bytes).to eq(nil)\n    end\n  end\n\n  describe \"bytes to megabytes\" do\n    it \"converts bytes to megabytes\" do\n      expect(subject.bytes_to_megabytes(1000000)).to eq(0.95)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/platform_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/platform\"\n\ndescribe Vagrant::Util::Platform do\n  include_context \"unit\"\n  before(:all) { described_class.reset! }\n  after { described_class.reset! }\n  subject { described_class }\n\n  describe \"#architecture\" do\n    let(:cpu_string) { \"unknown\" }\n\n    before do\n      allow(RbConfig::CONFIG).\n        to receive(:[]).with(\"target_cpu\").\n             and_return(cpu_string)\n    end\n\n    context \"when cpu is x86_64\" do\n      let(:cpu_string) { \"x86_64\" }\n\n      it \"should be mapped to amd64\" do\n        expect(described_class.architecture).to eq(\"amd64\")\n      end\n    end\n\n    context \"when cpu is x64\" do\n      let(:cpu_string) { \"x64\" }\n\n      it \"should be mapped to amd64\" do\n        expect(described_class.architecture).to eq(\"amd64\")\n      end\n    end\n\n    context \"when cpu is i386\" do\n      let(:cpu_string) { \"i386\" }\n\n      it \"should be mapped to i386\" do\n        expect(described_class.architecture).to eq(\"i386\")\n      end\n    end\n\n    context \"when cpu is 386\" do\n      let(:cpu_string) { \"386\" }\n\n      it \"should be mapped to 386\" do\n        expect(described_class.architecture).to eq(\"i386\")\n      end\n    end\n\n    context \"when cpu is arm64\" do\n      let(:cpu_string) { \"arm64\" }\n\n      it \"should be arm64\" do\n        expect(described_class.architecture).to eq(\"arm64\")\n      end\n    end\n\n    context \"when cpu is aarch64\" do\n      let(:cpu_string) { \"aarch64\" }\n\n      it \"should be mapped to arm64\" do\n        expect(described_class.architecture).to eq(\"arm64\")\n      end\n    end\n\n    context \"when cpu is unmapped value\" do\n      let(:cpu_string) { \"custom-cpu\" }\n\n      it \"should be returned as-is\" do\n        expect(described_class.architecture).to eq(cpu_string)\n      end\n    end\n\n    context \"when environment variable override is set\" do\n      let(:host_override) { \"custom-host-override\" }\n\n      before do\n        allow(ENV).to receive(:[]).\n                        with(\"VAGRANT_HOST_ARCHITECTURE\").\n                        and_return(host_override)\n      end\n\n      it \"should return the custom override\" do\n        expect(subject.architecture).to eq(host_override)\n      end\n    end\n  end\n\n  describe \"#cygwin_path\" do\n    let(:path) { \"C:\\\\msys2\\\\home\\\\vagrant\" }\n    let(:updated_path) { \"/home/vagrant\" }\n    let(:subprocess_result) do\n      double(\"subprocess_result\").tap do |result|\n        allow(result).to receive(:exit_code).and_return(0)\n        allow(result).to receive(:stdout).and_return(updated_path)\n      end\n    end\n\n    it \"takes a windows path and returns a formatted path\" do\n      allow(Vagrant::Util::Which).to receive(:which).and_return(\"C:/msys2/cygpath\")\n      allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(subprocess_result)\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"C:\\\\msys2\\\\cygpath\", \"-u\", \"-a\", \"C:\\\\msys2\\\\home\\\\vagrant\")\n\n      expect(subject.cygwin_path(path)).to eq(\"/home/vagrant\")\n    end\n  end\n\n  describe \"#msys_path\" do\n    let(:updated_path) { \"/home/vagrant\" }\n    let(:subprocess_result) do\n      double(\"subprocess_result\").tap do |result|\n        allow(result).to receive(:exit_code).and_return(0)\n        allow(result).to receive(:stdout).and_return(updated_path)\n      end\n    end\n    let(:old_path) { \"/old/path/bin:/usr/local/bin:/usr/bin\" }\n\n    it \"takes a windows path and returns a formatted path\" do\n      path = ENV[\"PATH\"]\n      allow(Vagrant::Util::Which).to receive(:which).and_return(\"C:/msys2/cygpath\")\n      allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(subprocess_result)\n      allow(ENV).to receive(:[]).with(\"PATH\").and_return(path)\n      allow(ENV).to receive(:[]).with(\"VAGRANT_OLD_ENV_PATH\").and_return(old_path)\n\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"C:\\\\msys2\\\\cygpath\", \"-u\", \"-a\", path)\n\n      expect(subject.msys_path(path)).to eq(\"/home/vagrant\")\n      expect(ENV[\"PATH\"]).to eq(path)\n    end\n  end\n\n  describe \"#cygwin?\" do\n    before do\n      allow(subject).to receive(:platform).and_return(\"test\")\n    end\n\n    around do |example|\n      with_temp_env(VAGRANT_DETECTED_OS: \"nope\", PATH: \"\") do\n        example.run\n      end\n    end\n\n    it \"returns true if VAGRANT_DETECTED_OS includes cygwin\" do\n      with_temp_env(VAGRANT_DETECTED_OS: \"cygwin\") do\n        expect(subject).to be_cygwin\n      end\n    end\n\n    it \"returns true if OSTYPE includes cygwin\" do\n      with_temp_env(OSTYPE: \"cygwin\") do\n        expect(subject).to be_cygwin\n      end\n    end\n\n    it \"returns true if platform has cygwin\" do\n      allow(subject).to receive(:platform).and_return(\"cygwin\")\n      expect(subject).to be_cygwin\n    end\n\n    it \"returns false if the PATH contains cygwin\" do\n      with_temp_env(PATH: \"C:/cygwin\") do\n        expect(subject).to_not be_cygwin\n      end\n    end\n\n    it \"returns false if nothing is available\" do\n      expect(subject).to_not be_cygwin\n    end\n  end\n\n  describe \"#msys?\" do\n    before do\n      allow(subject).to receive(:platform).and_return(\"test\")\n    end\n\n    around do |example|\n      with_temp_env(VAGRANT_DETECTED_OS: \"nope\", PATH: \"\") do\n        example.run\n      end\n    end\n\n    it \"returns true if VAGRANT_DETECTED_OS includes msys\" do\n      with_temp_env(VAGRANT_DETECTED_OS: \"msys\") do\n        expect(subject).to be_msys\n      end\n    end\n\n    it \"returns true if OSTYPE includes msys\" do\n      with_temp_env(OSTYPE: \"msys\") do\n        expect(subject).to be_msys\n      end\n    end\n\n    it \"returns true if platform has msys\" do\n      allow(subject).to receive(:platform).and_return(\"msys\")\n      expect(subject).to be_msys\n    end\n\n    it \"returns false if the PATH contains msys\" do\n      with_temp_env(PATH: \"C:/msys\") do\n        expect(subject).to_not be_msys\n      end\n    end\n\n    it \"returns false if nothing is available\" do\n      expect(subject).to_not be_msys\n    end\n  end\n\n  describe \"#fs_real_path\" do\n    it \"fixes drive letters on Windows\", :windows do\n      expect(described_class.fs_real_path(\"c:/foo\").to_s).to eql(\"C:/foo\")\n    end\n\n    it \"gracefully handles invalid input string errors\" do\n      bad_string = double(\"bad_string\")\n      allow(bad_string).to receive(:to_s).and_raise(ArgumentError)\n      allow_any_instance_of(String).to receive(:encode).with(\"filesystem\").and_return(bad_string)\n      allow(subject).to receive(:fs_case_sensitive?).and_return(false)\n\n      expect(described_class.fs_real_path(\"/dev/null\").to_s).to eql(\"/dev/null\")\n    end\n  end\n\n  describe \"#windows_unc_path\" do\n    it \"correctly converts a path\" do\n      expect(described_class.windows_unc_path(\"c:/foo\").to_s).to eql(\"\\\\\\\\?\\\\c:\\\\foo\")\n    end\n\n    context \"when given a UNC path\" do\n      let(:unc_path){ \"\\\\\\\\srvname\\\\path\" }\n\n      it \"should not modify the path\" do\n        expect(described_class.windows_unc_path(unc_path).to_s).to eql(unc_path)\n      end\n    end\n  end\n\n  describe \".systemd?\" do\n    before{ allow(subject).to receive(:windows?).and_return(false) }\n\n    context \"on windows\" do\n      before{ expect(subject).to receive(:windows?).and_return(true) }\n\n      it \"should return false\" do\n        expect(subject.systemd?).to be_falsey\n      end\n    end\n\n    it \"should return true if systemd is in use\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(double(:result, stdout: \"systemd\"))\n      expect(subject.systemd?).to be_truthy\n    end\n\n    it \"should return false if systemd is not in use\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(double(:result, stdout: \"other\"))\n      expect(subject.systemd?).to be_falsey\n    end\n  end\n\n  describe \".wsl_validate_matching_vagrant_versions!\" do\n    let(:exe_version){ Vagrant::VERSION.to_s }\n\n    before do\n      allow(Vagrant::Util::Which).to receive(:which).and_return(true)\n      allow(Vagrant::Util::Subprocess).to receive(:execute).with(\"vagrant.exe\", \"--version\").\n        and_return(double(exit_code: 0, stdout: \"Vagrant #{exe_version}\"))\n    end\n\n    it \"should not raise an error\" do\n      Vagrant::Util::Platform.wsl_validate_matching_vagrant_versions!\n    end\n\n    context \"when windows vagrant.exe is not installed\" do\n      before{ expect(Vagrant::Util::Which).to receive(:which).with(\"vagrant.exe\").and_return(nil) }\n\n      it \"should not raise an error\" do\n        Vagrant::Util::Platform.wsl_validate_matching_vagrant_versions!\n      end\n    end\n\n    context \"when versions do not match\" do\n      let(:exe_version){ \"1.9.9\" }\n\n      it \"should raise an error\" do\n        expect {\n          Vagrant::Util::Platform.wsl_validate_matching_vagrant_versions!\n        }.to raise_error(Vagrant::Errors::WSLVagrantVersionMismatch)\n      end\n    end\n  end\n\n  describe \".windows_hyperv_admin?\" do\n    before { allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_return(nil) }\n\n    it \"should return false when user is not in groups and cannot access Hyper-V\" do\n      expect(Vagrant::Util::Platform.windows_hyperv_admin?).to be_falsey\n    end\n\n    context \"when VAGRANT_IS_HYPERV_ADMIN environment variable is set\" do\n      before { allow(ENV).to receive(:[]).with(\"VAGRANT_IS_HYPERV_ADMIN\").and_return(\"1\") }\n\n      it \"should return true\" do\n        expect(Vagrant::Util::Platform.windows_hyperv_admin?).to be_truthy\n      end\n    end\n\n    context \"when user is in the Hyper-V administators group\" do\n      it \"should return true\" do\n        expect(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_return([\"Value\" => \"S-1-5-32-578\"].to_json)\n        expect(Vagrant::Util::Platform.windows_hyperv_admin?).to be_truthy\n      end\n    end\n\n    context \"when user is in the Domain Admins group\" do\n      it \"should return true\" do\n        expect(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_return([\"Value\" => \"S-1-5-21-000-000-000-512\"].to_json)\n        expect(Vagrant::Util::Platform.windows_hyperv_admin?).to be_truthy\n      end\n    end\n\n    context \"when user has access to Hyper-V\" do\n      it \"should return true\" do\n        expect(Vagrant::Util::PowerShell).to receive(:execute_cmd).with(/GetCurrent/).and_return(nil)\n        expect(Vagrant::Util::PowerShell).to receive(:execute_cmd).with(/Get-VMHost/).and_return(\"true\")\n        expect(Vagrant::Util::Platform.windows_hyperv_admin?).to be_truthy\n      end\n    end\n  end\n\n  describe \".windows_hyperv_enabled?\" do\n    it \"should return true if enabled\" do\n      allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_return('Enabled')\n\n      expect(Vagrant::Util::Platform.windows_hyperv_enabled?).to be_truthy\n    end\n\n    it \"should return false if disabled\" do\n      allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_return('Disabled')\n\n      expect(Vagrant::Util::Platform.windows_hyperv_enabled?).to be_falsey\n    end\n\n    it \"should return false if PowerShell cannot be validated\" do\n      allow_any_instance_of(Vagrant::Errors::PowerShellInvalidVersion).to receive(:translate_error)\n      allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_raise(Vagrant::Errors::PowerShellInvalidVersion)\n\n      expect(Vagrant::Util::Platform.windows_hyperv_enabled?).to be_falsey\n    end\n  end\n\n  context \"within the WSL\" do\n    before{ allow(subject).to receive(:wsl?).and_return(true) }\n\n    describe \".wsl_path?\" do\n      it \"should return true when path is not within /mnt\" do\n        expect(subject.wsl_path?(\"/tmp\")).to be(true)\n      end\n\n      it \"should return false when path is within /mnt\" do\n        expect(subject.wsl_path?(\"/mnt/c\")).to be(false)\n      end\n    end\n\n    describe \".wsl_rootfs\" do\n      let(:appdata_path){ \"C:\\\\Custom\\\\Path\" }\n      let(:registry_paths){ nil }\n\n      before do\n        allow(subject).to receive(:wsl_windows_appdata_local).and_return(appdata_path)\n        allow(Tempfile).to receive(:new).and_return(double(\"tempfile\", path: \"file.path\", close!: true))\n        allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_return(registry_paths)\n      end\n\n      context \"when no instance information is in the registry\" do\n        before do\n          expect(Dir).to receive(:open).with(/.*Custom.*Path.*/).and_yield(double(\"path\", path: appdata_path))\n          expect(File).to receive(:exist?).and_return(true)\n        end\n\n        it \"should only check the lxrun path\" do\n          expect(subject.wsl_rootfs).to include(appdata_path)\n        end\n      end\n\n      context \"with instance information in the registry\" do\n        let(:registry_paths) { [\"C:\\\\Path1\", \"C:\\\\Path2\"].join(\"\\r\\n\") }\n\n        before do\n          allow(Dir).to receive(:open).and_yield(double(\"path\", path: appdata_path))\n          allow(File).to receive(:exist?).and_return(false)\n        end\n\n        context \"when no matches are detected\" do\n          it \"should check all paths given\" do\n            expect(Dir).to receive(:open).and_yield(double(\"path\", path: appdata_path)).exactly(3).times\n            expect(File).to receive(:exist?).and_return(false).exactly(3).times\n            expect{ subject.wsl_rootfs }.to raise_error(Vagrant::Errors::WSLRootFsNotFoundError)\n          end\n\n          it \"should raise not found error\" do\n            expect{ subject.wsl_rootfs }.to raise_error(Vagrant::Errors::WSLRootFsNotFoundError)\n          end\n        end\n\n        context \"when file marker match found\" do\n          let(:matching_path){ registry_paths.split(\"\\r\\n\").last }\n          let(:matching_part){ matching_path.split(\"\\\\\").last }\n\n          before do\n            allow(File).to receive(:exist?).with(/#{matching_part}/).and_return(true)\n          end\n\n          it \"should return the matching path\" do\n            expect(Dir).to receive(:open).with(/#{matching_part}/).and_yield(double(\"path\", path: matching_part))\n            expect(subject.wsl_rootfs).to start_with(matching_path)\n          end\n\n          it \"should return matching path when access error encountered\" do\n            expect(Dir).to receive(:open).with(/#{matching_part}/).and_raise(Errno::EACCES)\n            expect(subject.wsl_rootfs).to start_with(matching_path)\n          end\n        end\n      end\n\n      context \"when wslpath command success\" do\n        it \"should check path returned by command\" do\n          expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(double(\"process\", exit_code: 0, stdout: \"/c/Custom/Path\"))\n          expect(Dir).to receive(:open).with(/^\\/c\\/Custom\\/Path\\//).and_yield(double(\"path\", path: appdata_path))\n          expect(File).to receive(:exist?).and_return(true)\n          expect(subject.wsl_rootfs).to include(appdata_path)\n        end\n      end\n\n      context \"when wslpath command failed\" do\n        it \"should check fallback path\" do\n          expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(double(\"process\", exit_code: 1))\n          expect(Dir).to receive(:open).with(/\\/mnt\\//).and_yield(double(\"path\", path: appdata_path))\n          expect(File).to receive(:exist?).and_return(true)\n          expect(subject.wsl_rootfs).to include(appdata_path)\n        end\n      end\n\n      context \"when wslpath command raise error\" do\n        it \"should check fallback path\" do\n          expect(Vagrant::Util::Subprocess).to receive(:execute).and_raise(Vagrant::Errors::CommandUnavailable, file: \"wslpath\")\n          expect(Dir).to receive(:open).with(/\\/mnt\\//).and_yield(double(\"path\", path: appdata_path))\n          expect(File).to receive(:exist?).and_return(true)\n          expect(subject.wsl_rootfs).to include(appdata_path)\n        end\n      end\n    end\n\n    describe \".wsl_to_windows_path\" do\n      let(:path){ \"/home/vagrant/test\" }\n\n      context \"when not within WSL\" do\n        before{ allow(subject).to receive(:wsl?).and_return(false) }\n\n        it \"should return the path unmodified\" do\n          expect(subject.wsl_to_windows_path(path)).to eq(path)\n        end\n      end\n\n      context \"when within WSL\" do\n        before{ allow(subject).to receive(:wsl?).and_return(true) }\n\n        context \"when windows access is not enabled\" do\n          before{ allow(subject).to receive(:wsl_windows_access?).and_return(false) }\n\n          it \"should return the path unmodified\" do\n            expect(subject.wsl_to_windows_path(path)).to eq(path)\n          end\n        end\n\n        context \"when windows access is enabled\" do\n          let(:rootfs_path){ \"C:\\\\WSL\\\\rootfs\" }\n\n          before do\n            allow(subject).to receive(:wsl_windows_access?).and_return(true)\n            allow(subject).to receive(:wsl_rootfs).and_return(rootfs_path)\n          end\n\n          it \"should generate expanded path when within WSL\" do\n            expect(subject.wsl_to_windows_path(path)).to eq(\"#{rootfs_path}#{path.gsub(\"/\", \"\\\\\")}\")\n          end\n\n          it \"should generate direct path when outside the WSL\" do\n            expect(subject.wsl_to_windows_path(\"/mnt/c/vagrant\")).to eq(\"c:\\\\vagrant\")\n          end\n\n          it \"should not modify path when already in windows format\" do\n            expect(subject.wsl_to_windows_path(\"C:\\\\vagrant\")).to eq(\"C:\\\\vagrant\")\n          end\n\n          context \"when within lxrun generated WSL instance\" do\n            let(:rootfs_path){ \"C:\\\\WSL\\\\lxss\" }\n\n            it \"should not include rootfs when accessing home\" do\n              expect(subject.wsl_to_windows_path(\"/home/vagrant\")).not_to include(\"rootfs\")\n            end\n\n            it \"should include rootfs when accessing non-home path\" do\n              expect(subject.wsl_to_windows_path(\"/tmp/test\")).to include(\"rootfs\")\n            end\n\n            it \"should properly handle Pathname\" do\n              expect(subject.wsl_to_windows_path(Pathname.new(\"/tmp/test\"))).to include(\"rootfs\")\n            end\n          end\n\n          context \"when wslpath command success\" do\n            it \"should return path returned by command\" do\n              expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(double(\"process\", exit_code: 0, stdout: \"C:\\\\Custom\\\\Path\"))\n              expect(subject.wsl_to_windows_path(path)).to eq(\"C:\\\\Custom\\\\Path\")\n            end\n          end\n\n          context \"when wslpath command failed\" do\n            it \"should return path by fallback\" do\n              expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(double(\"process\", exit_code: 1))\n              expect(subject.wsl_to_windows_path(path)).to eq(\"#{rootfs_path}#{path.gsub(\"/\", \"\\\\\")}\")\n            end\n          end\n\n          context \"when wslpath command raise error\" do\n            it \"should return path by fallback\" do\n              expect(Vagrant::Util::Subprocess).to receive(:execute).and_raise(Vagrant::Errors::CommandUnavailable, file: \"wslpath\")\n              expect(subject.wsl_to_windows_path(path)).to eq(\"#{rootfs_path}#{path.gsub(\"/\", \"\\\\\")}\")\n            end\n          end\n        end\n      end\n    end\n\n    describe \".wsl_windows_accessible_path\" do\n      context \"when within WSL\" do\n        before do\n          allow(subject).to receive(:wsl?).and_return(true)\n          allow(subject).to receive(:wsl_windows_home).and_return(\"C:\\\\Users\\\\vagrant\")\n          allow(ENV).to receive(:[]).with(\"VAGRANT_WSL_WINDOWS_ACCESS_USER_HOME_PATH\").and_return(nil)\n        end\n\n        context \"when wslpath command success\" do\n          it \"should return path returned by command\" do\n            expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(double(\"process\", exit_code: 0, stdout: \"/d/vagrant\"))\n            expect(subject.wsl_windows_accessible_path.to_s).to eq(\"/d/vagrant\")\n          end\n        end\n\n        context \"when wslpath command failed\" do\n          it \"should return path by fallback\" do\n            expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(double(\"process\", exit_code: 1))\n            expect(subject.wsl_windows_accessible_path.to_s).to eq(\"/mnt/c/Users/vagrant\")\n          end\n        end\n\n        context \"when wslpath command raise error\" do\n          it \"should return path by fallback\" do\n            expect(Vagrant::Util::Subprocess).to receive(:execute).and_raise(Vagrant::Errors::CommandUnavailable, file: \"wslpath\")\n            expect(subject.wsl_windows_accessible_path.to_s).to eq(\"/mnt/c/Users/vagrant\")\n          end\n        end\n      end\n    end\n\n    describe \".wsl_drvfs_mounts\" do\n      let(:mount_output) { <<-EOF\nrootfs on / type lxfs (rw,noatime)\nsysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime)\nproc on /proc type proc (rw,nosuid,nodev,noexec,noatime)\nnone on /dev type tmpfs (rw,noatime,mode=755)\ndevpts on /dev/pts type devpts (rw,nosuid,noexec,noatime)\nnone on /run type tmpfs (rw,nosuid,noexec,noatime,mode=755)\nnone on /run/lock type tmpfs (rw,nosuid,nodev,noexec,noatime)\nnone on /run/shm type tmpfs (rw,nosuid,nodev,noatime)\nnone on /run/user type tmpfs (rw,nosuid,nodev,noexec,noatime,mode=755)\nbinfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,noatime)\nC: on /mnt/c type drvfs (rw,noatime)\nEOF\n      }\n\n      before do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"mount\").\n          and_return(Vagrant::Util::Subprocess::Result.new(0, mount_output, \"\"))\n      end\n\n      it \"should locate DrvFs mount path\" do\n        expect(subject.wsl_drvfs_mounts).to eq([\"/mnt/c\"])\n      end\n\n      context \"when no DrvFs mounts exist\" do\n        let(:mount_output){ \"\" }\n\n        it \"should locate no paths\" do\n          expect(subject.wsl_drvfs_mounts).to eq([])\n        end\n      end\n    end\n\n    describe \".wsl_drvfs_path?\" do\n      before do\n        expect(subject).to receive(:wsl_drvfs_mounts).and_return([\"/mnt/c\"])\n      end\n\n      it \"should return true when path prefix is found\" do\n        expect(subject.wsl_drvfs_path?(\"/mnt/c/some/path\")).to be_truthy\n      end\n\n      it \"should return false when path prefix is not found\" do\n        expect(subject.wsl_drvfs_path?(\"/home/vagrant/some/path\")).to be_falsey\n      end\n    end\n  end\n\n  describe \".unix_windows_path\" do\n    it \"takes a windows path and returns a POSIX-like path\" do\n      expect(subject.unix_windows_path(\"C:\\\\Temp\\\\Windows\")).to eq(\"C:/Temp/Windows\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/powershell_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire 'vagrant/util/powershell'\n\ndescribe Vagrant::Util::PowerShell do\n  include_context \"unit\"\n\n  after{ described_class.reset! }\n\n  describe \".version\" do\n    before do\n      allow(described_class).to receive(:executable)\n        .and_return(\"powershell\")\n      allow(Vagrant::Util::Subprocess).to receive(:execute)\n    end\n\n    after do\n      described_class.version\n    end\n\n    it \"should execute powershell command\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"powershell\", any_args)\n    end\n\n    it \"should use the default timeout\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args, hash_including(\n        timeout: Vagrant::Util::PowerShell::DEFAULT_VERSION_DETECTION_TIMEOUT))\n    end\n\n    it \"should use environment variable provided timeout\" do\n      with_temp_env(\"VAGRANT_POWERSHELL_VERSION_DETECTION_TIMEOUT\" => \"1\") do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args, hash_including(\n          timeout: 1))\n        described_class.version\n      end\n    end\n\n    it \"should use default timeout when environment variable value is invalid\" do\n      with_temp_env(\"VAGRANT_POWERSHELL_VERSION_DETECTION_TIMEOUT\" => \"invalid value\") do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args, hash_including(\n          timeout: Vagrant::Util::PowerShell::DEFAULT_VERSION_DETECTION_TIMEOUT))\n        described_class.version\n      end\n    end\n  end\n\n  describe \".executable\" do\n    before do\n      allow(ENV).to receive(:[]).with(\"VAGRANT_PREFERRED_POWERSHELL\").and_return(nil)\n      allow(Vagrant::Util::Which).to receive(:which).and_return(nil)\n      allow(Vagrant::Util::Subprocess).to receive(:execute) do |*args|\n        Vagrant::Util::Subprocess::Result.new(0, args.last.sub(\"echo \", \"\"), \"\")\n      end\n    end\n\n    context \"when powershell found in PATH\" do\n      before{ expect(Vagrant::Util::Which).to receive(:which).\n          with(\"powershell\").and_return(\"powershell\") }\n\n      it \"should return powershell string\" do\n        expect(described_class.executable).to eq(\"powershell\")\n      end\n    end\n\n    context \"when pwsh found in PATH\" do\n      before { expect(Vagrant::Util::Which).to receive(:which).\n          with(\"pwsh\").and_return(\"pwsh\") }\n\n      it \"should return pwsh string\" do\n        expect(described_class.executable).to eq(\"pwsh\")\n      end\n    end\n\n    context \"when not found in PATH\" do\n      before { allow(File).to receive(:executable?) }\n\n      it \"should return nil\" do\n        expect(described_class.executable).to be_nil\n      end\n\n      it \"should check PATH with .exe extension\" do\n        expect(Vagrant::Util::Which).to receive(:which).with(\"powershell.exe\")\n        described_class.executable\n      end\n\n      it \"should return powershell.exe when found\" do\n        expect(Vagrant::Util::Which).to receive(:which).\n          with(\"powershell.exe\").and_return(\"powershell.exe\")\n        expect(described_class.executable).to eq(\"powershell.exe\")\n      end\n\n      it \"should check for powershell with full path\" do\n        expect(File).to receive(:executable?).with(/WindowsPowerShell\\/v1.0\\/powershell.exe/)\n        described_class.executable\n      end\n    end\n\n    context \"powershell preference\" do\n      before do\n        allow(Vagrant::Util::Which).to receive(:which)\n        allow(File).to receive(:executable?)\n      end\n\n      it \"should prefer pwsh found on in the PATH\" do\n        expect(Vagrant::Util::Which).to receive(:which).with(\"pwsh.exe\").and_return(\"pwsh.exe\")\n        expect(described_class.executable).to eq(\"pwsh.exe\")\n      end\n\n      it \"should use powershell.exe when found on PATH and pwsh.exe is not\" do\n        expect(Vagrant::Util::Which).to receive(:which).with(\"pwsh.exe\").and_return(\"powershell.exe\")\n        expect(described_class.executable).to eq(\"powershell.exe\")\n      end\n\n      it \"should prefer powershell.exe when env var is set and powershell.exe and pwsh.exe are on PATH\" do\n        expect(ENV).to receive(:[]).with(\"VAGRANT_PREFERRED_POWERSHELL\").and_return(\"powershell\")\n        expect(Vagrant::Util::Which).to receive(:which).with(\"pwsh.exe\").and_return(\"pwsh.exe\")\n        expect(Vagrant::Util::Which).to receive(:which).with(\"powershell.exe\").and_return(\"powershell.exe\")\n        expect(described_class.executable).to eq(\"powershell.exe\")\n      end\n\n      it \"should use pwsh.exe when env var is set to powershell but only pwsh.exe is avaialble\" do\n        expect(ENV).to receive(:[]).with(\"VAGRANT_PREFERRED_POWERSHELL\").and_return(\"powershell\")\n        expect(Vagrant::Util::Which).to receive(:which).with(\"pwsh.exe\").and_return(\"pwsh.exe\")\n        expect(Vagrant::Util::Which).to receive(:which).with(\"powershell.exe\").and_return(nil)\n        expect(described_class.executable).to eq(\"pwsh.exe\")\n      end\n    end\n  end\n\n  describe \".available?\" do\n    context \"when powershell executable is available\" do\n      before{ expect(described_class).to receive(:executable).and_return(\"powershell\") }\n\n      it \"should be true\" do\n        expect(described_class.available?).to be(true)\n      end\n    end\n\n    context \"when powershell executable is not available\" do\n      before{ expect(described_class).to receive(:executable).and_return(nil) }\n\n      it \"should be false\" do\n        expect(described_class.available?).to be(false)\n      end\n    end\n  end\n\n  describe \".execute\" do\n    before do\n      allow(described_class).to receive(:validate_install!)\n      allow(described_class).to receive(:executable)\n        .and_return(\"powershell\")\n      allow(Vagrant::Util::Subprocess).to receive(:execute)\n    end\n\n    it \"should validate installation before use\" do\n      expect(described_class).to receive(:validate_install!)\n      described_class.execute(\"command\")\n    end\n\n    it \"should include command to execute\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|\n        comm = args.detect{|s| s.to_s.include?(\"custom-command\") }\n        expect(comm.to_s).to include(\"custom-command\")\n      end\n      described_class.execute(\"custom-command\")\n    end\n\n    it \"should accept custom environment\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|\n        comm = args.detect{|s| s.to_s.include?(\"custom-command\") }\n        expect(comm.to_s).to include(\"$env:TEST_KEY=test-value\")\n      end\n      described_class.execute(\"custom-command\", env: {\"TEST_KEY\" => \"test-value\"})\n    end\n\n    it \"should define a custom module path\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|\n        comm = args.detect{|s| s.to_s.include?(\"custom-command\") }\n        expect(comm.to_s).to include(\"$env:PSModulePath+';C:\\\\My-Path'\")\n      end\n      described_class.execute(\"custom-command\", module_path: \"C:\\\\My-Path\")\n    end\n  end\n\n  describe \".execute_cmd\" do\n    let(:result) do\n      Vagrant::Util::Subprocess::Result.new(\n        exit_code, stdout, stderr)\n    end\n    let(:exit_code){ 0 }\n    let(:stdout){ \"\" }\n    let(:stderr){ \"\" }\n\n    before do\n      allow(described_class).to receive(:validate_install!)\n      allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(result)\n      allow(described_class).to receive(:executable).and_return(\"powershell\")\n    end\n\n    it \"should validate installation before use\" do\n      expect(described_class).to receive(:validate_install!)\n      described_class.execute_cmd(\"command\")\n    end\n\n    it \"should include command to execute\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|\n        comm = args.detect{|s| s.to_s.include?(\"custom-command\") }\n        expect(comm.to_s).to include(\"custom-command\")\n        result\n      end\n      described_class.execute_cmd(\"custom-command\")\n    end\n\n    it \"should accept custom environment\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|\n        comm = args.detect{|s| s.to_s.include?(\"custom-command\") }\n        expect(comm.to_s).to include(\"$env:TEST_KEY=test-value\")\n        result\n      end\n      described_class.execute_cmd(\"custom-command\", env: {\"TEST_KEY\" => \"test-value\"})\n    end\n\n    it \"should define a custom module path\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|\n        comm = args.detect{|s| s.to_s.include?(\"custom-command\") }\n        expect(comm.to_s).to include(\"$env:PSModulePath+';C:\\\\My-Path'\")\n        result\n      end\n      described_class.execute_cmd(\"custom-command\", module_path: \"C:\\\\My-Path\")\n    end\n\n    context \"with command output\" do\n      let(:stdout){ \"custom-output\" }\n\n      it \"should return stdout\" do\n        expect(described_class.execute_cmd(\"cmd\")).to eq(stdout)\n      end\n    end\n\n    context \"with failed command\" do\n      let(:exit_code){ 1 }\n\n      it \"should return nil\" do\n        expect(described_class.execute_cmd(\"cmd\")).to be_nil\n      end\n    end\n  end\n\n  describe \".execute_inline\" do\n    let(:result) do\n      Vagrant::Util::Subprocess::Result.new(\n        exit_code, stdout, stderr)\n    end\n    let(:exit_code){ 0 }\n    let(:stdout){ \"\" }\n    let(:stderr){ \"\" }\n    let(:command) { [\"run\", \"--this\", \"custom-command\"] }\n\n    before do\n      allow(described_class).to receive(:validate_install!)\n      allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(result)\n      allow(described_class).to receive(:executable).and_return(\"powershell\")\n    end\n\n    it \"should validate installation before use\" do\n      expect(described_class).to receive(:validate_install!)\n      described_class.execute_inline(command)\n    end\n\n    it \"should include command to execute\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|\n        comm = args.detect{|s| s.to_s.include?(\"custom-command\") }\n        expect(comm.to_s).to include(\"custom-command\")\n        result\n      end\n      described_class.execute_inline(command)\n    end\n\n    it \"should accept custom environment\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|\n        comm = args.detect{|s| s.to_s.include?(\"custom-command\") }\n        expect(comm.to_s).to include(\"$env:TEST_KEY=test-value\")\n        result\n      end\n      described_class.execute_inline(command, env: {\"TEST_KEY\" => \"test-value\"})\n    end\n\n    it \"should define a custom module path\" do\n      expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|\n        comm = args.detect{|s| s.to_s.include?(\"custom-command\") }\n        expect(comm.to_s).to include(\"$env:PSModulePath+';C:\\\\My-Path'\")\n        result\n      end\n      described_class.execute_inline(command, module_path: \"C:\\\\My-Path\")\n    end\n\n    it \"should return a result instance\" do\n      expect(described_class.execute_inline(command)).to eq(result)\n    end\n  end\n\n  describe \".validate_install!\" do\n    before do\n      allow(described_class).to receive(:available?).and_return(true)\n    end\n\n    context \"with version under minimum required\" do\n      before{ expect(described_class).to receive(:version).and_return(\"2.1\").at_least(:once) }\n\n      it \"should raise an error\" do\n        expect{ described_class.validate_install! }.to raise_error(Vagrant::Errors::PowerShellInvalidVersion)\n      end\n    end\n\n    context \"with version above minimum required\" do\n      before{ expect(described_class).to receive(:version).and_return(\"3.1\").at_least(:once) }\n\n      it \"should return true\" do\n        expect(described_class.validate_install!).to be(true)\n      end\n    end\n\n  end\n\n  describe \".powerup_command\" do\n    let(:result) do\n      Vagrant::Util::Subprocess::Result.new( exit_code, stdout, stderr)\n    end\n    let(:exit_code){ 0 }\n    let(:stdout){ \"\" }\n    let(:stderr){ \"\" }\n\n    context \"when the powershell executable is 'powershell'\" do\n      before do\n        allow(described_class).to receive(:executable).and_return(\"powershell\")\n      end\n\n      it \"should use the 'powershell' executable\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"powershell\", any_args).and_return(result)\n        described_class.powerup_command(\"run\", [], [])\n      end\n    end\n\n    context \"when the powershell executable is 'powershell.exe'\" do\n      before do\n        allow(described_class).to receive(:executable).and_return(\"powershell.exe\")\n      end\n\n      it \"should use the 'powershell.exe' executable\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).with(\"powershell.exe\", any_args).and_return(result)\n        described_class.powerup_command(\"run\", [], [])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/presence_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/presence\"\n\ndescribe Vagrant::Util::Presence do\n  subject { described_class }\n\n  describe \"#presence\" do\n    it \"returns false for nil\" do\n      expect(subject.presence(nil)).to be(false)\n    end\n\n    it \"returns false for false\" do\n      expect(subject.presence(false)).to be(false)\n    end\n\n    it \"returns false for an empty string\" do\n      expect(subject.presence(\"\")).to be(false)\n    end\n\n    it \"returns false for a string with null bytes\" do\n      expect(subject.presence(\"\\u0000\")).to be(false)\n    end\n\n    it \"returns false for an empty array\" do\n      expect(subject.presence([])).to be(false)\n    end\n\n    it \"returns false for an array with nil values\" do\n      expect(subject.presence([nil, nil])).to be(false)\n    end\n\n    it \"returns false for an empty hash\" do\n      expect(subject.presence({})).to be(false)\n    end\n\n    it \"returns true for true\" do\n      expect(subject.presence(true)).to be(true)\n    end\n\n    it \"returns the object for an object\" do\n      obj = Object.new\n      expect(subject.presence(obj)).to be(obj)\n    end\n\n    it \"returns the class for a class\" do\n      expect(subject.presence(String)).to be(String)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/retryable_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/retryable\"\n\ndescribe Vagrant::Util::Retryable do\n  let(:klass) do\n    Class.new do\n      extend Vagrant::Util::Retryable\n    end\n  end\n\n  it \"doesn't retry by default\" do\n    tries = 0\n\n    block = lambda do\n      tries += 1\n      raise RuntimeError, \"Try\"\n    end\n\n    # It should re-raise the error\n    expect { klass.retryable(&block) }.\n      to raise_error(RuntimeError)\n\n    # It should've tried once\n    expect(tries).to eq(1)\n  end\n\n  it \"retries the set number of times\" do\n    tries = 0\n\n    block = lambda do\n      tries += 1\n      raise RuntimeError, \"Try\"\n    end\n\n    # It should re-raise the error\n    expect { klass.retryable(tries: 5, &block) }.\n      to raise_error(RuntimeError)\n\n    # It should've tried all specified times\n    expect(tries).to eq(5)\n  end\n\n  it \"only retries on the given exception\" do\n    tries = 0\n\n    block = lambda do\n      tries += 1\n      raise StandardError, \"Try\"\n    end\n\n    # It should re-raise the error\n    expect { klass.retryable(tries: 5, on: RuntimeError, &block) }.\n      to raise_error(StandardError)\n\n    # It should've never tried since it was a different kind of error\n    expect(tries).to eq(1)\n  end\n\n  it \"can retry on multiple types of errors\" do\n    tries = 0\n\n    foo_error = Class.new(StandardError)\n    bar_error = Class.new(StandardError)\n\n    block = lambda do\n      tries += 1\n      raise foo_error, \"Try\" if tries == 1\n      raise bar_error, \"Try\" if tries == 2\n      raise RuntimeError, \"YAY\"\n    end\n\n    # It should re-raise the error\n    expect { klass.retryable(tries: 5, on: [foo_error, bar_error], &block) }.\n      to raise_error(RuntimeError)\n\n    # It should've never tried since it was a different kind of error\n    expect(tries).to eq(3)\n  end\n\n  it \"doesn't sleep between tries by default\" do\n    block = lambda do\n      raise RuntimeError, \"Try\"\n    end\n\n    # Sleep should never be called\n    expect(klass).not_to receive(:sleep)\n\n    # Run it.\n    expect { klass.retryable(tries: 5, &block) }.\n      to raise_error(RuntimeError)\n  end\n\n  it \"sleeps specified amount between retries\" do\n    block = lambda do\n      raise RuntimeError, \"Try\"\n    end\n\n    # Sleep should be called between each retry\n    expect(klass).to receive(:sleep).with(10).exactly(4).times\n\n    # Run it.\n    expect { klass.retryable(tries: 5, sleep: 10, &block) }.\n      to raise_error(RuntimeError)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/safe_chdir_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire 'tmpdir'\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire 'vagrant/util/safe_chdir'\n\ndescribe Vagrant::Util::SafeChdir do\n  let(:temp_dir) { Dir.mktmpdir(\"vagrant-test-util-safe-chdir\") }\n  let(:temp_dir2) { Dir.mktmpdir(\"vagrant-test-util-safe-chdir-2\") }\n\n  after do\n    FileUtils.rm_rf(temp_dir)\n    FileUtils.rm_rf(temp_dir2)\n  end\n\n  it \"should change directories\" do\n    expected = nil\n    result   = nil\n\n    Dir.chdir(temp_dir) do\n      expected = Dir.pwd\n    end\n\n    described_class.safe_chdir(temp_dir) do\n      result = Dir.pwd\n    end\n\n    expect(result).to eq(expected)\n  end\n\n  it \"should allow recursive chdir\" do\n    expected  = nil\n    result    = nil\n\n    Dir.chdir(temp_dir) do\n      expected = Dir.pwd\n    end\n\n    expect do\n      described_class.safe_chdir(temp_dir2) do\n        described_class.safe_chdir(temp_dir) do\n          result = Dir.pwd\n        end\n      end\n    end.to_not raise_error\n\n    expect(result).to eq(expected)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/scoped_hash_override_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/scoped_hash_override\"\n\ndescribe Vagrant::Util::ScopedHashOverride do\n  let(:klass) do\n    Class.new do\n      extend Vagrant::Util::ScopedHashOverride\n    end\n  end\n\n  it \"should not mess with non-overrides\" do\n    original = {\n      key: \"value\",\n      another_value: \"foo\"\n    }\n\n    expect(klass.scoped_hash_override(original, \"foo\")).to eq(original)\n  end\n\n  it \"should override if the scope matches\" do\n    original = {\n      key: \"value\",\n      scope__key: \"replaced\"\n    }\n\n    expected = {\n      key: \"replaced\",\n      scope__key: \"replaced\"\n    }\n\n    expect(klass.scoped_hash_override(original, \"scope\")).to eq(expected)\n  end\n\n  it \"should ignore non-matching scopes\" do\n    original = {\n      key: \"value\",\n      scope__key: \"replaced\",\n      another__key: \"value\"\n    }\n\n    expected = {\n      key: \"replaced\",\n      scope__key: \"replaced\",\n      another__key: \"value\"\n    }\n\n    expect(klass.scoped_hash_override(original, \"scope\")).to eq(expected)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/shell_quote_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/shell_quote\"\n\ndescribe Vagrant::Util::ShellQuote do\n  subject { described_class }\n\n  it \"quotes properly\" do\n    expected = \"foo '\\\\''bar'\\\\''\"\n    expect(subject.escape(\"foo 'bar'\", \"'\")).to eql(expected)\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/ssh_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/platform\"\nrequire \"vagrant/util/ssh\"\n\ndescribe Vagrant::Util::SSH do\n  include_context \"unit\"\n  let(:private_key_path) { temporary_file.to_s }\n\n  describe \"checking key permissions\" do\n    let(:key_path) { temporary_file }\n\n    it \"should do nothing on Windows\" do\n      allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n\n      key_path.chmod(0700)\n\n      # Get the mode now and verify that it is untouched afterwards\n      mode = key_path.stat.mode\n      described_class.check_key_permissions(key_path)\n      expect(key_path.stat.mode).to eq(mode)\n    end\n\n    it \"should fix the permissions\", :skip_windows do\n      key_path.chmod(0644)\n\n      described_class.check_key_permissions(key_path)\n      expect(key_path.stat.mode).to eq(0100600)\n    end\n  end\n\n  describe \"#exec\" do\n    let(:ssh_info) {{\n      host: \"localhost\",\n      port: 2222,\n      username: \"vagrant\",\n      private_key_path: [private_key_path],\n      compression: true,\n      dsa_authentication: true\n    }}\n\n    let(:ssh_path) { /.*ssh/ }\n\n    before {\n      allow(Vagrant::Util::Which).to receive(:which).with(\"ssh\", any_args).and_return(ssh_path)\n    }\n\n    it \"searches original PATH for executable\" do\n      expect(Vagrant::Util::Which).to receive(:which).with(\"ssh\", original_path: true).and_return(\"valid-return\")\n      allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil)\n      described_class.exec(ssh_info)\n    end\n\n    it \"searches current PATH if original PATH did not result in valid executable\" do\n      expect(Vagrant::Util::Which).to receive(:which).with(\"ssh\", original_path: true).and_return(nil)\n      expect(Vagrant::Util::Which).to receive(:which).with(\"ssh\").and_return(\"valid-return\")\n      allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil)\n      described_class.exec(ssh_info)\n    end\n\n    it \"raises an exception if there is no ssh\" do\n      allow(Vagrant::Util::Which).to receive(:which).and_return(nil)\n\n      expect { described_class.exec(ssh_info) }.\n        to raise_error Vagrant::Errors::SSHUnavailable\n    end\n\n    it \"raises an exception if there is no ssh and platform is windows\" do\n      allow(Vagrant::Util::Which).to receive(:which).and_return(nil)\n      allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n\n      expect { described_class.exec(ssh_info) }.\n        to raise_error Vagrant::Errors::SSHUnavailableWindows\n    end\n\n    it \"raises an exception if the platform is windows and uses PuTTY Link\" do\n      allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)\n      allow(Vagrant::Util::Subprocess).to receive(:execute).\n        and_return(double(\"output\", stdout: 'PuTTY Link'))\n\n      expect { described_class.exec(ssh_info) }.\n        to raise_error Vagrant::Errors::SSHIsPuttyLink\n    end\n\n    it \"invokes SSH with options if subprocess is not allowed\" do\n      allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil)\n\n      expect(described_class.exec(ssh_info)).to eq(nil)\n      expect(Vagrant::Util::SafeExec).to have_received(:exec)\n        .with(\n          ssh_path, \"vagrant@localhost\", \"-p\", \"2222\", \"-o\", \"LogLevel=FATAL\",\n          \"-o\", \"Compression=yes\",\n          \"-o\", \"DSAAuthentication=yes\",\n          \"-o\", \"StrictHostKeyChecking=no\",\n          \"-o\", \"UserKnownHostsFile=/dev/null\",\n          \"-o\", \"PubkeyAcceptedKeyTypes=+ssh-rsa\",\n          \"-o\", \"HostKeyAlgorithms=+ssh-rsa\",\n          \"-i\", ssh_info[:private_key_path][0],\n        )\n    end\n\n    context \"when deprecated algorithms are disabled\" do\n      let(:ssh_info) {{\n        host: \"localhost\",\n        port: 2222,\n        username: \"vagrant\",\n        disable_deprecated_algorithms: true,\n      }}\n\n      it \"does not include options to enable ssh-rsa key types and host key algorithms\" do\n        expect(Vagrant::Util::SafeExec).to receive(:exec) do |*args|\n          args.each do |arg|\n            expect(arg).not_to eq(\"PubkeyAcceptedKeyTypes=+ssh-rsa\")\n            expect(arg).not_to eq(\"HostKeyAlgorithms=+ssh-rsa\")\n          end\n        end\n        described_class.exec(ssh_info)\n      end\n    end\n\n    context \"when deprecated algorithms are not disabled\" do\n      let(:ssh_info) {{\n        host: \"localhost\",\n        port: 2222,\n        username: \"vagrant\",\n      }}\n\n      it \"includes options to enable ssh-rsa key types and host key algorithms\" do\n        expect(Vagrant::Util::SafeExec).to receive(:exec) do |*args|\n          expect(\n            args.any? { |arg| arg == \"PubkeyAcceptedKeyTypes=+ssh-rsa\" }\n          ).to be_truthy\n          expect(\n            args.any? { |arg| arg == \"HostKeyAlgorithms=+ssh-rsa\" }\n          ).to be_truthy\n        end\n        described_class.exec(ssh_info)\n      end\n    end\n\n    context \"when using '%' in a private_key_path\" do\n      let(:private_key_path) { \"/tmp/percent%folder\" }\n      let(:ssh_info) {{\n        host: \"localhost\",\n        port: 2222,\n        username: \"vagrant\",\n        private_key_path: [private_key_path],\n        compression: true,\n        dsa_authentication: true\n      }}\n\n      it \"uses the IdentityFile argument and escapes the '%' character\" do\n        allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil)\n        described_class.exec(ssh_info)\n\n        expect(Vagrant::Util::SafeExec).to have_received(:exec)\n          .with(\n            ssh_path, \"vagrant@localhost\", \"-p\", \"2222\", \"-o\", \"LogLevel=FATAL\",\n            \"-o\", \"Compression=yes\",\n            \"-o\", \"DSAAuthentication=yes\",\n            \"-o\", \"StrictHostKeyChecking=no\",\n            \"-o\", \"UserKnownHostsFile=/dev/null\",\n            \"-o\", \"PubkeyAcceptedKeyTypes=+ssh-rsa\",\n            \"-o\", \"HostKeyAlgorithms=+ssh-rsa\",\n            \"-o\", \"IdentityFile=\\\"/tmp/percent%%folder\\\"\",\n          )\n      end\n    end\n\n    context \"when disabling compression or dsa_authentication flags\" do\n      let(:ssh_info) {{\n        host: \"localhost\",\n        port: 2222,\n        username: \"vagrant\",\n        private_key_path: [private_key_path],\n        compression: false,\n        dsa_authentication: false\n      }}\n\n      it \"does not include compression or dsa_authentication flags if disabled\" do\n        allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil)\n\n        expect(described_class.exec(ssh_info)).to eq(nil)\n        expect(Vagrant::Util::SafeExec).to have_received(:exec)\n          .with(\n            ssh_path, \"vagrant@localhost\", \"-p\", \"2222\", \"-o\", \"LogLevel=FATAL\",\n            \"-o\", \"StrictHostKeyChecking=no\",\n            \"-o\", \"UserKnownHostsFile=/dev/null\",\n            \"-o\", \"PubkeyAcceptedKeyTypes=+ssh-rsa\",\n            \"-o\", \"HostKeyAlgorithms=+ssh-rsa\",\n            \"-i\", ssh_info[:private_key_path][0],\n          )\n      end\n    end\n\n    context \"when verify_host_key is true\" do\n      let(:ssh_info) {{\n        host: \"localhost\",\n        port: 2222,\n        username: \"vagrant\",\n        private_key_path: [private_key_path],\n        verify_host_key: true\n      }}\n\n      it \"does not disable StrictHostKeyChecking or set UserKnownHostsFile\" do\n        allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil)\n\n        expect(described_class.exec(ssh_info)).to eq(nil)\n        expect(Vagrant::Util::SafeExec).to have_received(:exec)\n          .with(\n            ssh_path, \"vagrant@localhost\", \"-p\", \"2222\", \"-o\", \"LogLevel=FATAL\",\n            \"-o\", \"PubkeyAcceptedKeyTypes=+ssh-rsa\",\n            \"-o\", \"HostKeyAlgorithms=+ssh-rsa\",\n            \"-i\", ssh_info[:private_key_path][0],\n          )\n      end\n    end\n\n    context \"when not on solaris not using plain mode or with keys_only enabled\" do\n      let(:ssh_info) {{\n        host: \"localhost\",\n        port: 2222,\n        username: \"vagrant\",\n        private_key_path: [private_key_path],\n        keys_only: true\n      }}\n\n      it \"adds IdentitiesOnly as an option for ssh\" do\n        allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil)\n        allow(Vagrant::Util::Platform).to receive(:solaris?).and_return(false)\n\n        expect(described_class.exec(ssh_info, {plain_mode: true})).to eq(nil)\n        expect(Vagrant::Util::SafeExec).to have_received(:exec)\n          .with(\n            ssh_path, \"localhost\", \"-p\", \"2222\", \"-o\", \"LogLevel=FATAL\",\n            \"-o\", \"StrictHostKeyChecking=no\",\n            \"-o\", \"UserKnownHostsFile=/dev/null\",\n            \"-o\", \"PubkeyAcceptedKeyTypes=+ssh-rsa\",\n            \"-o\", \"HostKeyAlgorithms=+ssh-rsa\",\n          )\n      end\n    end\n\n    context \"when forward_x11 is enabled\" do\n      let(:ssh_info) {{\n        host: \"localhost\",\n        port: 2222,\n        username: \"vagrant\",\n        private_key_path: [private_key_path],\n        forward_x11: true\n      }}\n\n      it \"enables ForwardX11 options\" do\n        allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil)\n\n        expect(described_class.exec(ssh_info)).to eq(nil)\n        expect(Vagrant::Util::SafeExec).to have_received(:exec)\n          .with(\n            ssh_path, \"vagrant@localhost\", \"-p\", \"2222\", \"-o\", \"LogLevel=FATAL\",\n            \"-o\", \"StrictHostKeyChecking=no\",\n            \"-o\", \"UserKnownHostsFile=/dev/null\",\n            \"-o\", \"PubkeyAcceptedKeyTypes=+ssh-rsa\",\n            \"-o\", \"HostKeyAlgorithms=+ssh-rsa\",\n            \"-i\", ssh_info[:private_key_path][0],\n            \"-o\", \"ForwardX11=yes\",\n            \"-o\", \"ForwardX11Trusted=yes\",\n          )\n      end\n    end\n\n    context \"when forward_agent is enabled\" do\n      let(:ssh_info) {{\n        host: \"localhost\",\n        port: 2222,\n        username: \"vagrant\",\n        private_key_path: [private_key_path],\n        forward_agent: true\n      }}\n\n      it \"enables agent forwarding options\" do\n        allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil)\n\n        expect(described_class.exec(ssh_info)).to eq(nil)\n        expect(Vagrant::Util::SafeExec).to have_received(:exec)\n          .with(\n            ssh_path, \"vagrant@localhost\", \"-p\", \"2222\", \"-o\", \"LogLevel=FATAL\",\n            \"-o\", \"StrictHostKeyChecking=no\",\n            \"-o\", \"UserKnownHostsFile=/dev/null\",\n            \"-o\", \"PubkeyAcceptedKeyTypes=+ssh-rsa\",\n            \"-o\", \"HostKeyAlgorithms=+ssh-rsa\",\n            \"-i\", ssh_info[:private_key_path][0],\n            \"-o\", \"ForwardAgent=yes\",\n          )\n      end\n    end\n\n    context \"when extra_args is provided as an array\" do\n      let(:ssh_info) {{\n        host: \"localhost\",\n        port: 2222,\n        username: \"vagrant\",\n        private_key_path: [private_key_path],\n        extra_args: [\"-L\", \"8008:localhost:80\"]\n      }}\n\n      it \"enables agent forwarding options\" do\n        allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil)\n\n        expect(described_class.exec(ssh_info)).to eq(nil)\n        expect(Vagrant::Util::SafeExec).to have_received(:exec)\n          .with(\n            ssh_path, \"vagrant@localhost\", \"-p\", \"2222\", \"-o\", \"LogLevel=FATAL\",\n            \"-o\", \"StrictHostKeyChecking=no\",\n            \"-o\", \"UserKnownHostsFile=/dev/null\",\n            \"-o\", \"PubkeyAcceptedKeyTypes=+ssh-rsa\",\n            \"-o\", \"HostKeyAlgorithms=+ssh-rsa\",\n            \"-i\", ssh_info[:private_key_path][0],\n            \"-L\", \"8008:localhost:80\",\n          )\n      end\n    end\n\n    context \"when extra_args is provided as a string\" do\n      let(:ssh_info) {{\n        host: \"localhost\",\n        port: 2222,\n        username: \"vagrant\",\n        private_key_path: [private_key_path],\n        extra_args: \"-6\"\n      }}\n\n      it \"enables agent forwarding options\" do\n        allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil)\n\n        expect(described_class.exec(ssh_info)).to eq(nil)\n        expect(Vagrant::Util::SafeExec).to have_received(:exec)\n          .with(\n            ssh_path, \"vagrant@localhost\", \"-p\", \"2222\", \"-o\", \"LogLevel=FATAL\",\n            \"-o\", \"StrictHostKeyChecking=no\",\n            \"-o\", \"UserKnownHostsFile=/dev/null\",\n            \"-o\", \"PubkeyAcceptedKeyTypes=+ssh-rsa\",\n            \"-o\", \"HostKeyAlgorithms=+ssh-rsa\",\n            \"-i\", ssh_info[:private_key_path][0],\n            \"-6\",\n          )\n      end\n    end\n\n    context \"when config is provided\" do\n      let(:ssh_info) {{\n        host: \"localhost\",\n        port: 2222,\n        username: \"vagrant\",\n        config: \"/path/to/config\"\n      }}\n\n      it \"enables ssh config loading\" do\n        allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil)\n        expect(Vagrant::Util::SafeExec).to receive(:exec) do |exe_path, *args|\n          expect(exe_path).to match(ssh_path)\n          config_options = [\"-F\", \"/path/to/config\"]\n          expect(args & config_options).to eq(config_options)\n        end\n\n        expect(described_class.exec(ssh_info)).to eq(nil)\n      end\n    end\n\n    context \"with subprocess enabled\" do\n      let(:ssh_info) {{\n        host: \"localhost\",\n        port: 2222,\n        username: \"vagrant\",\n        private_key_path: [private_key_path],\n      }}\n\n      it \"executes SSH in a subprocess with options and returns an exit code Fixnum\" do\n        # mock out ChildProcess\n        process = double()\n        allow(ChildProcess).to receive(:build).and_return(process)\n        allow(process).to receive(:io).and_return(double(\"process-io\"))\n        allow(process.io).to receive(:inherit!).and_return(true)\n        allow(process).to receive(:start).and_return(true)\n        allow(process).to receive(:wait).and_return(true)\n\n        allow(process).to receive(:exit_code).and_return(0)\n\n        expect(described_class.exec(ssh_info, {subprocess: true})).to eq(0)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/string_block_editor_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/string_block_editor\"\n\ndescribe Vagrant::Util::StringBlockEditor do\n  describe \"#keys\" do\n    it \"should return all the keys\" do\n      data = <<DATA\n# VAGRANT-BEGIN: foo\nvalue\n# VAGRANT-END: foo\nanother\n# VAGRANT-BEGIN: bar\ncontent\n# VAGRANT-END: bar\nDATA\n\n      expect(described_class.new(data).keys).to eq([\"foo\", \"bar\"])\n    end\n  end\n\n  describe \"#delete\" do\n    it \"should delete nothing if the key doesn't exist\" do\n      data = \"foo\"\n\n      instance = described_class.new(data)\n      instance.delete(\"key\")\n      expect(instance.value).to eq(data)\n    end\n\n    it \"should delete the matching blocks if they exist\" do\n      data = <<DATA\n# VAGRANT-BEGIN: foo\nvalue\n# VAGRANT-END: foo\n# VAGRANT-BEGIN: foo\nanother\n# VAGRANT-END: foo\nanother\n# VAGRANT-BEGIN: bar\ncontent\n# VAGRANT-END: bar\nDATA\n\n      new_data = <<DATA\nanother\n# VAGRANT-BEGIN: bar\ncontent\n# VAGRANT-END: bar\nDATA\n\n      instance = described_class.new(data)\n      instance.delete(\"foo\")\n      expect(instance.value).to eq(new_data)\n    end\n  end\n\n  describe \"#get\" do\n    let(:data) do\n      <<DATA\n# VAGRANT-BEGIN: bar\ncontent\n# VAGRANT-END: bar\n# VAGRANT-BEGIN: /Users/studio/Projects (studio)/tubes/.vagrant/machines/web/vmware_fusion/vm.vmwarevm\ncomplex\n# VAGRANT-END: /Users/studio/Projects (studio)/tubes/.vagrant/machines/web/vmware_fusion/vm.vmwarevm\nDATA\n    end\n\n    subject { described_class.new(data) }\n\n    it \"should get the value\" do\n      expect(subject.get(\"bar\")).to eq(\"content\")\n    end\n\n    it \"should get nil for nonexistent values\" do\n      expect(subject.get(\"baz\")).to be_nil\n    end\n\n    it \"should get complicated keys\" do\n      result = subject.get(\"/Users/studio/Projects (studio)/tubes/.vagrant/machines/web/vmware_fusion/vm.vmwarevm\")\n      expect(result).to eq(\"complex\")\n    end\n  end\n\n  describe \"#insert\" do\n    it \"should insert the given key and value\" do\n      data = <<DATA\n# VAGRANT-BEGIN: bar\ncontent\n# VAGRANT-END: bar\nDATA\n\n      new_data = <<DATA\n#{data.chomp}\n# VAGRANT-BEGIN: foo\nvalue\n# VAGRANT-END: foo\nDATA\n\n      instance = described_class.new(data)\n      instance.insert(\"foo\", \"value\")\n      expect(instance.value).to eq(new_data)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/subprocess_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\nrequire \"vagrant/util/subprocess\"\n\ndescribe Vagrant::Util::Subprocess do\n  let(:ls_test_commands) {[ described_class.new(\"ls\"),  described_class.new(\"ls\", {:detach => true}) ]}\n  let(:sleep_test_commands) {[ described_class.new(\"sleep\", \"5\"), described_class.new(\"sleep\", \"5\", {:detach => true}) ]}\n\n  describe '#execute' do\n    before do\n      # ensure we have `cat` and `echo` in our PATH so that we can run these\n      # tests successfully.\n      ['cat', 'echo'].each do |cmd|\n        if !Vagrant::Util::Which.which(cmd)\n          pending(\"cannot run subprocess tests without command #{cmd.inspect}\")\n        end\n      end\n    end\n\n    let (:cat) { described_class.new('cat', :notify => [:stdin]) }\n\n    it 'yields the STDIN stream for the process if we set :notify => :stdin' do\n      echo = described_class.new('echo', 'hello world', :notify => [:stdin])\n      echo.execute do |type, data|\n        expect(type).to eq(:stdin)\n        expect(data).to be_a(::IO)\n      end\n    end\n\n    it 'can close STDIN' do\n      result = cat.execute do |type, stdin|\n        # We should be able to close STDIN without raising an exception\n        stdin.close\n      end\n\n      # we should exit successfully.\n      expect(result.exit_code).to eq(0)\n    end\n\n    it 'can write to STDIN correctly' do\n      data = \"hello world\\n\"\n      result = cat.execute do |type, stdin|\n        stdin.write(data)\n        stdin.close\n      end\n\n      # we should exit successfully.\n      expect(result.exit_code).to eq(0)\n\n      # we should see our data as the output from `cat`\n      expect(result.stdout).to eq(data)\n    end\n\n    it 'does not wait for process if detach option specified' do\n      cat = described_class.new('cat', {:detach => true})\n      expect(cat.execute).to eq(nil)\n    end\n\n    context \"running within AppImage\" do\n      let(:appimage_ld_path) { nil }\n      let(:exec_path) { \"/exec/path\" }\n      let(:appimage_path) { \"/appimage\" }\n      let(:process) { double(\"process\", io: process_io, environment: process_env) }\n      let(:process_io) { double(\"process_io\") }\n      let(:process_env) { double(\"process_env\") }\n      let(:subject) { described_class.new(exec_path) }\n\n      before do\n        allow(process).to receive(:start)\n        allow(process).to receive(:duplex=)\n        allow(process).to receive(:alive?).and_return(false)\n        allow(process).to receive(:exited?).and_return(true)\n        allow(process).to receive(:poll_for_exit).and_return(0)\n        allow(process).to receive(:exit_code).and_return(0)\n        allow(process_io).to receive(:stdout=)\n        allow(process_io).to receive(:stderr=)\n        allow(process_io).to receive(:stdin).and_return(double(\"io_stdin\", \"sync=\" => true))\n        allow(process_env).to receive(:[]=)\n        allow(ENV).to receive(:[]).with(\"VAGRANT_INSTALLER_ENV\").and_return(\"1\")\n        allow(ENV).to receive(:[]).with(\"VAGRANT_APPIMAGE\").and_return(\"1\")\n        allow(ENV).to receive(:[]).with(\"VAGRANT_APPIMAGE_HOST_LD_LIBRARY_PATH\").and_return(appimage_ld_path)\n        allow(File).to receive(:file?).with(exec_path).and_return(true)\n        allow(ChildProcess).to receive(:build).and_return(process)\n        allow(Vagrant).to receive(:installer_embedded_dir).and_return(appimage_path)\n        allow(Vagrant).to receive(:user_data_path).and_return(\"\")\n        allow(Vagrant::Util::Platform).to receive(:darwin?).and_return(false)\n      end\n\n      after { subject.execute }\n\n      it \"should not update LD_LIBRARY_PATH when environment variable is not set\" do\n        expect(process_env).not_to receive(:[]=).with(\"LD_LIBRARY_PATH\", anything)\n      end\n\n      context \"when APPIMAGE_LD_LIBRARY_PATH environment variable is set\" do\n        let(:appimage_ld_path) { \"APPIMAGE_SYSTEM_LIBS\" }\n\n        it \"should set LD_LIBRARY_PATH when executable is not within appimage\" do\n          expect(process_env).to receive(:[]=).with(\"LD_LIBRARY_PATH\", appimage_ld_path)\n        end\n\n        context \"when executable is located within AppImage\" do\n          let(:exec_path) { \"#{appimage_path}/exec/path\" }\n\n          it \"should not set LD_LIBRARY_PATH\" do\n            expect(process_env).not_to receive(:[]=).with(\"LD_LIBRARY_PATH\", anything)\n          end\n        end\n      end\n    end\n  end\n\n  describe \"#running?\" do\n    it \"should return false when subprocess has not been started\" do\n      ls_test_commands.each do |sp|\n        expect(sp.running?).to be(false)\n      end\n    end\n\n    it \"should return false when subprocess has completed\" do\n      ls_test_commands.each do |sp|\n        sp.execute\n        sleep(0.1)\n        expect(sp.running?).to be(false)\n      end\n    end\n\n    it \"should return true when subprocess is running\" do\n      sleep_test_commands.each do |sp|\n        thread = Thread.new{ sp.execute }\n        sleep(0.3)\n        expect(sp.running?).to be(true)\n        sp.stop\n        thread.join\n      end\n    end\n  end\n\n  describe \"#stop\" do\n    context \"when subprocess has not been started\" do\n      it \"should return false\" do\n        ls_test_commands.each do |sp|\n          expect(sp.stop).to be(false)\n        end\n      end\n    end\n\n    context \"when subprocess has already completed\" do\n      it \"should return false\" do\n        ls_test_commands.each do |sp|\n          sp.execute\n          sleep(0.1)\n          expect(sp.stop).to be(false)\n        end\n      end\n    end\n\n    context \"when subprocess is running\" do\n      it \"should return true\" do\n        sleep_test_commands.each do |sp|\n          thread = Thread.new{ sp.execute }\n          sleep(0.1)\n          expect(sp.stop).to be(true)\n          thread.join\n        end\n      end\n\n      it \"should stop the process\" do\n        sleep_test_commands.each do |sp|\n          thread = Thread.new{ sp.execute }\n          sleep(0.1)\n          sp.stop\n          expect(sp.running?).to be(false)\n          thread.join\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/uploader_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire \"vagrant/util/uploader\"\n\ndescribe Vagrant::Util::Uploader do\n  let(:destination) { \"fake\" }\n  let(:file) { \"my/file.box\" }\n  let(:curl_options) { [destination, \"--request\", \"PUT\", \"--upload-file\", file, \"--fail\", {notify: :stderr}] }\n\n  let(:subprocess_result) do\n    double(\"subprocess_result\").tap do |result|\n      allow(result).to receive(:exit_code).and_return(exit_code)\n      allow(result).to receive(:stderr).and_return(\"\")\n    end\n  end\n\n  subject { described_class.new(destination, file, options) }\n\n  before :each do\n    allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(subprocess_result)\n  end\n\n  describe \"#upload!\" do\n    context \"with a good exit status\" do\n      let(:options) { {} }\n      let(:exit_code) { 0 }\n\n      it \"uploads the file and returns true\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).\n          with(\"curl\", *curl_options).\n          and_return(subprocess_result)\n\n        expect(subject.upload!).to be\n      end\n    end\n\n    context \"with a bad exit status\" do\n      let(:options) { {} }\n      let(:exit_code) { 1 }\n      it \"raises an exception\" do\n        expect(Vagrant::Util::Subprocess).to receive(:execute).\n          with(\"curl\", *curl_options).\n          and_return(subprocess_result)\n\n        expect { subject.upload! }.\n          to raise_error(Vagrant::Errors::UploaderError)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/util/which_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../../base\", __FILE__)\n\nrequire 'vagrant/util/platform'\nrequire 'vagrant/util/which'\n\ndescribe Vagrant::Util::Which do\n  def tester (file_extension, test_extension, mode, &block)\n    # create file in temp directory\n    filename = '__vagrant_unit_test__'\n    dir = Dir.tmpdir\n    file = Pathname(dir) + (filename + file_extension)\n    file.open(\"w\") { |f| f.write(\"#\") }\n    file.chmod(mode)\n\n    # set the path to the directory where the file is located\n    allow(ENV).to receive(:[]).with(\"PATH\").and_return(dir.to_s)\n    block.call filename + test_extension\n\n    file.unlink\n  end\n\n  it \"should return a path for an executable file\" do\n    tester '.bat', '.bat', 0755 do |name|\n      expect(described_class.which(name)).not_to be_nil\n    end\n  end\n\n  if Vagrant::Util::Platform.windows?\n    it \"should return a path for a Windows executable file\" do\n      tester '.bat', '', 0755 do |name|\n        expect(described_class.which(name)).not_to be_nil\n      end\n    end\n  end\n\n  it \"should return nil for a non-executable file\" do\n    tester '.txt', '.txt', 0644 do |name|\n      expect(described_class.which(name)).to be_nil\n    end\n  end\n\n  context \"original_path option\" do\n    before{ allow(ENV).to receive(:[]).with(\"PATH\").and_return(\"\") }\n\n    it \"should use the original path when instructed\" do\n      expect(ENV).to receive(:fetch).with(\"VAGRANT_OLD_ENV_PATH\", any_args).and_return(\"\")\n      described_class.which(\"file\", original_path: true)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant/vagrantfile_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../../base\", __FILE__)\n\nrequire \"pathname\"\nrequire \"tmpdir\"\n\nrequire \"vagrant/vagrantfile\"\n\ndescribe Vagrant::Vagrantfile do\n  include_context \"unit\"\n\n  let(:keys) { [] }\n  let(:loader) {\n    Vagrant::Config::Loader.new(\n      Vagrant::Config::VERSIONS, Vagrant::Config::VERSIONS_ORDER)\n  }\n\n  subject { described_class.new(loader, keys) }\n\n  before do\n    keys << :test\n  end\n\n  def configure(&block)\n    loader.set(:test, [[\"2\", block]])\n  end\n\n  # A helper to register a provider for use in tests.\n  def register_provider(name, config_class=nil, options=nil)\n    provider_cls = Class.new(VagrantTests::DummyProvider) do\n      if options && options[:unusable]\n        def self.usable?(raise_error=false)\n          raise Vagrant::Errors::VagrantError if raise_error\n          false\n        end\n      end\n    end\n\n    register_plugin(\"2\") do |p|\n      p.provider(name, options) { provider_cls }\n\n      if config_class\n        p.config(name, :provider) { config_class }\n      end\n    end\n\n    provider_cls\n  end\n\n  describe \"#config\" do\n    it \"exposes the global configuration\" do\n      configure do |config|\n        config.vm.box = \"what\"\n      end\n\n      expect(subject.config.vm.box).to eq(\"what\")\n    end\n  end\n\n  describe \"#machine\" do\n    let(:boxes) { Vagrant::BoxCollection.new(iso_env.boxes_dir) }\n    let(:data_path) { Pathname.new(Dir.mktmpdir(\"vagrant-machine-data-path\")) }\n    let(:env)   { iso_env.create_vagrant_env }\n    let(:iso_env) { isolated_environment }\n    let(:vagrantfile) { described_class.new(loader, keys) }\n\n    subject { vagrantfile.machine(:default, :foo, boxes, data_path, env) }\n\n    before do\n      @foo_config_cls = Class.new(Vagrant.plugin(\"2\", \"config\")) do\n        attr_accessor :value\n      end\n\n      @provider_cls = register_provider(\"foo\", @foo_config_cls)\n\n      configure do |config|\n        config.vm.box = \"foo\"\n        config.vm.provider \"foo\" do |p|\n          p.value = \"rawr\"\n        end\n      end\n\n      iso_env.box3(\"foo\", \"1.0\", :foo, vagrantfile: <<-VF)\n      Vagrant.configure(\"2\") do |config|\n        config.ssh.port = 123\n      end\n      VF\n    end\n\n    after do\n      FileUtils.rm_rf(data_path.to_s)\n    end\n\n    describe '#data_dir' do\n      subject { super().data_dir }\n      it { should eq(data_path) }\n    end\n\n    describe '#env' do\n      subject { super().env }\n      it      { should equal(env)    }\n    end\n\n    describe '#name' do\n      subject { super().name }\n      it     { should eq(:default)  }\n    end\n\n    describe '#provider' do\n      subject { super().provider }\n      it { should be_kind_of(@provider_cls) }\n    end\n\n    describe '#provider_name' do\n      subject { super().provider_name }\n      it { should eq(:foo) }\n    end\n\n    describe '#vagrantfile' do\n      subject { super().vagrantfile }\n      it { should equal(vagrantfile) }\n    end\n\n    it \"has the proper box\" do\n      expect(subject.box.name).to eq(\"foo\")\n    end\n\n    it \"has the valid configuration\" do\n      expect(subject.config.vm.box).to eq(\"foo\")\n    end\n\n    it \"loads the provider-specific configuration\" do\n      expect(subject.provider_config).to be_kind_of(@foo_config_cls)\n      expect(subject.provider_config.value).to eq(\"rawr\")\n    end\n  end\n\n  describe \"#machine_config\" do\n    let(:iso_env) { isolated_environment }\n    let(:boxes) { Vagrant::BoxCollection.new(iso_env.boxes_dir) }\n\n    it \"should return a basic configured machine\" do\n      provider_cls = register_provider(\"foo\")\n\n      configure do |config|\n        config.vm.box = \"foo\"\n      end\n\n      results = subject.machine_config(:default, :foo, boxes)\n      box     = results[:box]\n      config  = results[:config]\n      expect(config.vm.box).to eq(\"foo\")\n      expect(box).to be_nil\n      expect(results[:provider_cls]).to equal(provider_cls)\n    end\n\n    it \"configures without a provider or boxes\" do\n      register_provider(\"foo\")\n\n      configure do |config|\n        config.vm.box = \"foo\"\n      end\n\n      results = subject.machine_config(:default, nil, nil)\n      box     = results[:box]\n      config  = results[:config]\n      expect(config.vm.box).to eq(\"foo\")\n      expect(box).to be_nil\n      expect(results[:provider_cls]).to be_nil\n    end\n\n    it \"configures with sub-machine config\" do\n      register_provider(\"foo\")\n\n      configure do |config|\n        config.ssh.port = \"1\"\n        config.vm.box = \"base\"\n\n        config.vm.define \"foo\" do |f|\n          f.ssh.port = 100\n        end\n      end\n\n      results = subject.machine_config(:foo, :foo, boxes)\n      config  = results[:config]\n      expect(config.vm.box).to eq(\"base\")\n      expect(config.ssh.port).to eq(100)\n    end\n\n    it \"configures with box configuration if it exists\" do\n      register_provider(\"foo\")\n\n      configure do |config|\n        config.vm.box = \"base\"\n      end\n\n      iso_env.box3(\"base\", \"1.0\", :foo, vagrantfile: <<-VF)\n      Vagrant.configure(\"2\") do |config|\n        config.ssh.port = 123\n      end\n      VF\n\n      results = subject.machine_config(:default, :foo, boxes)\n      box     = results[:box]\n      config  = results[:config]\n      expect(config.vm.box).to eq(\"base\")\n      expect(config.ssh.port).to eq(123)\n      expect(box).to_not be_nil\n      expect(box.name).to eq(\"base\")\n    end\n\n    it \"does not configure box configuration if set to ignore\" do\n      register_provider(\"foo\")\n\n      configure do |config|\n        config.vm.box = \"base\"\n        config.vm.ignore_box_vagrantfile = true\n      end\n\n      iso_env.box3(\"base\", \"1.0\", :foo, vagrantfile: <<-VF)\n      Vagrant.configure(\"2\") do |config|\n        config.ssh.port = 123\n        config.vm.hostname = \"hello\"\n      end\n      VF\n\n      results = subject.machine_config(:default, :foo, boxes)\n      box     = results[:box]\n      config  = results[:config]\n      expect(config.vm.box).to eq(\"base\")\n      expect(config.ssh.port).to eq(nil)\n      expect(config.vm.hostname).to eq(nil)\n      expect(box).to_not be_nil\n      expect(box.name).to eq(\"base\")\n    end\n\n    it \"configures with the proper box version\" do\n      register_provider(\"foo\")\n\n      configure do |config|\n        config.vm.box = \"base\"\n        config.vm.box_version = \"~> 1.2\"\n      end\n\n      iso_env.box3(\"base\", \"1.0\", :foo, vagrantfile: <<-VF)\n      Vagrant.configure(\"2\") do |config|\n        config.ssh.port = 123\n      end\n      VF\n\n      iso_env.box3(\"base\", \"1.3\", :foo, vagrantfile: <<-VF)\n      Vagrant.configure(\"2\") do |config|\n        config.ssh.port = 245\n      end\n      VF\n\n      results = subject.machine_config(:default, :foo, boxes)\n      box     = results[:box]\n      config  = results[:config]\n      expect(config.vm.box).to eq(\"base\")\n      expect(config.ssh.port).to eq(245)\n      expect(box).to_not be_nil\n      expect(box.name).to eq(\"base\")\n      expect(box.version).to eq(\"1.3\")\n    end\n\n    it \"configures with the custom box architecture\" do\n      register_provider(\"foo\")\n      configure do |config|\n        config.vm.box = \"base\"\n        config.vm.box_architecture = \"custom-arch\"\n      end\n\n      results = subject.machine_config(:default, :foo, boxes)\n      config = results[:config]\n      expect(config.vm.box).to eq(\"base\")\n      expect(config.vm.box_architecture).to eq(\"custom-arch\")\n    end\n\n    it \"configures with the default box architecture\" do\n      register_provider(\"foo\")\n      configure do |config|\n        config.vm.box = \"base\"\n      end\n\n      results = subject.machine_config(:default, :foo, boxes)\n      config = results[:config]\n      expect(config.vm.box).to eq(\"base\")\n      expect(config.vm.box_architecture).to eq(:auto)\n    end\n\n    it \"configures box architecture to nil\" do\n      register_provider(\"foo\")\n      configure do |config|\n        config.vm.box = \"base\"\n        config.vm.box_architecture = nil\n      end\n\n      results = subject.machine_config(:default, :foo, boxes)\n      config = results[:config]\n      expect(config.vm.box).to eq(\"base\")\n      expect(config.vm.box_architecture).to be_nil\n    end\n\n    it \"configures with box config of other supported formats\" do\n      register_provider(\"foo\", nil, box_format: \"bar\")\n\n      configure do |config|\n        config.vm.box = \"base\"\n      end\n\n      iso_env.box3(\"base\", \"1.0\", :bar, vagrantfile: <<-VF)\n      Vagrant.configure(\"2\") do |config|\n        config.ssh.port = 123\n      end\n      VF\n\n      results = subject.machine_config(:default, :foo, boxes)\n      config  = results[:config]\n      expect(config.vm.box).to eq(\"base\")\n      expect(config.ssh.port).to eq(123)\n    end\n\n    it \"loads provider overrides if set\" do\n      register_provider(\"foo\")\n      register_provider(\"bar\")\n\n      configure do |config|\n        config.ssh.port = 1\n        config.vm.box = \"base\"\n\n        config.vm.provider \"foo\" do |_, c|\n          c.ssh.port = 100\n        end\n      end\n\n      # Test with the override\n      results = subject.machine_config(:default, :foo, boxes)\n      config  = results[:config]\n      expect(config.vm.box).to eq(\"base\")\n      expect(config.ssh.port).to eq(100)\n\n      # Test without the override\n      results = subject.machine_config(:default, :bar, boxes)\n      config  = results[:config]\n      expect(config.vm.box).to eq(\"base\")\n      expect(config.ssh.port).to eq(1)\n    end\n\n    it \"loads the proper box if in a provider override\" do\n      register_provider(\"foo\")\n\n      configure do |config|\n        config.vm.box = \"base\"\n        config.vm.box_version = \"1.0\"\n\n        config.vm.provider \"foo\" do |_, c|\n          c.vm.box = \"foobox\"\n          c.vm.box_version = \"2.0\"\n        end\n      end\n\n      iso_env.box3(\"base\", \"1.0\", :foo, vagrantfile: <<-VF)\n      Vagrant.configure(\"2\") do |config|\n        config.ssh.port = 123\n      end\n      VF\n\n      iso_env.box3(\"foobox\", \"2.0\", :foo, vagrantfile: <<-VF)\n      Vagrant.configure(\"2\") do |config|\n        config.ssh.port = 234\n      end\n      VF\n\n      results = subject.machine_config(:default, :foo, boxes)\n      config  = results[:config]\n      box     = results[:box]\n      expect(config.vm.box).to eq(\"foobox\")\n      expect(config.ssh.port).to eq(234)\n      expect(config.vm.box_version).to eq(\"2.0\")\n      expect(box).to_not be_nil\n      expect(box.name).to eq(\"foobox\")\n      expect(box.version).to eq(\"2.0\")\n    end\n\n    it \"raises an error if the machine is not found\" do\n      expect { subject.machine_config(:foo, :foo, boxes) }.\n        to raise_error(Vagrant::Errors::MachineNotFound)\n    end\n\n    it \"raises an error if the provider is not found\" do\n      expect { subject.machine_config(:default, :foo, boxes) }.\n        to raise_error(Vagrant::Errors::ProviderNotFound)\n    end\n\n    it \"raises an error if the provider is not found but gives suggestion\" do\n      register_provider(\"foo\")\n\n      expect { subject.machine_config(:default, :Foo, boxes) }.\n        to raise_error(Vagrant::Errors::ProviderNotFoundSuggestion)\n    end\n\n    it \"raises an error if the provider is not usable\" do\n      register_provider(\"foo\", nil, unusable: true)\n\n      expect { subject.machine_config(:default, :foo, boxes) }.\n        to raise_error(Vagrant::Errors::ProviderNotUsable)\n    end\n\n    it \"does not try to load the box if the box is empty\" do\n      provider_cls = register_provider(\"foo\")\n\n      configure do |config|\n        config.vm.box = \"\"\n      end\n\n      expect(boxes).not_to receive(:find)\n      results = subject.machine_config(:default, :foo, boxes)\n    end\n\n    context \"when provider validation is ignored\" do\n      before do\n        configure do |config|\n          config.vm.box = \"base\"\n          config.vm.box_version = \"1.0\"\n          config.vm.define :guest1\n          config.vm.define :guest2\n\n          config.vm.provider \"custom\" do |_, c|\n            c.ssh.port = 123\n          end\n        end\n\n        iso_env.box3(\"base\", \"1.0\", :custom, vagrantfile: <<-VF)\n        Vagrant.configure(\"2\") do |config|\n          config.vagrant.plugins = \"vagrant-custom\"\n        end\n        VF\n      end\n\n      it \"should not raise an error if provider is not found\" do\n        expect { subject.machine_config(:guest1, :custom, boxes, nil, false) }.\n          not_to raise_error\n      end\n\n      it \"should return configuration from box Vagrantfile\" do\n        config = subject.machine_config(:guest1, :custom, boxes, nil, false)[:config]\n        expect(config.vagrant.plugins).to be_a(Hash)\n        expect(config.vagrant.plugins.keys).to include(\"vagrant-custom\")\n      end\n    end\n\n    context \"local box metadata file\" do\n      let(:data_path) { double(:data_path) }\n      let(:meta_file) { double(:meta_file) }\n      let(:box_version) { \"2.0\" }\n\n      before do\n        register_provider(\"foo\")\n        iso_env.box3(\"base\", \"1.0\", :foo)\n        allow(data_path).to receive(:join).with(\"box_meta\").\n          and_return(meta_file)\n        allow(meta_file).to receive(:file?).and_return(false)\n        configure do |config|\n          config.vm.box = \"base\"\n          config.vm.box_version = box_version\n        end\n      end\n\n      it \"checks for local box metadata file\" do\n        expect(meta_file).to receive(:file?).and_return(false)\n        subject.machine_config(:default, :foo, boxes, data_path)\n      end\n\n      context \"file exists\" do\n        let(:meta_file_content) { '{\"name\":\"base\",\"version\":\"1.0\"}' }\n\n        before do\n          allow(meta_file).to receive(:file?).and_return(true)\n          allow(meta_file).to receive(:read).and_return(meta_file_content)\n        end\n\n        it \"reads the local box metadata file\" do\n          expect(meta_file).to receive(:read).and_return(meta_file_content)\n          subject.machine_config(:default, :foo, boxes, data_path)\n        end\n\n        it \"properly loads the box defined in metadata\" do\n          result = subject.machine_config(:default, :foo, boxes, data_path)\n          expect(result[:box]).not_to be_nil\n        end\n\n        context \"with invalid box version\" do\n          let(:box_version) { \"1.0\" }\n          let(:meta_file_content) { '{\"name\":\"base\",\"version\":\"2.0\"}' }\n\n          it \"loads box base on Vagrantfile information\" do\n            result = subject.machine_config(:default, :foo, boxes, data_path)\n            expect(result[:box]).not_to be_nil\n          end\n        end\n      end\n    end\n  end\n\n  describe \"#machine_names\" do\n    it \"returns the default name when single-VM\" do\n      configure { |config| }\n\n      expect(subject.machine_names).to eq([:default])\n    end\n\n    it \"returns all of the names in a multi-VM\" do\n      configure do |config|\n        config.vm.define \"foo\"\n        config.vm.define \"bar\"\n      end\n\n      expect(subject.machine_names).to eq(\n        [:foo, :bar])\n    end\n  end\n\n  describe \"#machine_names_and_options\" do\n    it \"returns the default name\" do\n      configure { |config| }\n\n      expect(subject.machine_names_and_options).to eq({\n        default: { config_version: \"2\" },\n      })\n    end\n\n    it \"returns all the machines\" do\n      configure do |config|\n        config.vm.define \"foo\"\n        config.vm.define \"bar\", autostart: false\n        config.vm.define \"baz\", autostart: true\n      end\n\n      expect(subject.machine_names_and_options).to eq({\n        foo: { config_version: \"2\" },\n        bar: { config_version: \"2\", autostart: false },\n        baz: { config_version: \"2\", autostart: true },\n      })\n    end\n  end\n\n  describe \"#primary_machine_name\" do\n    it \"returns the default name when single-VM\" do\n      configure { |config| }\n\n      expect(subject.primary_machine_name).to eq(:default)\n    end\n\n    it \"returns the designated machine in multi-VM\" do\n      configure do |config|\n        config.vm.define \"foo\"\n        config.vm.define \"bar\", primary: true\n        config.vm.define \"baz\"\n      end\n\n      expect(subject.primary_machine_name).to eq(:bar)\n    end\n\n    it \"returns nil if no designation in multi-VM\" do\n      configure do |config|\n        config.vm.define \"foo\"\n        config.vm.define \"baz\"\n      end\n\n      expect(subject.primary_machine_name).to be_nil\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/vagrant_test.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire File.expand_path(\"../base\", __FILE__)\n\ndescribe Vagrant do\n  include_context \"unit\"\n\n  it \"has the path to the source root\" do\n    expect(described_class.source_root).to eq(Pathname.new(File.expand_path(\"../../../\", __FILE__)))\n  end\n\n  describe \"plugin superclass\" do\n    describe \"v1\" do\n      it \"returns the proper class for version 1\" do\n        expect(described_class.plugin(\"1\")).to eq(Vagrant::Plugin::V1::Plugin)\n      end\n\n      it \"returns the proper components for version 1\" do\n        expect(described_class.plugin(\"1\", :command)).to eq(Vagrant::Plugin::V1::Command)\n        expect(described_class.plugin(\"1\", :communicator)).to eq(Vagrant::Plugin::V1::Communicator)\n        expect(described_class.plugin(\"1\", :config)).to eq(Vagrant::Plugin::V1::Config)\n        expect(described_class.plugin(\"1\", :guest)).to eq(Vagrant::Plugin::V1::Guest)\n        expect(described_class.plugin(\"1\", :host)).to eq(Vagrant::Plugin::V1::Host)\n        expect(described_class.plugin(\"1\", :provider)).to eq(Vagrant::Plugin::V1::Provider)\n        expect(described_class.plugin(\"1\", :provisioner)).to eq(Vagrant::Plugin::V1::Provisioner)\n      end\n    end\n\n    describe \"v2\" do\n      it \"returns the proper class for version 2\" do\n        expect(described_class.plugin(\"2\")).to eq(Vagrant::Plugin::V2::Plugin)\n      end\n\n      it \"returns the proper components for version 2\" do\n        expect(described_class.plugin(\"2\", :command)).to eq(Vagrant::Plugin::V2::Command)\n        expect(described_class.plugin(\"2\", :communicator)).to eq(Vagrant::Plugin::V2::Communicator)\n        expect(described_class.plugin(\"2\", :config)).to eq(Vagrant::Plugin::V2::Config)\n        expect(described_class.plugin(\"2\", :guest)).to eq(Vagrant::Plugin::V2::Guest)\n        expect(described_class.plugin(\"2\", :host)).to eq(Vagrant::Plugin::V2::Host)\n        expect(described_class.plugin(\"2\", :provider)).to eq(Vagrant::Plugin::V2::Provider)\n        expect(described_class.plugin(\"2\", :provisioner)).to eq(Vagrant::Plugin::V2::Provisioner)\n        expect(described_class.plugin(\"2\", :synced_folder)).to eq(Vagrant::Plugin::V2::SyncedFolder)\n      end\n    end\n\n    it \"raises an exception if an unsupported version is given\" do\n      expect { described_class.plugin(\"88\") }.\n        to raise_error(ArgumentError)\n    end\n  end\n\n  describe \"has_plugin?\" do\n\n    it \"should find the installed plugin by the registered name\" do\n      Class.new(described_class.plugin(Vagrant::Config::CURRENT_VERSION)) do\n        name \"i_am_installed\"\n      end\n\n      expect(described_class.has_plugin?(\"i_am_installed\")).to be(true)\n    end\n\n    it \"should return false if the plugin is not installed\" do\n      expect(described_class.has_plugin?(\"i_dont_exist\")).to be(false)\n    end\n\n    it \"finds plugins by gem name\" do\n      specs = [Gem::Specification.new]\n      specs[0].name = \"foo\"\n      allow(Vagrant::Plugin::Manager.instance).to receive(:installed_specs).and_return(specs)\n      allow(Vagrant::Plugin::Manager.instance).to receive(:ready?).and_return(true)\n\n      expect(described_class.has_plugin?(\"foo\")).to be(true)\n      expect(described_class.has_plugin?(\"bar\")).to be(false)\n    end\n\n    it \"finds plugins by gem name and version\" do\n      specs = [Gem::Specification.new]\n      specs[0].name = \"foo\"\n      specs[0].version = \"1.2.3\"\n      allow(Vagrant::Plugin::Manager.instance).to receive(:ready?).and_return(true)\n      allow(Vagrant::Plugin::Manager.instance).to receive(:installed_specs).and_return(specs)\n\n      expect(described_class.has_plugin?(\"foo\", \"~> 1.2.0\")).to be(true)\n      expect(described_class.has_plugin?(\"foo\", \"~> 1.0.0\")).to be(false)\n      expect(described_class.has_plugin?(\"bar\", \"~> 1.2.0\")).to be(false)\n    end\n  end\n\n  describe \"require_version\" do\n    it \"should succeed if valid range\" do\n      expect { described_class.require_version(Vagrant::VERSION) }.\n        to_not raise_error\n    end\n\n    it \"should not succeed if bad range\" do\n      expect { described_class.require_version(\"> #{Vagrant::VERSION}\") }.\n        to raise_error(Vagrant::Errors::VagrantVersionBad)\n    end\n  end\n\n  describe \"version?\" do\n    it \"should succeed if valid range\" do\n      expect(described_class.version?(Vagrant::VERSION)).to be(true)\n    end\n\n    it \"should not succeed if bad range\" do\n      expect(described_class.version?(\"> #{Vagrant::VERSION}\")).to be(false)\n    end\n  end\n\n  describe \"original_env\" do\n    before do\n      ENV[\"VAGRANT_OLD_ENV_foo\"] = \"test\"\n      ENV[\"VAGRANT_OLD_ENV_bar\"] = \"test\"\n    end\n\n    after do\n      ENV[\"VAGRANT_OLD_ENV_foo\"] = \"test\"\n      ENV[\"VAGRANT_OLD_ENV_bar\"] = \"test\"\n    end\n\n    it \"should return the original environment\" do\n      expect(Vagrant.original_env).to eq(\n        \"foo\" => \"test\",\n        \"bar\" => \"test\",\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "test/vagrant-spec/.runner-vmware.sh",
    "content": "#!/usr/bin/env bash\n# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: BUSL-1.1\n\n\nfunction cleanup {\n    vagrant destroy --force\n}\n\ntrap cleanup EXIT\n\nGEM_PATH=$(ls vagrant-spec*.gem)\n\nset -ex\n\nif [ -f \"${GEM_PATH}\" ]\nthen\n    mv \"${GEM_PATH}\" vagrant-spec.gem\nfi\n\nvagrant box update\nvagrant box prune\n\nguests=$(vagrant status | grep vmware | awk '{print $1}')\n\nvagrant up --no-provision\n\ndeclare -A pids\n\nfor guest in ${guests}\ndo\n    vagrant provision ${guest} &\n    pids[$guest]=$!\n    sleep 60\ndone\n\nresult=0\nset +e\n\nfor guest in ${guests}\ndo\n    wait ${pids[$guest]}\n    if [ $? -ne 0 ]\n    then\n        echo \"Provision failure for: ${guest}\"\n        result=1\n    fi\ndone\n\nexit $result\n"
  },
  {
    "path": "test/vagrant-spec/Vagrantfile.spec",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\n# Guest boxes to use for vagrant-spec\nGUEST_BOXES = {\n  'hashicorp/bionic64' => '1.0.282',\n  'hashicorp-vagrant/ubuntu-16.04' => '1.0.1',\n  'hashicorp-vagrant/centos-7.4' => '1.0.2',\n  # 'hashicorp-vagrant/windows-10' => '1.0.0',\n  'spox/osx-10.12' => '0.0.1'\n}\n\nDOCKER_IMAGES = {\n  'nginx' => 'latest'\n}\n\n# Host boxes to run vagrant-spec\nHOST_BOXES = {\n  'hashicorp/bionic64' => '1.0.282',\n  'hashicorp-vagrant/ubuntu-16.04' => '1.0.1',\n  'hashicorp-vagrant/centos-7.4' => '1.0.2',\n  # 'hashicorp-vagrant/windows-10' => '1.0.0',\n  'spox/osx-10.12' => '0.0.1'\n}\n\n# Not all boxes are named by their specific \"platform\"\n# so this allows Vagrant to use the right provision script\nPLATFORM_SCRIPT_MAPPING = {\n  \"ubuntu\" => \"ubuntu\",\n  \"bionic\" => \"ubuntu\",\n  \"centos\" => \"centos\",\n  \"windows\" => \"windows\"\n}\n\n# Determine what providers to test\nenabled_providers = ENV.fetch(\"VAGRANT_SPEC_PROVIDERS\", \"virtualbox\").split(\",\")\n# Set what boxes should be used\nenabled_guests = ENV[\"VAGRANT_GUEST_BOXES\"] ? ENV[\"VAGRANT_GUEST_BOXES\"].split(\",\") : GUEST_BOXES.keys\nenabled_docker_images = ENV[\"VAGRANT_DOCKER_IMAGES\"] ? ENV[\"VAGRANT_DOCKER_IMAGES\"].split(\",\") : DOCKER_IMAGES.keys\nenabled_hosts = ENV[\"VAGRANT_HOST_BOXES\"] ? ENV[\"VAGRANT_HOST_BOXES\"].split(\",\") : HOST_BOXES.keys\n\nguest_boxes = Hash[GUEST_BOXES.find_all{|name, version| enabled_guests.include?(name)}.compact]\ndocker_images = Hash[DOCKER_IMAGES.find_all{|name, version| enabled_docker_images.include?(name)}.compact]\nhost_boxes = Hash[HOST_BOXES.find_all{|name, version| enabled_hosts.include?(name)}.compact]\n\n# Grab vagrantcloud token, if available\nvagrantcloud_token = ENV[\"VAGRANT_CLOUD_TOKEN\"]\n\n# Download copies of the guest boxes for testing if missing\nenabled_providers.each do |provider_name|\n\n  next if provider_name == \"docker\"\n\n  guest_boxes.each do |guest_box, box_version|\n    box_owner, box_name = guest_box.split('/')\n    box_path = File.join(File.dirname(__FILE__), \"./boxes/#{guest_box.sub('/', '_')}.#{provider_name}.#{box_version}.box\")\n    if !File.exist?(box_path)\n      $stderr.puts \"Downloading guest box #{guest_box}\"\n      cmd = \"curl -Lf -o #{box_path} https://app.vagrantup.com/#{box_owner}/boxes/#{box_name}/versions/#{box_version}/providers/#{provider_name}.box\"\n      if vagrantcloud_token\n        cmd += \"?access_token=#{vagrantcloud_token}\"\n      end\n      result = system(cmd)\n      if !result\n        $stderr.puts\n        $stderr.puts \"ERROR: Failed to download guest box #{guest_box} for #{provider_name}!\"\n        exit 1\n      end\n    end\n  end\nend\n\nVagrant.configure(2) do |global_config|\n  host_boxes.each do |box_name, box_version|\n    platform = box_name.split('/').last.sub(/[^a-z]+$/, '')\n    enabled_providers.each do |provider_name|\n      global_config.vm.define(\"#{box_name.split('/').last}-#{provider_name}\") do |config|\n        config.vm.box = box_name\n        config.vm.box_version = box_version\n        config.vm.synced_folder '.', '/vagrant', disable: true\n        config.vm.synced_folder '../../', '/vagrant'\n        config.vm.provider :vmware_desktop do |vmware|\n          vmware.vmx[\"memsize\"] = ENV.fetch(\"VAGRANT_HOST_MEMORY\", \"5000\")\n          vmware.vmx['vhv.enable'] = 'TRUE'\n          vmware.vmx['vhv.allow'] = 'TRUE'\n        end\n        if platform == \"windows\"\n          config.vm.provision :shell,\n            path: \"./scripts/#{PLATFORM_SCRIPT_MAPPING[platform]}-setup.#{provider_name}.ps1\", run: \"once\"\n        else\n          config.vm.provision :shell,\n            path: \"./scripts/#{PLATFORM_SCRIPT_MAPPING[platform]}-setup.#{provider_name}.sh\", run: \"once\",\n            env: {\n              \"HASHIBOT_USERNAME\" => ENV[\"HASHIBOT_USERNAME\"],\n              \"HASHIBOT_TOKEN\" => ENV[\"HASHIBOT_TOKEN\"]\n            }\n        end\n        if provider_name == \"docker\"\n          docker_images.each_with_index do |image_info, idx|\n            docker_image, _ = image_info\n            spec_cmd_args = ENV[\"VAGRANT_SPEC_ARGS\"]\n            if idx != 0\n              spec_cmd_args = \"#{spec_cmd_args} --without-component cli/*\".strip\n            end\n            if platform == \"windows\"\n              config.vm.provision(\n                :shell,\n                path: \"./scripts/#{platform}-run.#{provider_name}.ps1\",\n                keep_color: true,\n                env: {\n                  \"VAGRANT_SPEC_ARGS\" => \"test --components provider/docker/docker/* #{spec_cmd_args}\".strip,\n                  \"VAGRANT_SPEC_DOCKER_IMAGE\" => docker_image\n                }\n              )\n            else\n              config.vm.provision(\n                :shell,\n                path: \"./scripts/#{PLATFORM_SCRIPT_MAPPING[platform]}-run.#{provider_name}.sh\",\n                keep_color: true,\n                env: {\n                  \"VAGRANT_SPEC_ARGS\" => \"test --components provider/docker/docker/* #{spec_cmd_args}\".strip,\n                  \"VAGRANT_SPEC_DOCKER_IMAGE\" => docker_image,\n                  \"VAGRANT_LOG\" => \"trace\",\n                  \"VAGRANT_LOG_LEVEL\" => \"trace\",\n                  \"VAGRANT_SPEC_LOG_PATH\" => \"/tmp/vagrant-spec.log\",\n                }\n              )\n            end\n          end\n        else\n          guest_boxes.each_with_index do |box_info, idx|\n            guest_box, box_version = box_info\n            guest_platform = guest_box.split('/').last.sub(/[^a-z]+$/, '')\n            guest_platform = PLATFORM_SCRIPT_MAPPING[guest_platform]\n            spec_cmd_args = ENV[\"VAGRANT_SPEC_ARGS\"]\n            if idx != 0\n              spec_cmd_args = \"#{spec_cmd_args} --without-component cli/*\".strip\n            end\n            if platform == \"windows\"\n              config.vm.provision(\n                :shell,\n                path: \"./scripts/#{platform}-run.#{provider_name}.ps1\",\n                keep_color: true,\n                env: {\n                  \"VAGRANT_SPEC_ARGS\" => \"#{spec_cmd_args}\".strip,\n                  \"VAGRANT_SPEC_BOX\" => \"c:/vagrant/#{guest_box.sub('/', '_')}.#{provider_name}.#{box_version}.box\",\n                  \"VAGRANT_SPEC_GUEST_PLATFORM\" => guest_platform,\n                }\n              )\n            else\n              components = [\n                \"provider/#{provider_name}/basic\",\n                \"provider/#{provider_name}/provisioner/shell\",\n              ]\n              config.vm.provision(\n                :shell,\n                path: \"./scripts/#{PLATFORM_SCRIPT_MAPPING[platform]}-run.#{provider_name}.sh\",\n                keep_color: true,\n                env: {\n                  # \"VAGRANT_SPEC_ARGS\" => \"test #{spec_cmd_args}\".strip,\n                  # TEMP: Forcing just the basic component of the provider suite as not all tests are passing yet.\n                  #       Hoping to widen this out over time to be unscoped with everything passing.\n                  \"VAGRANT_SPEC_ARGS\" => \"test --components #{components.join(\" \")}\",\n                  \"VAGRANT_SPEC_BOX\" => \"/vagrant/test/vagrant-spec/boxes/#{guest_box.sub('/', '_')}.#{provider_name}.#{box_version}.box\",\n                  \"VAGRANT_SPEC_GUEST_PLATFORM\" => guest_platform,\n                  \"VAGRANT_LOG\" => \"trace\",\n                  \"VAGRANT_LOG_LEVEL\" => \"trace\",\n                  \"VAGRANT_SPEC_LOG_PATH\" => \"/tmp/vagrant-spec.log\",\n                }\n              )\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/vagrant-spec/boxes/.keep",
    "content": ""
  },
  {
    "path": "test/vagrant-spec/configs/vagrant-spec.config.docker.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../acceptance/base\"\n\nVagrant::Spec::Acceptance.configure do |c|\n  c.component_paths << File.expand_path(\"../../../acceptance\", __FILE__)\n  c.skeleton_paths << File.expand_path(\"../../../acceptance/skeletons\", __FILE__)\n  # Allow for slow setup to still pass\n  c.assert_retries = 15\n  c.vagrant_path = ENV.fetch(\"VAGRANT_PATH\", \"vagrant\")\n  c.provider \"docker\",\n    image: ENV[\"VAGRANT_SPEC_DOCKER_IMAGE\"],\n    box: \"placeholder\"\nend\n"
  },
  {
    "path": "test/vagrant-spec/configs/vagrant-spec.config.virtualbox.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"../../acceptance/base\"\n\nVagrant::Spec::Acceptance.configure do |c|\n  c.component_paths << File.expand_path(\"../test/acceptance\", __FILE__)\n  c.skeleton_paths << File.expand_path(\"../test/acceptance/skeletons\", __FILE__)\n  # Allow for slow setup to still pass\n  c.assert_retries = 15\n  c.vagrant_path = ENV.fetch(\"VAGRANT_PATH\", \"vagrant\")\n  c.provider \"virtualbox\",\n    box: ENV[\"VAGRANT_SPEC_BOX\"],\n    contexts: [\"provider-context/virtualbox\"]\nend\n"
  },
  {
    "path": "test/vagrant-spec/readme.md",
    "content": "# Running vagrant-spec\n\nThe vagrant-spec project is where Vagrant acceptance tests live.\n__NOTE:__ You must use a hypervisor that allows for nested virtualization to run these tests.\nSo for the _vagrant_ project, it uses the vagrant vmware plugin as a host. If you\nwant to test this locally, please keep in mind that you will need this hypervisor\nto properly run the tests.\n\n## Requirements\n\n- vagrant installed (from source, or from packages)\n- vagrant vmware plugin\n- ![vagrant](https://github.com/hashicorp/vagrant) repo\n- ![vagrant-spec](https://github.com/hashicorp/vagrant-spec) repo\n\n## Relevant environment variables:\n\nBelow are some environment variables used for running vagrant-spec. Many of\nthese are required for defining which hosts and guests to run the tests on.\n\n- VAGRANT_CLOUD_TOKEN\n  + Token to use if fetching a private box (like windows). This does not have to be explicitly\n    set if you log into Vagrant cloud with `vagrant cloud login`.\n- VAGRANT_HOST_BOXES\n  - Vagrant box to use as a host for installing VirtualBox and bringing up Vagrant guests to test\n- VAGRANT_GUEST_BOXES\n  - Vagrant box to use as a guest to run tests on\n- VAGRANT_CWD\n  - Directory location of vagrant-spec Vagrantfile inside of the Vagrant source repo\n- VAGRANT_VAGRANTFILE\n  - Vagrantfile to use for running vagrant-spec. Unless changed, this should be set as `Vagrantfile.spec`.\n- VAGRANT_HOST_MEMORY\n  - Set how much memory your host will use (defaults to 2048)\n- VAGRANT_SPEC_ARGS\n  - Specific arguments to pass along to the vagrant-spec gem, such as running specific tests instead of the whole suite\n  - Example: `--component cli`\n\n## How to run\n\nFirst, we need to build vagrant-spec and copy the built gem into the Vagrant source repo:\n\n```\ncd vagrant-spec\ngem build *.gemspec\ncp vagrant-spec-0.0.1.gem /path/to/vagrant/vagrant-spec.gem\n```\n\nNext, make a decision as to which host and guest boxes will be used to run the tests.\nA list of valid hosts and guests can be found in the `Vagrantfile.spec` adjacent\nto this readme.\n\nFrom the root dir of the `vagrant` project, run the following command:\n\n```shell\nVAGRANT_CLOUD_TOKEN=REAL_TOKEN_HERE VAGRANT_HOST_BOXES=hashicorp-vagrant/centos-7.4 VAGRANT_GUEST_BOXES=hashicorp-vagrant/windows-10 VAGRANT_CWD=test/vagrant-spec/ VAGRANT_VAGRANTFILE=Vagrantfile.spec vagrant up --provider vmware_desktop\n```\n\nIf you are running windows, you must give your host box more memory than the default. That can be done through the environment variable `VAGRANT_HOST_MEMORY`\n\n```shell\nVAGRANT_HOST_MEMORY=10000 VAGRANT_CLOUD_TOKEN=REAL_TOKEN_HERE VAGRANT_HOST_BOXES=hashicorp-vagrant/centos-7.4 VAGRANT_GUEST_BOXES=hashicorp-vagrant/windows-10 VAGRANT_CWD=test/vagrant-spec/ VAGRANT_VAGRANTFILE=Vagrantfile.spec vagrant up --provider vmware_desktop\n```\n\n__Note:__ It is not required that you invoke Vagrant directly in the source repo, so\nif you wish to run it else where, be sure to properly set the `VAGRANT_CWD` environment\nvariable to point to the proper test directory inside of the Vagrant source.\n\n### How to run specific tests\n\nSometimes when debugging, it's useful to only run a small subset of tests, instead of\nwaiting for everything to run. This can be achieved by passing along arguments\nusing the `VAGRANT_SPEC_ARGS` environment variable:\n\nFor example, here is what you could set to only run cli tests\n\n```shell\nVAGRANT_SPEC_ARGS=\"--component cli\"\n```\n\nOr with the full command....\n\n```shell\nVAGRANT_SPEC_ARGS=\"--component cli\" VAGRANT_CLOUD_TOKEN=REAL_TOKEN_HERE VAGRANT_HOST_BOXES=hashicorp-vagrant/centos-7.4 VAGRANT_GUEST_BOXES=hashicorp-vagrant/windows-10 VAGRANT_CWD=test/vagrant-spec/ VAGRANT_VAGRANTFILE=Vagrantfile.spec vagrant up --provider vmware_desktop\n```\n\n### About Vagrantfile.spec\n\nThis Vagrantfile expects the box used to end in a specific \"platform\", so that it can associate\na provision script with the correct plaform. Because some boxes might not end in\ntheir platform (like `hashicorp-vagrant/ubuntu-16.04` versus `hashicorp/bionic64`),\nthere is a hash defined called `PLATFORM_SCRIPT_MAPPING` that will tell vagrant\nwhich platform script to provision with rather than relying on the box ending with\nthe name of the platform.\n"
  },
  {
    "path": "test/vagrant-spec/scripts/centos-run.virtualbox.sh",
    "content": "#!/bin/bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nset -x\n\nexport VAGRANT_EXPERIMENTAL=\"${VAGRANT_EXPERIMENTAL:-1}\"\nexport VAGRANT_SPEC_BOX=\"${VAGRANT_SPEC_BOX}\"\nvagrant-spec ${VAGRANT_SPEC_ARGS} --config /vagrant/test/vagrant-spec/configs/vagrant-spec.config.virtualbox.rb\nresult=$?\n\nexit $result\n"
  },
  {
    "path": "test/vagrant-spec/scripts/centos-setup.virtualbox.sh",
    "content": "#!/bin/bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nset -xe\n\ncurl -Lo /etc/yum.repos.d/virtualbox.repo http://download.virtualbox.org/virtualbox/rpm/rhel/virtualbox.repo\nyum groupinstall -y \"Development Tools\"\nyum install -y kernel-devel-$(uname -r)\nyum install -y VirtualBox-${VAGRANT_CENTOS_VIRTUALBOX_VERSION:-5.1}\n\n# Install Go\nwget -qO go.tar.gz https://go.dev/dl/go1.17.6.linux-amd64.tar.gz\ntar -xzf go.tar.gz --directory /usr/local\nexport PATH=$PATH:/usr/local/go/bin\ngo version\n\n# Install Ruby\ncurl -sSL https://rvm.io/pkuczynski.asc | sudo gpg --import -\ncurl -sSL https://get.rvm.io | bash -s stable --ruby\nsource .bashrc\n\npushd /vagrant\n\n# Get vagrant-plugin-sdk repo\ngit config --global url.\"https://${HASHIBOT_USERNAME}:${HASHIBOT_TOKEN}@github.com\".insteadOf \"https://github.com\"\n\n# Build Vagrant artifacts\ngem install bundler -v \"$(grep -A 1 \"BUNDLED WITH\" Gemfile.lock | tail -n 1)\"\nmake\nbundle install\nln -s /vagrant/vagrant /bin/vagrant\n\npopd\n\n# Install vagrant-spec\ngit clone https://github.com/hashicorp/vagrant-spec.git\npushd vagrant-spec\ngem build vagrant-spec.gemspec\ngem install vagrant-spec*.gem\nvagrant-spec -h\npopd\n"
  },
  {
    "path": "test/vagrant-spec/scripts/ubuntu-install-vagrant.sh",
    "content": "#!/bin/bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nset -e\n\n# Install Go\nwget -qO go.tar.gz https://go.dev/dl/go1.17.6.linux-amd64.tar.gz\ntar -xzf go.tar.gz --directory /usr/local\nexport PATH=$PATH:/usr/local/go/bin\ngo version\n\n# Install Ruby\ncurl -sSL https://rvm.io/pkuczynski.asc | sudo gpg --import -\ncurl -sSL https://get.rvm.io | bash -s stable\nsource /usr/local/rvm/scripts/rvm\nrvm install ruby-2.7.2\nrvm --default use ruby-2.7.2\n\n# Remove RVM's automatically installed bundler integration, which messes w/\n# Vagrant's ruby binary invocation\ngem uninstall -i /usr/local/rvm/rubies/ruby-2.7.2/lib/ruby/gems/2.7.0 rubygems-bundler\n\npushd /vagrant\n\n# Get vagrant-plugin-sdk repo\ngit config --global url.\"https://${HASHIBOT_USERNAME}:${HASHIBOT_TOKEN}@github.com\".insteadOf \"https://github.com\"\n\n# Build Vagrant artifacts\ngem install bundler -v \"$(grep -A 1 \"BUNDLED WITH\" Gemfile.lock | tail -n 1)\"\nmake\nbundle install\ngem build -o /tmp/vagrant.gem vagrant.gemspec\ngem install /tmp/vagrant.gem\n\npopd\n\n# Install vagrant-spec\ngit clone https://github.com/hashicorp/vagrant-spec.git\npushd vagrant-spec\n\n# TEMP: We are using a branch of vagrant-spec while we stabilize the changes\n# necessary. Once this branch lands we can remove this line and build from main.\ngit checkout vagrant-ruby\n\ngem build -o /tmp/vagrant-spec.gem vagrant-spec.gemspec\ngem install /tmp/vagrant-spec.gem\npopd\n"
  },
  {
    "path": "test/vagrant-spec/scripts/ubuntu-run.docker.sh",
    "content": "#!/bin/bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\nexport VAGRANT_SPEC_DOCKER_IMAGE=\"${VAGRANT_SPEC_DOCKER_IMAGE}\"\n\n# Explicitly use Go binary\nexport VAGRANT_PATH=/vagrant/vagrant\n\n# Explicitly set high open file limits... vagrant-ruby tends to run into the\n# default 1024 limit during some operations.\nulimit -n 65535\n\nvagrant-spec ${VAGRANT_SPEC_ARGS} --config /vagrant/test/vagrant-spec/configs/vagrant-spec.config.docker.rb\nresult=$?\n\nexit $result\n"
  },
  {
    "path": "test/vagrant-spec/scripts/ubuntu-run.virtualbox.sh",
    "content": "#!/bin/bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\nexport VAGRANT_EXPERIMENTAL=\"${VAGRANT_EXPERIMENTAL:-1}\"\nexport VAGRANT_SPEC_BOX=\"${VAGRANT_SPEC_BOX}\"\n\n# Explicitly use Go binary\nexport VAGRANT_PATH=/vagrant/vagrant\n\n# Explicitly set high open file limits... vagrant-ruby tends to run into the\n# default 1024 limit during some operations.\nulimit -n 65535\n\nvagrant-spec ${VAGRANT_SPEC_ARGS} --config /vagrant/test/vagrant-spec/configs/vagrant-spec.config.virtualbox.rb\nresult=$?\n\nexit $result\n"
  },
  {
    "path": "test/vagrant-spec/scripts/ubuntu-setup.docker.sh",
    "content": "#!/bin/bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nset -e\n\napt-get update -q\napt-get install -qq -y --force-yes curl apt-transport-https\napt-get purge -qq -y lxc-docker* || true\ncurl -sSL https://get.docker.com/ | sh\n\n/bin/bash /vagrant/test/vagrant-spec/scripts/ubuntu-install-vagrant.sh\n\n"
  },
  {
    "path": "test/vagrant-spec/scripts/ubuntu-setup.virtualbox.sh",
    "content": "#!/bin/bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nset -e\n\nexport DEBIAN_FRONTEND=noninteractive\napt-get update -q\napt-get install -qqy linux-headers-$(uname -r)\napt-get install -qqy virtualbox\napt-get install -qqy nfs-kernel-server\n\n/bin/bash /vagrant/test/vagrant-spec/scripts/ubuntu-install-vagrant.sh\n\n\n"
  },
  {
    "path": "test/vagrant-spec/scripts/windows-run.virtualbox.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\ncd /vagrant\nvagrant plugin install ./vagrant-spec.gem\n\nif ( $env:VAGRANT_EXPERIMENTAL -eq \"\" ) {\n  $env:VAGRANT_EXPERIMENTAL=\"1\"\n}\nvagrant vagrant-spec $Env:VAGRANT_SPEC_ARGS /vagrant/test/vagrant-spec/configs/vagrant-spec.config.virtualbox.rb\n"
  },
  {
    "path": "test/vagrant-spec/scripts/windows-setup.virtualbox.ps1",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nWrite-Output \"Downloading virtualbox guest additions\"\n$vboxadd_url = \"http://download.virtualbox.org/virtualbox/5.2.2/VBoxGuestAdditions_5.2.2.iso\"\n$vboxadd_output = \"C:/Windows/Temp/vboxguestadditions.iso\"\n\n(New-Object System.Net.WebClient).DownloadFile($vboxadd_url, $vboxadd_output)\n\nWrite-Output \"Mounting virtualbox guest additions\"\nMount-DiskImage -ImagePath $vboxadd_output\n\nWrite-Output \"Installing Virtualbox Guest Additions\"\nWrite-Output \"Checking for Certificates in vBox ISO\"\nif(test-path E:\\ -Filter *.cer)\n{\n  Get-ChildItem E:\\cert -Filter *.cer | ForEach-Object { certutil -addstore -f \"TrustedPublisher\" $_.FullName }\n}\nStart-Process -FilePath \"E:\\VBoxWindowsAdditions.exe\" -ArgumentList \"/S\" -Wait\n\nWrite-Output \"Downloading virtualbox\"\n$vbox_url = \"http://download.virtualbox.org/virtualbox/5.2.2/VirtualBox-5.2.2-119230-Win.exe\"\n$vbox_output = \"C:/Windows/Temp/virtualbox.exe\"\n\n(New-Object System.Net.WebClient).DownloadFile($vbox_url, $vbox_output)\n\nWrite-Output \"Installing virtualbox\"\n# Extract the contents of the installer\nStart-Process -FilePath $vbox_output `\n    -ArgumentList ('--extract','--silent','--path','C:\\Windows\\Temp') `\n    -Wait `\n    -NoNewWindow\n\n# Find the installer\n# Determine if this is a 64-bit or 32-bit CPU\n$architecture=\"x86\"\nif ((Get-WmiObject -Class Win32_OperatingSystem).OSArchitecture -eq \"64-bit\") {\n    $architecture = \"amd64\"\n}\n\ncd \"C:\\Windows\\Temp\"\n\n$matches = Get-ChildItem | Where-Object { $_.Name -match \"VirtualBox-.*_$($architecture).msi\" }\nif ($matches.Count -ne 1) {\n   Write-Host \"Multiple matches for VirtualBox MSI found: $($matches.Count)\"\n   exit 1\n}\n$installerPath = Resolve-Path $matches[0]\n\n# Run the installer\nStart-Process -FilePath \"$($env:systemroot)\\System32\\msiexec.exe\" `\n    -ArgumentList \"/i `\"$installerPath`\" /qn /norestart /l*v `\"$($pwd)\\vbox_install.log`\"\" `\n    -Verb RunAs `\n    -Wait `\n    -WorkingDirectory \"$pwd\"\n\ncd \"C:\\vagrant\\pkg\\dist\"\n$vagrant_matches = Get-ChildItem | Where-Object { $_.Name -match \"vagrant.*_x86_64.msi\" }\nif ($vagrant_matches.Count -ne 1) {\n   Write-Host \"Could not find vagrant installer\"\n   exit 1\n}\n$vagrant_installerPath = Resolve-Path $vagrant_matches[0]\n\nWrite-Output $vagrant_installerPath\n\nWrite-Output \"Installing vagrant\"\nStart-Process -FilePath \"$($env:systemroot)\\System32\\msiexec.exe\" `\n    -ArgumentList \"/i `\"$vagrant_installerPath`\" /qn /norestart /l*v `\"$($pwd)\\vagrant_install.log`\"\" `\n    -Verb RunAs `\n    -Wait `\n    -WorkingDirectory \"$pwd\"\n"
  },
  {
    "path": "vagrant-spec.config.example.rb",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\nrequire_relative \"test/acceptance/base\"\n\nVagrant::Spec::Acceptance.configure do |c|\n  c.component_paths << File.expand_path(\"../test/acceptance\", __FILE__)\n  c.skeleton_paths << File.expand_path(\"../test/acceptance/skeletons\", __FILE__)\n\n  c.provider \"virtualbox\",\n    box: \"<PATH TO MINIMAL BOX>\",\n    contexts: [\"provider-context/virtualbox\"]\nend\n"
  },
  {
    "path": "vagrant.gemspec",
    "content": "$:.unshift File.expand_path(\"../lib\", __FILE__)\nrequire \"vagrant/version\"\n\nGem::Specification.new do |s|\n  s.name          = \"vagrant\"\n  s.version       = Vagrant::VERSION\n  s.platform      = Gem::Platform::RUBY\n  s.authors       = [\"Mitchell Hashimoto\", \"John Bender\"]\n  s.email         = [\"mitchell.hashimoto@gmail.com\", \"john.m.bender@gmail.com\"]\n  s.homepage      = \"https://www.vagrantup.com\"\n  s.license       = 'BUSL-1.1'\n  s.summary       = \"Build and distribute virtualized development environments.\"\n  s.description   = \"Vagrant is a tool for building and distributing virtualized development environments.\"\n\n  s.required_ruby_version     = \">= 3.0\", \"< 3.5\"\n  s.required_rubygems_version = \">= 1.3.6\"\n\n  s.add_dependency \"base64\", \"~> 0.2.0\"\n  s.add_dependency \"bcrypt_pbkdf\", \"~> 1.1\"\n  s.add_dependency \"childprocess\", \"~> 5.1\"\n  s.add_dependency \"csv\", \"~> 3.3\"\n  s.add_dependency \"ed25519\", \"~> 1.3.0\"\n  s.add_dependency \"erubi\"\n  s.add_dependency \"hashicorp-checkpoint\", \"~> 0.1.5\"\n  s.add_dependency \"i18n\", \"~> 1.12\"\n  s.add_dependency \"listen\", \"~> 3.7\"\n  s.add_dependency \"log4r\", \"~> 1.1.9\", \"< 1.1.11\"\n  s.add_dependency \"logger\", \"~> 1.0\"\n  s.add_dependency \"mime-types\", \"~> 3.3\"\n  s.add_dependency \"net-ftp\", \"~> 0.2\"\n  s.add_dependency \"net-ssh\", \"~> 7.0\"\n  s.add_dependency \"net-sftp\", \"~> 4.0\"\n  s.add_dependency \"net-scp\", \"~> 4.0\"\n  s.add_dependency \"ostruct\", \"~> 0.6.0\"\n  s.add_dependency \"rb-kqueue\", \"~> 0.2.0\"\n  s.add_dependency \"rexml\", \"~> 3.2\"\n  s.add_dependency \"rubyzip\", \"~> 2.3.2\"\n  s.add_dependency \"vagrant_cloud\", \"~> 3.1.2\"\n  s.add_dependency \"wdm\", \"~> 0.2.0\"\n  s.add_dependency \"winrm\", \">= 2.3.9\", \"< 3.0\"\n  s.add_dependency \"winrm-elevated\", \">= 1.2.3\", \"< 2.0\"\n  s.add_dependency \"winrm-fs\", \">= 1.3.5\", \"< 2.0\"\n\n  # Needed for go generate to use grpc_tools_ruby_protoc\n  s.add_development_dependency \"grpc-tools\", \"~> 1.41\"\n\n  # required to include https://github.com/ruby/ipaddr/issues/35\n  s.add_dependency \"ipaddr\", \">= 1.2.4\"\n\n  # Constraint rake to properly handle deprecated method usage\n  # from within rspec\n  s.add_development_dependency \"rake\", \"~> 13.0\"\n  s.add_development_dependency \"rake-compiler\"\n  s.add_development_dependency \"rspec\", \"~> 3.11\"\n  s.add_development_dependency \"rspec-its\", \"~> 1.3.0\"\n  s.add_development_dependency \"fake_ftp\", \"~> 0.3.0\"\n  s.add_development_dependency \"webrick\", \"~> 1.7\"\n\n  # The following block of code determines the files that should be included\n  # in the gem. It does this by reading all the files in the directory where\n  # this gemspec is, and parsing out the ignored files from the gitignore.\n  # Note that the entire gitignore(5) syntax is not supported, specifically\n  # the \"!\" syntax, but it should mostly work correctly.\n  root_path      = File.dirname(__FILE__)\n  all_files      = Dir.chdir(root_path) { Dir.glob(\"**/{*,.*}\") }\n  all_files.reject! { |file| [\".\", \"..\"].include?(File.basename(file)) }\n  all_files.reject! { |file| file.start_with?(\"website/\") }\n  all_files.reject! { |file| file.start_with?(\"test/\") }\n  all_files.reject! { |file| file.start_with?(\"cmd/\") }\n  all_files.reject! { |file| file.start_with?(\"builtin/\") }\n  all_files.reject! { |file| file.start_with?(\"internal/\") }\n  all_files.reject! { |file| file.start_with?(\"vendor/\") }\n\n  gitignore_path = File.join(root_path, \".gitignore\")\n  gitignore      = File.readlines(gitignore_path)\n  gitignore.map!    { |line| line.chomp.strip }\n  gitignore.reject! { |line| line.empty? || line =~ /^(#|!)/ }\n\n  gitmodules_path = File.join(root_path, \".gitmodules\")\n  gitmodules      = File.readlines(gitmodules_path)\n  gitmodules.map!    { |line| line.chomp.strip }\n  gitmodules.reject! { |line| line.empty? || line =~ /^(#|!)/ }\n\n  unignored_files = all_files.reject do |file|\n    # Ignore any directories, the gemspec only cares about files\n    next true if File.directory?(file)\n\n    # Ignore any paths that match anything in the gitignore. We do\n    # two tests here:\n    #\n    #   - First, test to see if the entire path matches the gitignore.\n    #   - Second, match if the basename does, this makes it so that things\n    #     like '.DS_Store' will match sub-directories too (same behavior\n    #     as git).\n    #\n    gitignore.any? do |ignore|\n      File.fnmatch(ignore, file, File::FNM_PATHNAME) ||\n        File.fnmatch(ignore, File.basename(file), File::FNM_PATHNAME)\n    end\n\n    gitmodules.any? do |ignore|\n      File.fnmatch(ignore, file, File::FNM_PATHNAME) ||\n        File.fnmatch(ignore, File.basename(file), File::FNM_PATHNAME)\n    end\n  end\n\n  s.files         = unignored_files\n  s.executables   = unignored_files.map { |f| f[/^bin\\/(.*)/, 1] }.compact\n  s.extensions    = [\"ext/vagrant/vagrant_ssl/extconf.rb\"]\n  s.require_path  = 'lib'\nend\n"
  },
  {
    "path": "version.txt",
    "content": "2.4.10.dev\n"
  },
  {
    "path": "website/.editorconfig",
    "content": "# This file is for unifying the coding style for different editors and IDEs\n# editorconfig.org\n\nroot = true\n\n[*]\nend_of_line = lf\ncharset = utf-8\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nindent_style = space\nindent_size = 2\n\n[Makefile]\nindent_style = tab\n\n[{*.md,*.json}]\nmax_line_length = null\n"
  },
  {
    "path": "website/.env",
    "content": "NEXT_PUBLIC_ALGOLIA_APP_ID=YY0FFNI7MF\nNEXT_PUBLIC_ALGOLIA_INDEX=product_VAGRANT\nNEXT_PUBLIC_ALGOLIA_SEARCH_ONLY_API_KEY=c4c3e690f46940fa3ba9624da4d7fc0c\n"
  },
  {
    "path": "website/.eslintrc.js",
    "content": "/**\n * Copyright (c) HashiCorp, Inc.\n * SPDX-License-Identifier: BUSL-1.1\n */\n\nmodule.exports = {\n  ...require('@hashicorp/platform-cli/config/.eslintrc'),\n  ignorePatterns: ['public/'],\n}\n"
  },
  {
    "path": "website/.gitignore",
    "content": "node_modules\n.DS_Store\n.next\nout\n.mdx-data\n\n# As per Next.js conventions (https://nextjs.org/docs/basic-features/environment-variables#default-environment-variables)\n!.env\n.env*.local\n\nwebsite-preview\n"
  },
  {
    "path": "website/.nvmrc",
    "content": "v22\n"
  },
  {
    "path": "website/.stylelintrc.js",
    "content": "/**\n * Copyright (c) HashiCorp, Inc.\n * SPDX-License-Identifier: BUSL-1.1\n */\n\nmodule.exports = {\n  ...require('@hashicorp/platform-cli/config/stylelint.config'),\n}\n"
  },
  {
    "path": "website/LICENSE.md",
    "content": "# Proprietary License\n\nThis license is temporary while a more official one is drafted. However,\nthis should make it clear:\n\nThe text contents of this website are MPL 2.0 licensed.\n\nThe design contents of this website are proprietary and may not be reproduced\nor reused in any way other than to run the website locally. The license for\nthe design is owned solely by HashiCorp, Inc.\n"
  },
  {
    "path": "website/Makefile",
    "content": "######################################################\n# NOTE: This file is managed by the Digital Team's   #\n# Terraform configuration @ hashicorp/mktg-terraform #\n######################################################\n\n.DEFAULT_GOAL := website\n\n# Set the preview mode for the website shell to \"developer\" or \"io\"\nPREVIEW_MODE ?= developer\nREPO ?= vagrant\n\n# Enable setting alternate docker tool, e.g. 'make DOCKER_CMD=podman'\nDOCKER_CMD ?= docker\n\nCURRENT_GIT_BRANCH=$$(git rev-parse --abbrev-ref HEAD)\nLOCAL_CONTENT_DIR=\nPWD=$$(pwd)\n\nDOCKER_IMAGE=\"hashicorp/dev-portal\"\nDOCKER_IMAGE_LOCAL=\"dev-portal-local\"\nDOCKER_RUN_FLAGS=-it \\\n\t\t--publish \"3000:3000\" \\\n\t\t--rm \\\n\t\t--tty \\\n\t\t--volume \"$(PWD)/content:/app/content\" \\\n\t\t--volume \"$(PWD)/public:/app/public\" \\\n\t\t--volume \"$(PWD)/data:/app/data\" \\\n\t\t--volume \"$(PWD)/redirects.js:/app/redirects.js\" \\\n\t\t--volume \"next-dir:/app/website-preview/.next\" \\\n\t\t--volume \"$(PWD)/.env:/app/.env\" \\\n\t\t--volume \"$(PWD)/.env.development:/app/website-preview/.env.development\" \\\n\t\t--volume \"$(PWD)/.env.local:/app/website-preview/.env.local\" \\\n\t\t-e \"REPO=$(REPO)\" \\\n\t\t-e \"PREVIEW_FROM_REPO=$(REPO)\" \\\n\t\t-e \"IS_CONTENT_PREVIEW=true\" \\\n\t\t-e \"LOCAL_CONTENT_DIR=$(LOCAL_CONTENT_DIR)\" \\\n\t\t-e \"CURRENT_GIT_BRANCH=$(CURRENT_GIT_BRANCH)\" \\\n\t\t-e \"PREVIEW_MODE=$(PREVIEW_MODE)\"\n\n# Default: run this if working on the website locally to run in watch mode.\n.PHONY: website\nwebsite:\n\t@echo \"==> Downloading latest Docker image...\"\n\t@$(DOCKER_CMD) pull $(DOCKER_IMAGE)\n\t@echo \"==> Starting website...\"\n\t@$(DOCKER_CMD) run $(DOCKER_RUN_FLAGS) $(DOCKER_IMAGE)\n\n# Use this if you have run `website/build-local` to use the locally built image.\n.PHONY: website/local\nwebsite/local:\n\t@echo \"==> Starting website from local image...\"\n\t@$(DOCKER_CMD) run $(DOCKER_RUN_FLAGS) $(DOCKER_IMAGE_LOCAL)\n\n# Run this to generate a new local Docker image.\n.PHONY: website/build-local\nwebsite/build-local:\n\t@echo \"==> Building local Docker image\"\n\t@$(DOCKER_CMD) build https://github.com/hashicorp/dev-portal.git\\#main \\\n\t\t-t $(DOCKER_IMAGE_LOCAL)\n\n"
  },
  {
    "path": "website/README.md",
    "content": "⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]\n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n\n# Vagrant Website\n\nThis subdirectory contains the content for the [Vagrant Website](https://vagrantup.com/).\n\n<!--\n  This readme file contains several blocks of generated text, to make it easier to share common information\n  across documentation website readmes. To generate these blocks from their source, run `npm run generate:readme`\n\n  Any edits to the readme are welcome outside the clearly noted boundaries of the blocks. Alternately, a\n  block itself can be safely \"forked\" from the central implementation simply by removing the \"BEGIN\" and\n  \"END\" comments from around it.\n-->\n\n## Table of Contents\n\n- [Contributions](#contributions-welcome)\n- [Running the Site Locally](#running-the-site-locally)\n- [Editing Markdown Content](#editing-markdown-content)\n- [Editing Navigation Sidebars](#editing-navigation-sidebars)\n- [Changing the Release Version](#changing-the-release-version)\n- [Redirects](#redirects)\n- [Browser Support](#browser-support)\n- [Deployment](#deployment)\n\n<!-- BEGIN: contributions -->\n<!-- Generated text, do not edit directly -->\n\n## Contributions Welcome!\n\nIf you find a typo or you feel like you can improve the HTML, CSS, or JavaScript, we welcome contributions. Feel free to open issues or pull requests like any normal GitHub project, and we'll merge it in 🚀\n\n<!-- END: contributions -->\n\n<!-- BEGIN: local-development -->\n<!-- Generated text, do not edit directly -->\n\n## Running the Site Locally\n\nThe website can be run locally through node.js or [Docker](https://www.docker.com/get-started). If you choose to run through Docker, everything will be a little bit slower due to the additional overhead, so for frequent contributors it may be worth it to use node.\n\n> **Note:** If you are using a text editor that uses a \"safe write\" save style such as **vim** or **goland**, this can cause issues with the live reload in development. If you turn off safe write, this should solve the problem. In vim, this can be done by running `:set backupcopy=yes`. In goland, search the settings for \"safe write\" and turn that setting off.\n\n### With Docker\n\nRunning the site locally is simple. Provided you have Docker installed, clone this repo, run `make`, and then visit `http://localhost:3000`.\n\nThe docker image is pre-built with all the website dependencies installed, which is what makes it so quick and simple, but also means if you need to change dependencies and test the changes within Docker, you'll need a new image. If this is something you need to do, you can run `make build-image` to generate a local Docker image with updated dependencies, then `make website-local` to use that image and preview.\n\n### With Node\n\nIf your local development environment has a supported version (v22+) of [node installed](https://nodejs.org/en/) you can run:\n\n- `npm install`\n- `npm start`\n\n...and then visit `http://localhost:3000`.\n\nIf you pull down new code from github, you should run `npm install` again. Otherwise, there's no need to re-run `npm install` each time the site is run, you can just run `npm start` to get it going.\n\n<!-- END: local-development -->\n\n<!-- BEGIN: editing-markdown -->\n<!-- Generated text, do not edit directly -->\n\n## Editing Markdown Content\n\nDocumentation content is written in [Markdown](https://www.markdownguide.org/cheat-sheet/) and you'll find all files listed under the `/content` directory.\n\nTo create a new page with Markdown, create a file ending in `.mdx` in a `content/<subdirectory>`. The path in the content directory will be the URL route. For example, `content/docs/hello.mdx` will be served from the `/docs/hello` URL.\n\n> **Important**: Files and directories will only be rendered and published to the website if they are [included in sidebar data](#editing-navigation-sidebars). Any file not included in sidebar data will not be rendered or published.\n\nThis file can be standard Markdown and also supports [YAML frontmatter](https://middlemanapp.com/basics/frontmatter/). YAML frontmatter is optional, there are defaults for all keys.\n\n```yaml\n---\ntitle: 'My Title'\ndescription: \"A thorough, yet succinct description of the page's contents\"\n---\n```\n\nThe significant keys in the YAML frontmatter are:\n\n- `title` `(string)` - This is the title of the page that will be set in the HTML title.\n- `description` `(string)` - This is a description of the page that will be set in the HTML description.\n\n> ⚠️ If there is a need for a `/api/*` url on this website, the url will be changed to `/api-docs/*`, as the `api` folder is reserved by next.js.\n\n### Validating Content\n\nContent changes are automatically validated against a set of rules as part of the pull request process. If you want to run these checks locally to validate your content before comitting your changes, you can run the following command:\n\n```\nnpm run content-check\n```\n\nIf the validation fails, actionable error messages will be displayed to help you address detected issues.\n\n### Creating New Pages\n\nThere is currently a small bug with new page creation - if you create a new page and link it up via subnav data while the server is running, it will report an error saying the page was not found. This can be resolved by restarting the server.\n\n### Markdown Enhancements\n\nThere are several custom markdown plugins that are available by default that enhance [standard markdown](https://commonmark.org/) to fit our use cases. This set of plugins introduces a couple instances of custom syntax, and a couple specific pitfalls that are not present by default with markdown, detailed below:\n\n- > **Warning**: We are deprecating the current [paragraph alerts](https://github.com/hashicorp/remark-plugins/tree/master/plugins/paragraph-custom-alerts#paragraph-custom-alerts), in favor of the newer [MDX Inline Alert](#inline-alerts) components. The legacy paragraph alerts are represented by the symbols `~>`, `->`, `=>`, or `!>`.\n- If you see `@include '/some/path.mdx'`, this is a [markdown include](https://github.com/hashicorp/remark-plugins/tree/master/plugins/include-markdown#include-markdown-plugin). It's worth noting as well that all includes resolve from `website/content/partials` by default, and that changes to partials will not live-reload the website.\n- If you see `# Headline ((#slug))`, this is an example of an [anchor link alias](https://github.com/hashicorp/remark-plugins/tree/je.anchor-link-adjustments/plugins/anchor-links#anchor-link-aliases). It adds an extra permalink to a headline for compatibility and is removed from the output.\n- Due to [automatically generated permalinks](https://github.com/hashicorp/remark-plugins/tree/je.anchor-link-adjustments/plugins/anchor-links#anchor-links), any text changes to _headlines_ or _list items that begin with inline code_ can and will break existing permalinks. Be very cautious when changing either of these two text items.\n\n  Headlines are fairly self-explanatory, but here's an example of how to list items that begin with inline code look.\n\n  ```markdown\n  - this is a normal list item\n  - `this` is a list item that begins with inline code\n  ```\n\n  Its worth noting that _only the inline code at the beginning of the list item_ will cause problems if changed. So if you changed the above markup to...\n\n  ```markdown\n  - lsdhfhksdjf\n  - `this` jsdhfkdsjhkdsfjh\n  ```\n\n  ...while it perhaps would not be an improved user experience, no links would break because of it. The best approach is to **avoid changing headlines and inline code at the start of a list item**. If you must change one of these items, make sure to tag someone from the digital marketing development team on your pull request, they will help to ensure as much compatibility as possible.\n\n### Custom Components\n\nA number of custom [mdx components](https://mdxjs.com/) are available for use within any `.mdx` file. Each one is documented below:\n\n#### Inline Alerts\n\nThere are custom MDX components available to author alert data. [See the full documentation here](https://developer.hashicorp.com/swingset/components/mdxinlinealert). They render as colored boxes to draw the user's attention to some type of aside.\n\n```mdx\n## Alert types\n\n### Tip\n\n<Tip>\n  To provide general information to the user regarding the current context or\n  relevant actions.\n</Tip>\n\n### Highlight\n\n<Highlight>\n  To provide general or promotional information to the user prominently.\n</Highlight>\n\n### Note\n\n<Note>\n  To help users avoid an issue. Provide guidance and actions if possible.\n</Note>\n\n### Warning\n\n<Warning>\n  To indicate critical issues that need immediate action or help users\n  understand something critical.\n</Warning>\n\n## Title override prop\n\n<Note title=\"Hashiconf 2027\">To provide general information.</Note>\n```\n\n#### Tabs\n\nThe `Tabs` component creates tabbed content of any type, but is often used for code examples given in different languages. Here's an example of how it looks from the Vagrant documentation website:\n\n![Tabs Component](https://p176.p0.n0.cdn.getcloudapp.com/items/WnubALZ4/Screen%20Recording%202020-06-11%20at%2006.03%20PM.gif?v=1de81ea720a8cc8ade83ca64fb0b9edd)\n\n> Please refer to the [Swingset](https://react-components.vercel.app/?component=Tabs) documentation for the latest examples and API reference.\n\nIt can be used as such within a markdown file:\n\n````mdx\nNormal **markdown** content.\n\n<Tabs>\n<Tab heading=\"CLI command\">\n            <!-- Intentionally skipped line.. -->\n```shell-session\n$ command ...\n```\n            <!-- Intentionally skipped line.. -->\n</Tab>\n<Tab heading=\"API call using cURL\">\n\n```shell-session\n$ curl ...\n```\n\n</Tab>\n</Tabs>\n\nContinued normal markdown content\n````\n\nThe intentionally skipped line is a limitation of the mdx parser which is being actively worked on. All tabs must have a heading, and there is no limit to the number of tabs, though it is recommended to go for a maximum of three or four.\n\n#### Enterprise Alert\n\nThis component provides a standard way to call out functionality as being present only in the enterprise version of the software. It can be presented in two contexts, inline or standalone. Here's an example of standalone usage from the Consul docs website:\n\n![Enterprise Alert Component - Standalone](https://p176.p0.n0.cdn.getcloudapp.com/items/WnubALp8/Screen%20Shot%202020-06-11%20at%206.06.03%20PM.png?v=d1505b90bdcbde6ed664831a885ea5fb)\n\nThe standalone component can be used as such in markdown files:\n\n```mdx\n# Page Headline\n\n<EnterpriseAlert />\n\nContinued markdown content...\n```\n\nIt can also receive custom text contents if you need to change the messaging but wish to retain the style. This will replace the text `This feature is available in all versions of Consul Enterprise.` with whatever you add. For example:\n\n```mdx\n# Page Headline\n\n<EnterpriseAlert>\n  My custom text here, and <a href=\"#\">a link</a>!\n</EnterpriseAlert>\n\nContinued markdown content...\n```\n\nIt's important to note that once you are adding custom content, it must be html and can not be markdown, as demonstrated above with the link.\n\nNow let's look at inline usage, here's an example:\n\n![Enterprise Alert Component - Inline](https://p176.p0.n0.cdn.getcloudapp.com/items/L1upYLEJ/Screen%20Shot%202020-06-11%20at%206.07.50%20PM.png?v=013ba439263de8292befbc851d31dd78)\n\nAnd here's how it could be used in your markdown document:\n\n```mdx\n### Some Enterprise Feature <EnterpriseAlert inline />\n\nContinued markdown content...\n```\n\nIt's also worth noting that this component will automatically adjust to the correct product colors depending on the context.\n\n#### Other Components\n\nOther custom components can be made available on a per-site basis, the above are the standards. If you have questions about custom components that are not documented here, or have a request for a new custom component, please reach out to @hashicorp/digital-marketing.\n\n### Syntax Highlighting\n\nWhen using fenced code blocks, the recommendation is to tag the code block with a language so that it can be syntax highlighted. For example:\n\n````\n```\n// BAD: Code block with no language tag\n```\n\n```javascript\n// GOOD: Code block with a language tag\n```\n````\n\nCheck out the [supported languages list](https://prismjs.com/#supported-languages) for the syntax highlighter we use if you want to double check the language name.\n\nIt is also worth noting specifically that if you are using a code block that is an example of a terminal command, the correct language tag is `shell-session`. For example:\n\n🚫**BAD**: Using `shell`, `sh`, `bash`, or `plaintext` to represent a terminal command\n\n````\n```shell\n$ terraform apply\n```\n````\n\n✅**GOOD**: Using `shell-session` to represent a terminal command\n\n````\n```shell-session\n$ terraform apply\n```\n````\n\n<!-- END: editing-markdown -->\n\n<!-- BEGIN: editing-docs-sidebars -->\n<!-- Generated text, do not edit directly -->\n\n## Editing Navigation Sidebars\n\nThe structure of the sidebars are controlled by files in the [`/data` directory](data). For example, [data/docs-nav-data.json](data/docs-nav-data.json) controls the **docs** sidebar. Within the `data` folder, any file with `-nav-data` after it controls the navigation for the given section.\n\nThe sidebar uses a simple recursive data structure to represent _files_ and _directories_. The sidebar is meant to reflect the structure of the docs within the filesystem while also allowing custom ordering. Let's look at an example. First, here's our example folder structure:\n\n```text\n.\n├── docs\n│   └── directory\n│       ├── index.mdx\n│       ├── file.mdx\n│       ├── another-file.mdx\n│       └── nested-directory\n│           ├── index.mdx\n│           └── nested-file.mdx\n```\n\nHere's how this folder structure could be represented as a sidebar navigation, in this example it would be the file `website/data/docs-nav-data.json`:\n\n```json\n[\n  {\n    \"title\": \"Directory\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"directory\"\n      },\n      {\n        \"title\": \"File\",\n        \"path\": \"directory/file\"\n      },\n      {\n        \"title\": \"Another File\",\n        \"path\": \"directory/another-file\"\n      },\n      {\n        \"title\": \"Nested Directory\",\n        \"routes\": [\n          {\n            \"title\": \"Overview\",\n            \"path\": \"directory/nested-directory\"\n          },\n          {\n            \"title\": \"Nested File\",\n            \"path\": \"directory/nested-directory/nested-file\"\n          }\n        ]\n      }\n    ]\n  }\n]\n```\n\nA couple more important notes:\n\n- Within this data structure, ordering is flexible, but hierarchy is not. The structure of the sidebar must correspond to the structure of the content directory. So while you could put `file` and `another-file` in any order in the sidebar, or even leave one or both of them out, you could not decide to un-nest the `nested-directory` object without also un-nesting it in the filesystem.\n- The `title` property on each node in the `nav-data` tree is the human-readable name in the navigation.\n- The `path` property on each leaf node in the `nav-data` tree is the URL path where the `.mdx` document will be rendered, and the\n- Note that \"index\" files must be explicitly added. These will be automatically resolved, so the `path` value should be, as above, `directory` rather than `directory/index`. A common convention is to set the `title` of an \"index\" node to be `\"Overview\"`.\n\nBelow we will discuss a couple of more unusual but still helpful patterns.\n\n### Index-less Categories\n\nSometimes you may want to include a category but not have a need for an index page for the category. This can be accomplished, but as with other branch and leaf nodes, a human-readable `title` needs to be set manually. Here's an example of how an index-less category might look:\n\n```text\n.\n├── docs\n│   └── indexless-category\n│       └── file.mdx\n```\n\n```json\n// website/data/docs-nav-data.json\n[\n  {\n    \"title\": \"Indexless Category\",\n    \"routes\": [\n      {\n        \"title\": \"File\",\n        \"path\": \"indexless-category/file\"\n      }\n    ]\n  }\n]\n```\n\n### Custom or External Links\n\nSometimes you may have a need to include a link that is not directly to a file within the docs hierarchy. This can also be supported using a different pattern. For example:\n\n```json\n[\n  {\n    \"name\": \"Directory\",\n    \"routes\": [\n      {\n        \"title\": \"File\",\n        \"path\": \"directory/file\"\n      },\n      {\n        \"title\": \"Another File\",\n        \"path\": \"directory/another-file\"\n      },\n      {\n        \"title\": \"Tao of HashiCorp\",\n        \"href\": \"https://www.hashicorp.com/tao-of-hashicorp\"\n      }\n    ]\n  }\n]\n```\n\nIf the link provided in the `href` property is external, it will display a small icon indicating this. If it's internal, it will appear the same way as any other direct file link.\n\n<!-- END: editing-docs-sidebars -->\n\n<!-- BEGIN: releases -->\n<!-- Generated text, do not edit directly -->\n\n## Changing the Release Version\n\nTo change the version displayed for download on the website, head over to `data/version.js` and change the number there. It's important to note that the version number must match a version that has been released and is live on `releases.hashicorp.com` -- if it does not, the website will be unable to fetch links to the binaries and will not compile. So this version number should be changed _only after a release_.\n\n### Displaying a Prerelease\n\nIf there is a prerelease of any type that should be displayed on the downloads page, this can be done by editing `pages/downloads/index.jsx`. By default, the download component might look something like this:\n\n```jsx\n<ProductDownloader\n  product=\"<Product>\"\n  version={VERSION}\n  downloads={downloadData}\n  community=\"/resources\"\n/>\n```\n\nTo add a prerelease, an extra `prerelease` property can be added to the component as such:\n\n```jsx\n<ProductDownloader\n  product=\"<Product>\"\n  version={VERSION}\n  downloads={downloadData}\n  community=\"/resources\"\n  prerelease={{\n    type: 'release candidate', // the type of prerelease: beta, release candidate, etc.\n    name: 'v1.0.0', // the name displayed in text on the website\n    version: '1.0.0-rc1', // the actual version tag that was pushed to releases.hashicorp.com\n  }}\n/>\n```\n\nThis configuration would display something like the following text on the website, emphasis added to the configurable parameters:\n\n```\nA {{ release candidate }} for <Product> {{ v1.0.0 }} is available! The release can be <a href='https://releases.hashicorp.com/<product>/{{ 1.0.0-rc1 }}'>downloaded here</a>.\n```\n\nYou may customize the parameters in any way you'd like. To remove a prerelease from the website, simply delete the `prerelease` parameter from the above component.\n\n<!-- END: releases -->\n\n<!-- BEGIN: redirects -->\n<!-- Generated text, do not edit directly -->\n\n## Redirects\n\nThis website structures URLs based on the filesystem layout. This means that if a file is moved, removed, or a folder is re-organized, links will break. If a path change is necessary, it can be mitigated using redirects. It's important to note that redirects should only be used to cover for external links -- if you are moving a path which internal links point to, the internal links should also be adjusted to point to the correct page, rather than relying on a redirect.\n\nTo add a redirect, head over to the `redirects.js` file - the format is fairly simple - there's a `source` and a `destination` - fill them both in, indicate that it's a permanent redirect or not using the `permanent` key, and that's it. Let's look at an example:\n\n```\n{\n  source: '/foo',\n  destination: '/bar',\n  permanent: true\n}\n```\n\nThis redirect rule will send all incoming links to `/foo` to `/bar`. For more details on the redirects file format, [check out the docs on vercel](https://vercel.com/docs/configuration#project/redirects). All redirects will work both locally and in production exactly the same way, so feel free to test and verify your redirects locally. In the past testing redirects has required a preview deployment -- this is no longer the case. Please note however that if you add a redirect while the local server is running, you will need to restart it in order to see the effects of the redirect.\n\nThere is still one caveat though: redirects do not apply to client-side navigation. By default, all links in the navigation and docs sidebar will navigate purely on the client side, which makes navigation through the docs significantly faster, especially for those with low-end devices and/or weak internet connections. In the future, we plan to convert all internal links within docs pages to behave this way as well. This means that if there is a link on this website to a given piece of content that has changed locations in some way, we need to also _directly change existing links to the content_. This way, if a user clicks a link that navigates on the client side, or if they hit the url directly and the page renders from the server side, either one will work perfectly.\n\nLet's look at an example. Say you have a page called `/docs/foo` which needs to be moved to `/docs/nested/foo`. Additionally, this is a page that has been around for a while and we know there are links into `/docs/foo.html` left over from our previous website structure. First, we move the page, then adjust the docs sidenav, in `data/docs-navigation.js`. Find the category the page is in, and move it into the appropriate subcategory. Next, we add to `_redirects` as such. The `.html` version is covered automatically.\n\n```js\n{ source: '/foo', destination: '/nested/foo', permanent: true }\n```\n\nNext, we run a global search for internal links to `/foo`, and make sure to adjust them to be `/nested/foo` - this is to ensure that client-side navigation still works correctly. _Adding a redirect alone is not enough_.\n\nOne more example - let's say that content is being moved to an external website. A common example is guides moving to `learn.hashicorp.com`. In this case, we take all the same steps, except that we need to make a different type of change to the `docs-navigation` file. If previously the structure looked like:\n\n```js\n{\n  category: 'docs',\n  content: [\n    'foo'\n  ]\n}\n```\n\nIf we no longer want the link to be in the side nav, we can simply remove it. If we do still want the link in the side nav, but pointing to an external destination, we need to slightly change the structure as such:\n\n```js\n{\n  category: 'docs',\n  content: [\n    { title: 'Foo Title', href: 'https://learn.hashicorp.com/<product>/foo' }\n  ]\n}\n```\n\nAs the majority of items in the side nav are internal links, the structure makes it as easy as possible to represent these links. This alternate syntax is the most concise manner than an external link can be represented. External links can be used anywhere within the docs sidenav.\n\nIt's also worth noting that it is possible to do glob-based redirects, for example matching `/docs/*`, and you may see this pattern in the redirects file. This type of redirect is much higher risk and the behavior is a bit more nuanced, so if you need to add a glob redirect, please reach out to the website maintainers and ask about it first.\n\n<!-- END: redirects -->\n\n<!-- BEGIN: browser-support -->\n<!-- Generated text, do not edit directly -->\n\n## Browser Support\n\nWe support the following browsers targeting roughly the versions specified.\n\n| ![Chrome](https://raw.githubusercontent.com/alrra/browser-logos/main/src/chrome/chrome.svg) | ![Edge](https://raw.githubusercontent.com/alrra/browser-logos/main/src/edge/edge.svg) | ![Opera](https://raw.githubusercontent.com/alrra/browser-logos/main/src/opera/opera.svg) | ![Firefox](https://raw.githubusercontent.com/alrra/browser-logos/main/src/firefox/firefox.svg) | ![Safari](https://raw.githubusercontent.com/alrra/browser-logos/main/src/safari/safari.svg) |\n| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |\n| **Latest**                                                                                  | **Latest**                                                                            | **Latest**                                                                               | **Latest**                                                                                     | **Latest**                                                                                  |\n\n<!-- END: browser-support -->\n\n<!-- BEGIN: deployment -->\n<!-- Generated text, do not edit directly -->\n\n## Deployment\n\nThis website is hosted on Vercel and configured to automatically deploy anytime you push code to the `stable-website` branch. Any time a pull request is submitted that changes files within the `website` folder, a deployment preview will appear in the github checks which can be used to validate the way docs changes will look live. Deployments from `stable-website` will look and behave the same way as deployment previews.\n\n<!-- END: deployment -->\n"
  },
  {
    "path": "website/content/docs/boxes/base.mdx",
    "content": "---\nlayout: docs\npage_title: Creating a Base Box\ndescription: |-\n  There are a special category of boxes known as \"base boxes.\" These boxes\n  contain the bare minimum required for Vagrant to function, are generally\n  not made by repackaging an existing Vagrant environment (hence the \"base\"\n  in the \"base box\").\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Creating a Base Box\n\nThere are a special category of boxes known as \"base boxes.\" These boxes\ncontain the bare minimum required for Vagrant to function, are generally\nnot made by repackaging an existing Vagrant environment (hence the \"base\"\nin the \"base box\").\n\nFor example, the Ubuntu boxes provided by the Vagrant project (such as\n\"bionic64\") are base boxes. They were created from a minimal Ubuntu install\nfrom an ISO, rather than repackaging an existing environment.\n\nBase boxes are extremely useful for having a clean slate starting point from\nwhich to build future development environments. The Vagrant project hopes\nin the future to be able to provide base boxes for many more operating systems.\nUntil then, this page documents how you can create your own base box.\n\n~> **Advanced topic!** Creating a base box can be a time consuming\nand tedious process, and is not recommended for new Vagrant users. If you are\njust getting started with Vagrant, we recommend trying to find existing\nbase boxes to use first.\n\n## What's in a Base Box?\n\nA base box typically consists of only a bare minimum set of software\nfor Vagrant to function. As an example, a Linux box may contain only the\nfollowing:\n\n- Package manager\n- SSH\n- SSH user so Vagrant can connect\n- Perhaps Chef, Puppet, etc. but not strictly required.\n\nIn addition to this, each [provider](/vagrant/docs/providers/) may require\nadditional software. For example, if you are making a base box for VirtualBox,\nyou will want to include the VirtualBox guest additions so that shared folders\nwork properly. But if you are making an AWS base box, this is not required.\n\n## Creating a Base Box\n\nCreating a base box is actually provider-specific. This means that depending\non if you are using VirtualBox, VMware, AWS, etc. the process for creating\na base box is different. Because of this, this one document cannot be a\nfull guide to creating a base box.\n\nThis page will document some general guidelines for creating base boxes,\nhowever, and will link to provider-specific guides for creating base\nboxes.\n\nProvider-specific guides for creating base boxes are linked below:\n\n- [Docker Base Boxes](/vagrant/docs/providers/docker/boxes)\n- [Hyper-V Base Boxes](/vagrant/docs/providers/hyperv/boxes)\n- [VMware Base Boxes](/vagrant/docs/providers/vmware/boxes)\n- [VirtualBox Base Boxes](/vagrant/docs/providers/virtualbox/boxes)\n\n### Packer and Vagrant Cloud\n\nWe strongly recommend using [Packer](https://www.packer.io/) to create reproducible\nbuilds for your base boxes, as well as automating the builds. Read more about\n[automating Vagrant box creation with Packer](/packer/guides/packer-on-cicd/build-image-in-cicd)\nin the Packer documentation.\n\n### Disk Space\n\nWhen creating a base box, make sure the user will have enough disk space\nto do interesting things, without being annoying. For example, in VirtualBox,\nyou should create a dynamically resizing drive with a large maximum size.\nThis causes the actual footprint of the drive to be small initially, but\nto dynamically grow towards the max size as disk space is needed, providing\nthe most flexibility for the end user.\n\nIf you are creating an AWS base box, do not force the AMI to allocate\nterabytes of EBS storage, for example, since the user can do that on their\nown. But you should default to mounting ephemeral drives, because they're\nfree and provide a lot of disk space.\n\n### Memory\n\nLike disk space, finding the right balance of the default amount of memory\nis important. For most providers, the user can modify the memory with\nthe Vagrantfile, so do not use too much by default. It would be a poor\nuser experience (and mildly shocking) if a `vagrant up` from a base box\ninstantly required many gigabytes of RAM. Instead, choose a value such\nas 512MB, which is usually enough to play around and do interesting things\nwith a Vagrant machine, but can easily be increased when needed.\n\n### Peripherals (Audio, USB, etc.)\n\nDisable any non-necessary hardware in a base box such as audio and USB\ncontrollers. These are generally unnecessary for Vagrant usage and, again,\ncan be easily added via the Vagrantfile in most cases.\n\n## Default User Settings\n\nJust about every aspect of Vagrant can be modified. However, Vagrant does\nexpect some defaults which will cause your base box to \"just work\" out\nof the box. You should create these as defaults if you intend to publicly\ndistribute your box.\n\nIf you are creating a base box for private use, you should try _not_ to\nfollow these, as they open up your base box to security risks (known\nusers, passwords, private keys, etc.).\n\n### \"vagrant\" User\n\nBy default, Vagrant expects a \"vagrant\" user to SSH into the machine as.\nThis user should be setup with the\n[insecure keypairs](https://github.com/hashicorp/vagrant/tree/main/keys)\nthat Vagrant uses as a default to attempt to SSH. It should belong to a\ngroup named \"vagrant\". Also, even though Vagrant uses key-based\nauthentication by default, it is a general convention to set the\npassword for the \"vagrant\" user to \"vagrant\". This lets people login as\nthat user manually if they need to.\n\nTo configure SSH access with the insecure keypair, place the [public\nkeys](https://github.com/hashicorp/vagrant/tree/main/keys/vagrant.pub)\ninto the `~/.ssh/authorized_keys` file for the \"vagrant\" user. Note\nthat OpenSSH is very picky about file permissions. Therefore, make sure\nthat `~/.ssh` has `0700` permissions and the authorized keys file has\n`0600` permissions.\n\nWhen Vagrant boots a box and detects the insecure keypair, it will\nautomatically replace it with a randomly generated keypair for additional\nsecurity while the box is running.\n\n### Root Password: \"vagrant\"\n\nVagrant does not actually use or expect any root password. However, having\na generally well known root password makes it easier for the general public\nto modify the machine if needed.\n\nPublicly available base boxes usually use a root password of \"vagrant\" to\nkeep things easy.\n\n### Password-less Sudo\n\nThis is **important!**. Many aspects of Vagrant expect the default SSH user\nto have passwordless sudo configured. This lets Vagrant configure networks,\nmount synced folders, install software, and more.\n\nTo begin, some minimal installations of operating systems do not even include\n`sudo` by default. Verify that you install `sudo` in some way.\n\nAfter installing sudo, configure it (usually using `visudo`) to allow\npasswordless sudo for the \"vagrant\" user. This can be done with the\nfollowing line at the end of the configuration file:\n\n```\nvagrant ALL=(ALL) NOPASSWD: ALL\n```\n\nAdditionally, Vagrant does not use a pty or tty by default when connected\nvia SSH. You will need to make sure there is no line that has `requiretty` in\nit. Remove that if it exists. This allows sudo to work properly without a\ntty. Note that you _can_ configure Vagrant to request a pty, which lets\nyou keep this configuration. But Vagrant by default does not do this.\n\n### SSH Tweaks\n\nIn order to keep SSH speedy even when your machine or the Vagrant machine\nis not connected to the internet, set the `UseDNS` configuration to `no`\nin the SSH server configuration.\n\nThis avoids a reverse DNS lookup on the connecting SSH client which\ncan take many seconds.\n\n## Windows Boxes\n\nSupported Windows guest operating systems:\n\n- Windows 7\n- Windows 8\n- Windows Server 2008\n- Windows Server 2008 R2\n- Windows Server 2012\n- Windows Server 2012 R2\n\nWindows Server 2003 and Windows XP are _not_ supported, but if you are a die\nhard XP fan [this](https://stackoverflow.com/a/18593425/18475) may help you.\n\n### Base Windows Configuration\n\n- Turn off UAC\n- Disable complex passwords\n- Disable \"Shutdown Tracker\"\n- Disable \"Server Manager\" starting at login (for non-Core)\n\nIn addition to disabling UAC in the control panel, you also must disable\nUAC in the registry. This may vary from Windows version to Windows version,\nbut Windows 8/8.1 use the command below. This will allow some things like\nautomated Puppet installs to work within Vagrant Windows base boxes.\n\n```\nreg add HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System /v EnableLUA /d 0 /t REG_DWORD /f /reg:64\n```\n\n### Base WinRM Configuration\n\nTo enable and configure WinRM you will need to set the WinRM service to\nauto-start and allow unencrypted basic auth (obviously this is not secure).\nRun the following commands from a regular Windows command prompt:\n\n```\nwinrm quickconfig -q\nwinrm set winrm/config/winrs @{MaxMemoryPerShellMB=\"512\"}\nwinrm set winrm/config @{MaxTimeoutms=\"1800000\"}\nwinrm set winrm/config/service @{AllowUnencrypted=\"true\"}\nwinrm set winrm/config/service/auth @{Basic=\"true\"}\nsc config WinRM start= auto\n```\n\n### Additional WinRM 1.1 Configuration\n\nThese additional configuration steps are specific to Windows Server 2008\n(WinRM 1.1). For Windows Server 2008 R2, Windows 7 and later versions of\nWindows you can ignore this section.\n\n1. Ensure the Windows PowerShell feature is installed\n2. Change the WinRM port to 5985 or upgrade to WinRM 2.0\n\nThe following commands will change the WinRM 1.1 port to what's expected by\nVagrant:\n\n```\nnetsh firewall add portopening TCP 5985 \"Port 5985\"\nwinrm set winrm/config/listener?Address=*+Transport=HTTP @{Port=\"5985\"}\n```\n\n### Optional WinSSH Configuration\n\nWhen using the WinSSH communicator, you may run into an issue where a PowerShell\ncommand can't display a progress bar. A typical error message might look like:\n\n```\nWin32 internal error \"Access is denied\" 0x5 occurred while reading the console output buffer.\n```\n\nIn order to prevent this, we recommend setting `$ProgressPreference = \"SilentlyContinue\"`\nin your box's PowerShell profile:\n\n```\nif (!(Test-Path -Path $PROFILE)) {\n  New-Item -ItemType File -Path $PROFILE -Force\n}\n\nAdd-Content $PROFILE '$ProgressPreference = \"SilentlyContinue\"'\n```\n\n## Other Software\n\nAt this point, you have all the common software you absolutely _need_ for\nyour base box to work with Vagrant. However, there is some additional software\nyou can install if you wish.\n\nWhile we plan on it in the future, Vagrant still does not install Chef\nor Puppet automatically when using those provisioners. Users can use a shell\nprovisioner to do this, but if you want Chef/Puppet to just work out of the\nbox, you will have to install them in the base box.\n\nInstalling this is outside the scope of this page, but should be fairly\nstraightforward.\n\nIn addition to this, feel free to install and configure any other software\nyou want available by default for this base box.\n\n## Packaging the Box\n\nPackaging the box into a `box` file is provider-specific. Please refer to\nthe provider-specific documentation for creating a base box. Some\nprovider-specific guides are linked to towards the top of this page.\n\n## Distributing the Box\n\nYou can distribute the box file however you would like. However, if you want\nto support versioning, putting multiple providers at a single URL, pushing\nupdates, analytics, and more, we recommend you add the box to\n[HashiCorp's Vagrant Cloud](/vagrant/vagrant-cloud).\n\nYou can upload both public and private boxes to this service.\n\n## Testing the Box\n\nTo test the box, pretend you are a new user of Vagrant and give it a shot:\n\n```shell-session\n$ vagrant box add --name my-box /path/to/the/new.box\n...\n$ vagrant init my-box\n...\n$ vagrant up\n...\n```\n\nIf you made a box for some other provider, be sure to specify the\n`--provider` option to `vagrant up`. If the up succeeded, then your\nbox worked!\n"
  },
  {
    "path": "website/content/docs/boxes/box_repository.mdx",
    "content": "---\nlayout: docs\npage_title: Box Repository\ndescription: |-\n  Vagrant can download boxes from a Box Repository. [Vagrantcloud](https://vagrantcloud.com/)\n  is the HashiCorp maintained Box Repository.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Box Repository\n\nA Vagrant Box Repository provides Vagrant with some boxes and information\non how to get the boxes. This can exist on a local filesystem or a service\nlike Vagrantcloud.\n\nThere are two components that make up a Box Repository:\n\n- Vagrant Boxes - These are Vagrant `.box` files. See the \n  [box documentation](/vagrant/docs/boxes) for more information on Vagrant boxes.\n\n- Box Catalog Metadata - This is a JSON document (typically exchanged\n  during interactions with [HashiCorp's Vagrant Cloud](/vagrant/vagrant-cloud))\n  that specifies the name of the box, a description, available\n  versions, available providers, and URLs to the actual box files\n  (next component) for each provider and version. If this catalog\n  metadata does not exist, a box file can still be added directly, but\n  it will not support versioning and updating.\n\n## Box Catalog Metadata\n\nThe metadata is an optional component for a box (but highly recommended)\nthat enables [versioning](/vagrant//docs/boxes/versioning), updating, multiple\nproviders from a single file, and more.\n\n-> **You do not need to manually make the metadata.** If you\nhave an account with [HashiCorp's Vagrant Cloud](/vagrant/vagrant-cloud), you\ncan create boxes there, and HashiCorp's Vagrant Cloud automatically creates\nthe metadata for you. The format is still documented here.\n\nIt is a JSON document, structured in the following way:\n\n```json\n{\n  \"name\": \"hashicorp/bionic64\",\n  \"description\": \"This box contains Ubuntu 18.04 LTS 64-bit.\",\n  \"versions\": [\n    {\n      \"version\": \"0.1.0\",\n      \"providers\": [\n        {\n          \"name\": \"virtualbox\",\n          \"url\": \"http://example.com/bionic64_010_virtualbox.box\",\n          \"checksum_type\": \"sha1\",\n          \"checksum\": \"foo\",\n          \"architecture\": \"amd64\",\n          \"default_architecture\": true\n        }\n      ]\n    }\n  ]\n}\n```\n\nAs you can see, the JSON document can describe multiple versions of a box,\nmultiple providers, and can add/remove providers/architectures in different\nversions.\n\nThis JSON file can be passed directly to `vagrant box add` from the\nlocal filesystem using a file path or via a URL, and Vagrant will\ninstall the proper version of the box. In this case, the value for the\n`url` key in the JSON can also be a file path. If multiple providers\nare available, Vagrant will ask what provider you want to use.\n"
  },
  {
    "path": "website/content/docs/boxes/format.mdx",
    "content": "---\nlayout: docs\npage_title: Box File Format\ndescription: |-\n  The box file format for Vagrant supports a number different providers.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Box File Format\n\nA Vagrant `.box` file is a [tarball](<https://en.wikipedia.org/wiki/Tar_(computing)>)\n(`tar`, `tar.gz`, `zip`) that contains all the information for a provider\nto launch a Vagrant machine.\n\nThere are four different components that make up a box:\n\n- VM artifacts (required) - This is the VM image and other artifacts\n  in the format accepted by the provider the box is intended for. \n  For example, a box targeting the VirtualBox provider might have a `.ofv` \n  file and some `.vmdk` files.\n\n- metadata.json (required) - Contains a map with information about the box.\n  Most importantly the target provider.\n\n- info.json - This is a JSON document that can provide additional\n  information about the box that displays when a user runs\n  `vagrant box list -i`. More information is provided [here](/vagrant/docs/boxes/info).\n\n- Vagrantfile - The Vagrantfile embedded in the Vagrant box will provide\n  some defaults for users of the box. For more information on how \n  Vagrant merges Vagrantfiles including ones sourced within the \n  box file see the [Vagrantfile docs](/vagrant/vagrant-cloud)\n\nSo, if you extract a box and look at it's contents it will look like:\n```\n# contents of the hashicorp/bionic64 box\n# ref: https://app.vagrantup.com/hashicorp/boxes/bionic64\n$ ls hashicorp_bionic_box  \nVagrantfile                     metadata.json\nbox.ovf                         ubuntu-18.04-amd64-disk001.vmdk\n```\n\n## Box metadata.json\n\nWithin the archive, Vagrant does expect a single file:\n`metadata.json`. There is only one `metadata.json` per box file.\n`metadata.json` must contain at least the \"provider\" key with the\nprovider the box is for. Vagrant uses this to verify the provider of\nthe box. For example, if your box was for VirtualBox, the\n`metadata.json` would look like this:\n\n```json\n{\n  \"provider\": \"virtualbox\"\n}\n```\n\nIf there is no `metadata.json` file or the file does not contain valid JSON\nwith at least a \"provider\" key, then Vagrant will error when adding the box,\nbecause it cannot verify the provider.\n\nOther keys/values may be added to the metadata without issue. The value\nof the metadata file is passed opaquely into Vagrant and plugins can make\nuse of it. Values currently used by Vagrant core:\n\n* `provider` - (string) Provider for the box\n* `architecture` - (string) Architecture of the box\n"
  },
  {
    "path": "website/content/docs/boxes/index.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Boxes\ndescription: |-\n  Boxes are the package format for Vagrant environments. A box can be used by\n  anyone on any platform that Vagrant supports to bring up an identical\n  working environment.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant boxes\n\nBoxes are the package format for Vagrant environments. You specify a box environment and operating configurations in your [Vagrantfile](/vagrant/docs/vagrantfile). You can use a box on any [supported platform](/vagrant/downloads) to bring up identical working environments. To enable teams to use and manage the same boxes, [versions are supported](/vagrant/docs/boxes/versioning). \n\n~> **Note**: Boxes require a provider, a virtualization product, to operate. Before you can use a box, \nensure that you have properly installed a supported [provider](/vagrant/docs/providers).\n\nThe quickest way to get started is to select a pre-defined box environment from the\n[publicly available catalog on Vagrant Cloud](https://vagrantcloud.com/boxes/search).\nYou can also add and share your own customized boxes on Vagrant Cloud.\n\nThe `vagrant box` CLI utility provides all the functionality for box management. You can read the documentation on the [vagrant box](/vagrant/docs/cli/box)\ncommand for more information.\n\n## Discover boxes\n\nTo find boxes, explore the\n[public Vagrant box catalog](https://vagrantcloud.com/boxes/search)\nfor a box that matches your use case. The catalog contains most major operating\nsystems as bases, as well as specialized boxes to get you started with common \nconfigurations such as LAMP stacks, Ruby, and Python.\n\nThe boxes on the public catalog work with many different\n[providers](/vagrant/docs/providers/). The list of supported providers is located in the box descriptions.  \n\n### Add a box \n\nYou can add a box from the public catalog at any time. The box's description includes instructions\non how to add it to an existing Vagrantfile or initiate it as a new environment on the command-line.\n\nA common misconception is\nthat a namespace like \"ubuntu\" represents the official space for Ubuntu boxes.\nThis is untrue. Namespaces on Vagrant Cloud behave very similarly to namespaces on\nGitHub. Just as GitHub's support team is unable to assist with\nissues in someone's repository, HashiCorp's support team is unable to assist\nwith third-party published boxes.\n\n## Official boxes\n\nThere are only two officially-recommended box sets.\n\nHashiCorp publishes a basic Ubuntu 18.04 64-bit box that is available for minimal use cases. It is highly optimized, small in size, and includes support for VirtualBox, Hyper-V, and VMware. \n\nTo get started, use the `init` command to initialize your environment. \n\n```shell-session\n$ vagrant init hashicorp/bionic64\n```\n\nIf you have an existing Vagrantfile, add `hashicorp/bionic64`. \n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"hashicorp/bionic64\"\nend\n```\n\nFor other base operating system environments, we recommend the [Bento boxes](https://vagrantcloud.com/bento). The Bento boxes are [open source](https://github.com/chef/bento) and built for a number of providers including VMware, VirtualBox, and Parallels. There are a variety of operating systems and versions available.\n\nSpecial thanks to the Bento project for providing a solid base template for the `hashicorp/bionic64` box.\n\nIt is often a point of confusion but Canonical, the company that makes the Ubuntu operating system, publishes boxes under the \"ubuntu\" namespace on Vagrant Cloud. These boxes only support VirtualBox.\n\n## Create a box\n\nIf you are unable to find a box that meets your specific use case, you can create one. We recommend that you first create a [base box](/vagrant/docs/boxes/base) to have a clean slate to start from when you build future development environments.\n\nLearn more about [box formats](/vagrant/docs/boxes/format) to get started. \n\n"
  },
  {
    "path": "website/content/docs/boxes/info.mdx",
    "content": "---\nlayout: docs\npage_title: Box Info Format\ndescription: |-\n  A box can provide additional information to the user by supplying an info.json\n  file within the box.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Additional Box Information\n\nWhen creating a Vagrant box, you can supply additional information that might be\nrelevant to the user when running `vagrant box list -i`. For example, you could\npackage your box to include information about the author of the box and a\nwebsite for users to learn more:\n\n```\nbrian@localghost % vagrant box list -i\nhashicorp/bionic64     (virtualbox, 1.0.0)\n  - author: brian\n  - homepage: https://www.vagrantup.com\n```\n\n## Box Info\n\nTo accomplish this, you simply need to include a file named `info.json` when\ncreating a [base box](/vagrant/docs/boxes/base) which is a JSON document containing\nany and all relevant information that will be displayed to the user when the\n`-i` option is used with `vagrant box list`.\n\n```json\n{\n  \"author\": \"brian\",\n  \"homepage\": \"https://example.com\"\n}\n```\n\nThere are no special keys or values in `info.json`, and Vagrant will print each\nkey and value on its own line.\n\nThe [Box File Format](/vagrant/docs/boxes/format) provides more information about what\nelse goes into a Vagrant box.\n"
  },
  {
    "path": "website/content/docs/boxes/versioning.mdx",
    "content": "---\nlayout: docs\npage_title: Box Versioning\ndescription: |-\n  Since Vagrant 1.5, boxes support versioning. This allows the people who\n  make boxes to push updates to the box, and the people who use the box\n  have a simple workflow for checking for updates, updating their boxes,\n  and seeing what has changed.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Box Versioning\n\nSince Vagrant 1.5, boxes support versioning. This allows the people who\nmake boxes to push updates to the box, and the people who use the box\nhave a simple workflow for checking for updates, updating their boxes,\nand seeing what has changed.\n\nIf you are just getting started with Vagrant, box versioning is not too\nimportant, and we recommend learning about some other topics first. But\nif you are using Vagrant on a team or plan on creating your own boxes,\nversioning is very important. Luckily, having versioning built right in\nto Vagrant makes it easy to use and fit nicely into the Vagrant workflow.\n\nThis page will cover how to use versioned boxes. It does _not_ cover how\nto update your own custom boxes with versions. That is covered in\n[creating a base box](/vagrant/docs/boxes/base).\n\n## Viewing Versions and Updating\n\n`vagrant box list` only shows _installed_ versions of boxes. If you want\nto see all available versions of a box, you will have to find the box\non [HashiCorp's Vagrant Cloud](/vagrant/vagrant-cloud). An easy way to find a box\nis to use the url `https://vagrantcloud.com/$USER/$BOX`. For example, for\nthe `hashicorp/bionic64` box, you can find information about it at\n`https://vagrantcloud.com/hashicorp/bionic64`.\n\nYou can check if the box you are using is outdated with `vagrant box outdated`.\nThis can check if the box in your current Vagrant environment is outdated\nas well as any other box installed on the system.\n\nFinally, you can update boxes with `vagrant box update`. This will download\nand install the new box. This _will not_ magically update running Vagrant\nenvironments. If a Vagrant environment is already running, you will have to\ndestroy and recreate it to acquire the new updates in the box. The update\ncommand just downloads these updates locally.\n\n## Version Constraints\n\nYou can constrain a Vagrant environment to a specific version or versions\nof a box using the [Vagrantfile](/vagrant/docs/vagrantfile/) by specifying\nthe `config.vm.box_version` option.\n\nIf this option is not specified, the latest version is always used. This is\nequivalent to specifying a constraint of \">= 0\".\n\nThe box version configuration can be a specific version or a constraint of\nversions. Constraints can be any combination of the following:\n`= X`, `> X`, `< X`, `>= X`, `<= X`, `~> X`. You can combine multiple\nconstraints by separating them with commas. All the constraints should be\nself explanatory except perhaps for `~>`, known as the \"pessimistic constraint\".\nExamples explain it best: `~> 1.0` is equivalent to `>= 1.0, < 2.0`. And\n`~> 1.1.5` is equivalent to `>= 1.1.5, < 1.2.0`.\n\nYou can choose to handle versions however you see fit. However, many boxes\nin the public catalog follow [semantic versioning](http://semver.org/).\nBasically, only the first number (the \"major version\") breaks backwards\ncompatibility. In terms of Vagrant boxes, this means that any software that\nruns in version \"1.1.5\" of a box should work in \"1.2\" and \"1.4.5\" and so on,\nbut \"2.0\" might introduce big changes that break your software. By following\nthis convention, the best constraint is `~> 1.0` because you know it is safe\nno matter what version is in that range.\n\nPlease note that, while the semantic versioning specification allows for\nmore than three points and pre-release or beta versions, Vagrant boxes must be\nof the format `X.Y.Z` where `X`, `Y`, and `Z` are all positive integers.\n\n## Automatic Update Checking\n\nUsing the [Vagrantfile](/vagrant/docs/vagrantfile/), you can also configure\nVagrant to automatically check for updates during any `vagrant up`. This is\nenabled by default, but can easily be disabled with\n`config.vm.box_check_update = false` in your Vagrantfile.\n\nWhen this is enabled, Vagrant will check for updates on every `vagrant up`,\nnot just when the machine is being created from scratch, but also when it\nis resuming, starting after being halted, etc.\n\nIf an update is found, Vagrant will output a warning to the user letting\nthem know an update is available. That user can choose to ignore the warning\nfor now, or can update the box by running `vagrant box update`.\n\nVagrant can not and does not automatically download the updated box and\nupdate the machine because boxes can be relatively large and updating the\nmachine requires destroying it and recreating it, which can cause important\ndata to be lost. Therefore, this process is manual to the extent that the\nuser has to manually enter a command to do it.\n\n## Pruning Old Versions\n\nVagrant does not automatically prune old versions because it does not know\nif they might be in use by other Vagrant environments. Because boxes can\nbe large, you may want to actively prune them once in a while using\n`vagrant box remove`. You can see all the boxes that are installed\nusing `vagrant box list`.\n\nAnother option is to use `vagrant box prune` command to remove all installed boxes that are outdated and not currently in use.\n"
  },
  {
    "path": "website/content/docs/cli/aliases.mdx",
    "content": "---\nlayout: docs\npage_title: Aliases - Command-Line Interface\ndescription: |-\n  Custom Vagrant commands can be defined using aliases, allowing for a simpler,\n  easier, and more familiar command line interface.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Aliases\n\nInspired in part by Git's own\n[alias functionality](https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases),\naliases make your Vagrant experience simpler, easier, and more familiar by\nallowing you to create your own custom Vagrant commands.\n\nAliases can be defined within `VAGRANT_HOME/aliases` file, or in a custom file\ndefined using the `VAGRANT_ALIAS_FILE` environment variable, in the following\nformat:\n\n```shell\n# basic command-level aliases\nstart = up\nstop = halt\n\n# advanced command-line aliases\neradicate = !vagrant destroy && rm -rf .vagrant\n```\n\nIn a nutshell, aliases are defined using a standard `key = value` format, where\nthe `key` is the new Vagrant command, and the `value` is the aliased command.\nUsing this format, there are two types of aliases that can be defined: internal\nand external aliases.\n\n## Internal Aliases\n\nInternal command aliases call the CLI class directly, allowing you to alias\none Vagrant command to another Vagrant command. This technique can be very\nuseful for creating commands that you think _should_ exist. For example,\nif `vagrant stop` feels more intuitive than `vagrant halt`, the following alias\ndefinitions would make that change possible:\n\n```shell\nstop = halt\n```\n\nThis makes the following commands equivalent:\n\n```shell\nvagrant stop\nvagrant halt\n```\n\n## External Aliases\n\nWhile internal aliases can be used to define more intuitive Vagrant commands,\nexternal command aliases are used to define Vagrant commands with brand new\nfunctionality. These aliases are prefixed with the `!` character, which\nindicates to the interpreter that the alias should be executed as a shell\ncommand. For example, let's say that you want to be able to view the processor\nand memory utilization of the active project's virtual machine. To do this, you\ncould define a `vagrant metrics` command that returns the required information\nin an easy-to-read format, like so:\n\n```shell\nmetrics = !ps aux | grep \"[V]BoxHeadless\" | grep $(cat .vagrant/machines/default/virtualbox/id) | awk '{ printf(\"CPU: %.02f%%, Memory: %.02f%%\", $3, $4) }'\n```\n\nThe above alias, from within the context of an active Vagrant project, would\nprint the CPU and memory utilization directly to the console:\n\n```text\nCPU: 4.20%, Memory: 11.00%\n```\n"
  },
  {
    "path": "website/content/docs/cli/box.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant box - Command-Line Interface\ndescription: |-\n  The \"vagrant box\" command is used to manage \"vagrant box add\", \"vagrant box\n  remove\", and other box-related commands such as \"outdated\", \"list\", and\n  \"update\".\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Box\n\n**Command: `vagrant box`**\n\nThis is the command used to manage (add, remove, etc.) [boxes](/vagrant/docs/boxes).\n\nThe main functionality of this command is exposed via even more subcommands:\n\n- [`add`](#box-add)\n- [`list`](#box-list)\n- [`outdated`](#box-outdated)\n- [`prune`](#box-prune)\n- [`remove`](#box-remove)\n- [`repackage`](#box-repackage)\n- [`update`](#box-update)\n\n# Box Add\n\n**Command: `vagrant box add ADDRESS`**\n\nThis adds a box with the given address to Vagrant. The address can be\none of three things:\n\n- A shorthand name from the\n  [public catalog of available Vagrant images](https://vagrantcloud.com/boxes/search),\n  such as \"hashicorp/bionic64\".\n\n- File path or HTTP URL to a box in a [catalog](https://vagrantcloud.com/boxes/search).\n  For HTTP, basic authentication is supported and `http_proxy` environmental\n  variables are respected. HTTPS is also supported.\n\n- URL directly a box file. In this case, you must specify a `--name` flag\n  (see below) and versioning/updates will not work.\n\nIf an error occurs during the download or the download is interrupted with\na Ctrl-C, then Vagrant will attempt to resume the download the next time it\nis requested. Vagrant will only attempt to resume a download for 24 hours\nafter the initial download.\n\n## Options\n\n- `--box-version VALUE` - The version of the box you want to add. By default,\n  the latest version will be added. The value of this can be an exact version\n  number such as \"1.2.3\" or it can be a set of version constraints. A version\n  constraint looks like \">= 1.0, < 2.0\".\n\n- `--cacert CERTFILE` - The certificate for the CA used to verify the peer.\n  This should be used if the remote end does not use a standard root CA.\n\n- `--capath CERTDIR` - The certificate directory for the CA used to verify the peer.\n  This should be used if the remote end does not use a standard root CA.\n\n- `--cert CERTFILE` - A client certificate to use when downloading the box, if\n  necessary.\n\n- `--clean` - If given, Vagrant will remove any old temporary files from\n  prior downloads of the same URL. This is useful if you do not want Vagrant\n  to resume a download from a previous point, perhaps because the contents\n  changed.\n\n- `--force` - When present, the box will be downloaded and overwrite any\n  existing box with this name.\n\n- `--insecure` - When present, SSL certificates will not be verified if the\n  URL is an HTTPS URL.\n\n- `--provider PROVIDER` - If given, Vagrant will verify the box you are\n  adding is for the given provider. By default, Vagrant automatically\n  detects the proper provider to use.\n\n## Options for direct box files\n\nThe options below only apply if you are adding a box file directly (when\nyou are not using a catalog).\n\n- `--checksum VALUE` - A checksum for the box that is downloaded. If specified,\n  Vagrant will compare this checksum to what is actually downloaded and will\n  error if the checksums do not match. This is highly recommended since\n  box files are so large. If this is specified, `--checksum-type` must\n  also be specified. If you are downloading from a catalog, the checksum is\n  included within the catalog entry.\n\n- `--checksum-type TYPE` - The type of checksum that `--checksum` is if it\n  is specified. Supported values are currently \"md5\", \"sha1\", \"sha256\",\n  \"sha384\", and \"sha512\".\n\n- `--name VALUE` - Logical name for the box. This is the value that you\n  would put into `config.vm.box` in your Vagrantfile. When adding a box from\n  a catalog, the name is included in the catalog entry and does not have\n  to be specified.\n\n~> **Checksums for versioned boxes or boxes from HashiCorp's Vagrant Cloud:**\nFor boxes from HashiCorp's Vagrant Cloud, the checksums are embedded in the metadata\nof the box. The metadata itself is served over TLS and its format is validated.\n\n# Box List\n\n**Command: `vagrant box list`**\n\nThis command lists all the boxes that are installed into Vagrant.\n\n# Box Outdated\n\n**Command: `vagrant box outdated`**\n\nThis command tells you whether or not the box you are using in\nyour current Vagrant environment is outdated. If the `--global` flag\nis present, every installed box will be checked for updates.\n\nThis will show the latest version available for the specific provider type,\nwhich may be different than the absolute latest version available.\n\nChecking for updates involves refreshing the metadata associated with\na box. This generally requires an internet connection.\n\nBy default, if Vagrant has recently checked for a box that's out of date, it will\ncache that answer and not look up another update for one hour. This cached value\ncan be ignored if the `--force` flag is used.\n\n## Options\n\n- `--force` - Check for updates for all installed boxes and ignore cache interval.\n- `--global` - Check for updates for all installed boxes, not just the\n  boxes for the current Vagrant environment.\n\n# Box Prune\n\n**Command: `vagrant box prune`**\n\nThis command removes old versions of installed boxes. If the box is currently in use vagrant will ask for confirmation.\n\n## Options\n\n- `--provider PROVIDER` - The specific provider type for the boxes to destroy.\n\n- `--dry-run` - Only print the boxes that would be removed.\n\n- `--name NAME` - The specific box name to check for outdated versions.\n\n- `--force` - Destroy without confirmation even when box is in use.\n\n- `--keep-active-boxes` - When combined with `--force`, will keep boxes still actively in use.\n\n# Box Remove\n\n**Command: `vagrant box remove NAME`**\n\nThis command removes a box from Vagrant that matches the given name.\n\nIf a box has multiple providers, the exact provider must be specified\nwith the `--provider` flag. If a box has multiple versions, you can select\nwhat versions to delete with the `--box-version` flag or remove all versions\nwith the `--all` flag.\n\n## Options\n\n- `--box-version VALUE` - Version of version constraints of the boxes to\n  remove. See documentation on this flag for `box add` for more details.\n\n- `--all` - Remove all available versions of a box.\n\n- `--force` - Forces removing the box even if an active Vagrant\n  environment is using it.\n\n- `--provider VALUE` - The provider-specific box to remove with the given\n  name. This is only required if a box is backed by multiple providers.\n  If there is only a single provider, Vagrant will default to removing it.\n\n# Box Repackage\n\n**Command: `vagrant box repackage NAME PROVIDER VERSION`**\n\nThis command repackages the given box and puts it in the current\ndirectory so you can redistribute it. The name, provider, and version\nof the box can be retrieved using `vagrant box list`.\n\nWhen you add a box, Vagrant unpacks it and stores it internally. The\noriginal `*.box` file is not preserved. This command is useful for\nreclaiming a `*.box` file from an installed Vagrant box.\n\n# Box Update\n\n**Command: `vagrant box update`**\n\nThis command updates the box for the current Vagrant environment if there\nare updates available. The command can also update a specific box (outside\nof an active Vagrant environment), by specifying the `--box` flag.\n\n-> Note that updating the box will not update an already-running Vagrant\nmachine. To reflect the changes in the box, you will have to destroy and\nbring back up the Vagrant machine.\n\nIf you just want to check if there are updates available, use the\n`vagrant box outdated` command.\n\n## Options\n\n- `--box VALUE` - Name of a specific box to update. If this flag is not\n  specified, Vagrant will update the boxes for the active Vagrant\n  environment.\n\n- `--provider VALUE` - When `--box` is present, this controls what\n  provider-specific box to update. This is not required unless the box has\n  multiple providers. Without the `--box` flag, this has no effect.\n"
  },
  {
    "path": "website/content/docs/cli/cloud.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant cloud - Command-Line Interface\ndescription: |-\n  The \"vagrant cloud\" command can be used for taking actions against\n  Vagrant Cloud like searching or uploading a Vagrant Box\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Cloud\n\n**Command: `vagrant cloud`**\n\nThis is the command used to manage anything related to [Vagrant Cloud](https://vagrantcloud.com).\n\nThe main functionality of this command is exposed via subcommands:\n\n- [`auth`](#cloud-auth)\n- [`box`](#cloud-box)\n- [`provider`](#cloud-provider)\n- [`publish`](#cloud-publish)\n- [`search`](#cloud-search)\n- [`version`](#cloud-version)\n\n# Cloud Auth\n\n**Command: `vagrant cloud auth`**\n\nThe `cloud auth` command is for handling all things related to authorization with\nVagrant Cloud.\n\n- [`login`](#cloud-auth-login)\n- [`logout`](#cloud-auth-logout)\n- [`whoami`](#cloud-auth-whoami)\n\n## Cloud Auth Login\n\n**Command: `vagrant cloud auth login`**\n\nThe login command is used to authenticate with [HashiCorp's Vagrant Cloud](/vagrant/vagrant-cloud)\nserver. Logging in is only necessary if you are accessing protected boxes.\n\n**Logging in is not a requirement to use Vagrant.** The vast majority\nof Vagrant does _not_ require a login. Only certain features such as protected\nboxes.\n\nThe reference of available command-line flags to this command\nis available below.\n\n### Options\n\n- `--check` - This will check if you are logged in. In addition to outputting\n  whether you are logged in or not, the command exit status will be 0 if you are\n  logged in, or 1 if you are not.\n\n- `--logout` - This will log you out if you are logged in. If you are already\n  logged out, this command will do nothing. It is not an error to call this\n  command if you are already logged out.\n\n- `--token` - This will set the Vagrant Cloud login token manually to the provided\n  string. It is assumed this token is a valid Vagrant Cloud access token.\n\n### Examples\n\nSecurely authenticate to Vagrant Cloud using a username and password:\n\n```shell-session\n$ vagrant cloud auth login\n# ...\nVagrant Cloud username:\nVagrant Cloud password:\n```\n\nCheck if the current user is authenticated:\n\n```shell-session\n$ vagrant cloud auth login --check\nYou are already logged in.\n```\n\nSecurely authenticate with Vagrant Cloud using a token:\n\n```shell-session\n$ vagrant cloud auth login --token ABCD1234\nThe token was successfully saved.\n```\n\n## Cloud Auth Logout\n\n**Command: `vagrant cloud auth logout`**\n\nThis will log you out if you are logged in. If you are already\nlogged out, this command will do nothing. It is not an error to call this\ncommand if you are already logged out.\n\n## Cloud Auth Whoami\n\n**Command: `vagrant cloud auth whoami [TOKEN]`**\n\nThis command will validate your Vagrant Cloud token and will print the user who\nit belongs to. If a token is passed in, it will attempt to validate it instead\nof the token stored stored on disk.\n\n# Cloud Box\n\n**Command: `vagrant cloud box`**\n\nThe `cloud box` command is used to manage life cycle operations for all `box`\nentities on Vagrant Cloud.\n\n- [`create`](#cloud-box-create)\n- [`delete`](#cloud-box-delete)\n- [`show`](#cloud-box-show)\n- [`update`](#cloud-box-update)\n\n## Cloud Box Create\n\n**Command: `vagrant cloud box create ORGANIZATION/BOX-NAME`**\n\nThe box create command is used to create a new box entry on Vagrant Cloud.\n\n### Options\n\n- `--description DESCRIPTION` - A full description of the box. Can be\n  formatted with Markdown.\n- `--short-description DESCRIPTION` - A short summary of the box.\n- `--private` - Will make the new box private (Public by default)\n\n## Cloud Box Delete\n\n**Command: `vagrant cloud box delete ORGANIZATION/BOX-NAME`**\n\nThe box delete command will _permanently_ delete the given box entry on Vagrant Cloud. Before\nmaking the request, it will ask if you are sure you want to delete the box.\n\n## Cloud Box Show\n\n**Command: `vagrant cloud box show ORGANIZATION/BOX-NAME`**\n\nThe box show command will display information about the latest version for the given Vagrant box.\n\n## Cloud Box Update\n\n**Command: `vagrant cloud box update ORGANIZATION/BOX-NAME`**\n\nThe box update command will update an already created box on Vagrant Cloud with the given options.\n\n### Options\n\n- `--description DESCRIPTION` - A full description of the box. Can be\n  formatted with Markdown.\n- `--short-description DESCRIPTION` - A short summary of the box.\n- `--private` - Will make the new box private (Public by default)\n\n# Cloud Provider\n\n**Command: `vagrant cloud provider`**\n\nThe `cloud provider` command is used to manage the life cycle operations for all\n`provider` entities on Vagrant Cloud.\n\n- [`create`](#cloud-provider-create)\n- [`delete`](#cloud-provider-delete)\n- [`update`](#cloud-provider-update)\n- [`upload`](#cloud-provider-upload)\n\n## Cloud Provider Create\n\n**Command: `vagrant cloud provider create ORGANIZATION/BOX-NAME PROVIDER-NAME VERSION [URL]`**\n\nThe provider create command is used to create a new provider entry on Vagrant Cloud.\nThe `url` argument is expected to be a remote URL that Vagrant Cloud can use\nto download the provider. If no `url` is specified, the provider entry can be updated\nlater with a url or the [upload](#cloud-provider-upload) command can be used to\nupload a Vagrant [box file](/vagrant/docs/boxes).\n\n## Cloud Provider Delete\n\n**Command: `vagrant cloud provider delete ORGANIZATION/BOX-NAME PROVIDER-NAME VERSION`**\n\nThe provider delete command is used to delete a provider entry on Vagrant Cloud.\nBefore making the request, it will ask if you are sure you want to delete the provider.\n\n## Cloud Provider Update\n\n**Command: `vagrant cloud provider update ORGANIZATION/BOX-NAME PROVIDER-NAME VERSION [URL]`**\n\nThe provider update command will update an already created provider for a box on\nVagrant Cloud with the given options.\n\n## Cloud Provider Upload\n\n**Command: `vagrant cloud provider upload ORGANIZATION/BOX-NAME PROVIDER-NAME VERSION BOX-FILE`**\n\nThe provider upload command will upload a Vagrant [box file](/vagrant/docs/boxes) to Vagrant Cloud for\nthe specified version and provider.\n\n# Cloud Publish\n\n**Command: `vagrant cloud publish ORGANIZATION/BOX-NAME VERSION PROVIDER-NAME [PROVIDER-FILE]`**\n\nThe publish command is a complete solution for creating and updating a\nVagrant box on Vagrant Cloud. Instead of having to create each attribute of a Vagrant\nbox with separate commands, the publish command instead asks you to provide all\nthe information required before creating or updating a new box.\n\n## Options\n\n- `--box-version VERSION` - Version to create for the box\n- `--description DESCRIPTION` - A full description of the box. Can be\n  formatted with Markdown.\n- `--force` - Disables confirmation when creating or updating a box.\n- `--short-description DESCRIPTION` - A short summary of the box.\n- `--private` - Will make the new box private (Public by default)\n- `--release` - Automatically releases the box after creation (Unreleased by default)\n- `--url` - Valid remote URL to download the box file\n- `--version-description DESCRIPTION` - Description of the version that will be created.\n\n## Examples\n\nCreating a new box on Vagrant Cloud:\n\n```shell-session\n$ vagrant cloud publish briancain/supertest 1.0.0 virtualbox boxes/my/virtualbox.box -d \"A really cool box to download and use\" --version-description \"A cool version\" --release --short-description \"Download me!\"\nYou are about to create a box on Vagrant Cloud with the following options:\nbriancain/supertest (1.0.0) for virtualbox\nAutomatic Release:     true\nBox Description:       A really cool box to download and use\nBox Short Description: Download me!\nVersion Description:   A cool version\nDo you wish to continue? [y/N] y\nCreating a box entry...\nCreating a version entry...\nCreating a provider entry...\nUploading provider with file /Users/vagrant/boxes/my/virtualbox.box\nReleasing box...\nComplete! Published briancain/supertest\ntag:                  briancain/supertest\nusername:             briancain\nname:                 supertest\nprivate:              false\ndownloads:            0\ncreated_at:           2018-07-25T17:53:04.340Z\nupdated_at:           2018-07-25T18:01:10.665Z\nshort_description:    Download me!\ndescription_markdown: A really cool box to download and use\ncurrent_version:      1.0.0\nproviders:            virtualbox\n```\n\n# Cloud Search\n\n**Command: `vagrant cloud search QUERY`**\n\nThe cloud search command will take a query and search Vagrant Cloud for any matching\nVagrant boxes. Various filters can be applied to the results.\n\n## Options\n\n- `--json` - Format search results in JSON.\n- `--page PAGE` - The page to display. Defaults to the first page of results.\n- `--short` - Shows a simple list of box names for the results.\n- `--order ORDER` - Order to display results. Can either be `desc` or `asc`.\n  Defaults to `desc`.\n- `--limit LIMIT` - Max number of search results to display. Defaults to 25.\n- `--provider PROVIDER` - Filter search results to a single provider.\n- `--sort-by SORT` - The field to sort results on. Can be `created`, `downloads`\n  , or `updated`. Defaults to `downloads`.\n\n## Examples\n\nIf you are looking for a HashiCorp box:\n\n```text\nvagrant cloud search hashicorp --limit 5\n| NAME                    | VERSION | DOWNLOADS | PROVIDERS                       |\n+-------------------------+---------+-----------+---------------------------------+\n| hashicorp/precise64     | 1.1.0   | 6,675,725 | virtualbox,vmware_fusion,hyperv |\n| hashicorp/precise32     | 1.0.0   | 2,261,377 | virtualbox                      |\n| hashicorp/boot2docker   | 1.7.8   |    59,284 | vmware_desktop,virtualbox       |\n| hashicorp/connect-vm    | 0.1.0   |     6,912 | vmware_desktop,virtualbox       |\n| hashicorp/vagrant-share | 0.1.0   |     3,488 | vmware_desktop,virtualbox       |\n+-------------------------+---------+-----------+---------------------------------+\n```\n\n# Cloud Version\n\n**Command: `vagrant cloud version`**\n\nThe `cloud version` command is used to manage life cycle operations for all `version`\nentities for a box on Vagrant Cloud.\n\n- [`create`](#cloud-version-create)\n- [`delete`](#cloud-version-delete)\n- [`release`](#cloud-version-release)\n- [`revoke`](#cloud-version-revoke)\n- [`update`](#cloud-version-update)\n\n## Cloud Version Create\n\n**Command: `vagrant cloud version create ORGANIZATION/BOX-NAME VERSION`**\n\nThe cloud create command creates a version entry for a box on Vagrant Cloud.\n\n### Options\n\n- `--description DESCRIPTION` - Description of the version that will be created.\n\n## Cloud Version Delete\n\n**Command: `vagrant cloud version delete ORGANIZATION/BOX-NAME VERSION`**\n\nThe cloud delete command deletes a version entry for a box on Vagrant Cloud.\nBefore making the request, it will ask if you are sure you want to delete the version.\n\n## Cloud Version Release\n\n**Command: `vagrant cloud version release ORGANIZATION/BOX-NAME VERSION`**\n\nThe cloud release command releases a version entry for a box on Vagrant Cloud\nif it already exists. Before making the request, it will ask if you are sure you\nwant to release the version.\n\n## Cloud Version Revoke\n\n**Command: `vagrant cloud version revoke ORGANIZATION/BOX-NAME VERSION`**\n\nThe cloud revoke command revokes a version entry for a box on Vagrant Cloud\nif it already exists. Before making the request, it will ask if you are sure you\nwant to revoke the version.\n\n## Cloud Version Update\n\n**Command: `vagrant cloud version update ORGANIZATION/BOX-NAME VERSION`**\n\n### Options\n\n- `--description DESCRIPTION` - Description of the version that will be created.\n"
  },
  {
    "path": "website/content/docs/cli/connect.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant connect - Command-Line Interface\ndescription: |-\n  The \"vagrant connect\" command compliments the \"vagrant share\" command to allow\n  a user to remotely connect to your Vagrant environment.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Connect\n\n**Command: `vagrant connect NAME`**\n\nThe connect command complements the\n[share command](/vagrant/docs/cli/share) by enabling access to shared\nenvironments. You can learn about all the details of Vagrant Share in the\n[Vagrant Share section](/vagrant/docs/share/).\n\nThe reference of available command-line flags to this command\nis available below.\n\n## Options\n\n- `--disable-static-ip` - The connect command will not spin up a small\n  virtual machine to create a static IP you can access. When this flag is\n  set, the only way to access the connection is to use the SOCKS proxy\n  address outputted.\n\n- `--static-ip IP` - Tells connect what static IP address to use for the virtual\n  machine. By default, Vagrant connect will use an IP address that looks\n  available in the 172.16.0.0/16 space.\n\n- `--ssh` - Connects via SSH to an environment shared with\n  `vagrant share --ssh`.\n"
  },
  {
    "path": "website/content/docs/cli/destroy.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant destroy - Command-Line Interface\ndescription: |-\n  The \"vagrant destroy\" command is used to stop the running virtual machine and\n  terminate use of all resources that were in use by that machine.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Destroy\n\n**Command: `vagrant destroy [name|id]`**\n\nThis command stops the running machine Vagrant is managing and\ndestroys all resources that were created during the machine creation process.\nAfter running this command, your computer should be left at a clean state,\nas if you never created the guest machine in the first place.\n\nFor Linux-based guests, Vagrant uses the `shutdown` command to gracefully\nterminate the machine. Due to the varying nature of operating systems, the\n`shutdown` command may exist at many different locations in the guest's `$PATH`.\nIt is the guest machine's responsibility to properly populate the `$PATH` with\ndirectory containing the `shutdown` command.\n\n## Options\n\n- `-f` or `--force` - Do not ask for confirmation before destroying.\n- `--[no-]parallel` - Destroys multiple machines in parallel if the provider\n  supports it. Please consult the provider documentation to see if this feature\n  is supported.\n- `-g` or `--graceful` - Shuts down the machine gracefully.\n\n-> The `destroy` command does not remove a box that may have been installed on\nyour computer during `vagrant up`. Thus, even if you run `vagrant destroy`,\nthe box installed in the system will still be present on the hard drive. To\nreturn your computer to the state as it was before `vagrant up` command, you\nneed to use `vagrant box remove`.\n\nFor more information, read about the [`vagrant box remove`](/vagrant/docs/cli/box) command.\n"
  },
  {
    "path": "website/content/docs/cli/global-status.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant global-status - Command-Line Interface\ndescription: |-\n  The \"vagrant global-status\" command is used to determine the state of all\n  active Vagrant environments on the system for the currently logged in user.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Global Status\n\n**Command: `vagrant global-status`**\n\nThis command will tell you the state of all active Vagrant environments\non the system for the currently logged in user.\n\n~> **This command does not actively verify the state of machines**,\nand is instead based on a cache. Because of this, it is possible to see\nstale results (machines say they're running but they're not). For example,\nif you restart your computer, Vagrant would not know. To prune the invalid\nentries, run global status with the `--prune` flag.\n\nThe IDs in the output that look like `a1b2c3` can be used to control\nthe Vagrant machine from anywhere on the system. Any Vagrant command\nthat takes a target machine (such as `up`, `halt`, `destroy`) can be\nused with this ID to control it. For example: `vagrant destroy a1b2c3`.\n\n## Options\n\n- `--prune` - Prunes invalid entries from the list. This is much more time\n  consuming than simply listing the entries.\n\n## Environment Not Showing Up\n\nIf your environment is not showing up, you may have to do a `vagrant destroy`\nfollowed by a `vagrant up`.\n\nIf you just upgraded from a previous version of Vagrant, existing environments\nwill not show up in global-status until they are destroyed and recreated.\n"
  },
  {
    "path": "website/content/docs/cli/halt.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant halt - Command-Line Interface\ndescription: |-\n  The \"vagrant halt\" command is used to shut down the virtual machine that\n  Vagrant is currently managing.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Halt\n\n**Command: `vagrant halt [name|id]`**\n\nThis command shuts down the running machine Vagrant is managing.\n\nVagrant will first attempt to gracefully shut down the machine by running\nthe guest OS shutdown mechanism. If this fails, or if the `--force` flag is\nspecified, Vagrant will effectively just shut off power to the machine.\n\nFor Linux-based guests, Vagrant will:\n\n1. Attempt to detect and use Systemd to execute `systemctl poweroff`; but otherwise,\n2. Fallback to using the `shutdown` command to gracefully\nterminate the machine.\n\nDue to the varying nature of operating systems, these executables may exist at many\ndifferent locations in the guest's `$PATH`.\nIt is the guest machine's responsibility to properly populate the `$PATH` with\ndirectory containing the `shutdown` command.\n\n## Options\n\n- `-f` or `--force` - Do not attempt to gracefully shut down the machine.\n  This effectively pulls the power on the guest machine.\n"
  },
  {
    "path": "website/content/docs/cli/index.mdx",
    "content": "---\nlayout: docs\npage_title: Command-Line Interface\ndescription: Almost all interaction with Vagrant is done via the command-line interface.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Command-Line Interface\n\nAlmost all interaction with Vagrant is done through the command-line\ninterface.\n\nThe interface is available using the `vagrant` command, and comes installed\nwith Vagrant automatically. The `vagrant` command in turn has many subcommands,\nsuch as `vagrant up`, `vagrant destroy`, etc.\n\nIf you run `vagrant` by itself, help will be displayed showing all available\nsubcommands. In addition to this, you can run any Vagrant command with the\n`-h` flag to output help about that specific command. For example, try\nrunning `vagrant init -h`. The help will output a one sentence synopsis of\nwhat the command does as well as a list of all the flags the command\naccepts.\n\nIn depth documentation and use cases of various Vagrant commands is\navailable by reading the appropriate sub-section available in the left\nnavigational area of this site.\n\nYou may also wish to consult the\n[documentation](/vagrant/docs/other/environmental-variables) regarding the\nenvironmental variables that can be used to configure and control\nVagrant in a global way.\n\n## Autocompletion\n\nVagrant provides the ability to autocomplete commands. Currently, the\n`bash` and `zsh` shells are supported. These can be enabled by running\n`vagrant autocomplete install --bash --zsh`.\n"
  },
  {
    "path": "website/content/docs/cli/init.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant init - Command-Line Interface\ndescription: |-\n  The \"vagrant init\" command is used to initialize the current directory to be\n  a Vagrant environment by creating an initial Vagrantfile.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Init\n\n**Command: `vagrant init [name [url]]`**\n\nThis initializes the current directory to be a Vagrant environment\nby creating an initial [Vagrantfile](/vagrant/docs/vagrantfile/) if\none does not already exist.\n\nIf a first argument is given, it will prepopulate the `config.vm.box`\nsetting in the created Vagrantfile.\n\nIf a second argument is given, it will prepopulate the `config.vm.box_url`\nsetting in the created Vagrantfile.\n\n## Options\n\n- `--box-version` - (Optional) The box version or box version constraint to add\n  to the `Vagrantfile`.\n\n- `--force` - If specified, this command will overwrite any existing\n  `Vagrantfile`.\n\n- `--minimal` - If specified, a minimal Vagrantfile will be created. This\n  Vagrantfile does not contain the instructional comments that the normal\n  Vagrantfile contains.\n\n- `--output FILE` - This will output the Vagrantfile to the given file.\n  If this is \"-\", the Vagrantfile will be sent to stdout.\n\n- `--template FILE` - Provide a custom ERB template for generating the Vagrantfile.\n\n## Examples\n\nCreate a base Vagrantfile:\n\n```shell-session\n$ vagrant init hashicorp/bionic64\n```\n\nCreate a minimal Vagrantfile (no comments or helpers):\n\n```shell-session\n$ vagrant init -m hashicorp/bionic64\n```\n\nCreate a new Vagrantfile, overwriting the one at the current path:\n\n```shell-session\n$ vagrant init -f hashicorp/bionic64\n```\n\nCreate a Vagrantfile with the specific box, from the specific box URL:\n\n```shell-session\n$ vagrant init my-company-box https://example.com/my-company.box\n```\n\nCreate a Vagrantfile, locking the box to a version constraint:\n\n```shell-session\n$ vagrant init --box-version '> 0.1.5' hashicorp/bionic64\n```\n"
  },
  {
    "path": "website/content/docs/cli/login.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant login - Command-Line Interface\ndescription: |-\n  The \"vagrant login\" command is used to authenticate Vagrant with HashiCorp's\n  Vagrant Cloud service to use features like private boxes and \"vagrant push\".\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Login\n\n**Command: `vagrant login`**\n\nThe login command is used to authenticate with the\n[HashiCorp's Vagrant Cloud](/vagrant/vagrant-cloud) server. Logging in is only\nnecessary if you are accessing protected boxes or using\n[Vagrant Share](/vagrant/docs/share/).\n\n**Logging in is not a requirement to use Vagrant.** The vast majority\nof Vagrant does _not_ require a login. Only certain features such as protected\nboxes or [Vagrant Share](/vagrant/docs/share/) require a login.\n\nThe reference of available command-line flags to this command\nis available below.\n\n## Options\n\n- `--check` - This will check if you are logged in. In addition to outputting\n  whether you are logged in or not, the command will have exit status 0 if you are\n  logged in, and exit status 1 if you are not.\n\n- `--logout` - This will log you out if you are logged in. If you are already\n  logged out, this command will do nothing. It is not an error to call this\n  command if you are already logged out.\n\n- `--token` - This will set the Vagrant Cloud login token manually to the provided\n  string. It is assumed this token is a valid Vagrant Cloud access token.\n\n## Examples\n\nSecurely authenticate to Vagrant Cloud using a username and password:\n\n```shell-session\n$ vagrant login\n# ...\nVagrant Cloud username:\nVagrant Cloud password:\n```\n\nCheck if the current user is authenticated:\n\n```shell-session\n$ vagrant login --check\nYou are already logged in.\n```\n\nSecurely authenticate with Vagrant Cloud using a token:\n\n```shell-session\n$ vagrant login --token ABCD1234\nThe token was successfully saved.\n```\n"
  },
  {
    "path": "website/content/docs/cli/machine-readable.mdx",
    "content": "---\nlayout: docs\npage_title: Machine Readable Output - Command-Line Interface\ndescription: |-\n  Almost all commands in Vagrant accept a --machine-readable flag to enable\n  machine-readable output mode.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Machine Readable Output\n\nEvery Vagrant command accepts a `--machine-readable` flag which enables\nmachine readable output mode. In this mode, the output to the terminal\nis replaced with machine-friendly output.\n\nThis mode makes it easy to programmatically execute Vagrant and read data\nout of it. This output format is protected by our\n[backwards compatibility](/vagrant/docs/installation/backwards-compatibility)\npolicy. Until Vagrant 2.0 is released, however, the machine readable output\nmay change as we determine more use cases for it. But the backwards\ncompatibility promise should make it safe to write client libraries to\nparse the output format.\n\n~> **Advanced topic!** This is an advanced topic for use only if\nyou want to programmatically execute Vagrant. If you are just getting started\nwith Vagrant, you may safely skip this section.\n\n## Work-In-Progress\n\nThe machine-readable output is very new (released as part of Vagrant 1.4).\nWe're still gathering use cases for it and building up the output for each\nof the commands. It is likely that what you may want to achieve with\nthe machine-readable output is not possible due to missing information.\n\nIn this case, we ask that you please\n[open an issue](https://github.com/hashicorp/vagrant/issues)\nrequesting that certain information become available. We will most likely add\nit!\n\n## Format\n\nThe machine readable format is a line-oriented, comma-delimited text format.\nThis makes it extremely easy to parse using standard Unix tools such as awk or\ngrep in addition to full programming languages like Ruby or Python.\n\nThe format is:\n\n```\ntimestamp,target,type,data...\n```\n\nEach component is explained below:\n\n- **timestamp** is a Unix timestamp in UTC of when the message was printed.\n\n- **target** is the target of the following output. This is empty if the\n  message is related to Vagrant globally. Otherwise, this is generally a machine\n  name so you can relate output to a specific machine when multi-VM is in use.\n\n- **type** is the type of machine-readable message being outputted. There are\n  a set of standard types which are covered later.\n\n- **data** is zero or more comma-separated values associated with the prior\n  type. The exact amount and meaning of this data is type-dependent, so you\n  must read the documentation associated with the type to understand fully.\n\nWithin the format, if data contains a comma, it is replaced with\n`%!(VAGRANT_COMMA)`. This was preferred over an escape character such as \\'\nbecause it is more friendly to tools like awk.\n\nNewlines within the format are replaced with their respective standard escape\nsequence. Newlines become a literal `\\n` within the output. Carriage returns\nbecome a literal `\\r`.\n\n## Types\n\nThis section documents all the available types that may be outputted\nwith the machine-readable output.\n\n| Type              | Description                                                                                                                        |\n| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- |\n| box-name          | Name of a box installed into Vagrant.                                                                                              |\n| box-provider      | Provider for an installed box.                                                                                                     |\n| cli-command       | A subcommand of vagrant that is available.                                                                                         |\n| error-exit        | An error occurred that caused Vagrant to exit. This contains that error. Contains two data elements: type of error, error message. |\n| provider-name     | The provider name of the target machine. `targeted`                                                                                |\n| ssh-config        | The OpenSSH compatible SSH config for a machine. This is usually the result of the \"ssh-config\" command. `targeted`                |\n| state             | The state ID of the target machine. `targeted`                                                                                     |\n| state-human-long  | Human-readable description of the state of the machine. This is the long version, and may be a paragraph or longer. `targeted`     |\n| state-human-short | Human-readable description of the state of the machine. This is the short version, limited to at most a sentence. `targeted`       |\n"
  },
  {
    "path": "website/content/docs/cli/non-primary.mdx",
    "content": "---\nlayout: docs\npage_title: More Vagrant Commands - Command-Line Interface\ndescription: |-\n  In addition to the commands listed in the sidebar and shown in \"vagrant -h\",\n  Vagrant comes with some more commands that are hidden from basic help output.\n  These commands are hidden because they're not useful to beginners or they're\n  not commonly used. We call these commands \"non-primary subcommands\".\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# More Commands\n\nIn addition to the commands listed in the sidebar and shown in `vagrant -h`,\nVagrant comes with some more commands that are hidden from basic help output.\nThese commands are hidden because they're not useful to beginners or they're\nnot commonly used. We call these commands \"non-primary subcommands\".\n\nYou can view all subcommands, including the non-primary subcommands,\nby running `vagrant list-commands`, which itself is a non-primary subcommand!\n\nNote that while you have to run a special command to list the non-primary\nsubcommands, you do not have to do anything special to actually _run_ the\nnon-primary subcommands. They're executed just like any other subcommand:\n`vagrant COMMAND`.\n\nThe list of non-primary commands is below. Click on any command to learn\nmore about it.\n\n- [docker-exec](/vagrant/docs/providers/docker/commands)\n- [docker-logs](/vagrant/docs/providers/docker/commands)\n- [docker-run](/vagrant/docs/providers/docker/commands)\n- [rsync](/vagrant/docs/cli/rsync)\n- [rsync-auto](/vagrant/docs/cli/rsync-auto)\n"
  },
  {
    "path": "website/content/docs/cli/package.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant package - Command-Line Interface\ndescription: |-\n  The \"vagrant package\" command is used to package a currently-running\n  VirtualBox or Hyper-V vagrant environment into a reusable Vagrant box.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Package\n\n**Command: `vagrant package [name|id]`**\n\nThis packages a currently running _VirtualBox_ or _Hyper-V_ environment into a\nre-usable [box](/vagrant/docs/boxes). This command can only be used with\nother [providers](/vagrant/docs/providers/) based on the provider implementation\nand if the provider supports it.\n\n## Options\n\n- `--base NAME` - Instead of packaging a VirtualBox machine that Vagrant\n  manages, this will package a VirtualBox machine that VirtualBox manages.\n  `NAME` should be the name or UUID of the machine from the VirtualBox GUI.\n  Currently this option is only available for VirtualBox. \n  **In a multi-machine environment, the UUID is required.** This info can be \n  gathered in two different ways `ls -l ~/VirtualBox\\ VMs` or `vboxmanage list vms`.\n\n- `--output NAME` - The resulting package will be saved as `NAME`. By default,\n  it will be saved as `package.box`.\n\n- `--include x,y,z` - Additional files will be packaged with the box. These\n  can be used by a packaged Vagrantfile (documented below) to perform additional\n  tasks.\n\n- `--info path/to/info.json` - The package will include a custom JSON file containing\n  information to be displayed by the [list](/vagrant/docs/cli/box#box-list) command when invoked\n  with the `-i` flag\n\n- `--vagrantfile FILE` - Packages a Vagrantfile with the box, that is loaded\n  as part of the [Vagrantfile load order](/vagrant/docs/vagrantfile/#load-order)\n  when the resulting box is used.\n\n-> **A common misconception** is that the `--vagrantfile`\noption will package a Vagrantfile that is used when `vagrant init`\nis used with this box. This is not the case. Instead, a Vagrantfile\nis loaded and read as part of the Vagrant load process when the box is\nused. For more information, read about the [Vagrantfile load order](/vagrant/docs/vagrantfile/#load-order).\n"
  },
  {
    "path": "website/content/docs/cli/plugin.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant plugin - Command-Line Interface\ndescription: |-\n  The \"vagrant plugin\" command is used to manage Vagrant plugins including\n  installing, uninstalling, and license management.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Plugin\n\n**Command: `vagrant plugin`**\n\nThis is the command used to manage [plugins](/vagrant/docs/plugins/).\n\nThe main functionality of this command is exposed via another level\nof subcommands:\n\n- [`expunge`](#plugin-expunge)\n- [`install`](#plugin-install)\n- [`license`](#plugin-license)\n- [`list`](#plugin-list)\n- [`repair`](#plugin-repair)\n- [`uninstall`](#plugin-uninstall)\n- [`update`](#plugin-update)\n\n# Plugin Expunge\n\n**Command: `vagrant plugin expunge`**\n\nThis removes all user installed plugin information. All plugin gems, their\ndependencies, and the `plugins.json` file are removed. This command\nprovides a simple mechanism to fully remove all user installed custom plugins.\n\nWhen upgrading Vagrant it may be required to reinstall plugins due to\nan internal incompatibility. The expunge command can help make that process\neasier by attempting to automatically reinstall currently configured\nplugins:\n\n```shell-session\n# Delete all plugins and reinstall\n$ vagrant plugin expunge --reinstall\n```\n\nThis command accepts optional command-line flags:\n\n- `--force` - Do not prompt for confirmation prior to removal\n- `--global-only` - Only expunge global plugins\n- `--local` - Include plugins in local project\n- `--local-only` - Only expunge local project plugins\n- `--reinstall` - Attempt to reinstall plugins after removal\n\n# Plugin Install\n\n**Command: `vagrant plugin install <name>...`**\n\nThis installs a plugin with the given name or file path. If the name\nis not a path to a file, then the plugin is installed from remote\nrepositories, usually [RubyGems](https://rubygems.org). This command will\nalso update a plugin if it is already installed, but you can also use\n`vagrant plugin update` for that.\n\n```shell-session\n# Installing a plugin from a known gem source\n$ vagrant plugin install my-plugin\n\n# Installing a plugin from a local file source\n$ vagrant plugin install /path/to/my-plugin.gem\n```\n\nIf multiple names are specified, multiple plugins will be installed. If\nflags are given below, the flags will apply to _all_ plugins being installed\nby the current command invocation.\n\nIf the plugin is already installed, this command will reinstall it with\nthe latest version available.\n\nThis command accepts optional command-line flags:\n\n- `--entry-point ENTRYPOINT` - By default, installed plugins are loaded\n  internally by loading an initialization file of the same name as the plugin.\n  Most of the time, this is correct. If the plugin you are installing has\n  another entrypoint, this flag can be used to specify it.\n\n- `--local` - Install plugin to the local Vagrant project only.\n\n- `--plugin-clean-sources` - Clears all sources that have been defined so\n  far. This is an advanced feature. The use case is primarily for corporate\n  firewalls that prevent access to RubyGems.org.\n\n- `--plugin-source SOURCE` - Adds a source from which to fetch a plugin. Note\n  that this does not only affect the single plugin being installed, by all future\n  plugin as well. This is a limitation of the underlying plugin installer\n  Vagrant uses.\n\n- `--plugin-version VERSION` - The version of the plugin to install. By default,\n  this command will install the latest version. You can constrain the version\n  using this flag. You can set it to a specific version, such as \"1.2.3\" or\n  you can set it to a version constraint, such as \"> 1.0.2\". You can set it\n  to a more complex constraint by comma-separating multiple constraints:\n  \"> 1.0.2, < 1.1.0\" (do not forget to quote these on the command-line).\n\n# Plugin License\n\n**Command: `vagrant plugin license <name> <license-file>`**\n\nThis command installs a license for a proprietary Vagrant plugin,\nsuch as the [VMware Fusion provider](/vagrant/docs/providers/vmware).\n\n# Plugin List\n\n**Command: `vagrant plugin list`**\n\nThis lists all installed plugins and their respective installed versions.\nIf a version constraint was specified for a plugin when installing it, the\nconstraint will be listed as well. Other plugin-specific information may\nbe shown, too.\n\nThis command accepts optional command-line flags:\n\n- `--local` - Include local project plugins.\n\n# Plugin Repair\n\nVagrant may fail to properly initialize user installed custom plugins. This can\nbe caused my improper plugin installation/removal, or by manual manipulation of\nplugin related files like the `plugins.json` data file. Vagrant can attempt\nto automatically repair the problem.\n\nIf automatic repair is not successful, refer to the [expunge](#plugin-expunge)\ncommand\n\nThis command accepts optional command-line flags:\n\n- `--local` - Repair local project plugins.\n\n# Plugin Uninstall\n\n**Command: `vagrant plugin uninstall <name> [<name2> <name3> ...]`**\n\nThis uninstalls the plugin with the given name. Any dependencies of the\nplugin will also be uninstalled assuming no other plugin needs them.\n\nIf multiple plugins are given, multiple plugins will be uninstalled.\n\nThis command accepts optional command-line flags:\n\n- `--local` - Uninstall plugin from local project.\n\n# Plugin Update\n\n**Command: `vagrant plugin update [<name>]`**\n\nThis updates the plugins that are installed within Vagrant. If you specified\nversion constraints when installing the plugin, this command will respect\nthose constraints. If you want to change a version constraint, re-install\nthe plugin using `vagrant plugin install`.\n\nIf a name is specified, only that single plugin will be updated. If a\nname is specified of a plugin that is not installed, this command will not\ninstall it.\n\nThis command accepts optional command-line flags:\n\n- `--local` - Update plugin from local project.\n"
  },
  {
    "path": "website/content/docs/cli/port.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant port - Command-Line Interface\ndescription: |-\n  The \"vagrant port\" command is used to display the full list of guest ports\n  mapped to the host machine ports.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Port\n\n**Command: `vagrant port [name|id]`**\n\nThe port command displays the full list of guest ports mapped to the host\nmachine ports:\n\n```shell-session\n$ vagrant port\n    22 (guest) => 2222 (host)\n    80 (guest) => 8080 (host)\n```\n\nIn a multi-machine Vagrantfile, the name of the machine must be specified:\n\n```shell-session\n$ vagrant port my-machine\n```\n\n## Options\n\n- `--guest PORT` - This displays just the host port that corresponds to the\n  given guest port. If the guest is not forwarding that port, an error is\n  returned. This is useful for quick scripting, for example:\n\n  ```shell-session\n  $ ssh -p $(vagrant port --guest 22)\n  ```\n\n- `--machine-readable` - This tells Vagrant to display machine-readable output\n  instead of the human-friendly output. More information is available in the\n  [machine-readable output](/vagrant/docs/cli/machine-readable) documentation.\n"
  },
  {
    "path": "website/content/docs/cli/powershell.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant powershell - Command-Line Interface\ndescription: |-\n  The \"vagrant powershell\" command is used to open a PowerShell prompt running\n  inside the guest machine.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# PowerShell\n\n**Command: `vagrant powershell`**\n\nThis will open a PowerShell prompt on the host into a running Vagrant guest machine.\n\nThis command will only work if the machines supports PowerShell. Not every\nenvironment will support PowerShell. At the moment, only Windows is supported\nwith this command.\n\n## Options\n\n- `-c COMMAND` or `--command COMMAND` - This executes a single PowerShell command,\n  prints out the stdout and stderr, and exits.\n"
  },
  {
    "path": "website/content/docs/cli/provision.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant provision - Command-Line Interface\ndescription: |-\n  The \"vagrant provision\" command is used to run any provisioners configured\n  for the guest machine, such as Puppet, Chef, Ansible, Salt, or Shell.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Provision\n\n**Command: `vagrant provision [vm-name]`**\n\nRuns any configured [provisioners](/vagrant/docs/provisioning/)\nagainst the running Vagrant managed machine.\n\nThis command is a great way to quickly test any provisioners, and is especially\nuseful for incremental development of shell scripts, Chef cookbooks, or Puppet\nmodules. You can just make simple modifications to the provisioning scripts\non your machine, run a `vagrant provision`, and check for the desired results.\nRinse and repeat.\n\n# Options\n\n- `--provision-with x,y,z` - This will only run the given provisioners. For\n  example, if you have a `:shell` and `:chef_solo` provisioner and run\n  `vagrant provision --provision-with shell`, only the shell provisioner will\n  be run.\n"
  },
  {
    "path": "website/content/docs/cli/rdp.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant rdp - Command-Line Interface\ndescription: |-\n  The \"vagrant rdp\" command is used to start an RDP client for a remote desktop\n  session with the guest machine.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# RDP\n\n**Command: `vagrant rdp`**\n\nThis will start an RDP client for a remote desktop session with the\nguest. This only works for Vagrant environments that support remote\ndesktop, which is typically only Windows.\n\n## Raw Arguments\n\nYou can pass raw arguments through to your RDP client on the\ncommand-line by appending it after a `--`. Vagrant just passes\nthese through. For example:\n\n```shell-session\n$ vagrant rdp -- /span\n```\n\nThe above command on Windows will execute `mstsc.exe /span config.rdp`,\nallowing your RDP to span multiple desktops.\n\nOn Darwin hosts, such as Mac OS X, the additional arguments are added to the\ngenerated RDP configuration file. Since these files can contain multiple options\nwith different spacing, you _must_ quote multiple arguments. For example:\n\n```shell-session\n$ vagrant rdp -- \"screen mode id:i:0\" \"other config:s:value\"\n```\n\nNote that as of the publishing of this guide, the Microsoft RDP Client for Mac\ndoes _not_ perform validation on the configuration file. This means if you\nspecify an invalid configuration option or make a typographical error, the\nclient will silently ignore the error and continue!\n"
  },
  {
    "path": "website/content/docs/cli/reload.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant reload - Command-Line Interface\ndescription: |-\n  The \"vagrant reload\" command is the equivalent of running \"vagrant halt\"\n  followed by \"vagrant up\".\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Reload\n\n**Command: `vagrant reload [name|id]`**\n\nThe equivalent of running a [halt](/vagrant/docs/cli/halt) followed by an\n[up](/vagrant/docs/cli/up).\n\nThis command is usually required for changes made in the Vagrantfile to\ntake effect. After making any modifications to the Vagrantfile, a `reload`\nshould be called.\n\nThe configured provisioners will not run again, by default. You can force\nthe provisioners to re-run by specifying the `--provision` flag.\n\n# Options\n\n- `--provision` - Force the provisioners to run.\n\n- `--provision-with x,y,z` - This will only run the given provisioners. For\n  example, if you have a `:shell` and `:chef_solo` provisioner and run\n  `vagrant reload --provision-with shell`, only the shell provisioner will\n  be run.\n"
  },
  {
    "path": "website/content/docs/cli/resume.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant resume - Command-Line Interface\ndescription: |-\n  The \"vagrant resume\" command is used to bring a machine back into the \"up\"\n  state, perhaps if it was previously suspended via \"vagrant halt\" or \"vagrant\n  suspend\".\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Resume\n\n**Command: `vagrant resume [name|id]`**\n\nThis resumes a Vagrant managed machine that was previously suspended,\nperhaps with the [suspend command](/vagrant/docs/cli/suspend).\n\nThe configured provisioners will not run again, by default. You can force\nthe provisioners to re-run by specifying the `--provision` flag.\n\n# Options\n\n- `--provision` - Force the provisioners to run.\n\n- `--provision-with x,y,z` - This will only run the given provisioners. For\n  example, if you have a `:shell` and `:chef_solo` provisioner and run\n  `vagrant provision --provision-with shell`, only the shell provisioner will\n  be run.\n"
  },
  {
    "path": "website/content/docs/cli/rsync-auto.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant rsync-auto - Command-Line Interface\ndescription: |-\n  The \"vagrant rsync-auto\" command watches all local directories of any rsync\n  configured synced folders and automatically initiates an rsync transfer when\n  changes are detected.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# rsync-auto\n\n**Command: `vagrant rsync-auto`**\n\nThis command watches all local directories of any\n[rsync synced folders](/vagrant/docs/synced-folders/rsync) and automatically\ninitiates an rsync transfer when changes are detected. This command does\nnot exit until an interrupt is received.\n\nThe change detection is optimized to use platform-specific APIs to listen\nfor filesystem changes, and does not simply poll the directory.\n\n## Options\n\n- `--[no-]rsync-chown` - Use rsync to modify ownership of transferred files. Enabling\n  this option can result in faster completion due to a secondary process not being\n  required to update ownership. By default this is disabled.\n\n- `--[no-]poll` - Force Vagrant to watch for changes using filesystem\n  polling instead of filesystem events. This is required for some filesystems\n  that do not support events. Warning: enabling this will make `rsync-auto`\n  _much_ slower. By default, polling is disabled.\n\n## Machine State Changes\n\nThe `rsync-auto` command does not currently handle machine state changes\ngracefully. For example, if you start the `rsync-auto` command, then\nhalt the guest machine, then make changes to some files, then boot it\nback up, `rsync-auto` will not attempt to resync.\n\nTo ensure that the command works properly, you should start `rsync-auto`\nonly when the machine is running, and shut it down before any machine\nstate changes.\n\nYou can always force a resync with the [rsync](/vagrant/docs/cli/rsync) command.\n\n## Vagrantfile Changes\n\nIf you change or move your Vagrantfile, the `rsync-auto` command will have\nto be restarted. For example, if you add synced folders to the Vagrantfile,\nor move the directory that contains the Vagrantfile, the `rsync-auto`\ncommand will either not pick up the changes or may begin experiencing\nstrange behavior.\n\nBefore making any such changes, it is recommended that you turn off\n`rsync-auto`, then restart it afterwards.\n"
  },
  {
    "path": "website/content/docs/cli/rsync.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant rsync - Command-Line Interface\ndescription: The \"vagrant rsync\" command forces a re-sync of any rsync synced folders.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Rsync\n\n**Command: `vagrant rsync`**\n\nThis command forces a re-sync of any\n[rsync synced folders](/vagrant/docs/synced-folders/rsync).\n\nNote that if you change any settings within the rsync synced folders such\nas exclude paths, you will need to `vagrant reload` before this command will\npick up those changes.\n\n## Options\n\n- `--[no-]rsync-chown` - Use rsync to modify ownership of transferred files. Enabling\n  this option can result in faster completion due to a secondary process not being\n  required to update ownership. By default this is disabled.\n"
  },
  {
    "path": "website/content/docs/cli/share.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant share - Command-Line Interface\ndescription: |-\n  The \"vagrant share\" command initializes a new Vagrant share session, which\n  allows you to share your virtual machine with the public Internet.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Share\n\n**Command: `vagrant share`**\n\nThe share command initializes a Vagrant Share session, allowing you to\nshare your Vagrant environment with anyone in the world, enabling collaboration\ndirectly in your Vagrant environment in almost any network environment.\n\nYou can learn about all the details of Vagrant Share in the\n[Vagrant Share section](/vagrant/docs/share/).\n\nThe reference of available command-line flags to this command\nis available below.\n\n## Options\n\n- `--disable-http` - Disables the creation of a publicly accessible\n  HTTP endpoint to your Vagrant environment. With this set, the only way\n  to access your share is with `vagrant connect`.\n\n- `--http PORT` - The port of the HTTP server running in the Vagrant\n  environment. By default, Vagrant will attempt to find this for you.\n  This has no effect if `--disable-http` is set.\n\n- `--https PORT` - The port of an HTTPS server running in the Vagrant\n  environment. By default, Vagrant will attempt to find this for you.\n  This has no effect if `--disable-http` is set.\n\n- `--ssh` - Enables SSH sharing (more information below). By default, this\n  is not enabled.\n\n- `--ssh-no-password` - Disables the encryption of the SSH keypair created\n  when SSH sharing is enabled.\n\n- `--ssh-port PORT` - The port of the SSH server running in the Vagrant\n  environment. By default, Vagrant will attempt to find this for you.\n\n- `--ssh-once` - Allows SSH access only once. After the first attempt to\n  connect via SSH to the Vagrant environment, the generated keypair is\n  destroyed.\n"
  },
  {
    "path": "website/content/docs/cli/snapshot.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant snapshot - Command-Line Interface\ndescription: |-\n  The \"vagrant snapshot\" command is used to manage snapshots of the guest\n  machine.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Snapshot\n\n**Command: `vagrant snapshot`**\n\nThis is the command used to manage snapshots with the guest machine.\nSnapshots record a point-in-time state of a guest machine. You can then\nquickly restore to this environment. This lets you experiment and try things\nand quickly restore back to a previous state.\n\nSnapshotting is not supported by every provider. If it is not supported,\nVagrant will give you an error message.\n\nThe main functionality of this command is exposed via even more subcommands:\n\n- [`push`](#snapshot-push)\n- [`pop`](#snapshot-pop)\n- [`save`](#snapshot-save)\n- [`restore`](#snapshot-restore)\n- [`list`](#snapshot-list)\n- [`delete`](#snapshot-delete)\n\n# Snapshot Push\n\n**Command: `vagrant snapshot push`**\n\nThis takes a snapshot and pushes it onto the snapshot stack.\n\nThis is a shorthand for `vagrant snapshot save` where you do not need\nto specify a name. When you call the inverse `vagrant snapshot pop`, it will\nrestore the pushed state.\n\n~> **Warning:** If you are using `push` and `pop`, avoid using `save`\nand `restore` which are unsafe to mix.\n\n# Snapshot Pop\n\n**Command: `vagrant snapshot pop`**\n\nThis command is the inverse of `vagrant snapshot push`: it will restore\nthe pushed state.\n\n## Options\n\n- `--[no-]provision` - Force the provisioners to run (or prevent them\n  from doing so).\n\n- `--no-delete` - Prevents deletion of the snapshot after restoring\n  (so that you can restore to the same point again later).\n\n- `--no-start` - Prevents the guest from being started after restore\n\n# Snapshot Save\n\n**Command: `vagrant snapshot save [vm-name] NAME`**\n\nThis command saves a new named snapshot. If this command is used, the\n`push` and `pop` subcommands cannot be safely used.\n\n# Snapshot Restore\n\n**Command: `vagrant snapshot restore [vm-name] NAME`**\n\nThis command restores the named snapshot.\n\n- `--[no-]provision` - Force the provisioners to run (or prevent them\n  from doing so).\n\n- `--no-start` - Prevents the guest from being started after restore\n\n# Snapshot List\n\n**Command: `vagrant snapshot list`**\n\nThis command will list all the snapshots taken.\n\n# Snapshot Delete\n\n**Command: `vagrant snapshot delete [vm-name] NAME`**\n\nThis command will delete the named snapshot.\n\nSome providers require all \"child\" snapshots to be deleted first. Vagrant\nitself does not track what these children are. If this is the case (such\nas with VirtualBox), then you must be sure to delete the snapshots in the\nreverse order they were taken.\n\nThis command is typically _much faster_ if the machine is halted prior to\nsnapshotting. If this is not an option, or is not ideal, then the deletion\ncan also be done online with most providers.\n"
  },
  {
    "path": "website/content/docs/cli/ssh.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant ssh - Command-Line Interface\ndescription: |-\n  The \"vagrant ssh\" command is used to establish an SSH session into a running\n  virtual machine to give you shell access.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# SSH\n\n**Command: `vagrant ssh [name|id] [-- extra_ssh_args]`**\n\nThis will SSH into a running Vagrant machine and give you access to a shell.\n\nOn a simple vagrant project, the instance created will be named default.\n\nVagrant will ssh into this instance without the instance name:\n\n```shell-session\n$ vagrant ssh\n\nWelcome to your Vagrant-built virtual machine.\nLast login: Fri Sep 14 06:23:18 2012 from 10.0.2.2\n$ logout\nConnection to 127.0.0.1 closed.\n```\n\nOr you could use the name:\n\n```shell-session\n$ vagrant ssh default\n\n\nWelcome to your Vagrant-built virtual machine.\nLast login: Fri Jul 20 15:09:52 2018 from 10.0.2.2\n$ logout\nConnection to 127.0.0.1 closed.\n$\n```\n\nOn multi-machine setups, you can login to each VM using the name as displayed\non `vagrant status`\n\n```shell-session\n$ vagrant status\nCurrent machine states:\n\nnode1                     running (virtualbox)\nnode2                     running (virtualbox)\n\nThis environment represents multiple VMs. The VMs are all listed\nabove with their current state.\n$ vagrant ssh node1\n\nWelcome to your Vagrant-built virtual machine.\nLast login: Fri Sep 14 06:23:18 2012 from 10.0.2.2\nvagrant@bionic64:~$ logout\nConnection to 127.0.0.1 closed.\n$ vagrant ssh node2\n\nWelcome to your Vagrant-built virtual machine.\nLast login: Fri Sep 14 06:23:18 2012 from 10.0.2.2\nvagrant@bionic64:~$ logout\nConnection to 127.0.0.1 closed.\n$\n```\n\nOn a system with machines running from different projects, you could use the id\nas listed in `vagrant global-status`\n\n```shell-session\n$ vagrant global-status\nid       name   provider   state   directory\n-----------------------------------------------------------------------\n13759ff  node1  virtualbox running /Users/user/vagrant/folder\n\nThe above shows information about all known Vagrant environments\non this machine. This data is cached and may not be completely\nup-to-date (use \"vagrant global-status --prune\" to prune invalid\nentries). To interact with any of the machines, you can go to that\ndirectory and run Vagrant, or you can use the ID directly with\nVagrant commands from any directory.\n$ vagrant ssh 13759ff\n\nWelcome to your Vagrant-built virtual machine.\nLast login: Fri Jul 20 15:19:36 2018 from 10.0.2.2\nvagrant@bionic64:~$ logout\nConnection to 127.0.0.1 closed.\n$\n```\n\nIf a `--` (two hyphens) are found on the command line, any arguments after\nthis are passed directly into the `ssh` executable. This allows you to pass\nany arbitrary commands to do things such as reverse tunneling down into the\n`ssh` program.\n\n## Options\n\n- `-c COMMAND` or `--command COMMAND` - This executes a single SSH command, prints\n  out the stdout and stderr, and exits.\n\n- `-p` or `--plain` - This does an SSH without authentication, leaving\n  authentication up to the user.\n\n## SSH client usage\n\nVagrant will attempt to use the local SSH client installed on the host machine. On\nPOSIX machines, an SSH client must be installed and available on the PATH.\n\nFor Windows installations, an SSH client is provided within the installer\nimage. If no SSH client is found on the current PATH, Vagrant will use the\nSSH client it provided. Depending on the local environment used for running\nVagrant, the installer provided SSH client may not work correctly. For example,\nwhen using a cygwin or msys2 shell the SSH client will fail to work as expected\nwhen run interactively. Installing the SSH package built for the current working\nenvironment will resolve this issue.\n\n## Background Execution\n\nIf the command you specify runs in the background (such as appending a `&` to\na shell command), it will be terminated almost immediately. This is because\nwhen Vagrant executes the command, it executes it within the context of a\nshell, and when the shell exits, all of the child processes also exit.\n\nTo avoid this, you will need to detach the process from the shell. Please\nGoogle to learn how to do this for your shell. One method of doing this is\nthe `nohup` command.\n\n## Pageant on Windows\n\nThe SSH executable will not be able to access Pageant on Windows. While\nVagrant is capable of accessing Pageant via internal libraries, the\nSSH executable does not have support for Pageant. This means keys\nfrom Pageant will not be available for forwarding when using the\n`vagrant ssh` command.\n\nThird party programs exist to allow the SSH executable to access Pageant\nby creating a Unix socket for the SSH executable to read. For more information\nplease see [ssh-pageant](https://github.com/cuviper/ssh-pageant).\n"
  },
  {
    "path": "website/content/docs/cli/ssh_config.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant ssh-config - Command-Line Interface\ndescription: |-\n  The \"vagrant ssh-config\" command is used to output a valid SSH configuration\n  file capable of SSHing into the guest machine directly.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# SSH Config\n\n**Command: `vagrant ssh-config [name|id]`**\n\nThis will output valid configuration for an SSH config file to SSH\ninto the running Vagrant machine from `ssh` directly (instead of\nusing `vagrant ssh`).\n\n## Options\n\n- `--host NAME` - Name of the host for the outputted configuration.\n"
  },
  {
    "path": "website/content/docs/cli/status.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant status - Command-Line Interface\ndescription: |-\n  The \"vagrant status\" command is used to tell you the status of the virtual\n  machines in the current Vagrant environment.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Status\n\n**Command: `vagrant status [name|id]`**\n\nThis will tell you the state of the machines Vagrant is managing.\n\nIt is quite easy, especially once you get comfortable with Vagrant, to\nforget whether your Vagrant machine is running, suspended, not created, etc.\nThis command tells you the state of the underlying guest machine.\n"
  },
  {
    "path": "website/content/docs/cli/suspend.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant suspend - Command-Line Interface\ndescription: |-\n  The \"vagrant suspend\" command is used to suspend the guest machine Vagrant is\n  currently managing.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Suspend\n\n**Command: `vagrant suspend [name|id]`**\n\nThis suspends the guest machine Vagrant is managing, rather than fully\n[shutting it down](/vagrant/docs/cli/halt) or [destroying it](/vagrant/docs/cli/destroy).\n\nA suspend effectively saves the _exact point-in-time state_ of the machine,\nso that when you [resume](/vagrant/docs/cli/resume) it later, it begins running\nimmediately from that point, rather than doing a full boot.\n\nThis generally requires extra disk space to store all the contents of the\nRAM within your guest machine, but the machine no longer consumes the\nRAM of your host machine or CPU cycles while it is suspended.\n"
  },
  {
    "path": "website/content/docs/cli/up.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant up - Command-Line Interface\ndescription: |-\n  The \"vagrant up\" command is used to create, configuration, and provision a\n  guest machine according to your Vagrantfile.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Up\n\n**Command: `vagrant up [name|id]`**\n\nThis command creates and configures guest machines according to your\n[Vagrantfile](/vagrant/docs/vagrantfile/).\n\nThis is the single most important command in Vagrant, since it is how\nany Vagrant machine is created.\n\n## Options\n\n- `name` - Name of machine defined in [Vagrantfile](/vagrant/docs/vagrantfile/). Using\n  `name` to specify the Vagrant machine to act on must be done from within a\n  Vagrant project (directory where the Vagrantfile exists).\n\n- `id` - Machine id found with `vagrant global-status`. Using `id` allows\n  you to call `vagrant up id` from any directory.\n\n- `--[no-]destroy-on-error` - Destroy the newly created machine if a fatal,\n  unexpected error occurs. This will only happen on the first `vagrant up`.\n  By default this is set.\n\n- `--[no-]install-provider` - If the requested provider is not installed,\n  Vagrant will attempt to automatically install it if it can. By default this\n  is enabled.\n\n- `--[no-]parallel` - Bring multiple machines up in parallel if the provider\n  supports it. Please consult the provider documentation to see if this feature\n  is supported.\n\n- `--provider x` - Bring the machine up with the given\n  [provider](/vagrant/docs/providers/). By default this is \"virtualbox\".\n\n- `--[no-]provision` - Force, or prevent, the provisioners to run.\n\n- `--provision-with x,y,z` - This will only run the given provisioners. For\n  example, if you have a `:shell` and `:chef_solo` provisioner and run\n  `vagrant provision --provision-with shell`, only the shell provisioner will\n  be run.\n"
  },
  {
    "path": "website/content/docs/cli/upload.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant upload - Command-Line Interface\ndescription: |-\n  The \"vagrant upload\" command is used to upload files from the host\n  to a guest machine.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Upload\n\n**Command: `vagrant upload source [destination] [name|id]`**\n\nThis command uploads files and directories from the host to the guest\nmachine.\n\n## Options\n\n- `destination` - Path on the guest machine to upload file or directory.\n\n- `source` - Path to file or directory on host to upload to guest machine.\n\n- `--compress` - Compress the file or directory before uploading to guest machine.\n\n- `--compression-type type` - Type of compression to use when compressing\n  file or directory for upload. Defaults to `zip` for Windows guests and\n  `tgz` for non-Windows guests. Valid values: `tgz`, `zip`.\n\n- `--temporary` - Create a temporary location on the guest machine and upload\n  files to that location.\n"
  },
  {
    "path": "website/content/docs/cli/validate.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant validate - Command-Line Interface\ndescription: The \"vagrant validate\" command is used to validate your Vagrantfile.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Validate\n\n**Command: `vagrant validate`**\n\nThis command validates your [Vagrantfile](/vagrant/docs/vagrantfile/).\n\n## Options\n\n- `--ignore-provider` - Ignores provider config options.\n\n## Examples\n\nValidate the syntax of the Vagrantfile to ensure it is correctly structured and free of errors\n\n```shell-session\n$ vagrant validate\nVagrantfile validated successfully.\n```\n\nEnsure that the Vagrantfile is correctly structured while ignoring provider-specific configuration options:\n\n```shell-session\n$ vagrant validate --ignore-provider virtualbox\n==> default: Ignoring provider config for validation...\nVagrantfile validated successfully.\n```"
  },
  {
    "path": "website/content/docs/cli/version.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant version - Command-Line Interface\ndescription: |-\n  The \"vagrant version\" command is used to output the version of Vagrant\n  currently installed on the system.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Version\n\n**Command: `vagrant version`**\n\nThis command tells you the version of Vagrant you have installed\nas well as the latest version of Vagrant that is currently available.\n\nIn order to determine the latest available Vagrant version, this\ncommand must make a network call. If you only want to see the currently\ninstalled version, use `vagrant --version`.\n"
  },
  {
    "path": "website/content/docs/cli/winrm.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant winrm - Command-Line Interface\ndescription: |-\n  The \"vagrant winrm\" command is used execute commands on the remote\n  machine via WinRM\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# WinRM\n\n**Command: `vagrant winrm [name|id]`**\n\nExecutes the provided command(s) on the guest machine using the\nWinRM communicator. Commands are provided with the `--command`\noption and multiple `--command` flags may be provided for\nexecuting multiple commands. This command requires the guest\nmachine to be configured with the WinRM communicator.\n\n## Options\n\n- `--command COMMAND` - Command to execute.\n\n- `--elevated` - Run command(s) with elevated credentials.\n\n- `--shell (cmd|powershell)` - Shell to execute commands. Defaults to `powershell`.\n"
  },
  {
    "path": "website/content/docs/cli/winrm_config.mdx",
    "content": "---\nlayout: docs\npage_title: vagrant winrm-config - Command-Line Interface\ndescription: |-\n  The \"vagrant winrm-config\" command is used to output the WinRM configuration\n  used to connect to the guest machine.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# WinRM Config\n\n**Command: `vagrant winrm-config [name|id]`**\n\nThis will output the WinRM configuration used for connecting to\nthe guest machine. It requires that the WinRM communicator is in\nuse for the guest machine.\n\n## Options\n\n- `--host NAME` - Name of the host for the outputted configuration.\n"
  },
  {
    "path": "website/content/docs/cloud-init/configuration.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Cloud-Init Configuration\ndescription: Documentation of various configuration options for Vagrant cloud-init\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Configuration\n\nVagrant cloud-init has several options that allow users to define a config to be\nused with cloud-init.\n\nFor more detailed information about these config values and how to use cloud-init,\nplease read the [official documentation for cloud-init](https://cloudinit.readthedocs.io/en/latest/index.html).\n\n## cloud_init Options\n\nIt should be noted that Vagrant will not validate the correctness of the `cloud-init`\nconfig provided, only that a cloud-init config has been provided through `path`\nor directly `inline` in a Vagrantfile.\n\n- `content_type` (string) - Required argument that defines the Content-Type of the\n  given cloud_init config. Vagrant only supports the following options for `content_type`:\n\n  - `\"text/cloud-boothook\"`\n  - `\"text/cloud-config\"`\n  - `\"text/cloud-config-archive\"`\n  - `\"text/jinja2\"`\n  - `\"text/part-handler\"`\n  - `\"text/upstart-job\"`\n  - `\"text/x-include-once-url\"`\n  - `\"text/x-include-url\"`\n  - `\"text/x-shellscript\"`\n\n- `path` (string) - Path to a file on the host machine that contains\n  cloud-init user data. This will be added to the multipart user-data file along\n  with its `content_type`. Incompatible with the `inline` option.\n- `inline` (string) - Inline cloud-init user data. This will be added to the\n  multipart user-data file along with its `content_type`. Incompatible with `path`\n  option.\n\nExamples of how to define these options can be found in the\n[usage documentation](/vagrant/docs/cloud-init/configuration).\n\n### cloud_init Type\n\nWhen defining a config for cloud_init, you can optionally define a `type` for the config:\n\n```ruby\nconfig.vm.cloud_init :user_data, content_type: \"text/cloud-config\", path: \"config.cfg\"\n\nconfig.vm.cloud_init :user_data do |cloud_init|\n  cloud_init.content_type = \"text/cloud-config\"\n  cloud_init.path = \"config.cfg\"\nend\n```\n\nHowever, this is not a requirement. Leaving off `type` will default to `:user_data`.\n\n- `type` (Symbol) - This is an optional config that defines the type of\n  cloud-init config. Currently, the only supported `type` is `:user_data`. If a type\n  is not defined, it will default to `:user_data`.\n"
  },
  {
    "path": "website/content/docs/cloud-init/index.mdx",
    "content": "---\nlayout: docs\npage_title: Cloud-Init\ndescription: Introduction to using cloud-init with Vagrant\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant cloud-init\n\nFor examples on how to achieve this, among other use cases, please refer to the [usage](/vagrant/docs/cloud-init/usage)\nguide for more information!\n\nFor more information about what options are available for configuring cloud-init, see the\n[configuration section](/vagrant/docs/cloud-init/configuration).\n"
  },
  {
    "path": "website/content/docs/cloud-init/usage.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Cloud-Init Usage\ndescription: Various Vagrant Cloud-Init examples\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Basic Usage\n\nBelow are some very simple examples of how to use Vagrant Cloud-Init with the VirtualBox provider.\n\nFor more detailed information about these config values and how to use cloud-init,\nplease read the [official documentation for cloud-init](https://cloudinit.readthedocs.io/en/latest/index.html).\n\n## Basic Examples\n\nA cloud_init config can be defined as a \"hash\" of key values, or as a block. Below\nare two examples of this for defining a cloud_init config:\n\n```ruby\n# Simplified form\nconfig.vm.cloud_init content_type: \"text/x-shellscript\", path: \"./foo/bar.sh\"\n\n# Block form\nconfig.vm.cloud_init do |cloud_init|\n  cloud_init.content_type = \"text/cloud-config\"\n  cloud_init.inline = <<-EOF\n    package_update: true\n    packages:\n      - nginx\n  EOF\nend\n```\n\nThe first part will be read from a local file `./foo/bar`, and the second part\nwill be attached using the inline content. Both \"block\" and \"hash\" forms are supported,\nand should work interchangeably.\n\nIndividual machines may have their own cloud-init data:\n\n```ruby\nconfig.vm.define \"web\" do |web|\n  web.vm.cloud_init content_type: \"text/cloud-config\",\n    inline: <<-EOF\n      package_update: true\n      packages:\n        - nginx\n    EOF\n  end\nend\n\nconfig.vm.define \"db\" do |db|\n  db.vm.cloud_init content_type: \"text/cloud-config\",\n    inline: <<-EOF\n      package_update: true\n      packages:\n        - postgresql\n    EOF\nend\n```\n"
  },
  {
    "path": "website/content/docs/disks/configuration.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Disks Configuration\ndescription: Documentation of various configuration options for Vagrant Disks\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Configuration\n\nVagrant Disks has several options that allow users to define and attach disks to guests.\n\n## Disk Options\n\n- `disk_ext` (string) - Optional argument that defines what kind of file\n  extension a disk should have. Defaults to `\"vdi\"` if unspecified. For a list of\n  supported disk extensions, please check the specific provider being used.\n  Not used for type `:dvd.`\n\n- `file` (string) - For type `:dvd`, this is a required argument that should\n  point to an `.iso` file on the host machine. For type `:disk`, this is an\n  optional argument that can point to the location of a disk file that already\n  exists.\n\n- `name` (string) - Required option to give the disk a name. This name will\n  also be used as the filename when creating a virtual hard disk.\n\n- `primary` (boolean) - Optional argument that configures a given disk to be\n  the \"primary\" disk to manage on the guest. There can only be one `primary`\n  disk per guest, and it must be of type `:disk`. Defaults to `false` if not\n  specified.\n\n- `provider_config` (hash) - Additional provider specific options for managing a given disk. Please refer to\n  the provider specific documentation to see any available provider_config options.\n\n  Generally, the disk option accepts two kinds of ways to define a provider config:\n\n  - `providername__diskoption: value`\n    - The provider name followed by a double underscore, and then the provider specific option for that disk\n  - `{providername: {diskoption: value}, otherprovidername: {diskoption: value}`\n    - A hash where the top level key(s) are one or more providers, and each provider keys values are a hash of options and their values.\n\n- `size` (String) - The size of the disk to create. For example, `\"10GB\"`. Not\n  used for type `:dvd.`\n\n  **Note:** More specific examples of these can be found under the provider\n  specific disk page. The `provider_config` option will depend on the provider\n  you are using. Please read the provider specific documentation for disk\n  management to learn about what options are available to use.\n\n## Disk Types\n\nThe disk config currently accepts three kinds of disk types:\n\n- `disk` (symbol)\n- `dvd` (symbol)\n- `floppy` (symbol)\n\n**NOTE:** These types depend on the provider used, and may not yet be functional. Please\nrefer to the provider specific implementation for more details for what is supported.\n\nYou can set a disk type with the first argument of a disk config in your Vagrantfile:\n\n```ruby\nconfig.vm.disk :disk, name: \"backup\", size: \"10GB\"\nconfig.vm.disk :dvd, name: \"installer\", file: \"./installer.iso\"\nconfig.vm.disk :floppy, name: \"cool_files\"\n```\n\n## Provider Author Guide\n\nIf you are a vagrant plugin author who maintains a provider for Vagrant, this short guide will hopefully give some information on how to use the internal disk config object.\n\n~> **Warning!** This guide is still being written as we develop this\nnew feature for Vagrant. Is something missing, or could this be improved? Please\nlet us know on GitHub by opening an issue or open a pull request directly.\n\nAll providers must implement the capability `configure_disks`, and `cleanup_disks`.\nThese methods are responsible for the following:\n\n- `configure_disks` - Reads in a Vagrant config for defined disks from a Vagrantfile,\n  and creates and attaches the disks based on the given config\n- `cleanup_disks` - Compares the current Vagrant config for defined disks and detaches\n  any disks that are no longer valid for a guest.\n\nThese methods are called in the builtin Vagrant actions _Disk_ and _CleanupDisks_.\nIf the provider does not support these capabilities, they will be skipped over and no\ndisks will be configured. It is the providers job to implement these provider capabilities\nand handle the methods required to support disk creation and deletion. Vagrant will\nhandle parsing and supplying the config object based on what has been defined inside\na users Vagrantfile.\n\nFor a more detailed example of how to use this disk configuration with Vagrant, please\ncheck out how it was implemented using the VirtualBox provider.\n\n### The disk_meta file\n\nBoth builtin disk actions `configure_disks` and `cleanup_disks` expect to read and\nwrite down a `disk_meta` file inside a machines data dir. This file is specifically\nfor keeping track of the _last configured state_ for disks in a given provider.\nGenerally, this file is used as a way for Vagrant to keep track of what disks\nare being managed by Vagrant with the provider uses, so that it does not accidentally\ndelete or manage disks that were configured outside of Vagrants configuration.\n\nFor the VirtualBox provider, Vagrant uses this file to see what disks were configured\non the _last run_ of Vagrant, and compares that to the current configured state for\nthe Vagrantfile on the _current run_ of Vagrant. It specifically stores each disks\nUUID and disk name for use. If it notices a disk that is no longer in the\nVagrantfile, it can be assumed that the disk is no longer valid for that guest,\nand cleans up the disk.\n\nThis may not be required for your provider, however with the VirtualBox provider, Vagrant\nneeds a way to keep track of the defined disks managed by Vagrant and their disk UUIDs\nthat VirtualBox uses to keep track of these disks.\n\n### The provider_config hash\n\nThe disk config class supports an optional hash of options called `provider_config`.\nThis allows the user to define some additional options for a provider to use that\nmay be non-standard across different providers.\n"
  },
  {
    "path": "website/content/docs/disks/hyperv/common-issues.mdx",
    "content": "---\nlayout: docs\npage_title: Common Issues - Disks Hyper-V Provider\ndescription: |-\n  This page lists some common issues people run into with Vagrant and Hyper-V\n  as well as solutions for those issues.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Common Issues and Troubleshooting\n\nThis page lists some common issues people run into with Vagrant and Hyper-V\nas well as solutions for those issues.\n\n## Are my disks attached?\n\nA handy way to figure out what disks are attached (or not attached) to your guest\nis to open up the Hyper-V GUI and select the guest. When selecting a guest on the GUI,\nit should open more information about the guest, including storage information. Here\nyou should see a list of disks attached to your guest.\n\n## Applying Vagrant disk configuration changes to guests\n\nDue to how Hyper-V works, you must reload your guest for any disk config changes\nto be applied. So if you update your Vagrantfile to update or even remove disks, make\nsure to `vagrant reload` your guests for these changes to be applied.\n"
  },
  {
    "path": "website/content/docs/disks/hyperv/index.mdx",
    "content": "---\nlayout: docs\npage_title: Disks for Hyper-V Provider\ndescription: |-\n  Vagrant comes with support out of the box for Hyper-V, a free,\n  cross-platform consumer virtualization product.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Hyper-V\n\nBecause of how Hyper-V handles disk management, a Vagrant guest _must_ be powered\noff for any changes to be applied to a guest. If you make a configuration change\nwith a guests disk, you will need to `vagrant reload` the guest for any changes\nto be applied.\n\nFor more information on how to use Hyper-V to configure disks for a guest, refer\nto the [general usage](/vagrant/docs/disks/usage) and [configuration](/vagrant/docs/disks/configuration)\nguide for more information.\n"
  },
  {
    "path": "website/content/docs/disks/hyperv/usage.mdx",
    "content": "---\nlayout: docs\npage_title: Usage - Disks Hyper-V Provider\ndescription: |-\n  The Vagrant Hyper-V provider is used just like any other provider. Please\n  read the general basic usage page for providers.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Usage\n\nFor examples of how to use the disk feature with Hyper-V, please refer to the\n[general disk usage guide](/vagrant/docs/disks/usage) for more examples.\n\n## provider_config options\n\nMost options are used for either creating or attaching a hard disk to your guest.\nVagrant supports most options for these operations. You should be able to define\nthe PowerShell specific argument to a given Hyper-V command in the provider_config\nhash, and Vagrant should properly pass it along to the command.\n\nTo define a provider specific option, please refer to the [Disk Options documentation page](/vagrant/docs/disks/configuration) for more info.\n\n### Note about options defined below\n\nIt is possible these options could be out of date or stale. If you happen to see\nan option that has changed or is missing from this page, please open an issue\nor pull request on Vagrants GitHub page to correct this.\n\n### New-VHD Supported Options\n\nFor more information about each option, please visit the [New-VHD Hyper-V documentation](https://docs.microsoft.com/en-us/powershell/module/hyper-v/new-vhd?view=win10-ps).\n\n**Note:** By default, all Hyper-V disks are defined as a Dynamic virtual hard disk. If you\nwish to make the disk a fixed size, you can set the `Fixed` option below when creating\na new disk.\n\n- `BlockSizeBytes` (string) - Optional argument, i.e. `\"128MB\"`\n- `Differencing` (bool) - If set, the disk will be used to store differencing changes from parent disk (must set `ParentPath`)\n- `Fixed` (bool) - If set, the disk will be a fixed size, not dynamically allocated.\n- `LogicalSectorSizeBytes` (int) - Optional argument, must be either `512` or `4096`\n- `ParentPath` (string) - The parent disk path used if a `Differencing` disk is defined\n- `PhysicalSectorSizeBytes` (string) - Optional argument, must be either `512` or `4096`\n- `SourceDisk` (int) - Existing disk to use as a source for the new disk\n\n### Add-VMHardDiskDrive Supported Options\n\nFor more information about each option, please visit the [Add-VMHardDiskDrive Hyper-V documentation](https://docs.microsoft.com/en-us/powershell/module/hyper-v/add-vmharddiskdrive?view=win10-ps)\n\nGenerally, these options do not need to be set or handled by most users. Only\nuse these options if you are sure you know what you are doing. Vagrant will\nbe able to attach disks for you without these options, but they are available\nif it is required that you specify a specific location for a disk.\n\n- `ControllerLocation` (int) - The location that the disk should be attached to on the controller\n- `ControllerNumber` (int) - The controller to use for attaching the disk\n- `ControllerType` (string) - The kind of controller to use when attaching the a disk. Only `\"IDE\"` and `\"SCSI\"` are valid.\n"
  },
  {
    "path": "website/content/docs/disks/index.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Disks\ndescription: Introduction to Vagrant Disks\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant Disks\n\nVagrant Disks is a feature that allows users to define what mediums should be attached\nto their guests, as well as allowing users to resize their primary disk.\n\nFor examples on how to achieve this, among other use cases, please refer to the [usage](/vagrant/docs/disks/usage)\nguide for more information!\n\nFor more information about what options are available for configuring disks, see the\n[configuration section](/vagrant/docs/disks/configuration).\n\n## Supported Providers\n\nCurrently, only VirtualBox is supported. Please refer to the [VirtualBox documentation](/vagrant/docs/disks/virtualbox) for more information on using disks with the VirtualBox provider!\n"
  },
  {
    "path": "website/content/docs/disks/usage.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Disk Usage\ndescription: Various Vagrant Disk examples\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Basic Usage\n\nBelow are some very simple examples of how to use Vagrant Disks with the VirtualBox provider.\n\n## Basic Examples\n\n### Resizing your primary disk\n\nSometimes, the primary disk for a guest is not large enough and you will need to\nadd more space. To resize a disk, you can simply add a config like this below\nto expand the size of your guests drive:\n\n```ruby\nconfig.vm.disk :disk, size: \"100GB\", primary: true\n```\n\nNote: the `primary: true` is what tells Vagrant to expand the guests main drive.\nWithout this option, Vagrant will instead attach a _new_ disk to the guest.\n\nFor example, this Ubuntu guest will now come with 100GB of space, rather than the default:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.define \"hashicorp\" do |h|\n    h.vm.box = \"hashicorp/bionic64\"\n    h.vm.provider :virtualbox\n\n    h.vm.disk :disk, size: \"100GB\", primary: true\n  end\nend\n```\n\nIt should be noted that due to how VirtualBox functions, it is not possible to shrink\nthe size of a disk.\n\n### Attaching new hard disks\n\nVagrant can attach multiple disks to a guest using the VirtualBox provider. An example\nof attaching a single disk to a guest with 10 GB of storage can be found below:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.define \"hashicorp\" do |h|\n    h.vm.box = \"hashicorp/bionic64\"\n    h.vm.provider :virtualbox\n\n    h.vm.disk :disk, size: \"10GB\", name: \"extra_storage\"\n  end\nend\n```\n\nOptionally, if you need to attach many disks, you can use Ruby to generate multiple\ndisks for Vagrant to create and attach to your guest:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.define \"hashicorp\" do |h|\n    h.vm.box = \"hashicorp/bionic64\"\n    h.vm.provider :virtualbox\n\n    (0..3).each do |i|\n      h.vm.disk :disk, size: \"5GB\", name: \"disk-#{i}\"\n    end\n  end\nend\n```\n\nNote: VirtualBox has a hard limit on the number of disks that can be attached\nto a given storage controller, which is defined by the controller type.\nAttempting to configure more disks than are supported by the primary\ncontroller will result in a Vagrant error.\n\n### Attaching optical drives\n\nVagrant can attach `.iso` files as optical drives using the VirtualBox provider.\nAn example of attaching an optical drive to a guest can be found below:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.define \"hashicorp\" do |h|\n    h.vm.box = \"hashicorp/bionic64\"\n    h.vm.provider :virtualbox\n\n    h.vm.disk :dvd, name: \"installer\", file: \"./installer.iso\"\n  end\nend\n```\n\nAs with hard disks, configuring more disks than are supported by your VM's\nstorage controller arrangement will result in a Vagrant error.\n\n### Removing Disks\n\nIf you have removed a disk from your Vagrant config and wish for it to be\ndetached from the guest, you will need to `vagrant reload` your guest to apply\nthese changes. **NOTE:** Removing virtual hard disks created by Vagrant will\nalso delete the medium from your hard drive.\n"
  },
  {
    "path": "website/content/docs/disks/virtualbox/common-issues.mdx",
    "content": "---\nlayout: docs\npage_title: Common Issues - Disks VirtualBox Provider\ndescription: |-\n  This page lists some common issues people run into with Vagrant and VirtualBox\n  as well as solutions for those issues.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Common Issues and Troubleshooting\n\nThis page lists some common issues people run into with Vagrant and VirtualBox\nas well as solutions for those issues.\n\n## Are my disks attached?\n\nA handy way to figure out what disks are attached (or not attached) to your guest\nis to open up the VirtualBox GUI and select the guest. When selecting a guest on the GUI,\nit should open more information about the guest, including storage information. Here\nyou should see a list of disks attached to your guest.\n\n## How many disks can I attach?\n\nVagrant attaches all new disks defined to guest's primary controller. As of\nVirtualBox 6.1.x, storage controllers have the following limits to the number\nof disks that are supported per guest:\n\n- IDE Controllers: 4\n- SATA Controllers: 30\n- SCSI Controllers: 16\n\nTherefore if your primary disk is attached to a SATA Controller and you try to\ndefine and attach more than 30, it will result in an error. This number\n_includes_ the primary disk for the guest.\n\nDVD attachments are subject to the same limits. Optical disk attachments will\nbe attached to the storage controller with the highest boot priority (usually\nthe IDE controller).\n\n## Resizing VMDK format disks\n\nVMDK disks cannot be resized in their current state, so Vagrant will automatically\nconvert these disks to VDI, resize the disk, and convert it back to its original format.\nMany Vagrant boxes default to using the VMDK disk format, so resizing disks for\nmany users will require Vagrant to convert these disks. Generally, this will be transparent\nto the user. However if Vagrant crashes or if a user interrupts Vagrant during the\ncloning process, there is a chance that you might lose your data.\n\n## Applying Vagrant disk configuration changes to guests\n\nDue to how VirtualBox works, you must reload your guest for any disk config changes\nto be applied. So if you update your Vagrantfile to update or even remove disks, make\nsure to `vagrant reload` your guests for these changes to be applied.\n"
  },
  {
    "path": "website/content/docs/disks/virtualbox/index.mdx",
    "content": "---\nlayout: docs\npage_title: Disks for VirtualBox Provider\ndescription: |-\n  Vagrant comes with support out of the box for VirtualBox, a free,\n  cross-platform consumer virtualization product.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# VirtualBox\n\n**Vagrant currently only supports VirtualBox version 5.x and newer for configuring and\nattaching disks.**\n\nBecause of how VirtualBox handles disk management, a Vagrant guest _must_ be powered\noff for any changes to be applied to a guest. If you make a configuration change\nwith a guests disk, you will need to `vagrant reload` the guest for any changes\nto be applied.\n\nWhen new disks are defined to be attached to a guest, Vagrant will attach\ndisks to a particular storage controller based on the type of disk configured:\n\n- For the `:disk` type, Vagrant will use the storage controller containing the\n  boot disk. It will also create the disk if necessary.\n- For the `:dvd` type, Vagrant will attach the disk to the storage controller\n  that comes earliest in the machine's boot order. For example, if a VM has a\n  SATA controller and an IDE controller, the disk will be attached to the IDE\n  controller.\n\nVagrant will not be able to configure disks of a given type if the associated\nstorage controller does not exist. In this case, you may use\n[provider-specific customizations](/vagrant/docs/providers/virtualbox/configuration#vboxmanage-customizations)\nto add a required storage controller.\n\nIt should also be noted that storage controllers have different limits for the\nnumber of disks that can be attached. Attempting to configure more than the\nmaximum number of disks for a storage controller type will result in a Vagrant\nerror.\n\nFor more information on how to use VirtualBox to configure disks for a guest, refer\nto the [general usage](/vagrant/docs/disks/usage) and [configuration](/vagrant/docs/disks/configuration)\nguide for more information.\n"
  },
  {
    "path": "website/content/docs/disks/virtualbox/usage.mdx",
    "content": "---\nlayout: docs\npage_title: Usage - Disks VirtualBox Provider\ndescription: |-\n  The Vagrant VirtualBox provider is used just like any other provider. Please\n  read the general basic usage page for providers.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Usage\n\nFor examples of how to use the disk feature with VirtualBox, please refer to the\n[general disk usage guide](/vagrant/docs/disks/usage) for more examples.\n\n## provider_config options\n\nCurrently, there are no additional options supported for the `provider_config` option.\nThis page will be updated with any valid options as they become supported.\n"
  },
  {
    "path": "website/content/docs/disks/vmware/common-issues.mdx",
    "content": "---\nlayout: docs\npage_title: Common Issues - Disks VMware Provider\ndescription: |-\n  HashiCorp develops an official VMware Fusion and VMware Workstation provider\n  for Vagrant. This provider allows Vagrant to power VMware based machines and\n  take advantage of the improved stability and performance that VMware software\n  offers.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Common Issues and Troubleshooting\n\nThis page lists some common issues people run into with Vagrant and VMware\nas well as solutions for those issues.\n\n## Are my disks attached?\n\nA handy way to figure out what disks are attached (or not attached) to your guest\nis to open up the VMware GUI and open up the guest settings and selecting the\ndisks options.\n\n## How many disks can I attach?\n\nVagrant will attempt to attach all disks specified in the Vagrantfile. If more than\nfour `ide` type disks are specified, only the first four will be attached.\n\n## Applying Vagrant disk configuration changes to guests\n\nDue to how VMware works, you must reload your guest for any disk config changes\nto be applied. So if you update your Vagrantfile to update or even remove disks, make\nsure to `vagrant reload` your guests for these changes to be applied. Also note, that\nVagrant will not decrease the size of a disk.\n\n## Disk functionality with snapshots\n\nIf snapshots exist for a VM, disk functionality will be limited. Vagrant will return\nan error for any actions that are limited due to the existence of snapshots. In order\nto restore functionality the snapshots must be removed. This can be done using the\n[`vagrant snapshot delete`](/vagrant/docs/cli/snapshot) command. To delete all snapshots\nfor a VMware backed VM try `vagrant cap provider delete_all_snapshots --target <target vm name>`.\nNote once a snapshot is deleted, it can not be restored.\n"
  },
  {
    "path": "website/content/docs/disks/vmware/index.mdx",
    "content": "---\nlayout: docs\npage_title: Disks for VMware Provider\ndescription: |-\n  HashiCorp develops an official VMware Fusion and VMware Workstation provider\n  for Vagrant. This provider allows Vagrant to power VMware based machines and\n  take advantage of the improved stability and performance that VMware software\n  offers.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# VMware\n\nBecause of how VMware handles disk management, a Vagrant guest _must_ be powered\noff for any changes to be applied to a guest. If you make a configuration change\nwith a guests disk, you will need to `vagrant reload` the guest for any changes\nto be applied.\n\nFor more information on how to use VMware to configure disks for a guest, refer\nto the [general usage](/vagrant/docs/disks/usage) and [configuration](/vagrant/docs/disks/configuration)\nguide for more information.\n"
  },
  {
    "path": "website/content/docs/disks/vmware/usage.mdx",
    "content": "---\nlayout: docs\npage_title: Usage - Disks VMware Provider\ndescription: |-\n  HashiCorp develops an official VMware Fusion and VMware Workstation provider\n  for Vagrant. This provider allows Vagrant to power VMware based machines and\n  take advantage of the improved stability and performance that VMware software\n  offers.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Usage\n\nFor examples of how to use the disk feature with VMWware, please refer to the\n[general disk usage guide](/vagrant/docs/disks/usage) for more examples.\n\n## provider_config options\n\nVagrant supports some additional VMWware specific options for specifying disk.\n\nTo define a provider specific option, please refer to the [Disk Options documentation page](/vagrant/docs/disks/configuration) for more info.\n\n### Note about options defined below\n\nIt is possible these options could be out of date or stale. If you happen to see\nan option that has changed or is missing from this page, please open an issue\nor pull request on Vagrants GitHub page to correct this.\n\n- `bus_type` (string) - Sets the bus type when attaching the disk. Possible options are `sata`, `ide`, and `scsi`. Defaults to `scsi`\n- `adapter_type` (string) - Sets the adapter type when creating the disk. Possible options are `ide`, `buslogic` and `lsilogic`. Defaults to `lsilogic`\n"
  },
  {
    "path": "website/content/docs/experimental/index.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Experimental Feature Flag\ndescription: Introduction to Vagrants Experimental Feature Flag\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Experimental Feature Flag\n\nSome features that aren't ready for release can be enabled through this feature\nflag. There are a couple of different ways of going about enabling these features.\nIt is also worth noting that Vagrant will not validate the existence of a feature\nflag.\n\nFor example if you are on Linux or Mac, and you wish to enable every single experimental feature, you can set the flag\nto \"on\" by setting it to `1`:\n\n```shell\nexport VAGRANT_EXPERIMENTAL=\"1\"\n```\n\nYou can also enable some or many features if there are specific ones you would like,\nbut don't want every single feature enabled:\n\n```shell\n# Only enables feature_one\nexport VAGRANT_EXPERIMENTAL=\"feature_one\"\n```\n\n```shell\n# Enables both feature_one and feature_two\nexport VAGRANT_EXPERIMENTAL=\"feature_one,feature_two\"\n```\n\n## Valid experimental features\n\n~> **Advanced topic!** This is an advanced topic for use only if\nyou want to use new Vagrant features. If you are just getting\nstarted with Vagrant, you may safely skip this section.\n\nThis is a list of all the valid experimental features that Vagrant recognizes:\n\n* `none_communicator` - Allows Vagrant to manage remote machines without the ability to connect to them for configuration/provisioning.\n"
  },
  {
    "path": "website/content/docs/index.mdx",
    "content": "---\nlayout: docs\npage_title: Documentation\ndescription: |-\n  Welcome to the documentation for Vagrant - the command line utility for\n  managing the lifecycle of virtual machines. This website aims to document\n  every feature of Vagrant from top-to-bottom, covering as much detail as\n  possible.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant Documentation\n\nWelcome to the documentation for Vagrant - the command line utility for managing\nthe lifecycle of virtual machines. This website aims to document every feature\nof Vagrant from top-to-bottom, covering as much detail as possible. If you are\njust getting started with Vagrant, we highly recommended starting with\nthe [getting started tutorial](/vagrant/tutorials) on\nHashiCorp's Learn platform first, and then returning to this page.\n\nThe navigation will take you through each component of Vagrant. Click on a\nnavigation item to get started, or read more about\n[why developers, designers, and operators choose Vagrant](/vagrant/intro)\nfor their needs.\n"
  },
  {
    "path": "website/content/docs/installation/backwards-compatibility.mdx",
    "content": "---\nlayout: docs\npage_title: Backwards Compatibility\ndescription: Vagrant makes a very strict backwards-compatibility promise.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Backwards Compatibility\n\n## For 1.0.x\n\nVagrant 1.1+ provides full backwards compatibility for valid Vagrant 1.0.x\nVagrantfiles which do not use plugins. After installing Vagrant 1.1, your 1.0.x\nenvironments should continue working without modifications, and existing running\nmachines will continue to be managed properly.\n\nThis compatibility layer will remain in Vagrant up to and including Vagrant 2.0.\nIt may still exist after that, but Vagrant's compatibility promise is only for\ntwo versions. Seeing that major Vagrant releases take years to develop and\nrelease, it is safe to stick with your version 1.0.x Vagrantfile for the\ntime being.\n\nIf you use any Vagrant 1.0.x plugins, you must remove references to these from\nyour Vagrantfile prior to upgrading. Vagrant 1.1+ introduces a new plugin\nformat that will protect against this sort of incompatibility from ever\nhappening again.\n\n## For 1.x\n\nBackwards compatibility between 1.x is not promised, and Vagrantfile\nsyntax stability is not promised until 2.0 final. Any backwards\nincompatibilities within 1.x will be clearly documented.\n\nThis is similar to how Vagrant 0.x was handled. In practice, Vagrant 0.x\nonly introduced a handful of backwards incompatibilities during the entire\ndevelopment cycle, but the possibility of backwards incompatibilities\nis made clear so people are not surprised.\n\nVagrant 2.0 final will have a stable Vagrantfile format that will\nremain backwards compatible, just as 1.0 is considered stable.\n"
  },
  {
    "path": "website/content/docs/installation/index.mdx",
    "content": "---\nlayout: docs\npage_title: Install Vagrant\ndescription: |-\n  Vagrant is available for most platforms. Install the Vagrant\n  package using standard procedures for your operating system.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Install Vagrant\n\nTo get started with Vagrant, download the appropriate installer or\npackage for your platform from our\n[Vagrant downloads page](/vagrant/downloads). Install the package with the standard procedures for\nyour operating system.\n\nThe installer automatically adds `vagrant` to your system path\nso that it is available in terminals. If it is not found,\nlog out and back into your system; this is a common issue for Windows.\n\n~> **Rubygem installation is unsupported** Vagrant 1.0.x has the option to\nbe installed as a [RubyGem](https://en.wikipedia.org/wiki/RubyGems).\nHowever, this installation method is no longer supported. If you have an old version\nof Vagrant installed via Rubygems, remove it prior to installing newer\nversions of Vagrant.\n\n## First development environment\n\nIf you are new to Vagrant, the next step to set up a development environment is to install\na [box](/vagrant/tutorials/getting-started/getting-started-boxes).\n\n## How to use multiple hypervisors\n\nHypervisors often do not allow you to bring up virtual machines if you have more than one hypervisor in use.\n\nBelow are a couple of examples to allow you\nto use Vagrant and VirtualBox if another hypervisor is present.\n\n### Linux, VirtualBox, and KVM\n\nIf you encounter the following error message, it is because another hypervisor, like KVM, is in use.\n\n```shell-session\nThere was an error while executing `VBoxManage`, a CLI used by Vagrant for controlling VirtualBox. The command and stderr is shown below.\n\nCommand: [\"startvm\", <ID of the VM>, \"--type\", \"headless\"]\n\nStderr: VBoxManage: error: VT-x is being used by another hypervisor (VERR_VMX_IN_VMX_ROOT_MODE).\nVBoxManage: error: VirtualBox can't operate in VMX root mode. Please disable the KVM kernel extension, recompile your kernel and reboot\n(VERR_VMX_IN_VMX_ROOT_MODE)\nVBoxManage: error: Details: code NS_ERROR_FAILURE (0x80004005), component ConsoleWrap, interface IConsole\n```\n\nYou must add the additional hypervisors to the deny list in order for VirtualBox to run correctly.\n\nFirst, find out the name of the hypervisor.\n\n```shell-session\n$ lsmod | grep kvm\nkvm_intel             204800  6\nkvm                   593920  1 kvm_intel\nirqbypass              16384  1 kvm\n```\n\nUse the `blacklist` command to add the hypervisor to your denylist.\n\n```shell-session\n$ echo 'blacklist kvm-intel' >> /etc/modprobe.d/blacklist.conf\n```\n\nRestart your machine and try the `vagrant` command again.\n\n### Windows, VirtualBox, and Hyper-V\n\nIf you encounter an issue with Windows, you will get a blue screen if you attempt to bring up\na VirtualBox VM with Hyper-V enabled. \n\nIf you wish to use VirtualBox on Windows, you must ensure that Hyper-V is not enabled\non Windows. You can turn off the feature with the following Powershell command for Windows 10.\n\n```powershell\nDisable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All\n```\n\nFor Windows 11, you can use an elevated Powershell.\n\n```powershell\nbcdedit /set hypervisorlaunchtype off\n```\n\nYou can also disable Hyper-V in the Windows system settings.\n\n- Right click on the Windows button and select ‘Apps and Features’.\n- Select Turn Windows Features on or off.\n- Unselect Hyper-V and click OK.\n\nYou might have to reboot your machine for the changes to take effect. More information\nabout Hyper-V can be read [here](https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v).\n\n\n"
  },
  {
    "path": "website/content/docs/installation/source.mdx",
    "content": "---\nlayout: docs\npage_title: Installing Vagrant from Source\ndescription: |-\n  Vagrant installations from source is an advanced operation. It is only recommended\n  if you cannot use the official installer.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Install Vagrant from source\n\nVagrant installations from source is an advanced operation. We only recommended\nif you cannot use the official installer. This page details the prerequisites and three\nsteps to install Vagrant from source.\n\n1. Install Ruby\n1. Clone Vagrant\n1. Configure locally\n\n## Install Ruby\n\nTo develop and build Vagrant, you must install a specific version of Ruby. The\nspecific Ruby version that you will need is documented \nin `vagrant.gemspec`, located in the repository on GitHub. \nIt contains the most up-to-date requirements. \n\n\nYou will also need to be aware of plugin compatibility between Vagrant source installations and package based installations. Since Vagrant plugins are configured based on the current environment, plugins installed with Vagrant from source will not work with the package based Vagrant installation.\n\n## Clone Vagrant\n\nClone Vagrant's repository from GitHub into the directory where you keep code on your machine.\n\n```shell-session\n$ git clone https://github.com/hashicorp/vagrant.git\n```\n\nNext, `cd` into that path. You must initiate all commands from this directory.\n\n```shell-session\n$ cd /path/to/your/vagrant/clone\n```\n\nUse the `bundle` command with a required version\\* to install the requirements.\n\n```shell-session\n$ bundle install\n```\n\nYou can now start Vagrant with the `bundle` command. \n\n```shell-session\n$ bundle exec vagrant\n```\n\n## Configure locally\n\nIn order to use your locally-installed version of Vagrant in other projects, you will need to create a binstub and add it to your path.\n\nFirst, use the `bundle` command from your Vagrant directory.\n\n```shell-session\n$ bundle --binstubs exec\n```\n\nThis will generate files in `exec/`, including `vagrant`. You can now specify\nthe full path to the `exec/vagrant` anywhere on your operating system.\n\n```shell-session\n$ /path/to/vagrant/exec/vagrant init -m hashicorp/bionic64\n```\n\n-> **Tip:** Warning messages are expected when you use a local Vagrant from source installation, since it is not supported or recommended. \n\nIf you do not want to specify the full path to Vagrant (i.e. you just want to\nrun `vagrant`), you can create a symbolic link to your exec.\n\n```shell-session\n$ ln -sf /path/to/vagrant/exec/vagrant /usr/local/bin/vagrant\n```\n\nWhen you want to switch back to the official Vagrant version,\nremove the symlink.\n"
  },
  {
    "path": "website/content/docs/installation/uninstallation.mdx",
    "content": "---\nlayout: docs\npage_title: Uninstalling Vagrant\ndescription: |-\n  Uninstalling Vagrant is easy and straightforward. You can either uninstall\n  the Vagrant binary, the user data, or both. The sections below cover how to\n  do this on every platform.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Uninstall Vagrant\n\nTo uninstall Vagrant, you can either uninstall\nthe Vagrant binary, the user data, or both. The two sections below detail how to\nuninstall Vagrant on any platform.\n\n## Remove the Vagrant program\n\nWhen you remove the Vagrant program, it will remove the `vagrant` binary and all\ndependencies from your machine. It will _not_ remove user data. The next section provides more details on how to remove that directory from your system.\n\n### Windows machines\n\nUninstall using the **Add/Remove Programs** section of the control panel.\n\n### Mac OS X machines\n\nAs a super user, force remove the directory and remove it from your path with `pkgutil`.\\\n\n```shell-session\nsudo rm -rf /opt/vagrant /usr/local/bin/vagrant\nsudo pkgutil --forget com.vagrant.vagrant\n```\n\n### Linux machines\n\nAs a super user, force remove the Vagrant directories.\n\n```shell-session\nrm -rf /opt/vagrant\nrm -f /usr/bin/vagrant\n```\n\n## Remove user data\n\nThe removal of user data will remove all [boxes](/vagrant/docs/boxes),\n[plugins](/vagrant/docs/plugins), license files, and any stored state that may be used\nby Vagrant. Removing the user data effectively makes Vagrant think it\nis a fresh install.\n\nOn Linux and Mac OS platforms, the user data directory location is in the root of your home directory under `vagrant.d`. Remove the `~/.vagrant.d` directory to delete all the user data. On\nWindows, this directory is, `C:\\Users\\YourUsername\\.vagrant.d`, where\n`YourUsername` is the username of your local user.\n\nIf the Vagrant support team asks you to remove this\ndirectory to debug, you should make a backup.\n\nWhen you use Vagrant again, Vagrant will automatically regenerate any data necessary to operate.\n\n## Reinstall Vagrant\n\nIf you decide to reinstall Vagrant, you can\nfollow the [installation docs](/vagrant/docs/installation) again for any standard\nmethod.\n"
  },
  {
    "path": "website/content/docs/installation/upgrading-from-1-0.mdx",
    "content": "---\nlayout: docs\npage_title: Upgrade from Vagrant 1.0\ndescription: |-\n  The upgrade process from 1.0.x to 1.x is straightforward, Vagrant is\n  backwards compatible with Vagrant 1.0.x. To reinstall Vagrant\n  over your previous installation, download the latest package and\n  install it with standard procedures for your operating system.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Upgrade from Vagrant 1.0.x\n\nThe upgrade process from 1.0.x to 1.x is straightforward. Vagrant is quite\n[backwards compatible](/vagrant/docs/installation/backwards-compatibility)\nwith Vagrant 1.0.x, so you can simply reinstall Vagrant\nover your previous installation by downloading the latest package and\ninstalling it using standard procedures for your operating system.\n\nAs the [backwards compatibility](/vagrant/docs/installation/backwards-compatibility)\npage says, **Vagrant 1.0.x plugins will not work with Vagrant 1.1+**. Many\nof these plugins have been updated to work with newer versions of Vagrant,\nso you will need to verify if they've been updated. If not however, you will have\nto remove them before the upgrade.\n\nWe recommend that you remove _all_ plugins before the upgrade and then slowly\nadd them back. This usually makes for a smoother upgrade process.\n\n~> **If your version of Vagrant was installed via Rubygems**, you\nmust uninstall the old version prior to installing the package for the\nnew version of Vagrant. The Rubygems installation is no longer supported.\n"
  },
  {
    "path": "website/content/docs/installation/upgrading.mdx",
    "content": "---\nlayout: docs\npage_title: Upgrade Vagrant\ndescription: |-\n  This page details the general process to upgrade Vagrant for the\n  1.x.x series. If you need to upgrade from Vagrant 1.0.x, read the specific page\n  dedicated to that.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Upgrade Vagrant\n\nThis page details how to upgrade Vagrant in the 1.x.x series.\n\n~> If you need to upgrade from Vagrant 1.0.x, read the\n[specific page dedicated to that](/vagrant/docs/installation/upgrading-from-1-0).\n\nVagrant upgrades during the 1.x.x release series are straightforward:\n\n1. [Download](/vagrant/downloads) the new package\n2. Install it over the existing package\n\nThe installers will properly overwrite and remove old files. It is recommended\nthat no other Vagrant processes are running during the upgrade process.\n\n## Vagrantfile compatibility with 3.0\n\nNote that Vagrantfile stability for the new Vagrantfile syntax is not\nguaranteed until Vagrant 3.0. While Vagrantfiles made for 1.0.x will\n[continue to work](/vagrant/docs/installation/backwards-compatibility),\nnewer Vagrantfiles may have backwards incompatible changes until 3.0.\n\n## Issue reports\n\nIf you encounter any problems at upgrade time, [report them as an issue in Github](https://github.com/hashicorp/vagrant/issues). Upgrades are meant to be a smooth process and we consider it a bug if it was not.\n"
  },
  {
    "path": "website/content/docs/multi-machine.mdx",
    "content": "---\nlayout: docs\npage_title: Multi-Machine\ndescription: |-\n  Vagrant is able to define and control multiple guest machines per\n  Vagrantfile. This is known as a \"multi-machine\" environment.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Multi-Machine\n\nVagrant is able to define and control multiple guest machines per\nVagrantfile. This is known as a \"multi-machine\" environment.\n\nThese machines are generally able to work together or are somehow associated\nwith each other. Here are some use-cases people are using multi-machine\nenvironments for today:\n\n- Accurately modeling a multi-server production topology, such as separating\n  a web and database server.\n- Modeling a distributed system and how they interact with each other.\n- Testing an interface, such as an API to a service component.\n- Disaster-case testing: machines dying, network partitions, slow networks,\n  inconsistent world views, etc.\n\nHistorically, running complex environments such as these was done by\nflattening them onto a single machine. The problem with that is that it is\nan inaccurate model of the production setup, which can behave far differently.\n\nUsing the multi-machine feature of Vagrant, these environments can be modeled\nin the context of a single Vagrant environment without losing any of the\nbenefits of Vagrant.\n\n## Defining Multiple Machines\n\nMultiple machines are defined within the same project [Vagrantfile](/vagrant/docs/vagrantfile/)\nusing the `config.vm.define` method call. This configuration directive\nis a little funny, because it creates a Vagrant configuration within a\nconfiguration. An example shows this best:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"shell\", inline: \"echo Hello\"\n\n  config.vm.define \"web\" do |web|\n    web.vm.box = \"apache\"\n  end\n\n  config.vm.define \"db\" do |db|\n    db.vm.box = \"mysql\"\n  end\nend\n```\n\nAs you can see, `config.vm.define` takes a block with another variable. This\nvariable, such as `web` above, is the _exact_ same as the `config` variable,\nexcept any configuration of the inner variable applies only to the machine\nbeing defined. Therefore, any configuration on `web` will only affect the\n`web` machine.\n\nAnd importantly, you can continue to use the `config` object as well. The\nconfiguration object is loaded and merged before the machine-specific configuration,\njust like other Vagrantfiles within the\n[Vagrantfile load order](/vagrant/docs/vagrantfile/#load-order).\n\nIf you are familiar with programming, this is similar to how languages have\ndifferent variable scopes.\n\nWhen using these scopes, order of execution for things such as\nprovisioners becomes important. Vagrant enforces ordering outside-in, in\nthe order listed in the Vagrantfile. For example, with the Vagrantfile\nbelow:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision :shell, inline: \"echo A\"\n\n  config.vm.define :testing do |test|\n    test.vm.provision :shell, inline: \"echo B\"\n  end\n\n  config.vm.provision :shell, inline: \"echo C\"\nend\n```\n\nThe provisioners in this case will output \"A\", then \"C\", then \"B\". Notice\nthat \"B\" is last. That is because the ordering is outside-in, in\nthe order of the file.\n\nIf you want to apply a slightly different configuration to multiple machines,\nsee [this tip](/vagrant/docs/vagrantfile/tips#loop-over-vm-definitions).\n\n## Controlling Multiple Machines\n\nThe moment more than one machine is defined within a Vagrantfile, the\nusage of the various `vagrant` commands changes slightly. The change should\nbe mostly intuitive.\n\nCommands that only make sense to target a single machine, such as\n`vagrant ssh`, now _require_ the name of the machine to control. Using\nthe example above, you would say `vagrant ssh web` or `vagrant ssh db`.\n\nOther commands, such as `vagrant up`, operate on _every_ machine by\ndefault. So if you ran `vagrant up`, Vagrant would bring up both the\nweb and DB machine. You could also optionally be specific and say\n`vagrant up web` or `vagrant up db`.\n\nAdditionally, you can specify a regular expression for matching only\ncertain machines. This is useful in some cases where you specify many similar\nmachines, for example if you are testing a distributed service you may have\na `leader` machine as well as a `follower0`, `follower1`, `follower2`, etc. If you\nwant to bring up all the followers but not the leader, you can just do\n`vagrant up /follower[0-9]/`. If Vagrant sees a machine name within forward\nslashes, it assumes you are using a regular expression.\n\n## Communication Between Machines\n\nIn order to facilitate communication within machines in a multi-machine setup,\nthe various [networking](/vagrant/docs/networking/) options should be used.\nIn particular, the [private network](/vagrant/docs/networking/private_network) can\nbe used to make a private network between multiple machines and the host.\n\n## Specifying a Primary Machine\n\nYou can also specify a _primary machine_. The primary machine will be the\ndefault machine used when a specific machine in a multi-machine environment\nis not specified.\n\nTo specify a default machine, just mark it primary when defining it. Only\none primary machine may be specified.\n\n```ruby\nconfig.vm.define \"web\", primary: true do |web|\n  # ...\nend\n```\n\n## Autostart Machines\n\nBy default in a multi-machine environment, `vagrant up` will start\nall of the defined machines. The `autostart` setting allows you to tell\nVagrant to _not_ start specific machines. Example:\n\n```ruby\nconfig.vm.define \"web\"\nconfig.vm.define \"db\"\nconfig.vm.define \"db_follower\", autostart: false\n```\n\nWhen running `vagrant up` with the settings above, Vagrant will automatically\nstart the \"web\" and \"db\" machines, but will not start the \"db_follower\" machine.\nYou can manually force the \"db_follower\" machine to start by running\n`vagrant up db_follower`.\n"
  },
  {
    "path": "website/content/docs/networking/basic_usage.mdx",
    "content": "---\nlayout: docs\npage_title: Basic Usage - Networking\ndescription: |-\n  Vagrant offers multiple options for how you are able to connect your\n  guest machines to the network, but there is a standard usage pattern as\n  well as some points common to all network configurations that\n  are important to know.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Basic Usage of Networking\n\nVagrant offers multiple options for how you are able to connect your\nguest machines to the network, but there is a standard usage pattern as\nwell as some points common to all network configurations that\nare important to know.\n\n## Configuration\n\nAll networks are configured within your [Vagrantfile](/vagrant/docs/vagrantfile/)\nusing the `config.vm.network` method call. For example, the Vagrantfile\nbelow defines some port forwarding:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # ...\n  config.vm.network \"forwarded_port\", guest: 80, host: 8080\nend\n```\n\nEvery network type has an identifier such as `\"forwarded_port\"` in the above\nexample. Following this is a set of configuration arguments that can differ\nfor each network type. In the case of forwarded ports, two numeric arguments\nare expected: the port on the guest followed by the port on the host that\nthe guest port can be accessed by.\n\n## Multiple Networks\n\nMultiple networks can be defined by having multiple `config.vm.network`\ncalls within the Vagrantfile. The exact meaning of this can differ for\neach [provider](/vagrant/docs/providers/), but in general the order specifies\nthe order in which the networks are enabled.\n\n## Enabling Networks\n\nNetworks are automatically configured and enabled after they've been defined\nin the Vagrantfile as part of the `vagrant up` or `vagrant reload` process.\n\n## Setting Hostname\n\nA hostname may be defined for a Vagrant VM using the `config.vm.hostname`\nsetting. By default, this will modify `/etc/hosts`, adding the hostname\non a loopback interface that is not in use. For example:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # ...\n  config.vm.hostname = \"myhost.local\"\nend\n```\n\nwill add the entry `127.0.X.1 myhost myhost.local` to `/etc/hosts`.\n\nA public or private network with an assigned IP may be flagged for hostname.\nIn this case, the hostname will be added to the flagged network. Note, that\nif there are multiple networks only one may be flagged for hostname. For\nexample:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # ...\n  config.vm.hostname = \"myhost.local\"\n  config.vm.network \"public_network\", ip: \"192.168.0.1\", hostname: true\n  config.vm.network \"public_network\", ip: \"192.168.0.2\"\nend\n```\n\nwill add the entry `192.168.0.1 myhost myhost.local` to `/etc/hosts`.\n"
  },
  {
    "path": "website/content/docs/networking/forwarded_ports.mdx",
    "content": "---\nlayout: docs\npage_title: Forwarded Ports - Networking\ndescription: |-\n  Vagrant forwarded ports allow you to access a port on your host machine and\n  have all data forwarded to a port on the guest machine, over either TCP or\n  UDP.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Forwarded Ports\n\n**Network identifier: `forwarded_port`**\n\nVagrant forwarded ports allow you to access a port on your host machine and have\nall data forwarded to a port on the guest machine, over either TCP or UDP.\n\nFor example: If the guest machine is running a web server listening on port 80,\nyou can make a forwarded port mapping to port 8080 (or anything) on your host\nmachine. You can then open your browser to `localhost:8080` and browse the\nwebsite, while all actual network data is being sent to the guest.\n\n## Defining a Forwarded Port\n\nThe forwarded port configuration expects two parameters, the port on the\nguest and the port on the host. Example:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"forwarded_port\", guest: 80, host: 8080\nend\n```\n\nThis will allow accessing port 80 on the guest via port 8080 on the host.\n\nFor most providers, forwarded ports by default bind to all interfaces. This\nmeans that other devices on your network can access the forwarded ports.\nIf you want to restrict access, see the `guest_ip` and `host_ip` settings\nbelow.\n\n## Options Reference\n\nThis is a complete list of the options that are available for forwarded\nports. Only the `guest` and `host` options are required. Below this section,\nthere are more detailed examples of using these options.\n\n- `auto_correct` (boolean) - If true, the host port will be changed\n  automatically in case it collides with a port already in use. By\n  default, this is false.\n\n- `guest` (int) - The port on the guest that you want to be exposed on\n  the host. This can be any port.\n\n- `guest_ip` (string) - The guest IP to bind the forwarded port to. If\n  this is not set, the port will go to every IP interface. By default,\n  this is empty.\n\n- `host` (int) - The port on the host that you want to use to access the\n  port on the guest. This must be greater than port 1024 unless Vagrant\n  is running as root (which is not recommended).\n\n- `host_ip` (string) - The IP on the host you want to bind the forwarded\n  port to. If not specified, it will be bound to every IP. By default,\n  this is empty.\n\n- `protocol` (string) - Either \"udp\" or \"tcp\". This specifies the protocol\n  that will be allowed through the forwarded port. By default this is \"tcp\".\n\n- `id` (string) - Name of the rule (can be visible in VirtualBox). By\n  default this is \"protocol\"\"guest\" (example : \"tcp123\").\n\n## Forwarded Port Protocols\n\nBy default, any defined port will only forward the TCP protocol. As an optional\nthird parameter, you may specify `protocol: 'udp'` in order to pass UDP\ntraffic. If a given port needs to be able to listen to the same port on both\nprotocols, you must define the port twice with each protocol specified, like\nso:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"forwarded_port\", guest: 2003, host: 12003, protocol: \"tcp\"\n  config.vm.network \"forwarded_port\", guest: 2003, host: 12003, protocol: \"udp\"\nend\n```\n\n## Port Collisions and Correction\n\nIt is common when running multiple Vagrant machines to unknowingly create\nforwarded port definitions that collide with each other (two separate\nVagrant projects forwarded to port 8080, for example). Vagrant includes\nbuilt-in mechanism to detect this and correct it, automatically.\n\nPort collision detection is always done. Vagrant will not allow you to\ndefine a forwarded port where the port on the host appears to be accepting\ntraffic or connections.\n\nPort collision auto-correction must be manually enabled for each forwarded\nport, since it is often surprising when it occurs and can lead the Vagrant\nuser to think that the port was not properly forwarded. Enabling auto correct\nis easy:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"forwarded_port\", guest: 80, host: 8080,\n    auto_correct: true\nend\n```\n\nThe final `:auto_correct` parameter set to true tells Vagrant to auto\ncorrect any collisions. During a `vagrant up` or `vagrant reload`, Vagrant\nwill output information about any collisions detections and auto corrections\nmade, so you can take notice and act accordingly.\n\nYou can define allowed port range assignable by Vagrant when port collision is\ndetected via [config.vm.usable_port_range](/vagrant/docs/vagrantfile/machine_settings) property.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.usable_port_range = 8000..8999\nend\n```\n"
  },
  {
    "path": "website/content/docs/networking/index.mdx",
    "content": "---\nlayout: docs\npage_title: Networking\ndescription: |-\n  In order to access the Vagrant environment created, Vagrant exposes\n  some high-level networking options for things such as forwarded ports,\n  connecting to a public network, or creating a private network.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Networking\n\nIn order to access the Vagrant environment created, Vagrant exposes\nsome high-level networking options for things such as forwarded ports,\nconnecting to a public network, or creating a private network.\n\nThe high-level networking options are meant to define an abstraction that\nworks across multiple [providers](/vagrant/docs/providers/). This means that\nyou can take your Vagrantfile you used to spin up a VirtualBox machine and\nyou can reasonably expect that Vagrantfile to behave the same with something\nlike VMware.\n\nYou should first read the [basic usage](/vagrant/docs/networking/basic_usage) page\nand then continue by reading the documentation for a specific networking\nprimitive by following the navigation to the left.\n\n## Advanced Configuration\n\nIn some cases,\nthese options are _too_ high-level, and you may want to more finely tune\nand configure the network interfaces of the underlying machine. Most\nproviders expose [provider-specific configuration](/vagrant/docs/providers/configuration)\nto do this, so please read the documentation for your specific provider\nto see what options are available.\n\n-> **For beginners:** It is strongly recommended you use\nonly the high-level networking options until you are comfortable\nwith the Vagrant workflow and have things working at a basic level.\nProvider-specific network configuration can very quickly lock you out\nof your guest machine if improperly done.\n\n## Networking Assumptions\n\n### There is a NAT available\n\nVagrant assumes there is an available NAT device on eth0. This ensures\nthat Vagrant always has a way of communicating with the guest machine.\nIt is possible to change this manually (outside of Vagrant), however,\nthis may lead to inconsistent behavior.\nProviders might have additional assumptions. For example, in VirtualBox,\nthis assumption means that network adapter 1 is a NAT device.\n"
  },
  {
    "path": "website/content/docs/networking/private_network.mdx",
    "content": "---\nlayout: docs\npage_title: Private Networks - Networking\ndescription: |-\n  Vagrant private networks allow you to access your guest machine by some\n  address that is not publicly accessible from the global internet. In general,\n  this means your machine gets an address in the private address space.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Private Networks\n\n**Network identifier: `private_network`**\n\nVagrant private networks allow you to access your guest machine by some address\nthat is not publicly accessible from the global internet. In general, this\nmeans your machine gets an address in the [private address space](https://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces).\n\nMultiple machines within the same private network (also usually with the\nrestriction that they're backed by the same [provider](/vagrant/docs/providers/))\ncan communicate with each other on private networks.\n\n-> **Guest operating system support.** Private networks generally\nrequire configuring the network adapters on the guest machine. This process\nvaries from OS to OS. Vagrant ships with knowledge of how to configure\nnetworks on a variety of guest operating systems, but it is possible if you\nare using a particularly old or new operating system that private networks\nwill not properly configure.\n\n## DHCP\n\nThe easiest way to use a private network is to allow the IP to be assigned\nvia DHCP.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"private_network\", type: \"dhcp\"\nend\n```\n\nThis will automatically assign an IP address from the reserved address space.\nThe IP address can be determined by using `vagrant ssh` to SSH into the\nmachine and using the appropriate command line tool to find the IP,\nsuch as `ifconfig` or `ip addr show`.\n\n## Static IP\n\nYou can also specify a static IP address for the machine. This lets you\naccess the Vagrant managed machine using a static, known IP. The\nVagrantfile for a static IP looks like this:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"private_network\", ip: \"192.168.50.4\"\nend\n```\n\nIt is up to the users to make sure that the static IP does not collide\nwith any other machines on the same network.\n\nWhile you can choose any IP you would like, you _should_ use an IP from\nthe [reserved private address space](https://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces). These IPs are guaranteed to never be publicly routable,\nand most routers actually block traffic from going to them from the\noutside world.\n\nFor some operating systems, additional configuration options for the static\nIP address are available such as setting the default gateway or MTU.\n\n~> **Warning!** Do not choose an IP that overlaps with any\nother IP space on your system. This can cause the network to not be\nreachable.\n\n## IPv6\n\nYou can specify a static IP via IPv6. DHCP for IPv6 is not supported.\nTo use IPv6, just specify an IPv6 address as the IP:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"private_network\", ip: \"fde4:8dba:82e1::c4\"\nend\n```\n\nThis will assign that IP to the machine. The entire `/64` subnet will\nbe reserved. Please make sure to use the reserved local addresses approved\nfor IPv6.\n\nYou can also modify the prefix length by changing the `netmask` option\n(defaults to 64):\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"private_network\",\n    ip: \"fde4:8dba:82e1::c4\",\n    netmask: \"96\"\nend\n```\n\nIPv6 supports for private networks was added in Vagrant 1.7.5 and may\nnot work with every provider.\n\n## Disable Auto-Configuration\n\nIf you want to manually configure the network interface yourself, you\ncan disable Vagrant's auto-configure feature by specifying `auto_config`:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"private_network\", ip: \"192.168.50.4\",\n    auto_config: false\nend\n```\n\nIf you already started the Vagrant environment before setting `auto_config`,\nthe files it initially placed there will stay there. You will have to remove\nthose files manually or destroy and recreate the machine.\n\nThe files created by Vagrant depend on the OS. For example, for many\nLinux distros, this is `/etc/network/interfaces`. In general you should\nlook in the normal location that network interfaces are configured for your\ndistro.\n"
  },
  {
    "path": "website/content/docs/networking/public_network.mdx",
    "content": "---\nlayout: docs\npage_title: Public Networks - Networking\ndescription: |-\n  Vagrant public networks are less private than private networks, and the exact\n  meaning actually varies from provider to provider, hence the ambiguous\n  definition. The idea is that while private networks should never allow the\n  general public access to your machine, public networks can.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Public Networks\n\n**Network identifier: `public_network`**\n\nVagrant public networks are less private than private networks, and the exact\nmeaning actually varies from [provider to provider](/vagrant/docs/providers/),\nhence the ambiguous definition. The idea is that while\n[private networks](/vagrant/docs/networking/private_network) should never allow the\ngeneral public access to your machine, public networks can.\n\n-> **Confused?** We kind of are, too. It is likely that public\nnetworks will be replaced by `:bridged` in a future release, since\nthat is in general what should be done with public networks, and providers\nthat do not support bridging generally do not have any other features that map\nto public networks either.\n\n~> **Warning!** Vagrant boxes are insecure by default\nand by design, featuring public passwords, insecure keypairs\nfor SSH access, and potentially allow root access over SSH. With\nthese known credentials, your box is easily accessible by anyone on\nyour network. Before configuring Vagrant to use a public network,\nconsider _all_ potential security implications\nand review the [default box configuration](/vagrant/docs/boxes/base) to identify potential security risks.\n\n## DHCP\n\nThe easiest way to use a public network is to allow the IP to be assigned\nvia DHCP. In this case, defining a public network is trivially easy:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"public_network\"\nend\n```\n\nWhen DHCP is used, the IP can be determined by using `vagrant ssh` to\nSSH into the machine and using the appropriate command line tool to find\nthe IP, such as `ifconfig`.\n\n### Using the DHCP Assigned Default Route\n\nSome cases require the DHCP assigned default route to be untouched. In these cases one\nmay specify the `use_dhcp_assigned_default_route` option. As an example:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"public_network\",\n    use_dhcp_assigned_default_route: true\nend\n```\n\n## Static IP\n\nDepending on your setup, you may wish to manually set the IP of your\nbridged interface. To do so, add a `:ip` clause to the network definition.\n\n```ruby\nconfig.vm.network \"public_network\", ip: \"192.168.0.17\"\n```\n\n## Default Network Interface\n\nIf more than one network interface is available on the host machine, Vagrant will\nask you to choose which interface the virtual machine should bridge to. A default\ninterface can be specified by adding a `:bridge` clause to the network definition.\n\n```ruby\nconfig.vm.network \"public_network\", bridge: \"en1: Wi-Fi (AirPort)\"\n```\n\nThe string identifying the desired interface must exactly match the name of an\navailable interface. If it cannot be found, Vagrant will ask you to pick\nfrom a list of available network interfaces.\n\nWith some providers, it is possible to specify a list of adapters to bridge\nagainst:\n\n```ruby\nconfig.vm.network \"public_network\", bridge: [\n  \"en1: Wi-Fi (AirPort)\",\n  \"en6: Broadcom NetXtreme Gigabit Ethernet Controller\",\n]\n```\n\nIn this example, the first network adapter that exists and can successfully be\nbridge will be used.\n\n## Disable Auto-Configuration\n\nIf you want to manually configure the network interface yourself, you\ncan disable auto-configuration by specifying `auto_config`:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"public_network\", auto_config: false\nend\n```\n\nThen the shell provisioner can be used to configure the ip of the interface:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"public_network\", auto_config: false\n\n  # manual ip\n  config.vm.provision \"shell\",\n    run: \"always\",\n    inline: \"ifconfig eth1 192.168.0.17 netmask 255.255.255.0 up\"\n\n  # manual ipv6\n  config.vm.provision \"shell\",\n    run: \"always\",\n    inline: \"ifconfig eth1 inet6 add fc00::17/7\"\nend\n```\n\n## Default Router\n\nDepending on your setup, you may wish to manually override the default\nrouter configuration. This is required if you need to access the Vagrant box from\nother networks over the public network. To do so, you can use a shell\nprovisioner script:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"public_network\", ip: \"192.168.0.17\"\n\n  # default router\n  config.vm.provision \"shell\",\n    run: \"always\",\n    inline: \"route add default gw 192.168.0.1\"\n\n  # default router ipv6\n  config.vm.provision \"shell\",\n    run: \"always\",\n    inline: \"route -A inet6 add default gw fc00::1 eth1\"\n\n  # delete default gw on eth0\n  config.vm.provision \"shell\",\n    run: \"always\",\n    inline: \"eval `route -n | awk '{ if ($8 ==\\\"eth0\\\" && $2 != \\\"0.0.0.0\\\") print \\\"route del default gw \\\" $2; }'`\"\nend\n```\n\nOr, an alternative, simpler version, assuming you get DHCP from your public network:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"public_network\"\n\n  # default router\n  config.vm.provision \"shell\",\n    run: \"always\",\n    inline: \"ip route del default via 10.0.2.2 || true\"\nend\n```\n\nNote the above are fairly complex and will be guest OS specific, but we\ndocument the rough idea of how to do it because it is a common question.\n"
  },
  {
    "path": "website/content/docs/other/debugging.mdx",
    "content": "---\nlayout: docs\npage_title: Debugging and Troubleshooting\ndescription: |-\n  As much as we try to keep Vagrant stable and bug free, it is inevitable\n  that issues will arise and Vagrant will behave in unexpected ways. In\n  these cases, Vagrant has amazing support channels available to assist you.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Debugging\n\nAs much as we try to keep Vagrant stable and bug free, it is inevitable\nthat issues will arise and Vagrant will behave in unexpected ways.\n\nWhen using these support channels, it is generally helpful to include\ndebugging logs along with any error reports. These logs can often help you\ntroubleshoot any problems you may be having.\n\n!> **Scan for sensitive information!** Vagrant debug logs include information\nabout your system including environment variables and user information. If you\nstore sensitive information in the environment or in your user account, please\nscan or scrub the debug log of this information before uploading the contents to\nthe public Internet.\n\n~> **Submit debug logs using GitHub Gist.** If you plan on submitting a bug\nreport or issue that includes debug-level logs, please use a service like\n[Gist](https://gist.github.com). **Do not** paste the raw debug logs into an\nissue as it makes it very difficult to scroll and parse the information.\n\nTo enable detailed logging, set the `VAGRANT_LOG` environmental variable\nto the desired log level name, which is one of `debug` (loud), `info` (normal),\n`warn` (quiet), and `error` (very quiet). When asking for support, please\nset this to `debug`. When troubleshooting your own issues, you should start\nwith `info`, which is much quieter, but contains important information\nabout the behavior of Vagrant.\n\nOn Linux and Mac systems, this can be done by prepending the `vagrant`\ncommand with an environmental variable declaration:\n\n```shell-session\n$ VAGRANT_LOG=info vagrant up\n```\n\nOn Windows, multiple steps are required:\n\n```shell-session\n$ set VAGRANT_LOG=info\n$ vagrant up\n```\n\nYou can also get the debug level output using the `--debug` command line\noption. For example:\n\n```shell-session\n$ vagrant up --debug\n```\n\nOn Linux and Mac, if you are saving the output to a file, you may need to redirect stderr and\nstdout using `&>`:\n\n```shell-session\n$ vagrant up --debug &> vagrant.log\n```\n\nOn Windows in PowerShell (outputs to log and screen):\n\n```shell-session\n$ vagrant up --debug 2>&1 | Tee-Object -FilePath \".\\vagrant.log\"\n```\n"
  },
  {
    "path": "website/content/docs/other/environmental-variables.mdx",
    "content": "---\nlayout: docs\npage_title: Environmental Variables\ndescription: |-\n  Vagrant has a set of environmental variables that can be used to\n  configure and control it in a global way. This page lists those environmental\n  variables.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Environmental Variables\n\nVagrant has a set of environmental variables that can be used to\nconfigure and control it in a global way. This page lists those environmental\nvariables.\n\n## `CFLAGS`\n\nIf set the contents of this environment variable will be appended to the\nvalue generated by the Vagrant launcher.\n\n## `CPPFLAGS`\n\nIf set the contents of this environment variable will be appended to the\nvalue generated by the Vagrant launcher.\n\n## `CURL_CA_BUNDLE`\n\nIf set this environment variable will be passed through to the Vagrant\nprocess. By default Vagrant will use the CA certificate included with\nthe Vagrant installation.\n\n## `LDFLAGS`\n\nIf set the contents of this environment variable will be appended to the\nvalue generated by the Vagrant launcher.\n\n## `SSL_CERT_FILE`\n\nIf set this environment variable will be passed through to the Vagrant\nprocess. By default Vagrant will use the CA certificate included with\nthe Vagrant installation.\n\n## `VAGRANT_ALIAS_FILE`\n\n`VAGRANT_ALIAS_FILE` can be set to change the file where Vagrant aliases are\ndefined. By default, this is set to `~/.vagrant.d/aliases`.\n\n## `VAGRANT_ALLOW_PLUGIN_SOURCE_ERRORS`\n\nIf this is set to any value, then Vagrant will not error when a configured\nplugin source is unavailable. When installing a Vagrant plugin Vagrant\nwill error and halt if a plugin source is inaccessible. In some cases it\nmay be desirable to ignore inaccessible sources and continue with the\nplugin installation. Enabling this value will cause Vagrant to simply log\nthe plugin source error and continue.\n\n## `VAGRANT_BOX_UPDATE_CHECK_DISABLE`\n\nBy default, Vagrant will query the metadata API server to see if a newer\nbox version is available for download. This optional can be disabled on a\nper-Vagrantfile basis with `config.vm.box_check_update`, but it can also be\ndisabled globally setting `VAGRANT_BOX_UPDATE_CHECK_DISABLE` to any non-empty\nvalue.\n\nThis option will not affect global box functions like `vagrant box update`.\n\n## `VAGRANT_CHECKPOINT_DISABLE`\n\nVagrant does occasional network calls to check whether the version of Vagrant\nthat is running locally is up to date. We understand that software making remote\ncalls over the internet for any reason can be undesirable. To suppress these\ncalls, set the environment variable `VAGRANT_CHECKPOINT_DISABLE` to any\nnon-empty value.\n\nIf you use other HashiCorp tools like Packer and would prefer to configure this\nsetting only once, you can set `CHECKPOINT_DISABLE` instead.\n\n## `VAGRANT_CWD`\n\n`VAGRANT_CWD` can be set to change the working directory of Vagrant. By\ndefault, Vagrant uses the current directory you are in. The working directory\nis important because it is where Vagrant looks for the Vagrantfile. It\nalso defines how relative paths in the Vagrantfile are expanded, since they're\nexpanded relative to where the Vagrantfile is found.\n\nThis environmental variable is most commonly set when running Vagrant from\na scripting environment in order to set the directory that Vagrant sees.\n\n## `VAGRANT_DEBUG_LAUNCHER`\n\nFor performance reasons, especially for Windows users, Vagrant uses a static\nbinary to launch the actual Vagrant process. If you have _very_ early issues\nwhen launching Vagrant from the official installer, you can specify the\n`VAGRANT_DEBUG_LAUNCHER` environment variable to output debugging information\nabout the launch process.\n\n## `VAGRANT_DEFAULT_PROVIDER`\n\nThis configures the default provider Vagrant will use.\n\nThis normally does not need to be set since Vagrant is fairly intelligent\nabout how to detect the default provider. By setting this, you will force\nVagrant to use this provider for any _new_ Vagrant environments. Existing\nVagrant environments will continue to use the provider they came `up` with.\nOnce you `vagrant destroy` existing environments, this will take effect.\n\n## `VAGRANT_DEFAULT_TEMPLATE`\n\nThis configures the template used by `vagrant init` when the `--template` option\nis not provided.\n\n## `VAGRANT_DETECTED_ARCH`\n\nThis environment variable may be set by the Vagrant launcher to help determine\nthe current runtime architecture in use. In general Vagrant will set this value\nwhen running on a Windows host using a cygwin or msys based shell. The value\nthe Vagrant launcher may set in this environment variable will not always match\nthe actual architecture of the platform itself. Instead it signifies the detected\narchitecture of the environment it is running within. If this value is set, the\nVagrant launcher will not modify it.\n\n## `VAGRANT_DETECTED_OS`\n\nThis environment variable may be set by the Vagrant launcher to help determine\nthe current runtime platform. In general Vagrant will set this value when running\non a Windows host using a cygwin or msys based shell. If this value is set, the\nVagrant launcher will not modify it.\n\n## `VAGRANT_DISABLE_RESOLV_REPLACE`\n\nVagrant can optionally use the Ruby Resolv library in place of the libc resolver.\nThis can be disabled setting this environment variable.\n\n## `VAGRANT_DISABLE_VBOXSYMLINKCREATE`\n\nIf set, this will completely disable the ability to create symlinks with all VirtualBox\nshared folders. If this environment variable is not set, the VirtualBox synced\nfolders option `SharedFoldersEnableSymlinksCreate` will be enabled by default.\nThis option can be overridden on a per-folder basis within your Vagrantfile\nconfig by setting the `SharedFoldersEnableSymlinksCreate` option to true if you\ndo not wish to completely disable this feature for all VirtualBox guests.\n\nMore information on the option can be read in the [VirtualBox synced folders docs page.](/vagrant/docs/synced-folders/virtualbox#sharedfoldersenablesymlinkscreate)\n\n## `VAGRANT_DISABLE_SMBMFSYMLINKS`\n\nIf set, this will disable the `mfsymlinks` option for mounting SMB filesystems. If not set, then the `mfsymlinks` option will be enabled by default. This option can be overriden on a pre-folder basis with your Vagrantfile by setting `mount_options: ['mfsymlinks']`.\n\n## `VAGRANT_DOTFILE_PATH`\n\n`VAGRANT_DOTFILE_PATH` can be set to change the directory where Vagrant stores\nVM-specific state, such as the VirtualBox VM UUID. By default, this is set to\n`.vagrant`. If you keep your Vagrantfile in a Dropbox folder in order to share\nthe folder between your desktop and laptop (for example), Vagrant will overwrite\nthe files in this directory with the details of the VM on the most recently-used\nhost. To avoid this, you could set `VAGRANT_DOTFILE_PATH` to `.vagrant-laptop`\nand `.vagrant-desktop` on the respective machines. (Remember to update your\n`.gitignore`!)\n\n## `VAGRANT_ENABLE_RESOLV_REPLACE`\n\nUse the Ruby Resolv library in place of the libc resolver.\n\n## `VAGRANT_FORCE_COLOR`\n\nIf this is set to any value, then Vagrant will force colored output, even\nif it detected that there is no TTY or the current environment does not\nsupport it.\n\nThe equivalent behavior can be achieved by using the `--color` flag on a\ncommand-by-command basis. This environmental variable is useful for setting\nthis flag globally.\n\n## `VAGRANT_HOME`\n\n`VAGRANT_HOME` can be set to change the directory where Vagrant stores\nglobal state. By default, this is set to `~/.vagrant.d`. The Vagrant home\ndirectory is where things such as boxes are stored, so it can actually become\nquite large on disk.\n\n## `VAGRANT_IGNORE_WINRM_PLUGIN`\n\nVagrant will not display warning when `vagrant-winrm` plugin is installed.\n\n## `VAGRANT_INSTALL_LOCAL_PLUGINS`\n\nIf this is set to any value, Vagrant will not prompt for confirmation\nprior to installing local plugins which have been defined within the\nlocal Vagrantfile.\n\n## `VAGRANT_IS_HYPERV_ADMIN`\n\nDisable Vagrant's check for Hyper-V admin privileges and allow Vagrant to assume\nthe current user has full access to Hyper-V. This is useful if the internal\nprivilege check incorrectly determines the current user does not have access\nto Hyper-V.\n\n## `VAGRANT_LOCAL_PLUGINS_LOAD`\n\nIf this is set Vagrant will not stub the Vagrantfile when running\n`vagrant plugin` commands. When this environment variable is set the\n`--local` flag will not be required by `vagrant plugin` commands to\nenable local project plugins.\n\n## `VAGRANT_LOG`\n\n`VAGRANT_LOG` specifies the verbosity of log messages from Vagrant.\nBy default, Vagrant does not actively show any log messages.\n\nLog messages are very useful when troubleshooting issues, reporting\nbugs, or getting support. At the most verbose level, Vagrant outputs\nbasically everything it is doing.\n\nAvailable log levels are \"debug,\" \"info,\" \"warn,\" and \"error.\" Both\n\"warn\" and \"error\" are practically useless since there are very few\ncases of these, and Vagrant generally reports them within the normal\noutput.\n\n\"info\" is a good level to start with if you are having problems, because\nwhile it is much louder than normal output, it is still very human-readable\nand can help identify certain issues.\n\n\"debug\" output is _extremely_ verbose and can be difficult to read without\nsome knowledge of Vagrant internals. It is the best output to attach to\na support request or bug report, however.\n\n## `VAGRANT_MAX_REBOOT_RETRY_DURATION`\n\nBy default, Vagrant will wait up to 120 seconds for a machine to reboot.\nHowever, if you're finding your OS is taking longer than 120 seconds to\nreboot successfully, you can configure this environment variable and Vagrant\nwill wait for the configured number of seconds.\n\n## `VAGRANT_NO_COLOR`\n\nIf this is set to any value, then Vagrant will not use any colorized\noutput. This is useful if you are logging the output to a file or\non a system that does not support colors.\n\nThe equivalent behavior can be achieved by using the `--no-color` flag\non a command-by-command basis. This environmental variable is useful\nfor setting this flag globally.\n\n## `VAGRANT_NO_PARALLEL`\n\nIf this is set, Vagrant will not perform any parallel operations (such as\nparallel box provisioning). All operations will be performed in serial.\n\n## `VAGRANT_NO_PLUGINS`\n\nIf this is set to any value, then Vagrant will not load any 3rd party\nplugins. This is useful if you install a plugin and it is introducing\ninstability to Vagrant, or if you want a specific Vagrant environment to\nnot load plugins.\n\nNote that any `vagrant plugin` commands automatically do not load any\nplugins, so if you do install any unstable plugins, you can always use\nthe `vagrant plugin` commands without having to worry.\n\n## `VAGRANT_POWERSHELL_VERSION_DETECTION_TIMEOUT`\n\nVagrant will use a default timeout when checking for the installed version\nof PowerShell. Occasionally the default can be too low and Vagrant will report\nbeing unable to detect the installed version of PowerShell. This environment\nvariable can be used to extend the timeout used during PowerShell version\ndetection.\n\nWhen setting this environment variable, its value will be in seconds. By default,\nit will use 30 seconds as a timeout.\n\n## `VAGRANT_PREFERRED_POWERSHELL`\n\nWhen executing PowerShell commands, Vagrant will prefer to use `pwsh.exe`\nover `powershell.exe` by default. This environment variable can be used to\nmodify this preference and make Vagrant prefer `powershell.exe`. The value\nset in this environment variable are any supported PowerShell executables\nwhich currently are: `powershell` and `pwsh`.\n\n## `VAGRANT_PREFERRED_PROVIDERS`\n\nThis configures providers that Vagrant should prefer.\n\nMuch like the `VAGRANT_DEFAULT_PROVIDER` this environment variable normally\ndoes not need to be set. By setting this you will instruct Vagrant to\n_prefer_ providers defined in this environment variable for any _new_\nVagrant environments. Existing Vagrant environments will continue to use\nthe provider they came `up` with. Once you `vagrant destroy` existing environments,\nthis will take effect. A single provider can be defined within this environment\nvariable or a comma delimited list of providers.\n\n## `VAGRANT_PREFER_SYSTEM_BIN`\n\nIf this is set, Vagrant will prefer using utility executables (like `ssh` and `rsync`)\nfrom the local system instead of those vendored within the Vagrant installation.\n\nVagrant will default to using a system provided `ssh` on Windows. This\nenvironment variable can also be used to disable that behavior to force Vagrant to\nuse the embedded `ssh` executable by setting it to `0`.\n\n## `VAGRANT_SUPPRESS_GO_EXPERIMENTAL_WARNING`\n\nIf this is set, Vagrant-go will not output a warning message about compatibility \nwith Vagrant-ruby. This does not effect the stable Ruby release of Vagrant.\n\n## `VAGRANT_DISABLE_WINCURL`\n\nIf set Vagrant will use the mingw build of curl which uses the installer provided\nca-certificates bundle instead of the native Windows curl executable.\n\n## `VAGRANT_SERVER_URL`\n\nThis configures the remote server which Vagrant will connect to for fetching\nVagrant boxes. By default this is configured for Vagrant Cloud (https://vagrantcloud.com)\n\n## `VAGRANT_SERVER_ACCESS_TOKEN_BY_URL`\n\nIf this is set Vagrant will change the way it authenticates with the configured\nVagrant server. When set, the authentication behavior will be reverted to the\ndeprecated authentication behavior of:\n\n1. not adding an authentication header to the request\n2. setting the configured access token as a query parameter on URLs\n\nThis behavior can be useful for third party servers which do not accept the\nauthentication header currently used with Vagrant Cloud.\n\n## `VAGRANT_SKIP_SUBPROCESS_JAILBREAK`\n\nAs of Vagrant 1.7.3, Vagrant tries to intelligently detect if it is running in\nthe installer or running via Bundler. Although not officially supported, Vagrant\ntries its best to work when executed via Bundler. When Vagrant detects that you\nhave spawned a subprocess that lives outside of Vagrant's installer, Vagrant\nwill do its best to reset the preserved environment during the subprocess\nexecution.\n\nIf Vagrant detects it is running outside of the officially installer, the\noriginal environment will always be restored. You can disable this automatic\njailbreak by setting `VAGRANT_SKIP_SUBPROCESS_JAILBREAK`.\n\n## `VAGRANT_USER_AGENT_PROVISIONAL_STRING`\n\nVagrant will append the contents of this variable to the default user agent header.\n\n## `VAGRANT_USE_VAGRANT_TRIGGERS`\n\nVagrant will not display the warning about disabling the core trigger feature if\nthe community plugin is installed.\n\n## `VAGRANT_VAGRANTFILE`\n\nThis specifies the filename of the Vagrantfile that Vagrant searches for.\nBy default, this is \"Vagrantfile\". Note that this is _not_ a file path,\nbut just a filename.\n\nThis environmental variable is commonly used in scripting environments\nwhere a single folder may contain multiple Vagrantfiles representing\ndifferent configurations.\n\n## `VAGRANT_WINPTY_DISABLE`\n\nIf this is set, Vagrant will _not_ wrap interactive processes with winpty where\nrequired.\n"
  },
  {
    "path": "website/content/docs/other/index.mdx",
    "content": "---\nlayout: docs\npage_title: Other\ndescription: |-\n  This page covers Vagrant information that does not quite fit under the other\n  categories.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Other\n\nThis section covers other information that does not quite fit under the\nother categories.\n\n- [Debugging](/vagrant/docs/other/debugging)\n- [Environment Variables](/vagrant/docs/other/environmental-variables)\n"
  },
  {
    "path": "website/content/docs/other/macos-catalina.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant and macOS Catalina\ndescription: An overview of using Vagrant on macOS Catalina.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant and macOS Catalina\n\nThe latest version of macOS (Catalina) includes security changes that prevent\napplications from accessing data in your Documents, Desktop, and Downloads\nfolders without explicit permission. If you keep any virtual machine files in\nthese folders, you will need to allow access to these folders for your terminal\nemulator.\n\nInitially when you try to access one of these folders from the command line, you\nshould see a popup that says something like:\n\n> “Terminal” would like to access files in your Documents folder.\n\nClick \"OK\" to grant those permissions.\n\nIf you click \"Don't Allow\" and find that you need to grant access later on, you\ncan go to \"System Preferences\" -> \"Security & Privacy\" -> \"Files and Folders\"\nand you should see your terminal emulator there. Click on the lock, and then\nclick on the checkbox next to the folder that contains the files that Vagrant\nneeds to access.\n\nNote that granting the `vagrant` binary \"Full Disk Access\" is not sufficient or\nnecessary. If Terminal (or iTerm2/Hyper/etc.) is granted access to a particular\nfolder, then Vagrant will also be able to access that folder.\n"
  },
  {
    "path": "website/content/docs/other/wsl.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant and Windows Subsystem for Linux\ndescription: |-\n  An overview of using Vagrant on Windows within the Windows Subsystem\n  for Linux.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant and Windows Subsystem for Linux\n\nRecent versions of Windows 10 now include Windows Subsystem for Linux (WSL) as\nan optional Windows feature. The WSL supports running a Linux environment\nwithin Windows. Vagrant support for WSL is still in development and should\nbe considered _beta_.\n\n~> **Warning: Advanced Topic!** Using Vagrant within the Windows\nSubsystem for Linux is an advanced topic that only experienced Vagrant users\nwho are reasonably comfortable with Windows, WSL, and Linux should approach.\n\n## Vagrant Installation\n\nVagrant _must_ be installed within the Linux distribution used with WSL. While\nthe `vagrant.exe` executable provided by the Vagrant Windows installation is\naccessible from within the WSL, it will not function as expected.\n\nDownload the installer package for the Linux distribution from the releases\npage and install Vagrant.\n\n**NOTE: When Vagrant is installed on the Windows system the version installed\nwithin the Linux distribution _must_ match.**\n\n# Vagrant Usage\n\n## Windows Access\n\nBy default Vagrant will not access features available on the Windows system\nfrom within the WSL. This means the VirtualBox and Hyper-V providers will\nnot be available. To enable Windows access, which will also enable the\nVirtualBox and Hyper-V providers, set the `VAGRANT_WSL_ENABLE_WINDOWS_ACCESS`\nenvironment variable:\n\n```shell-session\n$ export VAGRANT_WSL_ENABLE_WINDOWS_ACCESS=\"1\"\n```\n\nWhen Windows access is enabled Vagrant will automatically adjust `VAGRANT_HOME`\nto be located on the Windows host. This is required to ensure `VAGRANT_HOME`\nis located on a DrvFs file system.\n\n## PATH modifications\n\nVagrant will detect when it is being run within the WSL and adjust how it\nlocates and executes third party executables. For example, when using the\nVirtualBox provider Vagrant will interact with VirtualBox installed on\nthe Windows system, not within the WSL. It is important to ensure that\nany required Windows executable is available within your `PATH` to allow\nVagrant to access them.\n\nFor example, when using the VirtualBox provider:\n\n```shell\nexport PATH=\"$PATH:/mnt/c/Program Files/Oracle/VirtualBox\"\n```\n\n## Synced Folders\n\nSupport for synced folders within the WSL is implementation dependent. In\nmost cases synced folders will not be supported when running Vagrant within\nWSL on a VolFs file system. Synced folder implementations must \"opt-in\" to\nsupporting usage from VolFs file systems. To use synced folders from within\nthe WSL that do not support VolFs file systems, move the Vagrant project\ndirectory to a DrvFs file system location (/mnt/c/ prefixed path for example).\n\n## Windows Access\n\nWorking within the WSL provides a layer of isolation from the actual\nWindows system. In most cases Vagrant will need access to the actual\nWindows system to function correctly. As most Vagrant providers will\nneed to be installed on Windows directly (not within the WSL) Vagrant\nwill require Windows access. Access to the Windows system is controlled\nvia an environment variable: `VAGRANT_WSL_ENABLE_WINDOWS_ACCESS`. If\nthis environment variable is set, Vagrant will access the Windows system\nto run executables and enable things like synced folders. When running\nin a bash shell within WSL, the environment variable can be setup like so:\n\n```shell-session\n$ export VAGRANT_WSL_ENABLE_WINDOWS_ACCESS=\"1\"\n```\n\nThis will enable Vagrant to access the Windows system outside of the\nWSL and properly interact with Windows executables. This will automatically\nmodify the `VAGRANT_HOME` environment variable if it is not already defined,\nsetting it to be within the user's home directory on Windows.\n\nIt is important to note that paths shared with the Windows system will\nnot have Linux permissions enforced. For example, when a directory within\nthe WSL is synced to a guest using the VirtualBox provider, any local\npermissions defined on that directory (or its contents) will not be\nvisible from the guest. Likewise, any files created from the guest within\nthe synced folder will be world readable/writeable in WSL.\n\nOther useful WSL related environment variables:\n\n- `VAGRANT_WSL_WINDOWS_ACCESS_USER` - Override current Windows username\n- `VAGRANT_WSL_DISABLE_VAGRANT_HOME` - Do not modify the `VAGRANT_HOME` variable\n- `VAGRANT_WSL_WINDOWS_ACCESS_USER_HOME_PATH` - Custom Windows system home path\n\nIf a Vagrant project directory is not within the user's home directory on the\nWindows system, certain actions that include permission checks may fail (like\n`vagrant ssh`). When accessing Vagrant projects outside the WSL Vagrant will\nskip these permission checks when the project path is within the path defined\nin the `VAGRANT_WSL_WINDOWS_ACCESS_USER_HOME_PATH` environment variable. For\nexample, if a user wants to run a Vagrant project from the WSL that is located\nat `C:\\TestDir\\vagrant-project`:\n\n```shell-session\nC:\\Users\\vagrant> cd C:\\TestDir\\vagrant-project\nC:\\TestDir\\vagrant-project> bash\nvagrant@vagrant-10:/mnt/c/TestDir/vagrant-project$ export VAGRANT_WSL_WINDOWS_ACCESS_USER_HOME_PATH=\"/mnt/c/TestDir\"\nvagrant@vagrant-10:/mnt/c/TestDir/vagrant-project$ vagrant ssh\n```\n\n## Using Docker\n\nThe docker daemon cannot be run inside the Windows Subsystem for Linux. However,\nthe daemon _can_ be run on Windows and accessed by Vagrant while running in the\nWSL. Once docker is installed and running on Windows, export the following\nenvironment variable to give Vagrant access:\n\n```shell-session\nvagrant@vagrant-10:/mnt/c/Users/vagrant$ export DOCKER_HOST=tcp://127.0.0.1:2375\n```\n"
  },
  {
    "path": "website/content/docs/plugins/action-hooks.mdx",
    "content": "---\nlayout: docs\npage_title: Plugin Development Basics - Action Hooks\ndescription: |-\n  Action hooks provide ways to interact with Vagrant at a very low level by\n  injecting middleware in various phases of Vagrant's lifecycle. This is an\n  advanced option, even for plugin development.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Action Hooks\n\nAction hooks provide ways to interact with Vagrant at a very low level by\ninjecting middleware in various phases of Vagrant's lifecycle. This is an\nadvanced option, even for plugin development.\n\n~> **Warning: Advanced Topic!** Developing plugins is an\nadvanced topic that only experienced Vagrant users who are reasonably\ncomfortable with Ruby should approach.\n\n## Public Action Hooks\n\nThe following action hooks are available in the core of Vagrant. Please note\nthat this list is not exhaustive and additional hooks can be added via plugins.\n\n- `environment_plugins_loaded` - called after the plugins have been loaded,\n  but before the configurations, provisioners, providers, etc. are loaded.\n\n* `environment_load` - called after the environment and all configurations are\n  fully loaded.\n\n- `environment_unload` - called after the environment is done being used. The\n  environment should not be used in this hook.\n\n* `machine_action_boot` - called after the hypervisor has reported the machine\n  was booted.\n\n- `machine_action_config_validate` - called after all `Vagrantfile`s have been\n  loaded, merged, and validated.\n\n* `machine_action_destroy` - called after the hypervisor has reported the\n  virtual machine is down.\n\n- `machine_action_halt` - called after the hypervisor has moved the machine\n  into a halted state (usually \"stopped\" but not \"terminated\").\n\n* `machine_action_package` - called after Vagrant has successfully packaged a\n  new box.\n\n- `machine_action_provision` - called after all provisioners have executed.\n\n* `machine_action_read_state` - called after Vagrant has loaded state from\n  disk and the hypervisor.\n\n- `machine_action_reload` - called after a virtual machine is reloaded (varies\n  by hypervisor).\n\n* `machine_action_resume` - called after a virtual machine is moved from the\n  halted to up state.\n\n- `machine_action_run_command` - called after a command is executed on the\n  machine.\n\n* `machine_action_ssh` - called after an SSH connection has been established.\n\n- `machine_action_ssh_run` - called after an SSH command is executed.\n\n* `machine_action_start` - called after the machine has been started.\n\n- `machine_action_suspend` - called after the machine has been suspended.\n\n* `machine_action_sync_folders` - called after synced folders have been set up.\n\n- `machine_action_up` - called after the machine has entered the up state.\n\n## Private API\n\nYou may find additional action hooks if you browse the Vagrant source code, but\nonly the list of action hooks here are guaranteed to persist between Vagrant\nreleases. Please do not rely on the internal API as it is subject to change\nwithout notice.\n"
  },
  {
    "path": "website/content/docs/plugins/commands.mdx",
    "content": "---\nlayout: docs\npage_title: Command Plugins - Plugin Development\ndescription: |-\n  This page documents how to add new commands to Vagrant, invocable\n  via \"vagrant YOUR-COMMAND\". Prior to reading this, you should be familiar\n  with the plugin development basics.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Plugin Development: Commands\n\nThis page documents how to add new commands to Vagrant, invocable\nvia `vagrant YOUR-COMMAND`. Prior to reading this, you should be familiar\nwith the [plugin development basics](/vagrant/docs/plugins/development-basics).\n\n~> **Warning: Advanced Topic!** Developing plugins is an\nadvanced topic that only experienced Vagrant users who are reasonably\ncomfortable with Ruby should approach.\n\n## Definition Component\n\nWithin the context of a plugin definition, new commands can be defined\nlike so:\n\n```ruby\ncommand \"foo\" do\n  require_relative \"command\"\n  Command\nend\n```\n\nCommands are defined with the `command` method, which takes as an argument\nthe name of the command, in this case \"foo.\" This means the command will be\ninvocable via `vagrant foo`. Then the block argument returns a class that\nimplements the `Vagrant.plugin(2, \"command\")` interface.\n\nYou can also define _non-primary commands_. These commands do not show\nup in the `vagrant -h` output. They only show up if the user explicitly\ndoes a `vagrant list-commands` which shows the full listing of available\ncommands. This is useful for highly specific commands or plugins that a\nbeginner to Vagrant would not be using anyways. Vagrant itself uses non-primary\ncommands to expose some internal functions, as well.\n\nTo define a non-primary command:\n\n```ruby\ncommand(\"foo\", primary: false) do\n  require_relative \"command\"\n  Command\nend\n```\n\n## Implementation\n\nImplementations of commands should subclass `Vagrant.plugin(2, :command)`,\nwhich is a Vagrant method that will return the proper superclass for\na version 2 command. The implementation itself is quite simple, since the\nclass needs to only implement a single method: `execute`. Example:\n\n```ruby\nclass Command < Vagrant.plugin(2, :command)\n  def execute\n    puts \"Hello!\"\n    0\n  end\nend\n```\n\nThe `execute` method is called when the command is invoked, and it should\nreturn the exit status (0 for success, anything else for error).\n\nThis is a command at its simplest form. Of course, the command superclass\ngives you access to the Vagrant environment and provides some helpers to\ndo common tasks such as command line parsing.\n\n## Parsing Command-Line Options\n\nThe `parse_options` method is available which will parse the command line\nfor you. It takes an [OptionParser](http://ruby-doc.org/stdlib-1.9.3/libdoc/optparse/rdoc/OptionParser.html)\nas an argument, and adds some common elements to it such as the `--help` flag,\nautomatically showing help if requested. View the API docs directly for more\ninformation.\n\nThis is recommended over raw parsing/manipulation of command line flags.\nThe following is an example of parsing command line flags pulled directly\nfrom the built-in Vagrant `destroy` command:\n\n```ruby\noptions = {}\noptions[:force] = false\n\nopts = OptionParser.new do |o|\n  o.banner = \"Usage: vagrant destroy [vm-name]\"\n  o.separator \"\"\n\n  o.on(\"-f\", \"--force\", \"Destroy without confirmation.\") do |f|\n    options[:force] = f\n  end\nend\n\n# Parse the options\nargv = parse_options(opts)\n```\n\n## Using Vagrant Machines\n\nThe `with_target_vms` method is a helper that helps you interact with\nthe machines that Vagrant manages in a standard Vagrant way. This method\nautomatically does the right thing in the case of multi-machine environments,\nhandling target machines on the command line (`vagrant foo my-vm`), etc.\nIf you need to do any manipulation of a Vagrant machine, including SSH\naccess, this helper should be used.\n\nAn example of using the helper, again pulled directly from the built-in\n`destroy` command:\n\n```ruby\nwith_target_vms(argv, reverse: true) do |machine|\n  machine.action(:destroy)\nend\n```\n\nIn this case, it asks for the machines in reverse order and calls the\ndestroy action on each of them. If a user says `vagrant destroy foo`, then\nthe helper automatically only yields the `foo` machine. If no parameter\nis given and it is a multi-machine environment, every machine in the environment\nis yielded, and so on. It just does the right thing.\n\n## Using the Raw Vagrant Environment\n\nThe raw loaded `Vagrant::Environment` object is available with the\n'@env' instance variable.\n"
  },
  {
    "path": "website/content/docs/plugins/configuration.mdx",
    "content": "---\nlayout: docs\npage_title: Custom Configuration - Plugin Development\ndescription: |-\n  This page documents how to add new configuration options to Vagrant,\n  settable with \"config.YOURKEY\" in Vagrantfiles. Prior to reading this,\n  you should be familiar with the plugin development basics.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Plugin Development: Configuration\n\nThis page documents how to add new configuration options to Vagrant,\nsettable with `config.YOURKEY` in Vagrantfiles. Prior to reading this,\nyou should be familiar with the\n[plugin development basics](/vagrant/docs/plugins/development-basics).\n\n~> **Warning: Advanced Topic!** Developing plugins is an\nadvanced topic that only experienced Vagrant users who are reasonably\ncomfortable with Ruby should approach.\n\n## Definition Component\n\nWithin the context of a plugin definition, new configuration keys can be defined\nlike so:\n\n```ruby\nconfig \"foo\" do\n  require_relative \"config\"\n  Config\nend\n```\n\nConfiguration keys are defined with the `config` method, which takes as an\nargument the name of the configuration variable as the argument. This\nmeans that the configuration object will be accessible via `config.foo`\nin Vagrantfiles. Then, the block argument returns a class that implements\nthe `Vagrant.plugin(2, :config)` interface.\n\n## Implementation\n\nImplementations of configuration keys should subclass `Vagrant.plugin(2, :config)`,\nwhich is a Vagrant method that will return the proper subclass for a version\n2 configuration section. The implementation is very simple, and acts mostly\nas a plain Ruby object. Here is an example:\n\n```ruby\nclass Config < Vagrant.plugin(2, :config)\n  attr_accessor :widgets\n\n  def initialize\n    @widgets = UNSET_VALUE\n  end\n\n  def finalize!\n    @widgets = 0 if @widgets == UNSET_VALUE\n  end\nend\n```\n\nWhen using this configuration class, it looks like the following:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # ...\n\n  config.foo.widgets = 12\nend\n```\n\nEasy. The only odd thing is the `UNSET_VALUE` bits above. This is actually\nso that Vagrant can properly automatically merge multiple configurations.\nMerging is covered in the next section, and `UNSET_VALUE` will be explained\nthere.\n\n## Merging\n\nVagrant works by loading [multiple Vagrantfiles and merging them](/vagrant/docs/vagrantfile/#load-order).\nThis merge logic is built-in to configuration classes. When merging two\nconfiguration objects, we will call them \"old\" and \"new\", it'll by default\ntake all the instance variables defined on \"new\" that are not `UNSET_VALUE`\nand set them onto the merged result.\n\nThe reason `UNSET_VALUE` is used instead of Ruby's `nil` is because\nit is possible that you want the default to be some value, and the user\nactually wants to set the value to `nil`, and it is impossible for Vagrant\nto automatically determine whether the user set the instance variable, or\nif it was defaulted as nil.\n\nThis merge logic is what you want almost every time. Hence, in the example\nabove, `@widgets` is set to `UNSET_VALUE`. If we had two Vagrant configuration\nobjects in the same file, then Vagrant would properly merge the follows.\nThe example below shows this:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.widgets = 1\nend\n\nVagrant.configure(\"2\") do |config|\n  # ... other stuff\nend\n\nVagrant.configure(\"2\") do |config|\n  config.widgets = 2\nend\n```\n\nIf this were placed in a Vagrantfile, after merging, the value of widgets\nwould be \"2\".\n\nThe `finalize!` method is called only once ever on the final configuration\nobject in order to set defaults. If `finalize!` is called, that configuration\nwill never be merged again, it is final. This lets you detect any `UNSET_VALUE`\nand set the proper default, as we do in the above example.\n\nOf course, sometimes you want custom merge logic. Let us say we\nwanted our widgets to be additive. We can override the `merge` method to\ndo this:\n\n```ruby\nclass Config < Vagrant.config(\"2\", :config)\n  attr_accessor :widgets\n\n  def initialize\n    @widgets = 0\n  end\n\n  def merge(other)\n    super.tap do |result|\n      result.widgets = @widgets + other.widgets\n    end\n  end\nend\n```\n\nIn this case, we did not use `UNSET_VALUE` for widgets because we did not\nneed that behavior. We default to 0 and always merge by summing the\ntwo widgets. Now, if we ran the example above that had the 3 configuration\nblocks, the final value of widgets would be \"3\".\n\n## Validation\n\nConfiguration classes are also responsible for validating their own\nvalues. Vagrant will call the `validate` method to do this. An example\nvalidation method is shown below:\n\n```ruby\nclass Config < Vagrant.plugin(\"2\", :config)\n  # ...\n\n  def validate(machine)\n    errors = _detected_errors\n    if @widgets <= 5\n      errors << \"widgets must be greater than 5\"\n    end\n\n    { \"foo\" => errors }\n  end\nend\n```\n\nThe validation method is given a `machine` object, since validation is\ndone for each machine that Vagrant is managing. This allows you to\nconditionally validate some keys based on the state of the machine and so on.\n\nThe `_detected_errors` method returns any errors already detected by Vagrant,\nsuch as unknown configuration keys. This returns an array of error messages,\nso be sure to turn it into the proper Hash object to return later.\n\nThe return value is a Ruby Hash object, where the key is a section name,\nand the value is a list of error messages. These will be displayed by\nVagrant. The hash must not contain any values if there are no errors.\n\n## Accessing\n\nAfter all the configuration options are merged and finalized, you will likely\nwant to access the finalized value in your plugin. The initializer function\nvaries with each type of plugin, but _most_ plugins expose an initializer like\nthis:\n\n```ruby\ndef initialize(machine, config)\n  @machine = machine\n  @config  = config\nend\n```\n\nWhen authoring a plugin, simply call `super` in your initialize function to\nsetup these instance variables:\n\n```ruby\ndef initialize(*)\n  super\n\n  @config.is_now_available\n  # ...existing code\nend\n\ndef my_helper\n  @config.is_here_too\nend\n```\n\nFor examples, take a look at Vagrant's own internal plugins in the `plugins`\nfolder in Vagrant's source on GitHub.\n"
  },
  {
    "path": "website/content/docs/plugins/development-basics.mdx",
    "content": "---\nlayout: docs\npage_title: Plugin Development Basics - Plugins\ndescription: |-\n  Plugins are a great way to augment or change the behavior and functionality\n  of Vagrant. Since plugins introduce additional external dependencies for\n  users, they should be used as a last resort when attempting to\n  do something with Vagrant.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Plugin Development Basics\n\nPlugins are a great way to augment or change the behavior and functionality\nof Vagrant. Since plugins introduce additional external dependencies for\nusers, they should be used as a last resort when attempting to\ndo something with Vagrant.\n\nBut if you need to introduce custom behaviors\ninto Vagrant, plugins are the best way, since they are safe against future\nupgrades and use a stable API.\n\n~> **Warning: Advanced Topic!** Developing plugins is an\nadvanced topic that only experienced Vagrant users who are reasonably\ncomfortable with Ruby should approach.\n\nPlugins are written using [Ruby](https://www.ruby-lang.org/en/) and are packaged\nusing [RubyGems](https://rubygems.org/). Familiarity with Ruby is required,\nbut the [packaging and distribution](/vagrant/docs/plugins/packaging) section should help\nguide you to packaging your plugin into a RubyGem.\n\n## Setup and Workflow\n\nBecause plugins are packaged as RubyGems, Vagrant plugins should be\ndeveloped as if you were developing a regular RubyGem. The easiest\nway to do this is to use the `bundle gem` command.\n\nOnce the directory structure for a RubyGem is setup, you will want\nto modify your Gemfile. Here is the basic structure of a Gemfile for\nVagrant plugin development:\n\n```ruby\nsource \"https://rubygems.org\"\n\ngroup :development do\n  gem \"vagrant\", git: \"https://github.com/hashicorp/vagrant.git\"\nend\n\ngroup :plugins do\n  gem \"my-vagrant-plugin\", path: \".\"\nend\n```\n\nThis Gemfile gets \"vagrant\" for development. This allows you to\n`bundle exec vagrant` to run Vagrant with your plugin already loaded,\nso that you can test it manually that way.\n\nThe only thing about this Gemfile that may stand out as odd is the\n\"plugins\" group and putting your plugin in that group. Because\n`vagrant plugin` commands do not work in development, this is how\nyou \"install\" your plugin into Vagrant. Vagrant will automatically\nload any gems listed in the \"plugins\" group. Note that this also\nallows you to add multiple plugins to Vagrant for development, if\nyour plugin works with another plugin.\n\nWhen you want to manually test your plugin, use\n`bundle exec vagrant` in order to run Vagrant with your plugin\nloaded (as we specified in the Gemfile).\n\n## Plugin Definition\n\nAll plugins are required to have a definition. A definition contains details\nabout the plugin such as the name of it and what components it contains.\n\nA definition at the bare minimum looks like the following:\n\n```ruby\nclass MyPlugin < Vagrant.plugin(\"2\")\n  name \"My Plugin\"\nend\n```\n\nA definition is a class that inherits from `Vagrant.plugin(\"2\")`. The \"2\"\nthere is the version that the plugin is valid for. API stability is only\npromised for each major version of Vagrant, so this is important. (The\n1.x series is working towards 2.0, so the API version is \"2\")\n\n**The most critical feature of a plugin definition** is that it must _always_\nload, no matter what version of Vagrant is running. Theoretically, Vagrant\nversion 87 (does not actually exist) would be able to load a version 2 plugin\ndefinition. This is achieved through clever lazy loading of individual components\nof the plugin, and is covered shortly.\n\n## Plugin Components\n\nWithin the definition, a plugin advertises what components it adds to\nVagrant. An example is shown below where a command and provisioner are\nadded:\n\n```ruby\nclass MyPlugin < Vagrant.plugin(\"2\")\n  name \"My Plugin\"\n\n  command \"run-my-plugin\" do\n    require_relative \"command\"\n    Command\n  end\n\n  provisioner \"my-provisioner\" do\n    require_relative \"provisioner\"\n    Provisioner\n  end\nend\n```\n\nLet us go over the major pieces of what is going on here. Note from a general\nRuby language perspective the above _should_ be familiar. The syntax should\nnot scare you. If it does, then please familiarize with Ruby further before\nattempting to write a plugin.\n\nThe first thing to note is that individual components are defined by\nmaking a method call with the component name, such as `command` or\n`provisioner`. These in turn take some parameters. In the case of our\nexample it is just the name of the command and the name of the provisioner.\nAll component definitions then take a block argument (a callback) that\nmust return the actual component implementation class.\n\nThe block argument is where the \"clever lazy loading\" (mentioned above)\ncomes into play. The component blocks should lazy load the actual file that\ncontains the implementation of the component, and then return that component.\n\nThis is done because the actual dependencies and APIs used when defining\ncomponents are not stable across major Vagrant versions. A command implementation\nwritten for Vagrant 2.0 will not be compatible with Vagrant 3.0 and so on. But\nthe _definition_ is just plain Ruby that must always be forward compatible\nto future Vagrant versions.\n\nTo repeat, **the lazy loading aspect of plugin components is critical**\nto the way Vagrant plugins work. All components must be lazily loaded\nand returned within their definition blocks.\n\nNow, each component has a different API. Please visit the relevant section\nusing the navigation to the left under \"Plugins\" to learn more about developing\neach type of component.\n\n## Error Handling\n\nOne of Vagrant's biggest strength is gracefully handling errors and reporting\nthem in human-readable ways. Vagrant has always strongly believed that if\na user sees a stack trace, it is a bug. It is expected that plugins will behave\nthe same way, and Vagrant provides strong error handling mechanisms to\nassist with this.\n\nError handling in Vagrant is done entirely by raising Ruby exceptions.\nBut Vagrant treats certain errors differently than others. If an error\nis raised that inherits from `Vagrant::Errors::VagrantError`, then the\n`vagrant` command will output the message of the error in nice red text\nto the console and exit with an exit status of 1.\n\nOtherwise, Vagrant reports an \"unexpected error\" that should be reported\nas a bug, and shows a full stack trace and other ugliness. Any stack traces\nshould be considered bugs.\n\nTherefore, to fit into Vagrant's error handling mechanisms, subclass\n`VagrantError` and set a proper message on your exception. To see\nexamples of this, look at Vagrant's [built-in errors](https://github.com/hashicorp/vagrant/blob/main/lib/vagrant/errors.rb).\n\n## Console Input and Output\n\nMost plugins are likely going to want to do some sort of input/output.\nPlugins should _never_ use Ruby's built-in `puts` or `gets` style methods.\nInstead, all input/output should go through some sort of Vagrant UI object.\nThe Vagrant UI object properly handles cases where there is no TTY, output\npipes are closed, there is no input pipe, etc.\n\nA UI object is available on every `Vagrant::Environment` via the `ui` property\nand is exposed within every middleware environment via the `:ui` key. UI\nobjects have [decent documentation](https://github.com/hashicorp/vagrant/blob/main/lib/vagrant/ui.rb)\nwithin the comments of their source.\n"
  },
  {
    "path": "website/content/docs/plugins/go-plugins/guests.mdx",
    "content": "---\nlayout: docs\npage_title: Custom Guests - Go Plugin Development\ndescription: |-\n  This page documents how to add new guest OS detection to Vagrant, allowing\n  Vagrant to properly configure new operating systems. Prior to reading this,\n  you should be familiar with the plugin development basics.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Go Plugin Development: Guests\n\nOutside of these components, the caller must provide all other arguments \nthat are required. \n\nThe most basic guest plugin is composed of: \n1. A detection function that determines if the plugin is usable on the system \n(is the expected guest)\n2. A set of capabilities that define actions that can be run against the guest\n3. An entry point defining the plugin options\n\n**Note**: To quickly get started writing Go guest plugins, clone the [vagrant-guest-plugin-skeleton](https://github.com/soapy1/vagrant-guest-plugin-skeleton)\ntemplate and follow the Readme.\n\nThe file structure of a guest plugin looks like:\n\n```\n- myplugin\n  |- main.go\n  \\- guest\n    |- myguest.go\n    \\- cap\n      |- mycapability.go\n```\n\nWhere `main.go` defines the plugin options. `guest/myguest.go` defines the core plugin\nfunctionality including the detection of the guest. `cap/*` has the definitions of \nall the guest plugin capabilities. These capabilities are the same as those for [Ruby\nplugins](/vagrant/docs/plugins/guest-capabilities). \n\n## Writing a guest plugin\n\nA guest must satisfy the interface defined for a guest component\n\n```\n    GuestDetectFunc() interface{}\n    ParentFunc() interface{}\n    HasCapabilityFunc() interface{}\n    CapabilityFunc(capName string) interface{}\n``` \nSrc: https://github.com/hashicorp/vagrant-plugin-sdk/blob/main/component/component.go  \n\n`GuestDetectFunc`: returns a function that defines the code that determines if the guest is \n                   detected. The returned function must return a `bool`.\n\n`ParentFunc`: returns a function that defines the code that determines the most immediate parent \n              plugin. A child plugin will inherit all the capabilities defined in the parent. The \n              returned function must return a `string`.\n\n`HasCapabilityFunc`: returns a function that defines a lookup for a capability. The returned \n                     function must return an `bool`.\n\n`CapabilityFunc`: returns a capability function that is defined by the plugin registered by a given name.\n\nAn example guest plugin\n\n```\n// file: myplugin/guest/myguest.go\npackage guest\n \nimport (\n \"github.com/hashicorp/vagrant-plugin-sdk/component\"\n sdkcore \"github.com/hashicorp/vagrant-plugin-sdk/core\"\n)\n \n// AlwaysTrueGuest is a Guest implementation for myplugin.\ntype AlwaysTrueGuest struct {\n}\n \n// DetectFunc implements component.Guest\nfunc (h *AlwaysTrueGuest) GuestDetectFunc() interface{} {\n  return h.Detect\n}\n \nfunc (h *AlwaysTrueGuest) Detect(t sdkcore.Target) (bool, error) {\n  return true, nil\n}\n \n// ParentsFunc implements component.Guest\nfunc (h *AlwaysTrueGuest) ParentFunc() interface{} {\n  return h.Parent\n}\n \nfunc (h *AlwaysTrueGuest) Parent() string {\n  // This plugin has no parents\n  return \"\"\n}\n \n// HasCapabilityFunc implements component.Guest\nfunc (h *AlwaysTrueGuest) HasCapabilityFunc() interface{} {\n  return h.CheckCapability\n}\n \nfunc (h *AlwaysTrueGuest) CheckCapability(n *component.NamedCapability) bool {\n  // This plugin has no capabilities\n  return false\n}\n \n// CapabilityFunc implements component.Guest\nfunc (h *AlwaysTrueGuest) CapabilityFunc(name string) interface{} {\n  return fmt.Errorf(\"requested capability %s not found\", name)\n}\n \nvar (\n _ component.Guest = (*AlwaysTrueGuest)(nil)\n)\n```\n\n```\n// file: myplugin/main.go\npackage myplugin\n \nimport (\n sdk \"github.com/hashicorp/vagrant-plugin-sdk\"\n \"github.com/hashicorp/vagrant/builtin/myplugin/guest\"\n)\n \n// Options are the SDK options to use for instantiation.\nvar ComponentOptions = []sdk.Option{\n sdk.WithComponents(\n    // Include the defined guest as a component defined in this plugin\n   &guest.AlwaysTrueGuest{},\n ),\n}\n \nfunc main() {\n sdk.Main(ComponentOptions...)\n os.Exit(0)\n}\n```\n\nIn this example, the guest plugin will always be detected. It does not define any \ncapabilities, or have any parent plugins.\n\n### Defining and registering guest capabilities\n\nA guest plugin may have capabilities two ways:\n1. By defining and implementing the capability in the plugin\n2. By inheriting the capability from a parent guest plugin\n\nDefine a capability by writing out a function that returns the desired capability\n\n```\n// file: myplugin/guest/cap/mycapability.go\npackage cap\n \nimport (\n \"io/ioutil\"\n \n \"github.com/hashicorp/vagrant-plugin-sdk/terminal\"\n)\n \nfunc WriteHelloFunc() interface{} {\n return WriteHello\n}\n \nfunc WriteHello(trm terminal.UI) error {\n trm.Output(\"Hello world\")\n return nil\n}\n```\n\nMake the capability available to the plugin by filling in the capability functions\n\n```\nmyplugin/guest/myguest.go\n// HasCapabilityFunc implements component.Guest\nfunc (h *AlwaysTrueGuest) HasCapabilityFunc() interface{} {\n return h.CheckCapability\n}\n \nfunc (h *AlwaysTrueGuest) CheckCapability(n *component.NamedCapability) bool {\n if n.Capability == \"write_hello\" {\n   return true\n }\n return false\n}\n \n// CapabilityFunc implements component.Guest\nfunc (h *AlwaysTrueGuest) CapabilityFunc(name string) interface{} {\n if name == \"write_hello\" {\n   return h.WriteHelloCap\n }\n return errors.New(\"Invalid capability requested\")\n}\n \nfunc (h *AlwaysTrueGuest) WriteHelloCap(ui terminal.UI) error {\n return cap.WriteHello(ui)\n}\n```\n\nA guest plugin may inherit the capabilities of a parent function by defining\na parent in the plugin implementation. This is done by setting the return\nvalue of the `Parent` function to the name of the desired parent plugin. Go\nbased guest plugins may use Ruby based plugins as their parent. \n\n```\n// file: myplugin/guest/myguest.go\n\n...\n \n// ParentsFunc implements component.Guest\nfunc (h *AlwaysTrueGuest) ParentFunc() interface{} {\n  return h.Parent\n}\n \nfunc (h *AlwaysTrueGuest) Parent() string {\n  // This plugin sets the parent to the \"debian\" plugin which is provided\n  // as a Ruby plugin. This AlwaysTrueGuest now inherits all the capabilities\n  // of the debian Ruby guest plugin. \n  return \"debian\"\n}\n```\n"
  },
  {
    "path": "website/content/docs/plugins/go-plugins/index.mdx",
    "content": "---\nlayout: docs\npage_title: Go Based Plugins\ndescription: |-\n  Vagrant comes with many great features out of the box to get your environments\n  up and running. Sometimes, however, you want to change the way Vagrant does\n  something or add additional functionality to Vagrant. This can be done via\n  Vagrant plugins.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Go Vagrant Plugins\n\nWith the introduction of Vagrant-go, Vagrant now supports running plugins \nimplemented in Go. Note that Vagrant-go can run Go and Ruby based plugins \nwhile Vagrant-ruby only runs Ruby based plugins.\n\n## Anatomy of a Go plugin\n\nWhen a plugin is started, it runs in its own process and is able to communicate \nwith the other plugins over GRPC.\n\nA Vagrant-go plugin must implement an interface for a [plugin component](https://github.com/hashicorp/vagrant-plugin-sdk/blob/main/component/component.go).\nA plugin may satisfy more than one component interface. The plugin interfaces \ndefine the `<name>Func` functions for the plugin. These functions are meant to \nreturn the actual function that represents the named action. For example the \n`Host` component defines a `DetectFunc`. So, a plugin must have a `DetectFunc` \nimplementation that returns a function that can detect if Vagrant is running\non the given host. For example\n\n```\n// DetectFunc implements component.Host\nfunc (h *AlwaysTrueHost) DetectFunc() interface{} {\n  return h.Detect\n}\n \nfunc (h *AlwaysTrueHost) Detect() bool {\n  // This plugin always detects that it is running on the expected host\n  return true\n}\n```\n\nUsing this pattern of `<name>Func` functions, Vagrant is able to allow flexibility\nover the receivers of the functions that actually define the behavior of the plugin.\nThis injection of dependencies is done using go-argmapper. So, in the same example, \nthe `Detect` function may be made to accept some arguments.\n\n```\n// DetectFunc implements component.Host\nfunc (h *AlwaysTrueHost) DetectFunc() interface{} {\n  return h.Detect\n}\n \nfunc (h *AlwaysTrueHost) Detect(string msg, trm terminal.UI) bool {\n  trm.Output(msg)\n  return true\n}\n```\n\nNow, in order to run the `Detect` function, the caller must provide a `string` and \n`terminal.UI` argument. By default, Vagrant will always inject the following arguments\ninto a call to a plugin:\n\n- `terminal.UI` component\n- basis\n- project (when available)\n- context\n- logger\n\nFor each component type, Vagrant may also inject some additional arguments.\n\nOutside of these components, the caller must provide all other arguments that are required. \nSo, in the case above, in order to successfully call this `Detect` function, the caller must \nalso provide a `string`. It is recommended that plugin authors do not rely on arguments being\ninjected into their implementations outside of these sets of arguments.\n"
  },
  {
    "path": "website/content/docs/plugins/guest-capabilities.mdx",
    "content": "---\nlayout: docs\npage_title: Guest Capabilities - Plugin Development\ndescription: |-\n  This page documents how to add new capabilities for guests to Vagrant,\n  allowing Vagrant to perform new actions on specific guest operating systems.\n  Prior to reading this, you should be familiar with the plugin development\n  basics.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Plugin Development: Guest Capabilities\n\nThis page documents how to add new capabilities for [guests](/vagrant/docs/plugins/guests)\nto Vagrant, allowing Vagrant to perform new actions on specific guest\noperating systems.\nPrior to reading this, you should be familiar\nwith the [plugin development basics](/vagrant/docs/plugins/development-basics).\n\n~> **Warning: Advanced Topic!** Developing plugins is an\nadvanced topic that only experienced Vagrant users who are reasonably\ncomfortable with Ruby should approach.\n\nGuest capabilities augment [guests](/vagrant/docs/plugins/guests) by attaching\nspecific \"capabilities\" to the guest, which are actions that can be performed\nin the context of that guest operating system.\n\nThe power of capabilities is that plugins can add new capabilities to\nexisting guest operating systems without modifying the core of Vagrant.\nIn earlier versions of Vagrant, all the guest logic was contained in the\ncore of Vagrant and was not easily augmented.\n\n## Definition Component\n\nWithin the context of a plugin definition, guest capabilities can be\ndefined like so:\n\n```ruby\nguest_capability \"ubuntu\", \"my_custom_capability\" do\n  require_relative \"cap/my_custom_capability\"\n  Cap::MyCustomCapability\nend\n```\n\nGuest capabilities are defined by calling the `guest_capability` method,\nwhich takes two parameters: the guest to add the capability to, and the\nname of the capability itself. Then, the block argument returns a class\nthat implements a method named the same as the capability. This is\ncovered in more detail in the next section.\n\n## Implementation\n\nImplementations should be classes or modules that have a method with\nthe same name as the capability. The method must be immediately accessible\non the class returned from the `guest_capability` component, meaning that\nif it is an instance method, an instance should be returned.\n\nIn general, class methods are used for capabilities. For example, here\nis the implementation for the capability above:\n\n```ruby\nmodule Cap\n  class MyCustomCapability\n    def self.my_custom_capability(machine)\n      # implementation\n    end\n  end\nend\n```\n\nAll capabilities get the Vagrant machine object as the first argument.\nAdditional arguments are determined by the specific capability, so view the\ndocumentation or usage of the capability you are trying to implement for more\ninformation.\n\nSome capabilities must also return values back to the caller, so be aware\nof that when implementing a capability.\n\nCapabilities always have access to communication channels such as SSH\non the machine, and the machine can generally be assumed to be booted.\n\n## Calling Capabilities\n\nSince you have access to the machine in every capability, capabilities can\nalso call _other_ capabilities. This is useful for using the inheritance\nmechanism of capabilities to potentially ask helpers for more information.\nFor example, the \"redhat\" guest has a \"network_scripts_dir\" capability that\nsimply returns the directory where networking scripts go.\n\nCapabilities on child guests of RedHat such as CentOS or Fedora use this\ncapability to determine where networking scripts go, while sometimes overriding\nit themselves.\n\nCapabilities can be called like so:\n\n```ruby\nmachine.guest.capability(:capability_name)\n```\n\nAny additional arguments given to the method will be passed on to the\ncapability, and the capability will return the value that the actual\ncapability returned.\n"
  },
  {
    "path": "website/content/docs/plugins/guests.mdx",
    "content": "---\nlayout: docs\npage_title: Custom Guests - Plugin Development\ndescription: |-\n  This page documents how to add new guest OS detection to Vagrant, allowing\n  Vagrant to properly configure new operating systems. Prior to reading this,\n  you should be familiar with the plugin development basics.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Plugin Development: Guests\n\nThis page documents how to add new guest OS detection to Vagrant, allowing\nVagrant to properly configure new operating systems.\nPrior to reading this, you should be familiar\nwith the [plugin development basics](/vagrant/docs/plugins/development-basics).\n\n~> **Warning: Advanced Topic!** Developing plugins is an\nadvanced topic that only experienced Vagrant users who are reasonably\ncomfortable with Ruby should approach.\n\nVagrant has many features that requires doing guest OS-specific\nactions, such as mounting folders, configuring networks, etc. These\ntasks vary from operating system to operating system. If you find that\none of these does not work for your operating system, then maybe the\nguest implementation is incomplete or incorrect.\n\n## Definition Component\n\nWithin the context of a plugin definition, new guests can be defined\nlike so:\n\n```ruby\nguest \"ubuntu\" do\n  require_relative \"guest\"\n  Guest\nend\n```\n\nGuests are defined with the `guest` method. The first argument is the\nname of the guest. This name is not actually used anywhere, but may in the\nfuture, so choose something helpful. Then, the block argument returns a\nclass that implements the `Vagrant.plugin(2, :guest)` interface.\n\n## Implementation\n\nImplementations of guests subclass `Vagrant.plugin(\"2\", \"guest\")`. Within\nthis implementation, only the `detect?` method needs to be implemented.\n\nThe `detect?` method is called by Vagrant at some point after the machine\nis booted in order to determine what operating system the guest is running.\nIf you detect that it is your operating system, return `true` from `detect?`.\nOtherwise, return `false`.\n\nCommunication channels to the machine are guaranteed to be running at this\npoint, so the most common way to detect the operating system is to do\nsome basic testing:\n\n```ruby\nclass MyGuest < Vagrant.plugin(\"2\", \"guest\")\n  def detect?(machine)\n    machine.communicate.test(\"cat /etc/myos-release\")\n  end\nend\n```\n\nAfter detecting an OS, that OS is used for various\n[guest capabilities](/vagrant/docs/plugins/guest-capabilities) that may be\nrequired.\n\n## Guest Inheritance\n\nVagrant also supports a form of inheritance for guests, since sometimes\noperating systems stem from a common root. A good example of this is Linux\nis the root of Debian, which further is the root of Ubuntu in many cases.\nInheritance allows guests to share a lot of common behavior while allowing\ndistro-specific overrides.\n\nInheritance is not done via standard Ruby class inheritance because Vagrant\nuses a custom [capability-based](/vagrant/docs/plugins/guest-capabilities) system.\nVagrant handles inheritance dispatch for you.\n\nTo subclass another guest, specify that guest's name as a second parameter\nin the guest definition:\n\n```ruby\nguest \"ubuntu\", \"debian\" do\n  require_relative \"guest\"\n  Guest\nend\n```\n\nWith the above component, the \"ubuntu\" guest inherits from \"debian.\" When\na capability is looked up for \"ubuntu\", all capabilities from \"debian\" are\nalso available, and any capabilities in \"ubuntu\" override parent capabilities.\n\nWhen detecting operating systems with `detect?`, Vagrant always does a\ndepth-first search by searching the children operating systems before\nchecking their parents. Therefore, it is guaranteed in the above example\nthat the `detect?` method on \"ubuntu\" will be called before \"debian.\"\n"
  },
  {
    "path": "website/content/docs/plugins/host-capabilities.mdx",
    "content": "---\nlayout: docs\npage_title: Host Capabilities - Plugin Development\ndescription: >-\n  This page documents how to add new capabilities for hosts to Vagrant, allowing\n  Vagrant to perform new actions on specific host operating systems. Prior to\n  reading this, you should be familiar with the plugin development basics.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Plugin Development: Host Capabilities\n\nThis page documents how to add new capabilities for [hosts](/vagrant/docs/plugins/hosts)\nto Vagrant, allowing Vagrant to perform new actions on specific host\noperating systems.\nPrior to reading this, you should be familiar\nwith the [plugin development basics](/vagrant/docs/plugins/development-basics).\n\n~> **Warning: Advanced Topic!** Developing plugins is an\nadvanced topic that only experienced Vagrant users who are reasonably\ncomfortable with Ruby should approach.\n\nHost capabilities augment [hosts](/vagrant/docs/plugins/hosts) by attaching\nspecific \"capabilities\" to the host, which are actions that can be performed\nin the context of that host operating system.\n\nThe power of capabilities is that plugins can add new capabilities to\nexisting host operating systems without modifying the core of Vagrant.\nIn earlier versions of Vagrant, all the host logic was contained in the\ncore of Vagrant and was not easily augmented.\n\n## Definition and Implementation\n\nThe definition and implementation of host capabilities is identical\nto [guest capabilities](/vagrant/docs/plugins/guest-capabilities).\n\nThe main difference from guest capabilities, however, is that instead of\ntaking a machine as the first argument, all host capabilities take an\ninstance of `Vagrant::Environment` as their first argument.\n\nAccess to the environment allows host capabilities to access global state,\nspecific machines, and also allows them to call other host capabilities.\n\n## Calling Capabilities\n\nSince you have access to the environment in every capability, capabilities can\nalso call _other_ host capabilities. This is useful for using the inheritance\nmechanism of capabilities to potentially ask helpers for more information.\nFor example, the \"linux\" guest has a \"nfs_check_command\" capability that\nreturns the command to use to check if NFS is running.\n\nCapabilities on child guests of Linux such as RedHat or Arch use this\ncapability to mostly inherit the Linux behavior, except for this minor\ndetail.\n\nCapabilities can be called like so:\n\n```ruby\nenvironment.host.capability(:capability_name)\n```\n\nAny additional arguments given to the method will be passed on to the\ncapability, and the capability will return the value that the actual\ncapability returned.\n"
  },
  {
    "path": "website/content/docs/plugins/hosts.mdx",
    "content": "---\nlayout: docs\npage_title: Custom Hosts - Plugin Development\ndescription: |-\n  This page documents how to add new host OS detection to Vagrant, allowing\n  Vagrant to properly execute host-specific operations on new operating systems.\n  Prior to reading this, you should be familiar with the plugin development\n  basics.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Plugin Development: Hosts\n\nThis page documents how to add new host OS detection to Vagrant, allowing\nVagrant to properly execute host-specific operations on new operating systems.\nPrior to reading this, you should be familiar\nwith the [plugin development basics](/vagrant/docs/plugins/development-basics).\n\n~> **Warning: Advanced Topic!** Developing plugins is an\nadvanced topic that only experienced Vagrant users who are reasonably\ncomfortable with Ruby should approach.\n\nVagrant has some features that require host OS-specific actions, such as\nexporting NFS folders. These tasks vary from operating system to operating\nsystem. Vagrant uses host detection as well as\n[host capabilities](/vagrant/docs/plugins/host-capabilities) to perform these\nhost OS-specific operations.\n\n## Definition Component\n\nWithin the context of a plugin definition, new hosts can be defined\nlike so:\n\n```ruby\nhost \"ubuntu\" do\n  require_relative \"host\"\n  Host\nend\n```\n\nHosts are defined with the `host` method. The first argument is the\nname of the host. This name is not actually used anywhere, but may in the\nfuture, so choose something helpful. Then, the block argument returns a\nclass that implements the `Vagrant.plugin(2, :host)` interface.\n\n## Implementation\n\nImplementations of hosts subclass `Vagrant.plugin(\"2\", \"host\")`. Within\nthis implementation, only the `detect?` method needs to be implemented.\n\nThe `detect?` method is called by Vagrant very early on in its initialization\nprocess to determine if the OS that Vagrant is running on is this host.\nIf you detect that it is your operating system, return `true` from `detect?`.\nOtherwise, return `false`.\n\n```ruby\nclass MyHost < Vagrant.plugin(\"2\", \"host\")\n  def detect?(environment)\n    File.file?(\"/etc/arch-release\")\n  end\nend\n```\n\nAfter detecting an OS, that OS is used for various\n[host capabilities](/vagrant/docs/plugins/host-capabilities) that may be\nrequired.\n\n## Host Inheritance\n\nVagrant also supports a form of inheritance for hosts, since sometimes\noperating systems stem from a common root. A good example of this is Linux\nis the root of Debian, which further is the root of Ubuntu in many cases.\nInheritance allows hosts to share a lot of common behavior while allowing\ndistro-specific overrides.\n\nInheritance is not done via standard Ruby class inheritance because Vagrant\nuses a custom [capability-based](/vagrant/docs/plugins/host-capabilities) system.\nVagrant handles inheritance dispatch for you.\n\nTo subclass another host, specify that host's name as a second parameter\nin the host definition:\n\n```ruby\nhost \"ubuntu\", \"debian\" do\n  require_relative \"host\"\n  Host\nend\n```\n\nWith the above component, the \"ubuntu\" host inherits from \"debian.\" When\na capability is looked up for \"ubuntu\", all capabilities from \"debian\" are\nalso available, and any capabilities in \"ubuntu\" override parent capabilities.\n\nWhen detecting operating systems with `detect?`, Vagrant always does a\ndepth-first search by searching the children operating systems before\nchecking their parents. Therefore, it is guaranteed in the above example\nthat the `detect?` method on \"ubuntu\" will be called before \"debian.\"\n"
  },
  {
    "path": "website/content/docs/plugins/index.mdx",
    "content": "---\nlayout: docs\npage_title: Plugins\ndescription: |-\n  Vagrant comes with many great features out of the box to get your environments\n  up and running. Sometimes, however, you want to change the way Vagrant does\n  something or add additional functionality to Vagrant. This can be done via\n  Vagrant plugins.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Plugins\n\nVagrant comes with many great features out of the box to get your environments up\nand running. Sometimes, however, you want to change the way Vagrant does something\nor add additional functionality to Vagrant. This can be done via Vagrant\n_plugins_.\n\nPlugins are powerful, first-class citizens that extend Vagrant using a\nwell-documented, stable API that can withstand major version upgrades.\n\nIn fact, most of the core of Vagrant is [implemented using plugins](https://github.com/hashicorp/vagrant/tree/main/plugins).\nSince Vagrant [dogfoods](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) its own\nplugin API, you can be confident that the interface is stable and well supported.\n\nUse the navigation on the left below the \"Plugins\" section to learn more\nabout how to use and build your own plugins.\n"
  },
  {
    "path": "website/content/docs/plugins/packaging.mdx",
    "content": "---\nlayout: docs\npage_title: Packaging and Distribution - Plugin Development\ndescription: |-\n  This page documents how to organize the file structure of your plugin\n  and distribute it so that it is installable using standard installation\n  methods. Prior to reading this, you should be familiar with the plugin\n  development basics.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Plugin Development: Packaging & Distribution\n\nThis page documents how to organize the file structure of your plugin\nand distribute it so that it is installable using\n[standard installation methods](/vagrant/docs/plugins/usage).\nPrior to reading this, you should be familiar\nwith the [plugin development basics](/vagrant/docs/plugins/development-basics).\n\n~> **Warning: Advanced Topic!** Developing plugins is an\nadvanced topic that only experienced Vagrant users who are reasonably\ncomfortable with Ruby should approach.\n\n## Example Plugin\n\nThe best way to describe packaging and distribution is to look at\nhow another plugin does it. The best example plugin available for this\nis [vagrant-aws](https://github.com/mitchellh/vagrant-aws).\n\nBy using [Bundler](http://bundler.io) and Rake, building a new\nvagrant-aws package is easy. By simply calling `rake package`, a\n`gem` file is dropped into the directory. By calling `rake release`,\nthe gem is built and it is uploaded to the central [RubyGems](https://rubygems.org)\nrepository so that it can be installed using `vagrant plugin install`.\n\nYour plugin can and should be this easy, too, since you basically\nget this for free by using Bundler.\n\n## Setting Up Your Project\n\nTo setup your project, run `bundle gem vagrant-my-plugin`. This will create a\n`vagrant-my-plugin` directory that has the initial layout to be a RubyGem.\n\nYou should modify the `vagrant-my-plugin.gemspec` file to add any\ndependencies and change any metadata. View the [vagrant-aws.gemspec](https://github.com/mitchellh/vagrant-aws/blob/master/vagrant-aws.gemspec)\nfor a good example.\n\n~> **Do not depend on Vagrant** for your gem. Vagrant is no longer distributed\nas a gem, and you can assume that it will always be available when your plugin is\ninstalled.\n\nOnce the directory structure for a RubyGem is setup, you will want\nto modify your Gemfile. Here is the basic structure of a Gemfile for\nVagrant plugin development:\n\n```ruby\nsource \"https://rubygems.org\"\n\ngroup :development do\n  gem \"vagrant\", git: \"https://github.com/hashicorp/vagrant.git\"\nend\n\ngroup :plugins do\n  gem \"my-vagrant-plugin\", path: \".\"\nend\n```\n\nThis Gemfile gets \"vagrant\" for development. This allows you to\n`bundle exec vagrant` to run Vagrant with your plugin already loaded,\nso that you can test it manually that way.\n\nThe only thing about this Gemfile that may stand out as odd is the\n\"plugins\" group and putting your plugin in that group. Because\n`vagrant plugin` commands do not work in development, this is how\nyou \"install\" your plugin into Vagrant. Vagrant will automatically\nload any gems listed in the \"plugins\" group. Note that this also\nallows you to add multiple plugins to Vagrant for development, if\nyour plugin works with another plugin.\n\nNext, create a `Rakefile` that has at the very least, the following\ncontents:\n\n```ruby\nrequire \"rubygems\"\nrequire \"bundler/setup\"\nBundler::GemHelper.install_tasks\n```\n\nIf you run `rake -T` now, which lists all the available rake tasks,\nyou should see that you have the `package` and `release` tasks. You\ncan now develop your plugin and build it!\n\nYou can view the [vagrant-aws Rakefile](https://github.com/mitchellh/vagrant-aws/blob/master/Rakefile)\nfor a more comprehensive example that includes testing.\n\n## Testing Your Plugin\n\nTo manually test your plugin during development, use\n`bundle exec vagrant` to execute Vagrant with your plugin loaded\n(thanks to the Gemfile setup we did earlier).\n\nFor automated testing, the\n[vagrant-spec](https://github.com/hashicorp/vagrant-spec)\nproject provides helpers for both unit and acceptance testing\nplugins. See the giant README for that project for a detailed\ndescription of how to integrate vagrant-spec into your project.\nVagrant itself (and all of its core plugins) use vagrant-spec\nfor automated testing.\n"
  },
  {
    "path": "website/content/docs/plugins/providers.mdx",
    "content": "---\nlayout: docs\npage_title: Custom Providers - Plugin Development\ndescription: |-\n  This page documents how to add support for new providers to Vagrant, allowing\n  Vagrant to run and manage machines powered by a system other than VirtualBox.\n  Prior to reading this, you should be familiar with the plugin development\n  basics.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Plugin Development: Providers\n\nThis page documents how to add support for new [providers](/vagrant/docs/providers/)\nto Vagrant, allowing Vagrant to run and manage machines powered by a\nsystem other than VirtualBox. Prior to reading this, you should be familiar\nwith the [plugin development basics](/vagrant/docs/plugins/development-basics).\n\nPrior to developing a provider you should also be familiar with how\n[providers work](/vagrant/docs/providers/) from\na user standpoint.\n\n~> **Warning: Advanced Topic!** Developing plugins is an\nadvanced topic that only experienced Vagrant users who are reasonably\ncomfortable with Ruby should approach.\n\n## Example Provider: AWS\n\nThe best way to learn how to write a provider is to see how one is\nwritten in practice. To augment this documentation, please heavily\nstudy the [vagrant-aws](https://github.com/mitchellh/vagrant-aws) plugin,\nwhich implements an AWS provider. The plugin is a good example of how to\nstructure, test, and implement your plugin.\n\n## Definition Component\n\nWithin the context of a plugin definition, new providers are defined\nlike so:\n\n```ruby\nprovider \"my_cloud\" do\n  require_relative \"provider\"\n  Provider\nend\n```\n\nProviders are defined with the `provider` method, which takes a single\nargument specifying the name of the provider. This is the name that is\nused with `vagrant up` to specify the provider. So in the case above,\nour provider would be used by calling `vagrant up --provider=my_cloud`.\n\nThe block argument then lazily loads and returns a class that\nimplements the `Vagrant.plugin(2, :provider)` interface, which is covered\nnext.\n\n## Provider Class\n\nThe provider class should subclass and implement\n`Vagrant.plugin(2, :provider)` which is an upgrade-safe way to let Vagrant\nreturn the proper parent class.\n\nThis class and the methods that need to be implemented are\n[very well documented](https://github.com/hashicorp/vagrant/blob/main/lib/vagrant/plugin/v2/provider.rb). The documentation done on the class in the comments should be\nenough to understand what needs to be done.\n\nViewing the [AWS provider class](https://github.com/mitchellh/vagrant-aws/blob/master/lib/vagrant-aws/provider.rb) as well as the\n[overall structure of the plugin](https://github.com/mitchellh/vagrant-aws) is recommended as a strong getting started point.\n\nInstead of going in depth over each method that needs to be implemented,\nthe documentation will cover high-level but important points to help you\ncreate your provider.\n\n## Box Format\n\nEach provider is responsible for having its own box format. This is\nactually an extremely simple step due to how generic boxes are. Before\nexplaining you should get familiar with the general\n[box file format](/vagrant/docs/boxes/format).\n\nThe only requirement for your box format is that the `metadata.json`\nfile have a `provider` key which matches the name of your provider you\nchose above.\n\nIn addition to this, you may put any data in the metadata as well\nas any files in the archive. Since Vagrant core itself does not care,\nit is up to your provider to handle the data of the box. Vagrant core\njust handles unpacking and verifying the box is for the proper\nprovider.\n\nAs an example of a couple box formats that are actually in use:\n\n- The `virtualbox` box format is just a flat directory of the contents\n  of a `VBoxManage export` command.\n\n- The `vmware_fusion` box format is just a flat directory of the\n  contents of a `vmwarevm` folder, but only including the bare essential\n  files for VMware to function.\n\n- The `aws` box format is just a Vagrantfile defaulting some configuration.\n  You can see an [example aws box unpacked here](https://github.com/mitchellh/vagrant-aws/tree/master/example_box).\n\nBefore anything with your provider is even written, you can verify\nyour box format works by doing `vagrant box add` with it. When you do\na `vagrant box list` you can see what boxes for what providers are installed.\n\nYou do _not need_ the provider plugin installed to add a box for that\nprovider.\n\n## Actions\n\nProbably the most important concept to understand when building a\nprovider is the provider \"action\" interface. It is the secret sauce that\nmakes providers do the magic they do.\n\nActions are built on top of the concept of\n[middleware](https://github.com/mitchellh/middleware), which\nallow providers to execute multiple distinct steps, have error recovery\nmechanics, as well as before/after behaviors, and much more.\n\nVagrant core requests specific actions from your provider through the\n`action` method on your provider class. The full list of actions requested\nis listed in the comments of that method on the superclass. If your\nprovider does not implement a certain action, then Vagrant core will show\na friendly error, so do not worry if you miss any, things will not explode\nor crash spectacularly.\n\nTake a look at how the VirtualBox provider\n[uses actions to build up complicated multi-step processes](https://github.com/hashicorp/vagrant/blob/main/plugins/providers/virtualbox/action.rb#L287). The AWS provider [uses a similar process](https://github.com/mitchellh/vagrant-aws/blob/master/lib/vagrant-aws/action.rb).\n\n## Built-in Middleware\n\nTo assist with common tasks, Vagrant ships with a set of\n[built-in middleware](https://github.com/hashicorp/vagrant/tree/main/lib/vagrant/action/builtin). Each of the middleware is well commented on the behavior and options\nfor each, and using these built-in middleware is critical to building\na well-behaved provider.\n\nThese built-in middleware can be thought of as a standard library for\nyour actions on your provider. The core VirtualBox provider uses these\nbuilt-in middleware heavily.\n\n## Persisting State\n\nIn the process of creating and managing a machine, providers generally need\nto store some sort of state somewhere. Vagrant provides each machine with\na directory to store this state.\n\nAs a use-case example for this, the VirtualBox provider stores the UUID\nof the VirtualBox virtual machine created. This allows the provider to track\nwhether the machine is created, running, suspended, etc.\n\nThe VMware provider actually copies the entire virtual machine into this\nstate directory, complete with virtual disk drives and everything.\n\nThe directory is available from the `data_dir` attribute of the `Machine`\ninstance given to initialize your provider. Within middleware actions, the\nmachine is always available via the `:machine` key on the environment. The\n`data_dir` attribute is a Ruby [Pathname](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/pathname/rdoc/Pathname) object.\n\nIt is important for providers to carefully manage all the contents of\nthis directory. Vagrant core itself does little to clean up this directory.\nTherefore, when a machine is destroyed, be sure to clean up all the state\nfrom this directory.\n\n## Configuration\n\nVagrant supports [provider-specific configuration](/vagrant/docs/providers/configuration),\nallowing for users to finely tune and control specific providers from\nVagrantfiles. It is easy for your custom provider to expose custom configuration\nas well.\n\nProvider-specific configuration is a special case of a normal\n[configuration plugin](/vagrant/docs/plugins/configuration). When defining the\nconfiguration component, name the configuration the same as the provider,\nand as a second parameter, specify `:provider`, like so:\n\n```ruby\nconfig(\"my_cloud\", :provider) do\n  require_relative \"config\"\n  Config\nend\n```\n\nAs long as the name matches your provider, and the second `:provider`\nparameter is given, Vagrant will automatically expose this as provider-specific\nconfiguration for your provider. Users can now do the following in their\nVagrantfiles:\n\n```ruby\nconfig.vm.provider :my_cloud do |config|\n  # Your specific configuration!\nend\n```\n\nThe configuration class returned from the `config` component in the plugin\nis the same as any other [configuration plugin](/vagrant/docs/plugins/configuration),\nso read that page for more information. Vagrant automatically handles\nconfiguration validation and such just like any other configuration piece.\n\nThe provider-specific configuration is available on the machine object\nvia the `provider_config` attribute. So within actions or your provider class,\nyou can access the config via `machine.provider_config`.\n\n-> **Best practice:** Your provider should _not require_\nprovider-specific configuration to function, if possible. Vagrant practices a\nstrong [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration)\nphilosophy. When a user installs your provider, they should ideally be able to\n`vagrant up --provider=your_provider` and have it just work.\n\n## Parallelization\n\nVagrant supports parallelizing some actions, such as `vagrant up`, if the\nprovider explicitly supports it. By default, Vagrant will not parallelize a\nprovider.\n\nWhen parallelization is enabled, multiple [actions](#actions) may be run\nin parallel. Therefore, providers must be certain that their action stacks\nare thread-safe. The core of Vagrant itself (such as box collections, SSH,\netc.) is thread-safe.\n\nProviders can explicitly enable parallelization by setting the `parallel`\noption on the provider component:\n\n```ruby\nprovider(\"my_cloud\", parallel: true) do\n  require_relative \"provider\"\n  Provider\nend\n```\n\nThat is the only change that is needed to enable parallelization.\n"
  },
  {
    "path": "website/content/docs/plugins/provisioners.mdx",
    "content": "---\nlayout: docs\npage_title: Custom Provisioners - Plugin Development\ndescription: |-\n  This page documents how to add new provisioners to Vagrant, allowing Vagrant\n  to automatically install software and configure software using a custom\n  provisioner. Prior to reading this, you should be familiar with the plugin\n  development basics.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Plugin Development: Provisioners\n\nThis page documents how to add new [provisioners](/vagrant/docs/provisioning/) to Vagrant,\nallowing Vagrant to automatically install software and configure software\nusing a custom provisioner. Prior to reading this, you should be familiar\nwith the [plugin development basics](/vagrant/docs/plugins/development-basics).\n\n~> **Warning: Advanced Topic!** Developing plugins is an\nadvanced topic that only experienced Vagrant users who are reasonably\ncomfortable with Ruby should approach.\n\n## Definition Component\n\nWithin the context of a plugin definition, new provisioners can be defined\nlike so:\n\n```ruby\nprovisioner \"custom\" do\n  require_relative \"provisioner\"\n  Provisioner\nend\n```\n\nProvisioners are defined with the `provisioner` method, which takes a\nsingle argument specifying the name of the provisioner. This is the\nname that used with `config.vm.provision` when configuring and enabling\nthe provisioner. So in the case above, the provisioner would be enabled\nusing `config.vm.provision :custom`.\n\nThe block argument then lazily loads and returns a class that implements\nthe `Vagrant.plugin(2, :provisioner)` interface, which is covered next.\n\n## Provisioner Class\n\nThe provisioner class should subclass and implement\n`Vagrant.plugin(2, :provisioner)` which is an upgrade-safe way to let\nVagrant return the proper parent class for provisioners.\n\nThis class and the methods that need to be implemented are\n[very well documented](https://github.com/hashicorp/vagrant/blob/main/lib/vagrant/plugin/v2/provisioner.rb).\nThe documentation on the class in the comments should be enough\nto understand what needs to be done.\n\nThere are two main methods that need to be implemented: the\n`configure` method and the `provision` method.\n\nThe `configure` method is called early in the machine booting process\nto allow the provisioner to define new configuration on the machine, such\nas sharing folders, defining networks, etc. As an example, the\n[Chef solo provisioner](https://github.com/hashicorp/vagrant/blob/main/plugins/provisioners/chef/provisioner/chef_solo.rb#L24)\nuses this to define shared folders.\n\nThe `provision` method is called when the machine is booted and ready\nfor SSH connections. In this method, the provisioner should execute\nany commands that need to be executed.\n"
  },
  {
    "path": "website/content/docs/plugins/usage.mdx",
    "content": "---\nlayout: docs\npage_title: Plugin Usage - Plugins\ndescription: |-\n  Installing a Vagrant plugin is easy, and should not take more than a few\n  seconds.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Plugin Usage\n\nInstalling a Vagrant plugin is easy, and should not take more than a few seconds.\n\nPlease refer to the documentation of any plugin you plan on using for\nmore information on how to use it, but there is one common method for\ninstallation and plugin activation.\n\n~> **Warning!** 3rd party plugins can introduce instabilities\ninto Vagrant due to the nature of them being written by non-core users.\n\n## Installation\n\nPlugins are installed using `vagrant plugin install`:\n\n```shell\n# Installing a plugin from a known gem source\n$ vagrant plugin install my-plugin\n\n# Installing a plugin from a local file source\n$ vagrant plugin install /path/to/my-plugin.gem\n```\n\nOnce a plugin is installed, it will automatically be loaded by Vagrant.\nPlugins which cannot be loaded should not crash Vagrant. Instead,\nVagrant will show an error message that a plugin failed to load.\n\n## Usage\n\nOnce a plugin is installed, you should refer to the plugin's documentation\nto see exactly how to use it. Plugins which add commands should be instantly\navailable via `vagrant`, provisioners should be available via\n`config.vm.provision`, etc.\n\n**Note:** In the future, the `vagrant plugin` command will include a\nsubcommand that will document the components that each plugin installs.\n\n## Updating\n\nPlugins can be updated by running `vagrant plugin update`. This will\nupdate every installed plugin to the latest version. You can update a\nspecific plugin by calling `vagrant plugin update NAME`. Vagrant will\noutput what plugins were updated and to what version.\n\nTo determine the changes in a specific version of a plugin, refer to\nthe plugin's homepage (usually a GitHub page or similar). It is the\nplugin author's responsibility to provide a change log if he or she\nchooses to.\n\n## Uninstallation\n\nUninstalling a plugin is as easy as installing it. Just use the\n`vagrant plugin uninstall` command and the plugin will be removed. Example:\n\n```shell-session\n$ vagrant plugin uninstall my-plugin\n```\n\n## Listing Plugins\n\nTo view what plugins are installed into your Vagrant environment at\nany time, use the `vagrant plugin list` command. This will list the plugins\nthat are installed along with their version.\n"
  },
  {
    "path": "website/content/docs/providers/basic_usage.mdx",
    "content": "---\nlayout: docs\npage_title: Basic Usage - Providers\ndescription: |-\n  Vagrant boxes are all provider-specific. A box for VirtualBox is incompatible\n  with the VMware Fusion provider, or any other provider. A box must be\n  installed for each provider, and can share the same name as other boxes as\n  long as the providers differ. So you can have both a VirtualBox and VMware\n  Fusion \"bionic64\" box.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Basic Provider Usage\n\n## Boxes\n\nVagrant boxes are all provider-specific. A box for VirtualBox is incompatible\nwith the VMware Fusion provider, or any other provider. A box must be installed\nfor each provider, and can share the same name as other boxes as long\nas the providers differ. So you can have both a VirtualBox and VMware Fusion\n\"bionic64\" box.\n\nInstalling boxes has not changed at all:\n\n```shell-session\n$ vagrant box add hashicorp/bionic64\n```\n\nVagrant now automatically detects what provider a box is for. This is\nvisible when listing boxes. Vagrant puts the provider in parentheses next\nto the name, as can be seen below.\n\n```shell-session\n$ vagrant box list\nbionic64 (virtualbox)\nbionic64 (vmware_fusion)\n```\n\n## Vagrant Up\n\nOnce a provider is installed, you can use it by calling `vagrant up`\nwith the `--provider` flag. This will force Vagrant to use that specific\nprovider. No other configuration is necessary!\n\nIn normal day-to-day usage, the `--provider` flag is not necessary\nsince Vagrant can usually pick the right provider for you. More details\non how it does this is below.\n\n```shell-session\n$ vagrant up --provider=vmware_fusion\n```\n\nIf you specified a `--provider` flag, you only need to do this for the\n`up` command. Once a machine is up and running, Vagrant is able to\nsee what provider is backing a running machine, so commands such as\n`destroy`, `suspend`, etc. do not need to be told what provider to use.\n\n-> Vagrant currently restricts you to bringing up one provider per machine.\nIf you have a multi-machine environment, you can bring up one machine\nbacked by VirtualBox and another backed by VMware Fusion, for example, but you\ncannot back the <em>same machine</em> with both VirtualBox and\nVMware Fusion. This is a limitation that will be removed in a future\nversion of Vagrant.\n\n## Default Provider\n\nAs mentioned earlier, you typically do not need to specify `--provider`\n_ever_. Vagrant is smart enough about being able to detect the provider\nyou want for a given environment.\n\nVagrant attempts to find the default provider in the following order:\n\n1. The `--provider` flag on a `vagrant up` is chosen above all else, if\n   it is present.\n\n2. If the `VAGRANT_DEFAULT_PROVIDER` environmental variable is set,\n   it takes next priority and will be the provider chosen.\n\n3. Vagrant will go through all of the `config.vm.provider` calls in the\n   Vagrantfile and try each in order. It will choose the first provider\n   that is usable. For example, if you configure Hyper-V, it will never\n   be chosen on Mac this way. It must be both configured and usable.\n\n4. Vagrant will go through all installed provider plugins (including the\n   ones that come with Vagrant), and find the first plugin that reports\n   it is usable. There is a priority system here: systems that are known\n   better have a higher priority than systems that are worse. For example,\n   if you have the VMware provider installed, it will always take priority\n   over VirtualBox.\n\n5. If Vagrant still has not found any usable providers, it will error.\n\nUsing this method, there are very few cases that Vagrant does not find the\ncorrect provider for you. This also allows each\n[Vagrantfile](/vagrant/docs/vagrantfile/) to define what providers\nthe development environment is made for by ordering provider configurations.\n\nA trick is to use `config.vm.provider` with no configuration at the top of\nyour Vagrantfile to define the order of providers you prefer to support:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # ... other config up here\n\n  # Prefer VMware Fusion before VirtualBox\n  config.vm.provider \"vmware_fusion\"\n  config.vm.provider \"virtualbox\"\nend\n```\n"
  },
  {
    "path": "website/content/docs/providers/configuration.mdx",
    "content": "---\nlayout: docs\npage_title: Configuration - Providers\ndescription: |-\n  While well-behaved Vagrant providers should work with any Vagrantfile with\n  sane defaults, providers generally expose unique configuration options so that\n  you can get the most out of each provider\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Configuration\n\nWhile well-behaved Vagrant providers should work with any Vagrantfile with sane\ndefaults, providers generally expose unique configuration\noptions so that you can get the most out of each provider.\n\nThis provider-specific configuration is done within the Vagrantfile\nin a way that is portable, easy to use, and easy to understand.\n\n## Portability\n\nAn important fact is that even if you configure other providers within\na Vagrantfile, the Vagrantfile remains portable even to individuals who\ndo not necessarily have that provider installed.\n\nFor example, if you configure VMware Fusion and send it to an individual\nwho does not have the VMware Fusion provider, Vagrant will silently ignore\nthat part of the configuration.\n\n## Provider Configuration\n\nConfiguring a specific provider looks like this:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # ...\n\n  config.vm.provider \"virtualbox\" do |vb|\n    vb.customize [\"modifyvm\", :id, \"--cpuexecutioncap\", \"50\"]\n  end\nend\n```\n\nMultiple `config.vm.provider` blocks can exist to configure multiple\nproviders.\n\nThe configuration format should look very similar to how provisioners\nare configured. The `config.vm.provider` takes a single parameter: the\nname of the provider being configured. Then, an inner block with custom\nconfiguration options is exposed that can be used to configure that\nprovider.\n\nThis inner configuration differs among providers, so please read the\ndocumentation for your provider of choice to see available configuration\noptions.\n\nRemember, some providers do not require any provider-specific configuration\nand work directly out of the box. Provider-specific configuration is meant\nas a way to expose more options to get the most of the provider of your\nchoice. It is not meant as a roadblock to running against a specific provider.\n\n## Overriding Configuration\n\nProviders can also override non-provider specific configuration, such\nas `config.vm.box` and any other Vagrant configuration. This is done by\nspecifying a second argument to `config.vm.provider`. This argument is\njust like the normal `config`, so set any settings you want, and they will\nbe overridden only for that provider.\n\nExample:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"bionic64\"\n\n  config.vm.provider \"vmware_fusion\" do |v, override|\n    override.vm.box = \"bionic64_fusion\"\n  end\nend\n```\n\nIn the above case, Vagrant will use the \"bionic64\" box by default, but\nwill use \"bionic64_fusion\" if the VMware Fusion provider is used.\n\n-> **The Vagrant Way:** The proper \"Vagrant way\" is to\navoid any provider-specific overrides if possible by making boxes\nfor multiple providers that are as identical as possible, since box\nnames can map to multiple providers. However, this is not always possible,\nand in those cases, overrides are available.\n"
  },
  {
    "path": "website/content/docs/providers/custom.mdx",
    "content": "---\nlayout: docs\npage_title: Custom Provider - Providers\ndescription: |-\n  To learn how to make your own custom Vagrant providers, read the Vagrant\n  plugin development guide on creating custom providers.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Custom Provider\n\nTo learn how to make your own custom Vagrant providers, read the Vagrant plugin\ndevelopment guide on [creating custom providers](/vagrant/docs/plugins/providers).\n"
  },
  {
    "path": "website/content/docs/providers/default.mdx",
    "content": "---\nlayout: docs\npage_title: Default Provider - Providers\ndescription: |-\n  By default, VirtualBox is the default provider for Vagrant. VirtualBox is\n  still the most accessible platform to use Vagrant: it is free, cross-platform,\n  and has been supported by Vagrant for years. With VirtualBox as the default\n  provider, it provides the lowest friction for new users to get started with\n  Vagrant.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Default Provider\n\nBy default, VirtualBox is the default provider for Vagrant. VirtualBox is\nstill the most accessible platform to use Vagrant: it is free, cross-platform,\nand has been supported by Vagrant for years. With VirtualBox as the default\nprovider, it provides the lowest friction for new users to get started with\nVagrant.\n\nHowever, you may find after using Vagrant for some time that you prefer\nto use another provider as your default. In fact, this is quite common.\nTo make this experience better, Vagrant allows specifying the default\nprovider to use by setting the `VAGRANT_DEFAULT_PROVIDER` environmental\nvariable.\n\nJust set `VAGRANT_DEFAULT_PROVIDER` to the provider you wish to be the\ndefault. For example, if you use Vagrant with VMware Fusion, you can set\nthe environmental variable to `vmware_desktop` and it will be your default.\n"
  },
  {
    "path": "website/content/docs/providers/docker/basics.mdx",
    "content": "---\nlayout: docs\npage_title: Basic Usage - Docker Provider\ndescription: |-\n  The Docker provider in Vagrant behaves just like any other provider.\n  If you are familiar with Vagrant already, then using the Docker provider\n  should be intuitive and simple.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Docker Basic Usage\n\nThe Docker provider in Vagrant behaves just like any other provider.\nIf you are familiar with Vagrant already, then using the Docker provider\nshould be intuitive and simple.\n\nThe Docker provider _does not_ require a `config.vm.box` setting. Since\nthe \"base image\" for a Docker container is pulled from the\nDocker Index or\nbuilt from a Dockerfile, the box does not\nadd much value, and is optional for this provider.\n\n## Docker Images\n\nThe first method that Vagrant can use to source a Docker container\nis via an image. This image can be from any Docker registry. An\nexample is shown below:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provider \"docker\" do |d|\n    d.image = \"foo/bar\"\n  end\nend\n```\n\nWhen `vagrant up --provider=docker` is run, this will bring up the\nimage `foo/bar`.\n\nThis is useful for extra components of your application that it might\ndepend on: databases, queues, etc. Typically, the primary application\nyou are working on is built with a Dockerfile, or via a container with\nSSH.\n\n## Dockerfiles\n\nVagrant can also automatically build and run images based on a local\nDockerfile. This is useful for iterating on an application locally\nthat is built into an image later. An example is shown below:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provider \"docker\" do |d|\n    d.build_dir = \".\"\n  end\nend\n```\n\nThe above configuration will look for a `Dockerfile` in the same\ndirectory as the Vagrantfile. When `vagrant up --provider=docker` is run, Vagrant\nautomatically builds that Dockerfile and starts a container\nbased on that Dockerfile.\n\nThe Dockerfile is rebuilt when `vagrant reload` is called.\n\n## Synced Folders and Networking\n\nWhen using Docker, Vagrant automatically converts synced folders\nand networking options into Docker volumes and forwarded ports.\nYou do not have to use the Docker-specific configurations to do this.\nThis helps keep your Vagrantfile similar to how it has always looked.\n\nThe Docker provider does not support specifying options for `owner` or `group`\non folders synced with a docker container.\n\n### Volume Consistency\n\nDocker's [volume consistency](https://docs.docker.com/v17.09/engine/admin/volumes/bind-mounts/) setting can be specified using the `docker_consistency` option when defining a synced folder. This can\n[greatly improve performance on macOS](https://docs.docker.com/docker-for-mac/osxfs-caching). An example is shown using the `cached` and `delegated` settings:\n\n```ruby\nconfig.vm.synced_folder \"/host/dir1\", \"/guest/dir1\", docker_consistency: \"cached\"\nconfig.vm.synced_folder \"/host/dir2\", \"/guest/dir2\", docker_consistency: \"delegated\"\n```\n\n## Host VM\n\nIf the system cannot run Linux containers natively, Vagrant automatically spins\nup a \"host VM\" to run Docker. This allows your Docker-based Vagrant environments\nto remain portable, without inconsistencies depending on the platform they are\nrunning on.\n\nVagrant will spin up a single instance of a host VM and run multiple\ncontainers on this one VM. This means that with the Docker provider,\nyou only have the overhead of one virtual machine, and only if it is\nabsolutely necessary.\n\nBy default, the host VM Vagrant spins up is\n[backed by boot2docker](https://github.com/hashicorp/vagrant/blob/main/plugins/providers/docker/hostmachine/Vagrantfile),\nbecause it launches quickly and uses little resources. But the host VM\ncan be customized to point to _any_ Vagrantfile. This allows the host VM\nto more closely match production by running a VM running Ubuntu, RHEL,\netc. It can run any operating system supported by Vagrant.\n\n-> **Synced folder note:** Vagrant will attempt to use the\n\"best\" synced folder implementation it can. For boot2docker, this is\noften rsync. In this case, make sure you have rsync installed on your\nhost machine. Vagrant will give you a human-friendly error message if\nit is not.\n\nAn example of changing the host VM is shown below. Remember that this\nis optional, and Vagrant will spin up a default host VM if it is not\nspecified:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provider \"docker\" do |d|\n    d.vagrant_vagrantfile = \"../path/to/Vagrantfile\"\n  end\nend\n```\n\nThe host VM will be spun up at the first `vagrant up` where the provider\nis Docker. To control this host VM, use the\n[global-status command](/vagrant/docs/cli/global-status)\nalong with global control.\n"
  },
  {
    "path": "website/content/docs/providers/docker/boxes.mdx",
    "content": "---\nlayout: docs\npage_title: Boxes - Docker Provider\ndescription: |-\n  The Docker provider does not require a Vagrant box. The \"config.vm.box\"\n  setting is completely optional.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Docker Boxes\n\nThe Docker provider does not require a Vagrant box. The `config.vm.box`\nsetting is completely optional.\n\nA box can still be used and specified, however, to provide defaults.\nBecause the `Vagrantfile` within a box is loaded as part of the\nconfiguration loading sequence, it can be used to configure the\nfoundation of a development environment.\n\nIn general, however, you will not need a box with the Docker provider.\n"
  },
  {
    "path": "website/content/docs/providers/docker/commands.mdx",
    "content": "---\nlayout: docs\npage_title: Commands - Docker Provider\ndescription: |-\n  The Docker provider exposes some additional Vagrant commands that are\n  useful for interacting with Docker containers. This helps with your\n  workflow on top of Vagrant so that you have full access to Docker\n  underneath.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Docker Commands\n\nThe Docker provider exposes some additional Vagrant commands that are\nuseful for interacting with Docker containers. This helps with your\nworkflow on top of Vagrant so that you have full access to Docker\nunderneath.\n\n### docker-exec\n\n`vagrant docker-exec` can be used to run one-off commands against\na Docker container that is currently running. If the container is not running,\nan error will be returned.\n\n```shell-session\n$ vagrant docker-exec app -- rake db:migrate\n```\n\nThe above would run `rake db:migrate` in the context of an `app` container.\n\nNote that the \"name\" corresponds to the name of the VM, **not** the name of the\nDocker container. Consider the following Vagrantfile:\n\n```ruby\nVagrant.configure(2) do |config|\n  config.vm.provider \"docker\" do |d|\n    d.image = \"consul\"\n  end\nend\n```\n\nThis Vagrantfile will start the official Docker Consul image. However, the\nassociated Vagrant command to `docker-exec` into this instance is:\n\n```shell-session\n$ vagrant docker-exec -it -- /bin/sh\n```\n\nIn particular, the command is actually:\n\n```shell-session\n$ vagrant docker-exec default -it -- /bin/sh\n```\n\nBecause \"default\" is the default name of the first defined VM. In a\nmulti-machine Vagrant setup as shown below, the \"name\" attribute corresponds\nto the name of the VM, **not** the name of the container:\n\n```ruby\nVagrant.configure do |config|\n  config.vm.define \"web\" do\n    config.vm.provider \"docker\" do |d|\n      d.image = \"nginx\"\n    end\n  end\n\n  config.vm.define \"consul\" do\n    config.vm.provider \"docker\" do |d|\n      d.image = \"consul\"\n    end\n  end\nend\n```\n\nThe following command is invalid:\n\n```shell-session\n# Not valid\n$ vagrant docker-exec -it nginx -- /bin/sh\n```\n\nThis is because the \"name\" of the VM is \"web\", so the command is actually:\n\n```shell-session\n$ vagrant docker-exec -it web -- /bin/sh\n```\n\nFor this reason, it is recommended that you name the VM the same as the\ncontainer. In the above example, it is unambiguous that the command to enter\nthe Consul container is:\n\n```shell-session\n$ vagrant docker-exec -it consul -- /bin/sh\n```\n\n### docker-logs\n\n`vagrant docker-logs` can be used to see the logs of a running container.\nBecause most Docker containers are single-process, this is used to see\nthe logs of that one process. Additionally, the logs can be tailed.\n\n### docker-run\n\n`vagrant docker-run` can be used to run one-off commands against\na Docker container. The one-off Docker container that is started shares\nall the volumes, links, etc. of the original Docker container. An\nexample is shown below:\n\n```shell-session\n$ vagrant docker-run app -- rake db:migrate\n```\n\nThe above would run `rake db:migrate` in the context of an `app` container.\n"
  },
  {
    "path": "website/content/docs/providers/docker/configuration.mdx",
    "content": "---\nlayout: docs\npage_title: Configuration- Docker Provider\ndescription: |-\n  The Docker provider has some provider-specific configuration options\n  you may set. A complete reference is shown on this page.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Docker Configuration\n\nThe Docker provider has some provider-specific configuration options\nyou may set. A complete reference is shown below.\n\n## Required\n\nOne of the following settings is required when using the Docker provider:\n\n- `build_dir` (string) - The path to a directory containing a Dockerfile.\n\n- `image` (string) - The image to launch, specified by the image ID or a name\n  such as `ubuntu:12.04`.\n\n- `git_repo` (string) - The URL of a git repository to build the image from.\n  Supports pulling specific tags, branches and revision, consult the\n  [docker documentation](https://docs.docker.com/engine/reference/commandline/build/#/git-repositories)\n  for more information.\n\n## Optional\n\nGeneral settings:\n\n- `build_args` (array of strings) - Extra arguments to pass to\n  `docker build` when `build_dir` is in use.\n\n- `cmd` (array of strings) - Custom command to run on the container.\n  Example: `[\"ls\", \"/app\"]`.\n\n- `compose` (boolean) - If true, Vagrant will use `docker-compose` to\n  manage the lifecycle and configuration of containers. This defaults\n  to false.\n\n- `compose_configuration` (Hash) - Configuration values used for populating\n  the `docker-compose.yml` file. The value of this Hash is directly merged\n  and written to the `docker-compose.yml` file allowing customization of\n  non-services items like networks and volumes.\n\n- `create_args` (array of strings) - Additional arguments to pass to\n  `docker run` when the container is started. This can be used to set\n  parameters that are not exposed via the Vagrantfile.\n\n- `dockerfile` (string) - Name of the Dockerfile in the build directory.\n  This defaults to \"Dockerfile\"\n\n- `env` (hash) - Environmental variables to expose into the container.\n\n- `expose` (array of integers) - Ports to expose from the container\n  but not to the host machine. Useful for links.\n\n- `link` (method, string argument) - Link this container to another\n  by name. The argument should be in the format of `(name:alias)`.\n  Example: `docker.link(\"db:db\")`. Note, if you are linking to\n  another container in the same Vagrantfile, make sure you call\n  `vagrant up` with the `--no-parallel` flag.\n\n- `force_host_vm` (boolean) - If true, then a host VM will be spun up\n  even if the computer running Vagrant supports Linux containers. This\n  is useful to enforce a consistent environment to run Docker. This value\n  defaults to \"false\" on Linux, Mac, and Windows hosts and defaults to \"true\"\n  on other hosts. Users on other hosts who choose to use a different Docker\n  provider or opt-in to the native Docker builds can explicitly set this\n  value to false to disable the behavior.\n\n- `has_ssh` (boolean) - If true, then Vagrant will support SSH with\n  the container. This allows `vagrant ssh` to work, provisioners, etc.\n  This defaults to false.\n\n- `host_vm_build_dir_options` (hash) - Synced folder options for the\n  `build_dir`, since the build directory is synced using a synced folder\n  if a host VM is in use.\n\n- `name` (string) - Name of the container. Note that this has to be unique\n  across all containers on the host VM. By default Vagrant will generate\n  some random name.\n\n- `pull` (bool) - If true, the image will be pulled on every `up` and\n  `reload`. Defaults to false.\n\n- `ports` (array of strings) - Ports to expose from the container to the\n  host. These should be in the format of `host:container`.\n\n- `remains_running` (boolean) - If true, Vagrant expects this container\n  to remain running and will make sure that it does for a certain amount\n  of time. If false, then Vagrant expects that this container will\n  automatically stop at some point, and will not error if it sees it do that.\n\n- `stop_timeout` (integer) - The amount of time to wait when stopping\n  a container before sending a SIGTERM to the process.\n\n- `vagrant_machine` (string) - The name of the Vagrant machine in the\n  `vagrant_vagrantfile` to use as the host machine. This defaults to\n  \"default\".\n\n- `vagrant_vagrantfile` (string) - Path to a Vagrantfile that contains\n  the `vagrant_machine` to use as the host VM if needed.\n\n- `volumes` (array of strings) - List of directories to mount as\n  volumes into the container. These directories must exist in the\n  host where Docker is running. If you want to sync folders from the\n  host Vagrant is running, just use synced folders.\n\nBelow, we have settings related to auth. If these are set, then Vagrant\nwill `docker login` prior to starting containers, allowing you to pull\nimages from private repositories.\n\n- `email` (string) - Email address for logging in.\n\n- `username` (string) - Username for logging in.\n\n- `password` (string) - Password for logging in.\n\n- `auth_server` (string) - The server to use for authentication. If not\n  set, the Docker Hub will be used.\n"
  },
  {
    "path": "website/content/docs/providers/docker/index.mdx",
    "content": "---\nlayout: docs\npage_title: Docker Provider\ndescription: |-\n  Vagrant comes with support out of the box for\n  using Docker as a provider. This allows for your development environments\n  to be backed by Docker containers rather than virtual machines. Additionally,\n  it provides for a good workflow for developing Dockerfiles.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Docker\n\nVagrant comes with support out of the box for\nusing Docker as a provider. This allows for your development environments\nto be backed by Docker containers rather than virtual machines. Additionally,\nit provides for a good workflow for developing Dockerfiles.\n\n~> **Warning: Docker knowledge assumed.** We assume that\nyou know what Docker is and that you are comfortable with the basics\nof Docker. If not, we recommend starting with another provider such\nas [VirtualBox](/vagrant/docs/providers/virtualbox).\n\nUse the navigation to the left to find a specific Docker topic\nto read more about.\n"
  },
  {
    "path": "website/content/docs/providers/docker/networking.mdx",
    "content": "---\nlayout: docs\npage_title: Networking - Docker Provider\ndescription: |-\n  The Vagrant Docker provider supports using the private network using the\n  `docker network` commands.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Networking\n\nVagrant uses the `docker network` command under the hood to create and manage\nnetworks for containers. Vagrant will do its best to create and manage networks\nfor any containers configured inside the Vagrantfile. Each docker network is grouped\nby the subnet used for a requested ip address.\n\nFor each newly unique network, Vagrant will run the `docker network create` subcommand\nwith the provided options from the network config inside your Vagrantfile. If multiple\nnetworks share the same subnet, Vagrant will reuse that existing network for multiple\ncontainers. Once these networks have been created, Vagrant will attach these\nnetworks to the requested containers using the `docker network connect` for each\nnetwork.\n\nVagrant names the networks inside docker as `vagrant_network` or `vagrant_network_<subnet here>`\nwhere `<subnet_here>` is the subnet for the network if defined by the user. An\nexample of these networks is shown later in this page. If no subnet is requested\nfor the network, Vagrant will connect the `vagrant_network` to the container.\n\nWhen destroying containers through Vagrant, Vagrant will clean up the network if\nthere are no more containers using the network.\n\n## Docker Network Options\n\nMost of the options work similar to other Vagrant providers. Defining either an\nip or using `type: 'dhcp'` will give you a network on your container.\n\n```ruby\ndocker.vm.network :private_network, type: \"dhcp\"\ndocker.vm.network :private_network, ip: \"172.20.128.2\"\n```\n\nIf you want to set something specific with a new network you can use scoped options\nwhich align with the command line flags for the [docker network create](https://docs.docker.com/engine/reference/commandline/network_create/)\ncommand. If there are any specific options you want to enable from the `docker network create`\ncommand, you can specify them like this:\n\n```ruby\ndocker.vm.network :private_network, type: \"dhcp\", docker_network__internal: true\n```\n\nThis will enable the `internal` option for the network when created with `docker network create`.\n\nWhere `option` corresponds to the given flag that will be provided to the `docker network create`\ncommand. Similarly, if there is a value you wish to enable when connecting a container\nto a given network, you can use the following value in your network config:\n\n```ruby\ndocker_connect__option: \"value\"\n```\n\nWhen the docker provider creates a new network a netmask is required. If the netmask\nis not provided, Vagrant will default to a `/24` for IPv4 and `/64` for IPv6. To provide\na different mask, set it using the `netmask` option:\n\n```ruby\ndocker.vm.network :private_network, ip: \"172.20.128.2\", netmask: 16\n```\n\nFor networks which set the type to \"dhcp\", it is also possible to specify a specific\nsubnet for the network connection. This allows containers to connect to networks other\nthan the default `vagrant_network` network. The docker provider supports specifying\nthe desired subnet in two ways. The first is by using the `ip` and `netmask` options:\n\n```ruby\ndocker.vm.network :private_network, type: \"dhcp\", ip: \"172.20.128.0\", netmask: 24\n```\n\nThe second is by using the `subnet` option:\n\n```ruby\ndocker.vm.network :private_network, type: \"dhcp\", subnet: \"172.20.128.0/24\"\n```\n\n### Public Networks\n\nThe Vagrant docker provider also supports defining public networks. The easiest way\nto define a public network is by setting the `type` option to \"dhcp\":\n\n```ruby\ndocker.vm.network :public_network, type: \"dhcp\"\n```\n\nA bridge interface is required when setting up a public network. When no bridge\ndevice name is provided, Vagrant will prompt for the appropriate device to use. This\ncan also be set using the `bridge` option:\n\n```ruby\ndocker.vm.network :public_network, type: \"dhcp\", bridge: \"eth0\"\n```\n\nThe `bridge` option also supports a list of interfaces which can be used for\nsetting up the network. Vagrant will inspect the defined interfaces and use\nthe first active interface when setting up the network:\n\n```ruby\ndocker.vm.network :public_network, type: \"dhcp\", bridge: [\"eth0\", \"wlan0\"]\n```\n\nThe available IP range for the bridge interface must be known when setting up\nthe docker network. Even though a DHCP service may be available on the public\nnetwork, docker will manage IP addresses provided to containers. This means\nthat the subnet provided when defining the available IP range for the network\nshould not be included within the subnet managed by the DHCP service. Vagrant\nwill prompt for the available IP range information, however, it can also be\nprovided in the Vagrantfile using the `docker_network__ip_range` option:\n\n```ruby\ndocker.vm.network :public_network, type: \"dhcp\", bridge: \"eth0\", docker_network__ip_range: \"192.168.1.252/30\"\n```\n\nFinally, the gateway for the interface is required during setup. The docker\nprovider will default the gateway address to the first address available for\nthe subnet of the bridge device. Vagrant will prompt for confirmation to use\nthe default address. The address can also be manually set in the Vagrantfile\nusing the `docker_network__gateway` option:\n\n```ruby\ndocker.vm.network :public_network, type: \"dhcp\", bridge: \"eth0\", docker_network__gateway: \"192.168.1.2\"\n```\n\nMore examples are shared below which demonstrate creating a few common network\ninterfaces.\n\n## Docker Network Example\n\nThe following Vagrantfile will generate these networks for a container:\n\n1. A IPv4 IP address assigned by DHCP\n2. A IPv4 IP address 172.20.128.2 on a network with subnet 172.20.0.0/16\n3. A IPv6 IP address assigned by DHCP on subnet 2a02:6b8:b010:9020:1::/80\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.define \"docker\"  do |docker|\n    docker.vm.network :private_network, type: \"dhcp\", docker_network__internal: true\n    docker.vm.network :private_network,\n        ip: \"172.20.128.2\", netmask: \"16\"\n    docker.vm.network :private_network, type: \"dhcp\", subnet: \"2a02:6b8:b010:9020:1::/80\"\n    docker.vm.provider \"docker\" do |d|\n      d.build_dir = \"docker_build_dir\"\n    end\n  end\nend\n```\n\nYou can test that your container has the proper configured networks by looking\nat the result of running `ip addr`, for example:\n\n```\nbrian@localghost:vagrant-sandbox % docker ps                                                             ±[●][master]\nCONTAINER ID        IMAGE                                  COMMAND                  CREATED             STATUS              PORTS                                              NAMES\n370f4e5d2217        196a06ef12f5                           \"tail -f /dev/null\"      5 seconds ago       Up 3 seconds        80/tcp, 443/tcp                                    vagrant-sandbox_docker-1_1551810440\nbrian@localghost:vagrant-sandbox % docker exec 370f4e5d2217 ip addr                                      ±[●][master]\n1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000\n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n    inet 127.0.0.1/8 scope host lo\n       valid_lft forever preferred_lft forever\n    inet6 ::1/128 scope host\n       valid_lft forever preferred_lft forever\n24: eth0@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default\n    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0\n    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0\n       valid_lft forever preferred_lft forever\n27: eth1@if28: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default\n    link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0\n    inet 172.19.0.2/16 brd 172.19.255.255 scope global eth1\n       valid_lft forever preferred_lft forever\n30: eth2@if31: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default\n    link/ether 02:42:ac:14:80:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0\n    inet 172.20.128.2/16 brd 172.20.255.255 scope global eth2\n       valid_lft forever preferred_lft forever\n33: eth3@if34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default\n    link/ether 02:42:ac:15:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0\n    inet 172.21.0.2/16 brd 172.21.255.255 scope global eth3\n       valid_lft forever preferred_lft forever\n    inet6 2a02:6b8:b010:9020:1::2/80 scope global nodad\n       valid_lft forever preferred_lft forever\n    inet6 fe80::42:acff:fe15:2/64 scope link\n       valid_lft forever preferred_lft forever\n```\n\nYou can also connect your containers to a docker network that was created outside\nof Vagrant:\n\n```shell-session\n$ docker network create my-custom-network --subnet=172.20.0.0/16\n```\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.define \"docker\"  do |docker|\n    docker.vm.network :private_network, type: \"dhcp\", name: \"my-custom-network\"\n    docker.vm.provider \"docker\" do |d|\n      d.build_dir = \"docker_build_dir\"\n    end\n  end\nend\n```\n\nVagrant will not delete or modify these outside networks when deleting the container, however.\n\n## Useful Debugging Tips\n\nThe `docker network` command provides some helpful insights to what might be going\non with the networks Vagrant creates. For example, if you want to know what networks\nyou currently have running on your machine, you can run the `docker network ls` command:\n\n```\nbrian@localghost:vagrant-sandbox % docker network ls                                                     ±[●][master]\nNETWORK ID          NAME                                        DRIVER              SCOPE\na2bfc26bd876        bridge                                      bridge              local\n2a2845e77550        host                                        host                local\nf36682aeba68        none                                        null                local\n00d4986c7dc2        vagrant_network                             bridge              local\nd02420ff4c39        vagrant_network_2a02:6b8:b010:9020:1::/80   bridge              local\n799ae9dbaf98        vagrant_network_172.20.0.0/16               bridge              local\n```\n\nYou can also inspect any network for more information:\n\n```\nbrian@localghost:vagrant-sandbox % docker network inspect vagrant_network                                ±[●][master]\n[\n    {\n        \"Name\": \"vagrant_network\",\n        \"Id\": \"00d4986c7dc2ed7bf1961989ae1cfe98504c711f9de2f547e5dfffe2bb819fc2\",\n        \"Created\": \"2019-03-05T10:27:21.558824922-08:00\",\n        \"Scope\": \"local\",\n        \"Driver\": \"bridge\",\n        \"EnableIPv6\": false,\n        \"IPAM\": {\n            \"Driver\": \"default\",\n            \"Options\": {},\n            \"Config\": [\n                {\n                    \"Subnet\": \"172.19.0.0/16\",\n                    \"Gateway\": \"172.19.0.1\"\n                }\n            ]\n        },\n        \"Internal\": false,\n        \"Attachable\": false,\n        \"Ingress\": false,\n        \"ConfigFrom\": {\n            \"Network\": \"\"\n        },\n        \"ConfigOnly\": false,\n        \"Containers\": {\n            \"370f4e5d2217e698b16376583fbf051dd34018e5fd18958b604017def92fea63\": {\n                \"Name\": \"vagrant-sandbox_docker-1_1551810440\",\n                \"EndpointID\": \"166b7ca8960a9f20a150bb75a68d07e27e674781ed9f916e9aa58c8bc2539a61\",\n                \"MacAddress\": \"02:42:ac:13:00:02\",\n                \"IPv4Address\": \"172.19.0.2/16\",\n                \"IPv6Address\": \"\"\n            }\n        },\n        \"Options\": {},\n        \"Labels\": {}\n    }\n]\n```\n\n## Caveats\n\nFor now, Vagrant only looks at the subnet when figuring out if it should create\na new network for a guest container. If you bring up a container with a network,\nand then change or add some new options (but leave the subnet the same), it will\nnot apply those changes or create a new network.\n\nBecause the `--link` flag for the `docker network connect` command is considered\nlegacy, Vagrant does not support that option when creating containers and connecting\nnetworks.\n\n## More Information\n\nFor more information on how docker manages its networks, please refer to their\ndocumentation:\n\n- https://docs.docker.com/network/\n- https://docs.docker.com/engine/reference/commandline/network/\n"
  },
  {
    "path": "website/content/docs/providers/hyperv/boxes.mdx",
    "content": "---\nlayout: docs\npage_title: Creating a Base Box - Hyper-V Provider\ndescription: |-\n  As with every Vagrant provider, the Vagrant Hyper-V provider has a custom box\n  format that affects how base boxes are made.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Creating a Base Box\n\nAs with [every Vagrant provider](/vagrant/docs/providers/basic_usage), the\nVagrant Hyper-V provider has a custom box format that affects how base boxes are made.\n\nPrior to reading this, you should read the\n[general guide to creating base boxes](/vagrant/docs/boxes/base). Actually,\nit would probably be most useful to keep this open in a separate tab\nas you may be referencing it frequently while creating a base box. That\npage contains important information about common software to install\non the box.\n\nAdditionally, it is helpful to understand the\n[basics of the box file format](/vagrant/docs/boxes/format).\n\n~> **Advanced topic!** This is a reasonably advanced topic that\na beginning user of Vagrant does not need to understand. If you are\njust getting started with Vagrant, skip this and use an available\nbox. If you are an experienced user of Vagrant and want to create\nyour own custom boxes, this is for you.\n\n## Additional Software\n\nIn addition to the software that should be installed based on the\n[general guide to creating base boxes](/vagrant/docs/boxes/base),\nHyper-V base boxes require some additional software.\n\n### Hyper-V Kernel Modules\n\nYou will need to install Hyper-V kernel modules. While this improves performance,\nit also enables necessary features such as reporting its IP address so that\nVagrant can access it.\n\nYou can verify Hyper-V kernel modules are properly installed by\nrunning `lsmod` on Linux machines and looking for modules prefixed with\n`hv_`. Additionally, you will need to verify that the \"Network\" tab for your\nvirtual machine in the Hyper-V manager is reporting an IP address. If it\nis not reporting an IP address, Vagrant will not be able to access it.\n\nFor most newer Linux distributions, the Hyper-V modules will be available\nout of the box.\n\nUbuntu 12.04 requires some special steps to make networking work. These\nare reproduced here in case similar steps are needed with other distributions.\nWithout these commands, Ubuntu 12.04 will not report an IP address to\nHyper-V:\n\n```shell-session\n$ sudo apt-get install linux-tools-3.11.0-15-generic\n$ sudo apt-get install hv-kvp-daemon-init\n$ sudo cp /usr/lib/linux-tools/3.11.0-15/hv_* /usr/sbin/\n```\n\n## Packaging the Box\n\nTo package a Hyper-V box, export the virtual machine from the\nHyper-V Manager using the \"Export\" feature. This will create a directory\nwith a structure similar to the following:\n\n```\n.\n|-- Snapshots\n|-- Virtual Hard drives\n|-- Virtual Machines\n```\n\nDelete the \"Snapshots\" folder. It is of no use to the Vagrant Hyper-V\nprovider and can only add to the size of the box if there are snapshots\nin that folder.\n\nThen, create the \"metadata.json\" file necessary for the box, as documented\nin [basics of the box file format](/vagrant/docs/boxes/format). The proper\nprovider value to use for the metadata is \"hyperv\".\n\nFinally, create an archive of those contents (but _not_ the parent folder)\nusing a tool such as `tar`:\n\n```shell-session\n$ tar cvzf ~/custom.box ./*\n```\n\nA common mistake is to also package the parent folder by accident. Vagrant\nwill not work in this case. To verify you've packaged it properly, add the\nbox to Vagrant and try to bring up the machine.\n\n## Additional Help\n\nThere is also some less structured help available from the experience of\nother users. These are not official documentation but if you are running\ninto trouble they may help you:\n\n- [Ubuntu 14.04.2 without secure boot](https://github.com/hashicorp/vagrant/issues/5419#issuecomment-86235427)\n"
  },
  {
    "path": "website/content/docs/providers/hyperv/configuration.mdx",
    "content": "---\nlayout: docs\npage_title: Configuration- Hyper-V Provider\ndescription: |-\n  The Vagrant Hyper-V provider has some provider-specific configuration options\n  you may set.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Configuration\n\nThe Vagrant Hyper-V provider has some provider-specific configuration options\nyou may set. A complete reference is shown below:\n\n- `auto_start_action` (Nothing, StartIfRunning, Start) - Automatic start action for VM on host startup. Default: Nothing.\n- `auto_stop_action` (ShutDown, TurnOff, Save) - Automatic stop action for VM on host shutdown. Default: ShutDown.\n- `cpus` (integer) - Number of virtual CPUs allocated to VM at startup.\n- `differencing_disk` (boolean) - **Deprecated** Use differencing disk instead of cloning entire VHD (use `linked_clone` instead) Default: false.\n- `enable_virtualization_extensions` (boolean) - Enable virtualization extensions for the virtual CPUs. Default: false\n- `enable_checkpoints` (boolean) Enable checkpoints of the VM. Default: true\n- `enable_automatic_checkpoints` (boolean) Enable automatic checkpoints of the VM. Default: false\n- `enable_enhanced_session_mode` (boolean) - Enable enhanced session transport type for the VM. Default: false\n- `ip_address_timeout` (integer) - Number of seconds to wait for the VM to report an IP address. Default: 120.\n- `linked_clone` (boolean) - Use differencing disk instead of cloning entire VHD. Default: false\n- `mac` (string) - MAC address for the guest network interface\n- `maxmemory` (integer) - Maximum number of megabytes allowed to be allocated for the VM. When set Dynamic Memory Allocation will be enabled. Set to `nil` to disable Dynamic Memory.\n- `memory` (integer) - Number of megabytes allocated to VM at startup. If `maxmemory` is set, this will be amount of memory allocated at startup.\n- `vlan_id` (integer) - VLAN ID for the guest network interface.\n- `vmname` (string) - Name of virtual machine as shown in Hyper-V manager. Default: Generated name.\n- `vm_integration_services` (Hash) - Hash to set the state of integration services. (Note: Unknown key values will be passed directly.)\n  - `guest_service_interface` (boolean)\n  - `heartbeat` (boolean)\n  - `key_value_pair_exchange` (boolean)\n  - `shutdown` (boolean)\n  - `time_synchronization` (boolean)\n  - `vss` (boolean)\n\n## VM Integration Services\n\nThe `vm_integration_services` configuration option consists of a simple Hash. The key values are the\nnames of VM integration services to enable or disable for the VM. Vagrant includes an internal\nmapping of known services which allows them to be provided in a \"snake case\" format. When a provided\nkey is unknown, the key value is used \"as-is\" without any modifications.\n\nFor example, if a new `CustomVMSRV` VM integration service was added and Vagrant is not aware of this\nnew service name, it can be provided as the key value explicitly:\n\n```ruby\nconfig.vm.provider \"hyperv\" do |h|\n  h.vm_integration_services = {\n    guest_service_interface: true,\n    CustomVMSRV: true\n  }\nend\n```\n\nThis example would enable the `GuestServiceInterface` (which Vagrant is aware) and `CustomVMSRV` (which\nVagrant is _not_ aware) VM integration services.\n"
  },
  {
    "path": "website/content/docs/providers/hyperv/index.mdx",
    "content": "---\nlayout: docs\npage_title: Hyper-V Provider\ndescription: |-\n  Vagrant comes with support out of the box for Hyper-V, a native hypervisor\n  written by Microsoft. Hyper-V is available by default for almost all\n  Windows 8.1 and later installs.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Hyper-V\n\nVagrant comes with support out of the box for [Hyper-V](https://en.wikipedia.org/wiki/Hyper-V),\na native hypervisor written by Microsoft. Hyper-V is available by default for\nalmost all Windows 8.1 and later installs.\n\nThe Hyper-V provider is compatible with Windows 8.1 and later only. Prior versions\nof Hyper-V do not include the necessary APIs for Vagrant to work.\n\nHyper-V must be enabled prior to using the provider. Most Windows installations\nwill not have Hyper-V enabled by default. Hyper-V is available by default for\nalmost all Windows Enterprise, Professional, or Education 8.1 and later installs.\nTo enable Hyper-V, go to \"Programs and Features\", click on \"Turn Windows\nfeatures on or off\" and check the box next to \"Hyper-V\". Or install via\nPowerShell with:\n\n```powershell\nEnable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All\n```\n\nSee official documentation [here](https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v).\n"
  },
  {
    "path": "website/content/docs/providers/hyperv/limitations.mdx",
    "content": "---\nlayout: docs\npage_title: Limitations - Hyper-V Provider\ndescription: |-\n  The Hyper-V provider works in almost every way like the VirtualBox\n  or VMware provider would, but has some limitations that are inherent to\n  Hyper-V itself.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Limitations\n\nThe Vagrant Hyper-V provider works in almost every way like the VirtualBox\nor VMware provider would, but has some limitations that are inherent to\nHyper-V itself.\n\n## Limited Networking\n\nVagrant does not yet know how to create and configure new networks for\nHyper-V. When launching a machine with Hyper-V, Vagrant will prompt you\nasking what virtual switch you want to connect the virtual machine to.\n\nA result of this is that networking configurations in the Vagrantfile\nare completely ignored with Hyper-V. Vagrant cannot enforce a static IP\nor automatically configure a NAT.\n\nHowever, the IP address of the machine will be reported as part of\nthe `vagrant up`, and you can use that IP address as if it were\na host only network.\n\n## Snapshots\n\nRestoring snapshot VMs using `vagrant snapshot pop` or\n`vagrant snapshot restore` will sometimes raise errors when mounting\nSMB shared folders, however these mounts will still work inside the guest.\n"
  },
  {
    "path": "website/content/docs/providers/hyperv/usage.mdx",
    "content": "---\nlayout: docs\npage_title: Usage - Hyper-V Provider\ndescription: |-\n  The Hyper-V provider is used just like any other provider. Please read the\n  general basic usage page for providers.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Usage\n\nThe Vagrant Hyper-V provider is used just like any other provider. Please\nread the general [basic usage](/vagrant/docs/providers/basic_usage) page for\nproviders.\n\nThe value to use for the `--provider` flag is `hyperv`.\n\nHyper-V also requires that you execute Vagrant with administrative\nprivileges. Creating and managing virtual machines with Hyper-V requires\nadmin rights. Vagrant will show you an error if it does not have the proper\npermissions.\n\nBoxes for Hyper-V can be easily found on\n[HashiCorp's Vagrant Cloud](https://vagrantcloud.com/boxes/search). To get started, you might\nwant to try the `hashicorp/bionic64` box.\n"
  },
  {
    "path": "website/content/docs/providers/index.mdx",
    "content": "---\nlayout: docs\npage_title: Providers\ndescription: |-\n  While Vagrant ships out of the box with support for VirtualBox, Hyper-V, and\n  Docker. Vagrant has the ability to manage other types of machines as well.\n  This is done by using other providers with Vagrant.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Providers\n\nWhile Vagrant ships out of the box with support for [VirtualBox](https://www.virtualbox.org),\n[Hyper-V](https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/about/), and [Docker](https://www.docker.io),\nVagrant has the ability to manage other types of machines as well. This is done\nby using other _providers_ with Vagrant.\n\nAlternate providers can offer different features that make more sense in your use case.\nFor example, if you are using Vagrant for any real work, [VMware](https://www.vmware.com)\nproviders are recommended since they're well supported and generally more\nstable and performant than VirtualBox.\n\nBefore you can use another provider, you must install it. Installation of other providers\nis done via the Vagrant plugin system.\n\nOnce the provider is installed, usage is straightforward and simple, as\nyou would expect with Vagrant. Read into the relevant subsections found in\nthe navigation to the left for more information.\n"
  },
  {
    "path": "website/content/docs/providers/installation.mdx",
    "content": "---\nlayout: docs\npage_title: Installation - Providers\ndescription: |-\n  Providers are distributed as Vagrant plugins, and are therefore installed\n  using standard plugin installation steps. After installing a plugin which\n  contains a provider, the provider should immediately be available.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Provider Installation\n\nProviders are distributed as Vagrant plugins, and are therefore installed\nusing [standard plugin installation steps](/vagrant/docs/plugins/usage). After\ninstalling a plugin which contains a provider, the provider should\nimmediately be available.\n"
  },
  {
    "path": "website/content/docs/providers/virtualbox/boxes.mdx",
    "content": "---\nlayout: docs\npage_title: Creating a Base Box - VirtualBox Provider\ndescription: |-\n  As with every Vagrant provider, the Vagrant VirtualBox provider has a custom\n  box format that affects how base boxes are made.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Creating a Base Box\n\nAs with [every Vagrant provider](/vagrant/docs/providers/basic_usage), the\nVagrant VirtualBox provider has a custom box format that affects how base\nboxes are made.\n\nPrior to reading this, you should read the\n[general guide to creating base boxes](/vagrant/docs/boxes/base). Actually,\nit would probably be most useful to keep this open in a separate tab\nas you may be referencing it frequently while creating a base box. That\npage contains important information about common software to install\non the box.\n\nAdditionally, it is helpful to understand the\n[basics of the box file format](/vagrant/docs/boxes/format).\n\n~> **Advanced topic!** This is a reasonably advanced topic that\na beginning user of Vagrant does not need to understand. If you are\njust getting started with Vagrant, skip this and use an available\nbox. If you are an experienced user of Vagrant and want to create\nyour own custom boxes, this is for you.\n\n## Virtual Machine\n\nThe virtual machine created in VirtualBox can use any configuration you would\nlike, but Vagrant has some hard requirements:\n\n- The first network interface (adapter 1) _must_ be a NAT adapter.\n  Vagrant uses this to connect the first time.\n\n- The MAC address of the first network interface (the NAT adapter)\n  should be noted, since you will need to put it in a Vagrantfile\n  later as the value for `config.vm.base_mac`. To get this value, use\n  the VirtualBox GUI.\n\nOther than the above, you are free to customize the base virtual machine\nas you see fit.\n\n## Additional Software\n\nIn addition to the software that should be installed based on the\n[general guide to creating base boxes](/vagrant/docs/boxes/base),\nVirtualBox base boxes require some additional software.\n\n### VirtualBox Guest Additions\n\n[VirtualBox Guest Additions](https://www.virtualbox.org/manual/ch04.html)\nmust be installed so that things such as shared folders can function.\nInstalling guest additions also usually improves performance since the guest\nOS can make some optimizations by knowing it is running within VirtualBox.\n\nBefore installing the guest additions, you will need the Linux kernel headers\nand the basic developer tools. On Ubuntu, you can easily install these like\nso:\n\n```shell-session\n$ sudo apt-get install linux-headers-$(uname -r) build-essential dkms\n```\n\n#### To install via the GUI:\n\nNext, make sure that the guest additions image is available by using the\nGUI and clicking on \"Devices\" followed by \"Install Guest Additions\".\nThen mount the CD-ROM to some location. On Ubuntu, this usually looks like\nthis:\n\n```shell-session\n$ sudo mount /dev/cdrom /media/cdrom\n```\n\nFinally, run the shell script that matches your system to install the\nguest additions. For example, for Linux on x86, it is the following:\n\n```shell-session\n$ sudo sh /media/cdrom/VBoxLinuxAdditions.run\n```\n\nIf the command succeeds, then the guest additions are now installed!\n\n#### To install via the command line:\n\nYou can find the appropriate guest additions version to match your VirtualBox\nversion by selecting the appropriate version\n[here](http://download.virtualbox.org/virtualbox/). The examples below use\n4.3.8, which was the latest VirtualBox version at the time of writing.\n\n```text\nwget http://download.virtualbox.org/virtualbox/4.3.8/VBoxGuestAdditions_4.3.8.iso\nsudo mkdir /media/VBoxGuestAdditions\nsudo mount -o loop,ro VBoxGuestAdditions_4.3.8.iso /media/VBoxGuestAdditions\nsudo sh /media/VBoxGuestAdditions/VBoxLinuxAdditions.run\nrm VBoxGuestAdditions_4.3.8.iso\nsudo umount /media/VBoxGuestAdditions\nsudo rmdir /media/VBoxGuestAdditions\n```\n\nIf you did not install a Desktop environment when you installed the operating\nsystem, as recommended to reduce size, the install of the VirtualBox additions\nshould warn you about the lack of OpenGL or Window System Drivers, but you can\nsafely ignore this.\n\nIf the commands succeed, then the guest additions are now installed!\n\n## Packaging the Box\n\nVagrant includes a simple way to package VirtualBox base boxes. Once you've\ninstalled all the software you want to install, you can run this command:\n\n```shell-session\n$ vagrant package --base my-virtual-machine\n```\n\nWhere \"my-virtual-machine\" is replaced by the name of the virtual machine\nin VirtualBox to package as a base box.\n\nIt will take a few minutes, but after it is complete, a file \"package.box\"\nshould be in your working directory which is the new base box. At this\npoint, you've successfully created a base box!\n\n## Raw Contents\n\nThis section documents the actual raw contents of the box file. This is not\nas useful when creating a base box but can be useful in debugging issues\nif necessary.\n\nA VirtualBox base box is an archive of the resulting files of\n[exporting](https://www.virtualbox.org/manual/ch08.html#vboxmanage-export)\na VirtualBox virtual machine. Here is an example of what is contained\nin such a box:\n\n```shell-session\n$ tree\n.\n|-- Vagrantfile\n|-- box-disk1.vmdk\n|-- box.ovf\n|-- metadata.json\n\n0 directories, 4 files\n```\n\nIn addition to the files from exporting a VirtualBox VM, there is\nthe \"metadata.json\" file used by Vagrant itself.\n\nAlso, there is a \"Vagrantfile.\" This contains some configuration to\nproperly set the MAC address of the NAT network device, since VirtualBox\nrequires this to be correct in order to function properly. If you are\nnot using `vagrant package --base` above, you will have to set the\n`config.vm.base_mac` setting in this Vagrantfile to the MAC address\nof the NAT device without colons.\n\nWhen bringing up a VirtualBox backed machine, Vagrant\n[imports](https://www.virtualbox.org/manual/ch08.html#vboxmanage-import)\nthe \"box.ovf\" file found in the box contents.\n"
  },
  {
    "path": "website/content/docs/providers/virtualbox/common-issues.mdx",
    "content": "---\nlayout: docs\npage_title: Common Issues - VirtualBox Provider\ndescription: |-\n  This page lists some common issues people run into with Vagrant and VirtualBox\n  as well as solutions for those issues.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Common Issues\n\nThis page lists some common issues people run into with Vagrant and VirtualBox\nas well as solutions for those issues.\n\n## Hanging on Windows\n\nIf Vagrant commands are hanging on Windows because they're communicating\nto VirtualBox, this may be caused by a permissions issue with VirtualBox.\nThis is easy to fix. Starting VirtualBox as a normal user or as an\nadministrator will prevent you from using it in the opposite way. Please keep\nin mind that when Vagrant interacts with VirtualBox, it will interact with\nit with the same access level as the console running Vagrant.\n\nTo fix this issue, completely shut down all VirtualBox machines and GUIs.\nWait a few seconds. Then, launch VirtualBox only with the access level you\nwish to use.\n\n## DNS Not Working\n\nIf DNS is not working within your VM, then you may need to enable\na DNS proxy (built-in to VirtualBox). Please [see the StackOverflow answers\nhere](https://serverfault.com/questions/453185/vagrant-virtualbox-dns-10-0-2-3-not-working)\nfor a guide on how to do that.\n"
  },
  {
    "path": "website/content/docs/providers/virtualbox/configuration.mdx",
    "content": "---\nlayout: docs\npage_title: Configuration - VirtualBox Provider\ndescription: |-\n  The VirtualBox provider exposes some additional configuration options\n  that allow you to more finely control your VirtualBox-powered Vagrant\n  environments.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Configuration\n\nThe VirtualBox provider exposes some additional configuration options\nthat allow you to more finely control your VirtualBox-powered Vagrant\nenvironments.\n\n## GUI vs. Headless\n\nBy default, VirtualBox machines are started in headless mode, meaning\nthere is no UI for the machines visible on the host machine. Sometimes,\nyou want to have a UI. Common use cases include wanting to see a browser\nthat may be running in the machine, or debugging a strange boot issue.\nYou can easily tell the VirtualBox provider to boot with a GUI:\n\n```ruby\nconfig.vm.provider \"virtualbox\" do |v|\n  v.gui = true\nend\n```\n\n## Virtual Machine Name\n\nYou can customize the name that appears in the VirtualBox GUI by\nsetting the `name` property. By default, Vagrant sets it to the containing\nfolder of the Vagrantfile plus a timestamp of when the machine was created.\nBy setting another name, your VM can be more easily identified.\n\n```ruby\nconfig.vm.provider \"virtualbox\" do |v|\n  v.name = \"my_vm\"\nend\n```\n\n## Default NIC Type\n\nBy default Vagrant will not set the NIC type for network interfaces. This\nallows VirtualBox to apply the default NIC type for the guest. If you would\nlike to use a specific NIC type by default for guests, set the `default_nic_type`\noption:\n\n```ruby\nconfig.vm.provider \"virtualbox\" do |v|\n  v.default_nic_type = \"82543GC\"\nend\n```\n\n## Linked Clones\n\nBy default new machines are created by importing the base box. For large\nboxes this produces a large overhead in terms of time (the import operation)\nand space (the new machine contains a copy of the base box's image).\nUsing linked clones can drastically reduce this overhead.\n\nLinked clones are based on a master VM, which is generated by importing the\nbase box only once the first time it is required. For the linked clones only\ndifferencing disk images are created where the parent disk image belongs to\nthe master VM.\n\n```ruby\nconfig.vm.provider \"virtualbox\" do |v|\n  v.linked_clone = true\nend\n```\n\nTo have backward compatibility:\n\n```ruby\nconfig.vm.provider \"virtualbox\" do |v|\n  v.linked_clone = true if Gem::Version.new(Vagrant::VERSION) >= Gem::Version.new('1.8.0')\nend\n```\n\nIf you do not want backward compatibility and want to force users to\nsupport linked cloning, you can use `Vagrant.require_version` with 1.8.\n\n-> **Note:** the generated master VMs are currently not removed\nautomatically by Vagrant. This has to be done manually. However, a master\nVM can only be removed when there are no linked clones connected to it.\n\n## Checking for Guest Additions\n\nBy default Vagrant will check for the [VirtualBox Guest\nAdditions](https://www.virtualbox.org/manual/ch04.html) when starting a\nmachine, and will output a warning if the guest additions are missing or\nout-of-date. You can skip the guest additions check by setting the\n`check_guest_additions` option:\n\n```ruby\nconfig.vm.provider \"virtualbox\" do |v|\n  v.check_guest_additions = false\nend\n```\n\n## VBoxManage Customizations\n\n[VBoxManage](https://www.virtualbox.org/manual/ch08.html) is a utility that can\nbe used to make modifications to VirtualBox virtual machines from the command\nline.\n\nVagrant exposes a way to call any command against VBoxManage just prior\nto booting the machine:\n\n```ruby\nconfig.vm.provider \"virtualbox\" do |v|\n  v.customize [\"modifyvm\", :id, \"--cpuexecutioncap\", \"50\"]\nend\n```\n\nIn the example above, the VM is modified to have a host CPU execution\ncap of 50%, meaning that no matter how much CPU is used in the VM, no\nmore than 50% would be used on your own host machine. Some details:\n\n- The `:id` special parameter is replaced with the ID of the virtual\n  machine being created, so when a VBoxManage command requires an ID, you\n  can pass this special parameter.\n\n- Multiple `customize` directives can be used. They will be executed in the\n  order given.\n\nThere are some convenience shortcuts for memory and CPU settings:\n\n```ruby\nconfig.vm.provider \"virtualbox\" do |v|\n  v.memory = 1024\n  v.cpus = 2\nend\n```\n"
  },
  {
    "path": "website/content/docs/providers/virtualbox/index.mdx",
    "content": "---\nlayout: docs\npage_title: VirtualBox Provider\ndescription: |-\n  Vagrant comes with support out of the box for VirtualBox, a free,\n  cross-platform consumer virtualization product.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# VirtualBox\n\nVagrant comes with support out of the box for [VirtualBox](https://www.virtualbox.org),\na free, cross-platform consumer virtualization product.\n\nThe VirtualBox provider is compatible with VirtualBox versions 4.0.x, 4.1.x,\n4.2.x, 4.3.x, 5.0.x, 5.1.x, 5.2.x, 6.0.x, 6.1.x, 7.0.x, 7.1.x, and 7.2.x. Other versions\nare unsupported and the provider will display an error message. Please note that\nbeta and pre-release versions VirtualBox are not supported and may not be well-behaved.\n\nVirtualBox must be installed on its own prior to using the provider, or\nthe provider will display an error message asking you to install it.\nVirtualBox can be installed by [downloading](https://www.virtualbox.org/wiki/Downloads)\na package or installer for your operating system and using standard procedures\nto install that package.\n\nUse the navigation to the left to find a specific VirtualBox topic to read more about.\n"
  },
  {
    "path": "website/content/docs/providers/virtualbox/networking.mdx",
    "content": "---\nlayout: docs\npage_title: Networking - VirtualBox Provider\ndescription: |-\n  The Vagrant VirtualBox provider supports using the private network as a\n  VirtualBox internal network. By default, private networks are host-only\n  networks, because those are the easiest to work with.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Networking\n\n## VirtualBox Host-Only Networks\n\nBy default, private networks are [host-only networks](https://www.virtualbox.org/manual/ch06.html#network_hostonly),\nbecause those are the easiest to work with. In VirtualBox, since you can\ncreate multiple host-only networks, it is also possible to specify which\nhost-only network you want the Vagrant VirtualBox provider to use for\na given interface. To do this, use the `name` argument with the name of\nthe host-only interface to use.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"private_network\", type: \"dhcp\",\n    name: \"vboxnet3\"\nend\n```\n\n## VirtualBox Internal Network\n\nThe Vagrant VirtualBox provider supports using the private network as a\nVirtualBox [internal network](https://www.virtualbox.org/manual/ch06.html#network_internal).\nBy default, private networks are host-only networks, because those are the\neasiest to work with. However, internal networks can be enabled as well.\n\nTo specify a private network as an internal network for VirtualBox\nuse the `virtualbox__intnet` option with the network. The `virtualbox__`\n(double underscore) prefix tells Vagrant that this option is only for the\nVirtualBox provider.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"private_network\", ip: \"192.168.50.4\",\n    virtualbox__intnet: true\nend\n```\n\nAdditionally, if you want to specify that the VirtualBox provider join\na specific internal network, specify the name of the internal network:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"private_network\", ip: \"192.168.50.4\",\n    virtualbox__intnet: \"mynetwork\"\nend\n```\n\n## VirtualBox NIC Type\n\nYou can specify a specific NIC type for the created network interface\nby using the `nic_type` parameter. This is not prefixed by `virtualbox__`\nfor legacy reasons, but is VirtualBox-specific.\n\nThis is an advanced option and should only be used if you know what\nyou are using, since it can cause the network device to not work at all.\n\nExample:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.network \"private_network\", ip: \"192.168.50.4\",\n    nic_type: \"virtio\"\nend\n```\n"
  },
  {
    "path": "website/content/docs/providers/virtualbox/usage.mdx",
    "content": "---\nlayout: docs\npage_title: Usage - VirtualBox Provider\ndescription: |-\n  The Vagrant VirtualBox provider is used just like any other provider. Please\n  read the general basic usage page for providers.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Usage\n\nThe Vagrant VirtualBox provider is used just like any other provider. Please\nread the general [basic usage](/vagrant/docs/providers/basic_usage) page for\nproviders.\n\nThe value to use for the `--provider` flag is `virtualbox`.\n\nThe Vagrant VirtualBox provider does not support parallel execution at this\ntime. Specifying the `--parallel` option will have no effect.\n"
  },
  {
    "path": "website/content/docs/providers/vmware/boxes.mdx",
    "content": "---\nlayout: docs\npage_title: Box Format - VMware Provider\ndescription: |-\n  As with every Vagrant provider, the Vagrant VMware providers have a custom box\n  format.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Boxes\n\nAs with [every Vagrant provider](/vagrant/docs/providers/basic_usage), the\nVagrant VMware providers have a custom box format.\n\nThis page documents the format so that you can create your own base boxes.\nNote that currently you must make these base boxes by hand. A future release\nof Vagrant will provide additional mechanisms for automatically creating such\nimages.\n\n-> **Note:** This is a reasonably advanced topic that\na beginning user of Vagrant does not need to understand. If you are\njust getting started with Vagrant, skip this and use an available\nbox. If you are an experienced user of Vagrant and want to create\nyour own custom boxes, this is for you.\n\nPrior to reading this page, please understand the\n[basics of the box file format](/vagrant/docs/boxes/format).\n\n## Contents\n\nA VMware base box is a compressed archive of the necessary contents\nof a VMware \"vmwarevm\" file. Here is an example of what is contained\nin such a box:\n\n```shell-session\n$ tree\n.\n|-- disk-s001.vmdk\n|-- disk-s002.vmdk\n|-- ...\n|-- disk.vmdk\n|-- metadata.json\n|-- bionic64.nvram\n|-- bionic64.vmsd\n|-- bionic64.vmx\n|-- bionic64.vmxf\n\n0 directories, 17 files\n```\n\nThe files that are strictly required for a VMware machine to function are:\nnvram, vmsd, vmx, vmxf, and vmdk files.\n\nThere is also the \"metadata.json\" file used by Vagrant itself. This file\ncontains nothing but the defaults which are documented on the\n[box format](/vagrant/docs/boxes/format) page.\n\nWhen bringing up a VMware backed machine, Vagrant copies all of the contents\nin the box into a privately managed \"vmwarevm\" folder, and uses the first\n\"vmx\" file found to control the machine.\n\n-> **Vagrant 1.8 and higher support linked clones**. Prior versions\nof Vagrant do not support linked clones. For more information on\nlinked clones, please see the documentation.\n\n## VMX Allowlisting\n\nSettings in the VMX file control the behavior of the VMware virtual machine\nwhen it is booted. In the past Vagrant has removed the configured network\ndevice when creating a new instance and inserted a new configuration. With\nthe introduction of [\"predictable network interface names\"][iface-names] this\napproach can cause unexpected behaviors or errors with VMware Vagrant boxes.\nWhile some boxes that use the predictable network interface names are configured\nto handle the VMX modifications Vagrant makes, it is better if Vagrant does\nnot make the modification at all.\n\nVagrant will now warn if a allowlisted setting is detected within a Vagrant\nbox VMX file. If it is detected, a warning will be shown alerting the user\nand providing a configuration snippet. The configuration snippet can be\nused in the Vagrantfile if Vagrant fails to start the virtual machine.\n\n### Making compatible boxes\n\nThese are the VMX settings the allowlisting applies to:\n\n- `ethernet*.pcislotnumber`\n\nIf the newly created box does not depend on Vagrant's existing behavior of\nmodifying this setting, it can disable Vagrant from applying the modification\nby adding a Vagrantfile to the box with the following content:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provider \"vmware_desktop\" do |vmware|\n    vmware.allowlist_verified = true\n  end\nend\n```\n\nThis will prevent Vagrant from displaying a warning to the user as well as\ndisable the VMX settings modifications.\n\n## Installed Software\n\nBase boxes for VMware should have the following software installed, as\na bare minimum:\n\n- SSH server with key-based authentication setup. If you want the box to\n  work with default Vagrant settings, the SSH user must be set to accept\n  the [insecure keypair](https://github.com/hashicorp/vagrant/blob/main/keys/vagrant.pub)\n  that ships with Vagrant.\n\n- [VMware Tools](https://kb.vmware.com/kb/340) so that things such as shared\n  folders can function. There are many other benefits to installing the tools,\n  such as improved networking performance.\n\n## Optimizing Box Size\n\nPrior to packaging up a box, you should shrink the hard drives as much as\npossible. This can be done with `vmware-vdiskmanager` which is usually\nfound in `/Applications/VMware Fusion.app/Contents/Library` for VMware Fusion. You first\nwant to defragment then shrink the drive. Usage shown below:\n\n```shell-session\n$ vmware-vdiskmanager -d /path/to/main.vmdk\n...\n$ vmware-vdiskmanager -k /path/to/main.vmdk\n...\n```\n\n## Packaging\n\nRemove any extraneous files from the \"vmwarevm\" folder\nand package it. Be sure to compress the tar with gzip (done below in a\nsingle command) since VMware hard disks are not compressed by default.\n\n```shell-session\n$ cd /path/to/my/vm.vmwarevm\n$ tar cvzf custom.box ./*\n```\n\n[iface-names]: https://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/\n"
  },
  {
    "path": "website/content/docs/providers/vmware/configuration.mdx",
    "content": "---\nlayout: docs\npage_title: Configuration - VMware Provider\ndescription: |-\n  While Vagrant VMware providers are a drop-in replacement for VirtualBox, there\n  are some additional features that are exposed that allow you to more finely\n  configure VMware-specific aspects of your machines.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Configuration\n\nWhile Vagrant VMware Desktop provider is a drop-in replacement for VirtualBox, there\nare some additional features that are exposed that allow you to more finely\nconfigure VMware-specific aspects of your machines.\n\nConfiguration settings for the provider are set in the Vagrantfile:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"my-box\"\n  config.vm.provider \"vmware_desktop\" do |v|\n    v.gui = true\n  end\nend\n```\n\n## Provider settings\n\n- `allowlist_verified` (bool, symbol) - Flag that VMware box has been properly configured\n  for allow list VMX settings. `true` if verified, `false` if unverified, `:disable_warning`\n  to silence allowlist warnings.\n- `base_address` (string) - Address to be reserved from the DHCP server. This option\n  requires the `base_mac` option to be set as well.\n- `base_mac` (string) - Custom MAC address to be used for the default NAT interface device\n- `clone_directory` (string) - Path for storing VMware clones. This value can\n  also be set using the `VAGRANT_VMWARE_CLONE_DIRECTORY` environment variable.\n  This defaults to `./.vagrant`\n- `enable_vmrun_ip_lookup` (bool) - Use vmrun to discover guest IP address.\n  This defaults to `true`\n- `functional_hgfs` (bool) - HGFS is functional within the guest.\n  This defaults to detected capability of the guest\n- `gui` (bool) - Launch guest with a GUI.\n  This defaults to `false`\n- `linked_clone` (bool) - Use linked clones instead of full copy clones.\n  This defaults to `true` if linked clones are functional based on VMware installation.\n- `nat_device` (string) - The host vmnet device to use for the default NAT interface. By\n  default this will be automatically detected with a fallback to `vmnet8`.\n- `port_forward_network_pause` - Number of seconds to pause after applying port forwarding\n  configuration. This allows guest time to acquire DHCP address if previous address is\n  dropped when VMware network services are restarted.\n  This defaults to `0`\n- `ssh_info_public` (bool) - Use the public IP address for SSH connections to guest.\n  This defaults to `false`\n- `unmount_default_hgfs` (bool) - Unmount the default HGFS mount point within the guest.\n  This defaults to `false`\n- `utility_port` (integer) - Listen port of the Vagrant VMware Utility service.\n  This defaults to `9922`\n- `utility_certificate_path` (string) - Path to the Vagrant VMware Utility service\n  certificates directory.\n  The default value is dependent on the host\n- `verify_vmnet` (bool) - Verify vmnet devices health before usage.\n  This defaults to `true`\n- `vmx` (hash) - VMX key/value pairs to set or unset. If the value is `nil`, the key will\n  be deleted.\n\n### VM Clone Directory\n\nBy default, the VMware provider will clone the VMware VM in the box\nto the \".vagrant\" folder relative to the folder where the Vagrantfile is.\nUsually, this is fine. For some people, for example those who use a\ndifferential backup software such as Time Machine, this is very annoying\nbecause you cannot regularly ignore giant virtual machines as part of backups.\n\nThe directory where the provider clones the virtual machine can be\ncustomized by setting the `VAGRANT_VMWARE_CLONE_DIRECTORY` environmental\nvariable. This does not need to be unique per project. Each project will\nget a different sub-directory within this folder. Therefore, it is safe to\nset this systemwide.\n\n### Linked Clones\n\nBy default new machines are created using a linked clone to the base\nbox. This reduces the time and required disk space incurred by directly\nimporting the base box.\n\nLinked clones are based on a master VM, which is generated by importing the\nbase box only once the first time it is required. For the linked clones only\ndifferencing disk images are created where the parent disk image belongs to\nthe master VM. To disable linked clones:\n\n```ruby\nconfig.vm.provider \"vmware_desktop\" do |v|\n  v.linked_clone = false\nend\n```\n\n### VMX Customization\n\nIf you want to add or remove specific keys from the VMX file, you can do\nthat:\n\n```ruby\nconfig.vm.provider \"vmware_desktop\" do |v|\n  v.vmx[\"custom-key\"]  = \"value\"\n  v.vmx[\"another-key\"] = nil\nend\n```\n\nIn the example above, the \"custom-key\" key will be set to \"value\" and the\n\"another-key\" key will be removed from the VMX file.\n\nVMX customization is done as the final step before the VMware machine is\nbooted, so you have the ability to possibly undo or misconfigure things\nthat Vagrant has set up itself.\n\nVMX is an undocumented format and there is no official reference for\nthe available keys and values. This customization option is exposed for\npeople who have knowledge of exactly what they want.\n\nThe most common keys people look for are setting memory and CPUs.\nThe example below sets both:\n\n```ruby\nconfig.vm.provider \"vmware_desktop\" do |v|\n  v.vmx[\"memsize\"] = \"1024\"\n  v.vmx[\"numvcpus\"] = \"2\"\nend\n```\n"
  },
  {
    "path": "website/content/docs/providers/vmware/faq.mdx",
    "content": "---\nlayout: docs\npage_title: Frequently Asked Questions - VMware Provider\ndescription: |-\n  Frequently asked questions related to using Vagrant with VMware\n  Workstation and VMware Fusion\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Frequently Asked Questions\n\n## Q: How do I use the VMware Fusion Tech Preview?\n\nThe Vagrant VMware utility expects VMware Fusion to be installed in a specific\npath on macOS. Since the VMware Fusion Tech Preview installs into a different\npath, the Vagrant VMware utility will fail to detect the installation and will\nnot start. To resolve this you can create a symlink from the VMware Fusion\nTech Preview installation path to the normal VMware Fusion installation path:\n\n```shell-session\nsudo ln -s /Applications/VMware\\ Fusion\\ Tech\\ Preview.app /Applications/VMware\\ Fusion.app\n```\n\n\n## Q: How do I upgrade my currently installed Vagrant VMware plugin?\n\nYou can update the Vagrant VMware plugin to the latest version by re-running the\ninstall command:\n\n```shell-session\n$ vagrant plugin install vagrant-vmware-desktop\n```\n\nTo upgrade the Vagrant VMware utility, download the latest version from the\n[Vagrant VMware utility downloads page](/vagrant/downloads/vmware) and install the\nsystem package to your local system.\n\n## Q: Do I need a license for the Vagrant VMware plugin?\n\nNo, the Vagrant VMware plugin has been open sourced under the MPL. A license\nis no longer required for using the Vagrant VMware plugin. If the plugin you\nare using still requires a license, simply upgrade your plugin:\n\n```\n$ vagrant plugin update vagrant-vmware-desktop\n```\n"
  },
  {
    "path": "website/content/docs/providers/vmware/index.mdx",
    "content": "---\nlayout: docs\npage_title: VMware Provider\ndescription: |-\n  HashiCorp develops an official VMware Fusion and VMware Workstation provider\n  for Vagrant. This provider allows Vagrant to power VMware based machines and\n  take advantage of the improved stability and performance that VMware software\n  offers.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# VMware\n\n[HashiCorp](https://www.hashicorp.com) develops an official\n[VMware Fusion](https://www.vmware.com/products/fusion/overview.html)\nand [VMware Workstation](https://www.vmware.com/products/workstation/)&nbsp;\n[provider](/vagrant/docs/providers/) for Vagrant. This provider allows\nVagrant to power VMware based machines and take advantage of the\nimproved stability and performance that VMware software offers.\n\nThe Vagrant VMware plugin is now open sourced under the MPL. The code\nrepository for the Vagrant VMware plugin is available on [GitHub](https://github.com/hashicorp/vagrant-vmware-desktop).\n\nThis provider is a drop-in replacement for VirtualBox. However, there are some\nVMware-specific things such as box formats, configurations, etc. that are\ndocumented here.\n\nPlease note that VMware Fusion and VMware Workstation are third-party products\nthat must be purchased and installed separately prior to using the provider.\n\nUse the navigation to the left to find a specific VMware topic to read\nmore about.\n"
  },
  {
    "path": "website/content/docs/providers/vmware/installation.mdx",
    "content": "---\nlayout: docs\npage_title: Installation - VMware Provider\ndescription: |-\n  The Vagrant VMware provider requires a two step installation\n  process which includes a system package and a Vagrant plugin.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Installation\n\nIf you are upgrading from the Vagrant VMware Workstation or Vagrant\nVMware Fusion plugins, please halt or destroy all VMware VMs currently\nbeing managed by Vagrant. Then continue with the instructions below.\n\nInstallation of the Vagrant VMware provider requires two steps. First the\nVagrant VMware Utility must be installed. This can be done by downloading\nand installing the correct system package from the [Vagrant VMware Utility\ndownloads page](/vagrant/downloads/vmware).\n\nNext, install the Vagrant VMware provider plugin using the standard plugin\ninstallation procedure:\n\n```shell-session\n$ vagrant plugin install vagrant-vmware-desktop\n```\n\nFor more information on plugin installation, please see the\n[Vagrant plugin usage documentation](/vagrant/docs/plugins/usage).\n\n## Upgrading to v1.x\n\nIt is **extremely important** that the VMware plugin is upgraded to 1.0.0 or\nabove. This release resolved critical security vulnerabilities. To learn more,\nplease [read our release announcement](https://www.hashicorp.com/blog/introducing-the-vagrant-vmware-desktop-plugin).\n\nAfter upgrading, please verify that the following paths are empty. The upgrade\nprocess should remove these for you, but for security reasons it is important\nto double check. If you're a new user or installing the VMware provider on a\nnew machine, you may skip this step. If you're a Windows user, you may skip this\nstep as well.\n\nThe path `~/.vagrant.d/gems/*/vagrant-vmware-{fusion,workstation}`\nshould no longer exist. The gem `vagrant-vmware-desktop` may exist since this\nis the name of the new plugin. If the old directories exist, remove them. An\nexample for a Unix-like shell is shown below:\n\n```shell-session\n# Check if they exist and verify that they're the correct paths as shown below.\n$ ls ~/.vagrant.d/gems/*/vagrant-vmware-{fusion,workstation}\n...\n\n# Remove them\n$ rm -rf ~/.vagrant.d/gems/*/vagrant-vmware-{fusion,workstation}\n```\n\n## Updating the Vagrant VMware Desktop plugin\n\nThe Vagrant VMware Desktop plugin can be updated directly from Vagrant. Run the\nfollowing command to update Vagrant to the latest version of the Vagrant VMware\nDesktop plugin:\n\n```shell-session\n$ vagrant plugin update vagrant-vmware-desktop\n```\n"
  },
  {
    "path": "website/content/docs/providers/vmware/known-issues.mdx",
    "content": "---\nlayout: docs\npage_title: Known Issues - VMware Provider\ndescription: |-\n  This page tracks some known issues or limitations of the VMware provider.\n  Note that none of these are generally blockers to using the provider, but\n  are good to know.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Known Issues\n\nThis page tracks some known issues or limitations of the VMware provider.\nNote that none of these are generally blockers to using the provider, but\nare good to know.\n\n## Network disconnect\n\nWhen Vagrant applies port forwarding rules while bring up a guest instance,\nother running VMware VMs may experience a loss of network connectivity. The\ncause of this connectivity issue is the restarting of the VMware NAT service\nto apply new port forwarding rules. Since new rules cannot be applied to the\nNAT service while it is running, it is required to restart the service, which\nresults in the loss of connectivity.\n\n## Forwarded Ports Failing in Workstation on Windows\n\nVMware Workstation has a bug on Windows where forwarded ports do not work\nproperly. Vagrant actually works around this bug and makes them work. However,\nif you run the virtual network editor on Windows, the forwarded ports will\nsuddenly stop working.\n\nIn this case, run `vagrant reload` and things will begin working again.\n\nThis issue has been reported to VMware, but a fix has not been released yet.\n\n## Big Sur Related Issues\n\n### Creating Network Devices\n\nStarting with the Big Sur release VMware Fusion is no longer able to create\nnetwork devices with custom subnet and mask settings to attach to guests. This\nmeans custom IP addresses are not valid when creating a private network. When\ncreating a private network device to attach to a guest, simply define it with\nno options:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"hashicorp/bionic64\"\n  config.vm.network :private_network\nend\n```\n\n### Port Forwarding\n\nVMware Fusion currently does not support port forwarding to the localhost. To\nwork around this issue, the vagrant-vmware-utility provides functionality to\nsimulate port forwarding behavior available within VMware Fusion prior to\nBig Sur. The port forward management is contained to the vagrant-vmware-utility\nand is not accessible via the VMware Fusion networking UI.\n\n### DHCP Reservations\n\nDue VMware Fusion no longer utilizing its own service for DHCP, defining DHCP\nreservations is currently not working with VMware Fusion on Big Sur.\n"
  },
  {
    "path": "website/content/docs/providers/vmware/usage.mdx",
    "content": "---\nlayout: docs\npage_title: Usage - VMware Provider\ndescription: |-\n  The Vagrant VMware providers are used just like any other provider. Please\n  read the general basic usage page for providers.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Usage\n\nThe Vagrant VMware provider is used just like any other provider. Please\nread the general [basic usage](/vagrant/docs/providers/basic_usage) page for\nproviders.\n\nThe value to use for the `--provider` flag is `vmware_desktop`. For compatibility\nwith older versions of the plugin, `vmware_fusion` can be used for VMware\nFusion, and `vmware_workstation` for VMware Workstation.\n\nThe Vagrant VMware provider does not support parallel execution at this time.\nSpecifying the `--parallel` option will have no effect.\n\nTo get started, create a new `Vagrantfile` that points to a VMware box:\n\n```ruby\n# vagrant init hashicorp/bionic64\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"hashicorp/bionic64\"\nend\n```\n\nThen run:\n\n```shell-session\n$ vagrant up --provider vmware_desktop\n```\n\nThis will download and bring up a new VMware Fusion/Workstation virtual machine\nin Vagrant.\n"
  },
  {
    "path": "website/content/docs/providers/vmware/vagrant-vmware-utility.mdx",
    "content": "---\nlayout: docs\npage_title: Installation - VMware Provider\ndescription: |-\n  The Vagrant VMware Utility works with the Vagrant VMware Provider to\n  interact with the system VMware installation.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant VMware Utility Installation\n\n## System Packages\n\nThe Vagrant VMware Utility is provided as a system package. To install the\nutility, download and install the correct system package from the downloads\npage.\n\n<Button title={`Download ${VMWARE_UTILITY_VERSION}`} url=\"/vmware/downloads\" />\n\n## Manual Installation\n\nIf there is no officially supported system package of the utility available,\nit may be possible to manually install utility. This applies to Linux platforms\nonly. First, download the latest zip package from the releases page.\n\nNext create a directory for the executable and unpack the executable as\nroot.\n\n```shell-session\n$ sudo mkdir -p /opt/vagrant-vmware-desktop/bin\n$ sudo unzip -d /opt/vagrant-vmware-desktop/bin vagrant-vmware-utility_1.0.0_linux_amd64.zip\n```\n\nAfter the executable has been installed, the utility setup tasks must be run. First,\ngenerate the required certificates:\n\n```shell-session\n$ sudo /opt/vagrant-vmware-desktop/bin/vagrant-vmware-utility certificate generate\n```\n\nThe path provided from this command can be used to set the [`utility_certificate_path`](/vagrant/docs/providers/vmware/configuration#utility_certificate_path) in the Vagrantfile\nconfiguration if installing to a non-standard path.\n\nFinally, install the service. This will also enable the service.\n\n```shell-session\n$ sudo /opt/vagrant-vmware-desktop/bin/vagrant-vmware-utility service install\n```\n\n# Usage\n\nThe Vagrant VMware Utility provides the Vagrant VMware provider plugin access\nto various VMware functionalities. The Vagrant VMware Utility is required by\nthe Vagrant VMware Desktop provider plugin.\n\n## Vagrant VMware Utility Access\n\nThe Vagrant VMware Utility provides support for all users on the system using\nthe Vagrant VMware Desktop plugin. If access restrictions to the Utility need\nto be applied to users on the system, this can be accomplished by restricting\nuser access to the certificates used for connecting to the service.\n\nOn Windows platforms these certificates can be found at:\n\n```text\nC:\\ProgramData\\HashiCorp\\vagrant-vmware-desktop\\certificates\n```\n\nOn POSIX-style platforms these certificates can be found at:\n\n```text\n/opt/vagrant-vmware-desktop/certificates\n```\n\n## Vagrant VMware Utility Service\n\nThe Vagrant VMware Utility consists of a small service which runs on the\nhost platform. When the utility installer package is installed, the service\nis configured to automatically start. If the plugin reports errors communicating\nwith the service, it may have stopped for some reason. The most common cause of\nthe service not being in a running state is the VMware application not being\ninstalled. The service can be started again by using the proper command below:\n\n### Windows\n\nOn Windows platforms a service is created called `vagrant-vmware-utility`. The\nservice can be manually started using the services GUI (`services.msc`) or by\nrunning the following command from a `cmd.exe` in administrator mode:\n\n```shell-session\n$ net.exe start vagrant-vmware-utility\n```\n\n### macOS\n\n```shell-session\n$ sudo launchctl load -w /Library/LaunchDaemons/com.vagrant.vagrant-vmware-utility.plist\n```\n\n### Linux systemd\n\n```shell-session\n$ sudo systemctl start vagrant-vmware-utility\n```\n\n### Linux SysVinit\n\n```shell-session\n$ sudo /etc/init.d/vagrant-vmware-utility start\n```\n\n### Linux runit\n\n```shell-session\n$ sudo sv start vagrant-vmware-utility\n```\n\n## Utility Service Configuration\n\nWhen installing the Vagrant VMware utility service, a configuration file is generated\nthat is used when the process is started. On Windows, this can be found at:\n\n```text\nC:\\ProgramData\\HashiCorp\\vagrant-vmware-desktop\\config\\service.hcl\n```\n\nOn POSIX-style systems, it can be found at:\n\n```text\n/opt/vagrant-vmware-desktop/config/service.hcl\n```\n\nThe configuration file uses the [HCL](https://github.com/hashicorp/hcl) configuration\nlanguage. It supports a subset of the options provided by the CLI. An example configuration\nfile looks like:\n\n```hcl\ncore {\n  level = \"info\"\n}\n\napi {\n  port = 9922\n  driver = \"advanced\"\n  license_override = \"standard\"\n}\n```\n\n### Core options\n\n- `level` (string) - Output level of the logger\n- `log_file` (string) - Store logs to file at given path\n- `log_append` (bool) - Append log output to existing file\n\n### API options\n\n- `port` (int) - Port to bind the API (changes require changes to [Vagrant configuration](/vagrant/docs/providers/vmware/configuration#utility_port))\n- `driver` (string) - Internal driver to use (utility will auto-detect correct driver)\n- `license_override` (string) - Override the detected VMware license (standard or professional)\n\n#### Restarting the service\n\nAfter updating the the configuration file, the service must be restarted. The method\nfor restarting the service will depend on your host platform.\n\nFor Windows:\n\nOn Windows platforms a service is created called `vagrant-vmware-utility`. The\nservice can be manually stopped and started using the services GUI (`services.msc`) or by\nrunning the following command from a `cmd.exe` in administrator mode:\n\n```shell-session\n$ net.exe stop vagrant-vmware-utility\n$ net.exe start vagrant-vmware-utility\n```\n\nFor macOS:\n\n```shell-session\n$ sudo launchctl unload -w /Library/LaunchDaemons/com.vagrant.vagrant-vmware-utility.plist\n$ sudo launchctl load -w /Library/LaunchDaemons/com.vagrant.vagrant-vmware-utility.plist\n```\n\nFor Linux systemd:\n\n```shell-session\n$ sudo systemctl restart vagrant-vmware-utility\n```\n\nFor Linux SysVinit:\n\n```shell-session\n$ sudo /etc/init.d/vagrant-vmware-utility restart\n```\n\nFor Linux runit:\n\n```shell-session\n$ sudo sv restart vagrant-vmware-utility\n```\n\n### macOS service configuration\n\nThe Vagrant VMware utility service configuration on macOS is slightly different\nthan other platforms. If the `port` option is updated in the configuration file,\nit will not be applied after restarting the service. This is due to the port\nbeing defined directly within the service file so it properly matches the service\nsocket information. To update the port on macOS, it is easier to uninstall the\nservice and then install it again:\n\n```shell-session\n$ /opt/vagrant-vmware-desktop/bin/vagrant-vmware-utility service uninstall\n$ /opt/vagrant-vmware-desktop/bin/vagrant-vmware-utility service install -port=9999\n```\n"
  },
  {
    "path": "website/content/docs/provisioning/ansible.mdx",
    "content": "---\nlayout: docs\npage_title: Ansible - Provisioning\ndescription: |-\n  The Vagrant Ansible provisioner allows you to provision the guest using\n  Ansible playbooks by executing \"ansible-playbook\" from the Vagrant host.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Ansible Provisioner\n\n**Provisioner name: `ansible`**\n\nThe Vagrant Ansible provisioner allows you to provision the guest using [Ansible](http://ansible.com) playbooks by executing **`ansible-playbook` from the Vagrant host**.\n\n~> **Warning:**\nIf you are not familiar with Ansible and Vagrant already, we recommend starting with the [shell provisioner](/vagrant/docs/provisioning/shell). However, if you are comfortable with Vagrant already, Vagrant is a great way to learn Ansible.\n\n## Setup Requirements\n\n- **[Install Ansible](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) on your Vagrant host**.\n\n- Your Vagrant host should ideally provide a recent version of OpenSSH that [supports ControlPersist](https://docs.ansible.com/faq.html#how-do-i-get-ansible-to-reuse-connections-enable-kerberized-ssh-or-have-ansible-pay-attention-to-my-local-ssh-config-file).\n\nIf installing Ansible directly on the Vagrant host is not an option in your development environment, you might be looking for the [Ansible Local provisioner](/vagrant/docs/provisioning/ansible_local) alternative.\n\n## Usage\n\nThis page only documents the specific parts of the `ansible` (remote) provisioner. General Ansible concepts like Playbook or Inventory are shortly explained in the [introduction to Ansible and Vagrant](/vagrant/docs/provisioning/ansible_intro).\n\n### Simplest Configuration\n\nTo run Ansible against your Vagrant guest, the basic `Vagrantfile` configuration looks like:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n\n  #\n  # Run Ansible from the Vagrant Host\n  #\n  config.vm.provision \"ansible\" do |ansible|\n    ansible.playbook = \"playbook.yml\"\n  end\n\nend\n```\n\n## Options\n\nThis section lists the _specific_ options for the Ansible (remote) provisioner. In addition to the options listed below, this provisioner supports the [**common options** for both Ansible provisioners](/vagrant/docs/provisioning/ansible_common).\n\n- `ask_become_pass` (boolean) - require Ansible to [prompt for a password](https://docs.ansible.com/intro_getting_started.html#remote-connection-information) when switching to another user with the [become/sudo mechanism](http://docs.ansible.com/ansible/become.html).\n\n  The default value is `false`.\n\n- `ask_sudo_pass` (boolean) - Backwards compatible alias for the [ask_become_pass](#ask_become_pass) option.\n\n  ~> **Deprecation:**\n  The `ask_sudo_pass` option is deprecated and will be removed in a future release. Please use the [**`ask_become_pass`**](#ask_become_pass) option instead.\n\n* `ask_vault_pass` (boolean) - require Ansible to [prompt for a vault password](https://docs.ansible.com/playbooks_vault.html#vault).\n\n  The default value is `false`.\n\n* `force_remote_user` (boolean) - require Vagrant to set the `ansible_ssh_user` setting in the generated inventory, or as an extra variable when a static inventory is used. All the Ansible `remote_user` parameters will then be overridden by the value of `config.ssh.username` of the [Vagrant SSH Settings](/vagrant/docs/vagrantfile/ssh_settings).\n\n  If this option is set to `false` Vagrant will set the Vagrant SSH username as a default Ansible remote user, but `remote_user` parameters of your Ansible plays or tasks will still be taken into account and thus override the Vagrant configuration.\n\n  The default value is `true`.\n\n  -> **Compatibility Note:** This option was introduced in Vagrant 1.8.0. Previous Vagrant versions behave like if this option was set to `false`.\n\n- `host_key_checking` (boolean) - require Ansible to [enable SSH host key checking](https://docs.ansible.com/intro_getting_started.html#host-key-checking).\n\n  The default value is `false`.\n\n- `raw_ssh_args` (array of strings) - require Ansible to apply a list of OpenSSH client options.\n\n  Example: `['-o ControlMaster=no']`.\n\n  It is an _unsafe wildcard_ that can be used to pass additional SSH settings to Ansible via `ANSIBLE_SSH_ARGS` environment variable, overriding any other SSH arguments (e.g. defined in an [`ansible.cfg` configuration file](https://docs.ansible.com/intro_configuration.html#ssh-args)).\n\n## Tips and Tricks\n\n### Ansible Parallel Execution\n\nVagrant is designed to provision [multi-machine environments](/vagrant/docs/multi-machine) in sequence, but the following configuration pattern can be used to take advantage of Ansible parallelism:\n\n```ruby\n# Vagrant 1.7+ automatically inserts a different\n# insecure keypair for each new VM created. The easiest way\n# to use the same keypair for all the machines is to disable\n# this feature and rely on the legacy insecure key.\n# config.ssh.insert_key = false\n#\n# Note:\n# As of Vagrant 1.7.3, it is no longer necessary to disable\n# the keypair creation when using the auto-generated inventory.\n\nN = 3\n(1..N).each do |machine_id|\n  config.vm.define \"machine#{machine_id}\" do |machine|\n    machine.vm.hostname = \"machine#{machine_id}\"\n    machine.vm.network \"private_network\", ip: \"192.168.77.#{20+machine_id}\"\n\n    # Only execute once the Ansible provisioner,\n    # when all the machines are up and ready.\n    if machine_id == N\n      machine.vm.provision :ansible do |ansible|\n        # Disable default limit to connect to all the machines\n        ansible.limit = \"all\"\n        ansible.playbook = \"playbook.yml\"\n      end\n    end\n  end\nend\n```\n\n-> **Tip:**\nIf you apply this parallel provisioning pattern with a static Ansible inventory,\nyou will have to organize the things so that [all the relevant private keys are\nprovided to the `ansible-playbook` command](https://github.com/hashicorp/vagrant/pull/5765#issuecomment-120247738).\nThe same kind of considerations applies if you are using multiple private keys\nfor a same machine (see [`config.ssh.private_key_path` SSH setting](/vagrant/docs/vagrantfile/ssh_settings)).\n\n### Force Paramiko Connection Mode\n\nThe Ansible provisioner is implemented with native OpenSSH support in mind, and there is no official support for [paramiko](https://github.com/paramiko/paramiko/) (A native Python SSHv2 protocol library).\n\nIf you really need to use this connection mode though, it is possible to enable paramiko as illustrated in the following configuration examples:\n\nWith auto-generated inventory:\n\n```ruby\nansible.raw_arguments = [\"--connection=paramiko\"]\n```\n\nWith a custom inventory, the private key must be specified (e.g. via an `ansible.cfg` configuration file, `--private-key` argument, or as part of your inventory file):\n\n```ruby\nansible.inventory_path = \"./my-inventory\"\nansible.raw_arguments  = [\n  \"--connection=paramiko\",\n  \"--private-key=/home/.../.vagrant/machines/.../private_key\"\n]\n```\n"
  },
  {
    "path": "website/content/docs/provisioning/ansible_common.mdx",
    "content": "---\nlayout: docs\npage_title: Common Ansible Options - Provisioning\ndescription: This page details the common options to the Vagrant Ansible provisioners.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Shared Ansible Options\n\nThe following options are available to both Vagrant Ansible provisioners:\n\n- [`ansible`](/vagrant/docs/provisioning/ansible)\n- [`ansible_local`](/vagrant/docs/provisioning/ansible_local)\n\nThese options get passed to the `ansible-playbook` command that ships with Ansible, either via command line arguments or environment variables, depending on Ansible own capabilities.\n\nSome of these options are for advanced usage only and should not be used unless you understand their purpose.\n\n- `become` (boolean) - Perform all the Ansible playbook tasks [as another user](https://docs.ansible.com/ansible/latest/become.html), different from the user used to log into the guest system.\n\n  The default value is `false`.\n\n- `become_user` (string) - Set the default username to be used by the Ansible `become` [privilege escalation](https://docs.ansible.com/ansible/latest/become.html) mechanism.\n\n  By default this option is not set, and the Ansible default value (`root`) will be used.\n\n- `compatibility_mode` (string) - Set the **minimal** version of Ansible to be supported. Vagrant will only use parameters that are compatible with the given version.\n\n  Possible values:\n\n  - `\"auto\"` _(Vagrant will automatically select the optimal compatibility mode by checking the Ansible version currently available)_\n  - `\"1.8\"` _(Ansible versions prior to 1.8 should mostly work well, but some options might not be supported)_\n  - `\"2.0\"` _(The generated Ansible inventory will be incompatible with Ansible 1.x)_\n\n  By default this option is set to `\"auto\"`. If Vagrant is not able to detect any supported Ansible version, it will fall back on the compatibility mode `\"1.8\"` with a warning.\n\n  Vagrant will error if the specified compatibility mode is incompatible with the current Ansible version.\n\n  ~> **Attention:** Vagrant doesn't perform any validation between the `compatibility_mode` value and the value of the [`version`](#version) option.\n\n  -> **Compatibility Note:** This option was introduced in Vagrant 2.0. The behavior of previous Vagrant versions can be simulated by setting the `compatibility_mode` to `\"1.8\"`.\n\n- `config_file` (string) - The path to an [Ansible Configuration file](https://docs.ansible.com/ansible/latest/intro_configuration.html).\n\n  By default, this option is not set, and Ansible will [search for a possible configuration file in some default locations](/vagrant/docs/provisioning/ansible_intro#the-ansible-configuration-file).\n\n- `extra_vars` (string or hash) - Pass additional variables (with highest priority) to the playbook.\n\n  This parameter can be a path to a JSON or YAML file, or a hash.\n\n  Example:\n\n  ```ruby\n  ansible.extra_vars = {\n    ntp_server: \"pool.ntp.org\",\n    nginx: {\n      port: 8008,\n      workers: 4\n    }\n  }\n  ```\n\n  These variables take the highest precedence over any other variables.\n\n- `galaxy_command` (template string) - The command pattern used to install Galaxy roles when `galaxy_role_file` is set.\n\n  The following (optional) placeholders can be used in this command pattern:\n\n  - `%{role_file}` is replaced by the absolute path to the `galaxy_role_file` option\n  - `%{roles_path}` is\n    - replaced by the absolute path to the `galaxy_roles_path` option when such option is defined, or\n    - replaced by the absolute path to a `roles` subdirectory sitting in the `playbook` parent directory.\n\n  By default, this option is set to\n\n  `ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force`\n\n- `galaxy_role_file` (string) - The path to the Ansible Galaxy role file.\n\n  By default, this option is set to `nil` and Galaxy support is then disabled.\n\n  Note: if an absolute path is given, the `ansible_local` provisioner will assume that it corresponds to the exact location on the guest system.\n\n  ```ruby\n  ansible.galaxy_role_file = \"requirements.yml\"\n  ```\n\n- `galaxy_roles_path` (string) - The path to the directory where Ansible Galaxy roles must be installed\n\n  By default, this option is set to `nil`, which means that the Galaxy roles will be installed in a `roles` subdirectory located in the parent directory of the `playbook` file.\n\n- `groups` (hash) - Set of inventory groups to be included in the [auto-generated inventory file](/vagrant/docs/provisioning/ansible_intro).\n\n  Example:\n\n  ```ruby\n  ansible.groups = {\n    \"web\" => [\"vm1\", \"vm2\"],\n    \"db\"  => [\"vm3\"]\n  }\n  ```\n\n  Example with [group variables](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#assigning-a-variable-to-many-machines-group-variables):\n\n  ```ruby\n  ansible.groups = {\n    \"atlanta\" => [\"host1\", \"host2\"],\n    \"atlanta:vars\" => {\"ntp_server\" => \"ntp.atlanta.example.com\",\n                       \"proxy\" => \"proxy.atlanta.example.com\"}\n  }\n  ```\n\n  Notes:\n\n  - Alphanumeric patterns are not supported (e.g. `db-[a:f]`, `vm[01:10]`).\n  - This option has no effect when the `inventory_path` option is defined.\n\n- `host_vars` (hash) - Set of inventory host variables to be included in the [auto-generated inventory file](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#assigning-a-variable-to-one-machine-host-variables).\n\n  Example:\n\n  ```ruby\n  ansible.host_vars = {\n    \"host1\" => {\"http_port\" => 80,\n                \"maxRequestsPerChild\" => 808,\n                \"comments\" => \"text with spaces\"},\n    \"host2\" => {\"http_port\" => 303,\n                \"maxRequestsPerChild\" => 909}\n  }\n  ```\n\n  Note: This option has no effect when the `inventory_path` option is defined.\n\n- `inventory_path` (string) - The path to an Ansible inventory resource (e.g. a [static inventory file](https://docs.ansible.com/ansible/latest/intro_inventory.html), a [dynamic inventory script](https://docs.ansible.com/ansible/latest/intro_dynamic_inventory.html) or even [multiple inventories stored in the same directory](https://docs.ansible.com/ansible/latest/intro_dynamic_inventory.html#using-multiple-inventory-sources)).\n\n  By default, this option is disabled and Vagrant generates an inventory based on the `Vagrantfile` information.\n\n- `limit` (string or array of strings) - Set of machines or groups from the inventory file to further control which hosts [are affected](https://docs.ansible.com/ansible/latest/glossary.html#limit-groups).\n\n  The default value is set to the machine name (taken from `Vagrantfile`) to ensure that `vagrant provision` command only affect the expected machine.\n\n  Setting `limit = \"all\"` can be used to make Ansible connect to all machines from the inventory file.\n\n- `playbook_command` (string) - The command used to run playbooks.\n\n  The default value is `ansible-playbook`\n\n- `raw_arguments` (array of strings) - a list of additional `ansible-playbook` arguments.\n\n  It is an _unsafe wildcard_ that can be used to apply Ansible options that are not (yet) supported by this Vagrant provisioner. As of Vagrant 1.7, `raw_arguments` has the highest priority and its values can potentially override or break other Vagrant settings.\n\n  Examples:\n\n  - `['--check', '-M', '/my/modules']`\n  - `[\"--connection=paramiko\", \"--forks=10\"]`\n\n    ~> **Attention:** The `ansible` provisioner does not support whitespace characters in `raw_arguments`\n    elements. Therefore **don't write** something like `[\"-c paramiko\"]`, which\n    will result with an invalid `\" paramiko\"` parameter value.\n\n- `skip_tags` (string or array of strings) - Only plays, roles and tasks that [_do not match_ these values will be executed](https://docs.ansible.com/ansible/latest/playbooks_tags.html).\n\n- `start_at_task` (string) - The task name where the [playbook execution will start](https://docs.ansible.com/ansible/latest/playbooks_startnstep.html#start-at-task).\n\n- `sudo` (boolean) - Backwards compatible alias for the [`become`](#become) option.\n\n  ~> **Deprecation:**\n  The `sudo` option is deprecated and will be removed in a future release. Please use the [**`become`**](#become) option instead.\n\n* `sudo_user` (string) - Backwards compatible alias for the [`become_user`](#become_user) option.\n\n  ~> **Deprecation:**\n  The `sudo_user` option is deprecated and will be removed in a future release. Please use the [**`become_user`**](#become_user) option instead.\n\n- `tags` (string or array of strings) - Only plays, roles and tasks [tagged with these values will be executed](https://docs.ansible.com/ansible/latest/playbooks_tags.html) .\n\n- `vault_password_file` (string) - The path of a file containing the password used by [Ansible Vault](https://docs.ansible.com/ansible/latest/playbooks_vault.html#vault).\n\n- `verbose` (boolean or string) - Set Ansible's verbosity to obtain detailed logging\n\n  Default value is `false` (minimal verbosity).\n\n  Examples: `true` (equivalent to `v`), `-vvv` (equivalent to `vvv`), `vvvv`.\n\n  Note that when the `verbose` option is enabled, the `ansible-playbook` command used by Vagrant will be displayed.\n\n- `version` (string) - The expected Ansible version.\n\n  This option is disabled by default.\n\n  When an Ansible version is defined (e.g. `\"2.1.6.0\"`), the Ansible provisioner will be executed only if Ansible is installed at the requested version.\n\n  When this option is set to `\"latest\"`, no version check is applied.\n\n  -> **Tip:** With the `ansible_local` provisioner, it is currently possible to use this option\n  to specify which version of Ansible must be automatically installed, but **only** in combination with the [**`install_mode`**](/vagrant/docs/provisioning/ansible_local#install_mode) set to **`:pip`**.\n"
  },
  {
    "path": "website/content/docs/provisioning/ansible_intro.mdx",
    "content": "---\nlayout: docs\npage_title: Ansible - Short Introduction\ndescription: |-\n  This page includes options and information that is applicable to both Vagrant\n  Ansible provisioners.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Ansible and Vagrant\n\nThe information below is applicable to both Vagrant Ansible provisioners:\n\n- [`ansible`](/vagrant/docs/provisioning/ansible), where Ansible is executed on the **Vagrant host**\n- [`ansible_local`](/vagrant/docs/provisioning/ansible_local), where Ansible is executed on the **Vagrant guest**\n\nThe list of common options for these two provisioners is documented in a [separate documentation page](/vagrant/docs/provisioning/ansible_common).\n\nThis documentation page will not go into how to use Ansible or how to write Ansible playbooks, since Ansible is a complete deployment and configuration management system that is beyond the scope of Vagrant documentation.\n\nTo learn more about Ansible, please consult the [Ansible Documentation Site](https://docs.ansible.com/).\n\n## The Playbook File\n\nThe first component of a successful Ansible provisioner setup is the Ansible playbook which contains the steps that should be run on the guest. Ansible's\n[playbook documentation](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html) goes into great detail on how to author playbooks, and there are a number of [tips and tricks](https://docs.ansible.com/ansible/latest/tips_tricks/ansible_tips_tricks.html) that can be applied to use Ansible's powerful features effectively.\n\nA playbook that installs and starts (or restarts) the NTP daemon via YUM looks like:\n\n```\n---\n- hosts: all\n  become: yes\n  tasks:\n    - name: ensure ntpd is at the latest version\n      yum: pkg=ntp state=latest\n      notify:\n      - restart ntpd\n  handlers:\n    - name: restart ntpd\n      service: name=ntpd state=restarted\n```\n\nYou can of course target other operating systems that do not have YUM by changing the playbook tasks. Ansible ships with a number of [modules](https://docs.ansible.com/modules.html) that make running otherwise tedious tasks dead simple.\n\n### Running Ansible\n\nThe `playbook` option is strictly required by both Ansible provisioners ([`ansible`](/vagrant/docs/provisioning/ansible) and [`ansible_local`](/vagrant/docs/provisioning/ansible_local)), as illustrated in this basic `Vagrantfile` configuration:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n\n  # Use :ansible or :ansible_local to\n  # select the provisioner of your choice\n  config.vm.provision :ansible do |ansible|\n    ansible.playbook = \"playbook.yml\"\n  end\nend\n```\n\nSince an Ansible playbook can include many files, you may also collect the related files in a [directory structure](https://docs.ansible.com/ansible/2.8/user_guide/playbooks_best_practices.html#directory-layout) like this:\n\n```text\n.\n|-- Vagrantfile\n|-- provisioning\n|   |-- group_vars\n|           |-- all\n|   |-- roles\n|           |-- bar\n|           |-- foo\n|   |-- playbook.yml\n```\n\nIn such an arrangement, the `ansible.playbook` path should be adjusted accordingly:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"ansible\" do |ansible|\n    ansible.playbook = \"provisioning/playbook.yml\"\n  end\nend\n```\n\n## The Inventory File\n\nWhen using Ansible, it needs to know on which machines a given playbook should run. It does this by way of an [inventory](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#intro-inventory) file which lists those machines. In the context of Vagrant, there are two ways to approach working with inventory files.\n\n### Auto-Generated Inventory\n\nThe first and simplest option is to not provide one to Vagrant at all. Vagrant will generate an inventory file encompassing all of the virtual machines it manages, and use it for provisioning machines.\n\n#### Ansible provisioner example\n\nExample with the [`ansible`](/vagrant/docs/provisioning/ansible) provisioner.\n\n```text\n# Generated by Vagrant\n\ndefault ansible_host=127.0.0.1 ansible_port=2200 ansible_user='vagrant' ansible_ssh_private_key_file='/home/.../.vagrant/machines/default/virtualbox/private_key'\n```\n\nNote that the generated inventory file is stored as part of your local Vagrant environment in\n`.vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory`.\n\n#### Ansible local provision\n\nExample with the [`ansible_local`](/vagrant/docs/provisioning/ansible_local) provisioner\n\n```text\n# Generated by Vagrant\n\ndefault ansible_connection=local\n```\n\nNote that the generated inventory file is uploaded to the guest VM in a subdirectory of [`tmp_path`](/vagrant/docs/provisioning/ansible_local), e.g. `/tmp/vagrant-ansible/inventory/vagrant_ansible_local_inventory`.\n\n#### Host Variables\n\nAs of Vagrant 1.8.0, the [`host_vars`](/vagrant/docs/provisioning/ansible_common#host_vars) option can be used to set [variables for individual hosts](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#assigning-a-variable-to-one-machine-host-variables) in the generated inventory file (see also the notes on group variables below).\n\nWith this configuration example:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.define \"host1\"\n  config.vm.define \"host2\"\n  config.vm.provision \"ansible\" do |ansible|\n    ansible.playbook = \"playbook.yml\"\n    ansible.host_vars = {\n      \"host1\" => {\"http_port\" => 80,\n                  \"maxRequestsPerChild\" => 808},\n      \"host2\" => {\"http_port\" => 303,\n                  \"maxRequestsPerChild\" => 909}\n    }\n  end\nend\n```\n\nVagrant would generate the following inventory file:\n\n```text\n# Generated by Vagrant\n\nhost1 ansible_host=... http_port=80 maxRequestsPerChild=808\nhost2 ansible_host=... http_port=303 maxRequestsPerChild=909\n```\n\n#### Groups and Group Variables\n\nThe [`groups`](/vagrant/docs/provisioning/ansible_common#groups) option can be used to pass a hash of group names and group members to be included in the generated inventory file.\n\nAs of Vagrant 1.8.0, it is also possible to specify [group variables](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#assigning-a-variable-to-many-machines-group-variables), and group members as [host ranges (with numeric or alphabetic patterns)](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#inventory-basics-formats-hosts-and-groups).\n\nWith this configuration example:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n\n  config.vm.box = \"ubuntu/trusty64\"\n\n  config.vm.define \"machine1\"\n  config.vm.define \"machine2\"\n\n  config.vm.provision \"ansible\" do |ansible|\n    ansible.playbook = \"playbook.yml\"\n    ansible.groups = {\n      \"group1\" => [\"machine1\"],\n      \"group2\" => [\"machine2\"],\n      \"group3\" => [\"machine[1:2]\"],\n      \"group4\" => [\"other_node-[a:d]\"], # silly group definition\n      \"all_groups:children\" => [\"group1\", \"group2\"],\n      \"group1:vars\" => {\"variable1\" => 9,\n                        \"variable2\" => \"example\"}\n    }\n  end\nend\n```\n\nVagrant would generate the following inventory file:\n\n```text\n# Generated by Vagrant\n\nmachine1 ansible_host=127.0.0.1 ansible_port=2200 ansible_user='vagrant' ansible_ssh_private_key_file='/home/.../.vagrant/machines/machine1/virtualbox/private_key'\nmachine2 ansible_host=127.0.0.1 ansible_port=2222 ansible_user='vagrant' ansible_ssh_private_key_file='/home/.../.vagrant/machines/machine2/virtualbox/private_key'\n\n[group1]\nmachine1\n\n[group2]\nmachine2\n\n[group3]\nmachine[1:2]\n\n[group4]\nother_node-[a:d]\n\n[all_groups:children]\ngroup1\ngroup2\n\n[group1:vars]\nvariable1=9\nvariable2=example\n```\n\n**Notes:**\n\n- Prior to Vagrant 1.7.3, the `ansible_ssh_private_key_file` variable was not set in generated inventory, but passed as command line argument to `ansible-playbook` command.\n- The generation of group variables blocks (e.g. `[group1:vars]`) is only possible since Vagrant 1.8.0. Note however that setting variables directly in the inventory is not the [preferred practice in Ansible](https://docs.ansible.com/intro_inventory.html#splitting-out-host-and-group-specific-data). If possible, group (or host) variables should be set in `YAML` files stored in the `group_vars/` or `host_vars/` directories in the playbook (or inventory) directory instead.\n- Unmanaged machines and undefined groups are not added to the inventory, to avoid useless Ansible errors (e.g. _unreachable host_ or _undefined child group_)\n\n  For example, `machine3` and `group3` in the example below would not be added to the generated inventory file:\n\n  ```ruby\n  ansible.groups = {\n    \"group1\" => [\"machine1\"],\n    \"group2\" => [\"machine2\", \"machine3\"],\n    \"all_groups:children\" => [\"group1\", \"group2\", \"group3\"]\n  }\n  ```\n\n- [Host range patterns (numeric and alphabetic ranges)](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#inventory-basics-formats-hosts-and-groups) will not be validated by Vagrant. As of Vagrant 1.8.0, host range patterns will be added as group members to the inventory anyway, this might lead to errors in Ansible (e.g _unreachable host_).\n\n### Static Inventory\n\nThe second option is for situations where you would like to have more control over the inventory management.\n\nWith the [`inventory_path`](/vagrant/docs/provisioning/ansible_common#inventory_path) option, you can reference a specific inventory resource (e.g. a static inventory file, a [dynamic inventory script](https://docs.ansible.com/intro_dynamic_inventory.html) or even [multiple inventories stored in the same directory](https://docs.ansible.com/intro_dynamic_inventory.html#using-multiple-inventory-sources)). Vagrant will then use this inventory information instead of generating it.\n\nA very simple inventory file for use with Vagrant might look like:\n\n```text\ndefault ansible_host=192.168.111.222\n```\n\nWhere the above IP address is one set in your Vagrantfile:\n\n```text\nconfig.vm.network :private_network, ip: \"192.168.111.222\"\n```\n\n**Notes:**\n\n- The machine names in `Vagrantfile` and `ansible.inventory_path` files should correspond, unless you use `ansible.limit` option to reference the correct machines.\n- The `ansible.inventory_path` option by default is only scoped to apply to a single guest in the inventory file, and does not apply to all defined guests. To allow access to all available machines in the inventory, it is necessary to set `ansible.limit = \"all\"`.\n- The SSH host addresses (and ports) must obviously be specified twice, in `Vagrantfile` and `ansible.inventory_path` files.\n- Sharing hostnames across Vagrant host and guests might be a good idea (e.g. with some Ansible configuration task, or with a plugin like [`vagrant-hostmanager`](https://github.com/smdahlen/vagrant-hostmanager)).\n\n### The Ansible Configuration File\n\nCertain settings in Ansible are (only) adjustable via a [configuration file](https://docs.ansible.com/intro_configuration.html), and you might want to ship such a file in your Vagrant project.\n\nWhen shipping an Ansible configuration file it is good to know that:\n\n- as of Ansible 1.5, the lookup order is the following:\n  - any path set as `ANSIBLE_CONFIG` environment variable\n  - `ansible.cfg` in the runtime working directory\n  - `.ansible.cfg` in the user home directory\n  - `/etc/ansible/ansible.cfg`\n- Ansible commands don't look for a configuration file relative to the playbook file location (e.g. in the same directory)\n- an `ansible.cfg` file located in the same directory as your `Vagrantfile` will be used by default.\n- it is also possible to reference any other location with the [config_file](/vagrant/docs/provisioning/ansible_common#config_file) provisioner option. In this case, Vagrant will set the `ANSIBLE_CONFIG` environment variable accordingly.\n"
  },
  {
    "path": "website/content/docs/provisioning/ansible_local.mdx",
    "content": "---\nlayout: docs\npage_title: Ansible Local - Provisioning\ndescription: >-\n  The Vagrant Ansible Local provisioner allows you to provision the guest using\n  Ansible playbooks by executing \"ansible-playbook\" directly on the guest\n\n  machine.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Ansible Local Provisioner\n\n**Provisioner name: `ansible_local`**\n\nThe Vagrant Ansible Local provisioner allows you to provision the guest using [Ansible](http://ansible.com) playbooks by executing **`ansible-playbook` directly on the guest machine**.\n\n~> **Warning:** If you are not familiar with Ansible and Vagrant already, we recommend starting with the [shell provisioner](/vagrant/docs/provisioning/shell). However, if you are comfortable with Vagrant already, Vagrant is a great way to learn Ansible.\n\n## Setup Requirements\n\nThe main advantage of the Ansible Local provisioner in comparison to the [Ansible (remote) provisioner](/vagrant/docs/provisioning/ansible) is that it does not require any additional software on your Vagrant host.\n\nOn the other hand, [Ansible must obviously be installed](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) on your guest machine(s).\n\n**Note:** By default, Vagrant will _try_ to automatically install Ansible if it is not yet present on the guest machine (see the `install` option below for more details).\n\n## Usage\n\nThis page only documents the specific parts of the `ansible_local` provisioner. General Ansible concepts like Playbook or Inventory are shortly explained in the [introduction to Ansible and Vagrant](/vagrant/docs/provisioning/ansible_intro).\n\nThe Ansible Local provisioner requires that all the Ansible Playbook files are available on the guest machine, at the location referred by the `provisioning_path` option. Usually these files are initially present on the host machine (as part of your Vagrant project), and it is quite easy to share them with a Vagrant [Synced Folder](/vagrant/docs/synced-folders/).\n\n### Simplest Configuration\n\nTo run Ansible from your Vagrant guest, the basic `Vagrantfile` configuration looks like:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # Run Ansible from the Vagrant VM\n  config.vm.provision \"ansible_local\" do |ansible|\n    ansible.playbook = \"playbook.yml\"\n  end\nend\n```\n\n**Requirements:**\n\n- The `playbook.yml` file is stored in your Vagrant's project home directory.\n\n- The [default shared directory](/vagrant/docs/synced-folders/basic_usage) is enabled (`.` &rarr; `/vagrant`).\n\n## Options\n\nThis section lists the _specific_ options for the Ansible Local provisioner. In addition to the options listed below, this provisioner supports the [**common options** for both Ansible provisioners](/vagrant/docs/provisioning/ansible_common).\n\n- `install` (boolean) - Try to automatically install Ansible on the guest system.\n\n  This option is enabled by default.\n\n  Vagrant will try to install (or upgrade) Ansible when one of these conditions are met:\n\n  - Ansible is not installed (or cannot be found).\n  - The [`version`](/vagrant/docs/provisioning/ansible_common#version) option is set to `\"latest\"`.\n  - The current Ansible version does not correspond to the [`version`](/vagrant/docs/provisioning/ansible_common#version) option.\n\n    ~> **Attention:**\n    There is no guarantee that this automated installation will replace a custom Ansible setup, that might be already present on the Vagrant box.\n\n* `install_mode` (`:default`, `:pip`, or `:pip_args_only`) - Select the way to automatically install Ansible on the guest system.\n\n  - `:default`: Ansible is installed from the operating system package manager. This mode doesn't support `version` selection. For many platforms (e.g Debian, FreeBSD, OpenSUSE) the official package repository is used, except for the following Linux distributions:\n\n    - On Ubuntu-like systems, the latest Ansible release is installed from the `ppa:ansible/ansible` repository. The compatibility is maintained only for active long-term support (LTS) versions.\n    - On RedHat-like systems, the latest Ansible release is installed from the [EPEL](http://fedoraproject.org/wiki/EPEL) repository.\n\n  - `:pip`: Ansible is installed from [PyPI](https://pypi.python.org/pypi) with [pip](https://pip.pypa.io) package installer. With this mode, Vagrant will systematically try to [install the latest pip version](https://pip.pypa.io/en/stable/installing/#installing-with-get-pip-py). With the `:pip` mode you can optionally install a specific Ansible release by setting the [`version`](/vagrant/docs/provisioning/ansible_common#version) option.\n\n    Example:\n\n    ```ruby\n    config.vm.provision \"ansible_local\" do |ansible|\n      ansible.playbook = \"playbook.yml\"\n      ansible.install_mode = \"pip\"\n      ansible.version = \"2.2.1.0\"\n    end\n    ```\n\n    With this configuration, Vagrant will install `pip` and then execute the command\n\n    ```shell\n    sudo pip install --upgrade ansible==2.2.1.0\n    ```\n\n    As-is `pip` is installed if needed via a default command which looks like\n\n    ```shell\n    curl https://bootstrap.pypa.io/get-pip.py | sudo python\n    ```\n\n    This can be problematic in certain scenarios, for example, when behind a proxy. It is possible to override this default command by providing an explicit command to run as part of the config using `pip_install_cmd`. For example:\n\n    ```ruby\n    config.vm.provision \"ansible_local\" do |ansible|\n      ansible.playbook = \"playbook.yml\"\n      ansible.install_mode = \"pip\"\n      ansible.pip_install_cmd = \"https_proxy=http://your.proxy.server:port curl -s https://bootstrap.pypa.io/get-pip.py | sudo https_proxy=http/your.proxy.server:port python\"\n      ansible.version = \"2.2.1.0\"\n    end\n    ```\n\n    In this case case `pip` will be installed via the command:\n\n    ```shell\n    https_proxy=http://your.proxy.server:port curl -s https://bootstrap.pypa.io/get-pip.py | sudo https_proxy=http://your.proxy.server:porpython\n    ```\n\n    If `pip_install_cmd` is not provided in the config, then `pip` is installed via the default command.\n\n  - `:pip_args_only`: This mode is very similar to the `:pip` mode, with the difference that in this case no pip arguments will be automatically set by Vagrant.\n\n    Example:\n\n    ```ruby\n    config.vm.provision \"ansible_local\" do |ansible|\n      ansible.playbook = \"playbook.yml\"\n      ansible.install_mode = \"pip_args_only\"\n      ansible.pip_args = \"-r /vagrant/requirements.txt\"\n    end\n    ```\n\n    With this configuration, Vagrant will install `pip` and then execute the command\n\n    ```shell\n    sudo pip install -r /vagrant/requirements.txt\n    ```\n\n    The default value of `install_mode` is `:default`, and any invalid value for this option will silently fall back to the default value.\n\n* `pip_args` (string) - When Ansible is installed via pip, this option allows the definition of additional pip arguments to be passed along on the command line (for example, [`--index-url`](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-i)).\n\n  By default, this option is not set.\n\n  Example:\n\n  ```ruby\n  config.vm.provision \"ansible_local\" do |ansible|\n    ansible.playbook = \"playbook.yml\"\n    ansible.install_mode = :pip\n    ansible.pip_args = \"--index-url https://pypi.internal\"\n  end\n  ```\n\n  With this configuration, Vagrant will install `pip` and then execute the command\n\n  ```shell\n  sudo pip install --index-url https://pypi.internal --upgrade ansible\n  ```\n\n* `provisioning_path` (string) - An absolute path on the guest machine where the Ansible files are stored. The `ansible-galaxy` and `ansible-playbook` commands are executed from this directory. This is the location to place an [ansible.cfg](http://docs.ansible.com/ansible/intro_configuration.html) file, in case you need it.\n\n  The default value is `/vagrant`.\n\n* `tmp_path` (string) - An absolute path on the guest machine where temporary files are stored by the Ansible Local provisioner.\n\n  The default value is `/tmp/vagrant-ansible`\n\n## Tips and Tricks\n\n### Install Galaxy Roles in a path owned by root\n\n~> **Disclaimer:** This tip is not a recommendation to install galaxy roles out of the vagrant user space, especially if you rely on SSH agent forwarding to fetch the roles.\n\nBe careful that `ansible-galaxy` command is executed by default as vagrant user. Setting `galaxy_roles_path` to a folder like `/etc/ansible/roles` will fail, and `ansible-galaxy` will extract the role a second time in `/home/vagrant/.ansible/roles/`. Then if your playbook uses `become` to run as `root`, it will fail with a _\"role was not found\"_ error.\n\nTo work around that, you can use `ansible.galaxy_command` to prepend the command with `sudo`, as illustrated in the example below:\n\n```ruby\nVagrant.configure(2) do |config|\n  config.vm.box = \"centos/7\"\n  config.vm.provision \"ansible_local\" do |ansible|\n    ansible.become = true\n    ansible.playbook = \"playbook.yml\"\n    ansible.galaxy_role_file = \"requirements.yml\"\n    ansible.galaxy_roles_path = \"/etc/ansible/roles\"\n    ansible.galaxy_command = \"sudo ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force\"\n  end\nend\n```\n\n### Ansible Parallel Execution from a Guest\n\nWith the following configuration pattern, you can install and execute Ansible only on a single guest machine (the `\"controller\"`) to provision all your machines.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n\n  config.vm.box = \"ubuntu/trusty64\"\n\n  config.vm.define \"node1\" do |machine|\n    machine.vm.network \"private_network\", ip: \"172.17.177.21\"\n  end\n\n  config.vm.define \"node2\" do |machine|\n    machine.vm.network \"private_network\", ip: \"172.17.177.22\"\n  end\n\n  config.vm.define 'controller' do |machine|\n    machine.vm.network \"private_network\", ip: \"172.17.177.11\"\n\n    machine.vm.provision :ansible_local do |ansible|\n      ansible.playbook       = \"example.yml\"\n      ansible.verbose        = true\n      ansible.install        = true\n      ansible.limit          = \"all\" # or only \"nodes\" group, etc.\n      ansible.inventory_path = \"inventory\"\n    end\n  end\n\nend\n```\n\nYou need to create a static `inventory` file that corresponds to your `Vagrantfile` machine definitions:\n\n```\ncontroller ansible_connection=local\nnode1      ansible_host=172.17.177.21 ansible_ssh_private_key_file=/vagrant/.vagrant/machines/node1/virtualbox/private_key\nnode2      ansible_host=172.17.177.22 ansible_ssh_private_key_file=/vagrant/.vagrant/machines/node2/virtualbox/private_key\n\n[nodes]\nnode[1:2]\n```\n\nAnd finally, you also have to create an [`ansible.cfg` file](https://docs.ansible.com/intro_configuration.html#openssh-specific-settings) to fully disable SSH host key checking. More SSH configurations can be added to the `ssh_args` parameter (e.g. agent forwarding, etc.)\n\n```\n[defaults]\nhost_key_checking = no\n\n[ssh_connection]\nssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes\n```\n"
  },
  {
    "path": "website/content/docs/provisioning/basic_usage.mdx",
    "content": "---\nlayout: docs\npage_title: Basic Usage - Provisioning\ndescription: |-\n  While Vagrant offers multiple options for how you are able to provision\n  your machine, there is a standard usage pattern as well as some important\n  points common to all provisioners that are important to know.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Basic Usage of Provisioners\n\nWhile Vagrant offers multiple options for how you are able to provision\nyour machine, there is a standard usage pattern as well as some important\npoints common to all provisioners that are important to know.\n\n## Options\n\nEvery Vagrant provisioner accepts a few base options. The only required\noption is what type a provisioner is:\n\n- `name` (string) - The name of the provisioner. Note: if no `type` option is given,\n  this option _must_ be the type of provisioner it is. If you wish to give it a\n  different name you must also set the `type` option to define the kind of provisioner.\n- `type` (string) - The class of provisioner to configure. (i.e. `\"shell\"` or `\"file\"`)\n- `before` (string or symbol) - The exact name of an already defined provisioner\n  that _this_ provisioner should run before. If defined as a symbol, its only valid\n  values are `:each` or `:all`, which makes the provisioner run before each and\n  every root provisioner, or before all provisioners respectively.\n- `after` (string or symbol) - The exact name of an already defined provisioner\n  that _this_ provisioner should run after. If defined as a symbol, its only valid\n  values are `:each` or `:all`, which makes the provisioner run after each and\n  every root provisioner, or before all provisioners respectively.\n- `communicator_required` (boolean) - Specifies the machine must be accessible by\n  Vagrant in order to run the provisioner. If set to true, the provisioner will\n  only run if Vagrant can establish communication with the guest. If set to false\n  the provisioner will run regardless of Vagrant's ability to communicate with the\n  guest. Defaults to true.\n\nMore information about how to use `before` and `after` options can be read [below](#dependency-provisioners).\n\n## Configuration\n\nFirst, every provisioner is configured within your\n[Vagrantfile](/vagrant/docs/vagrantfile/)\nusing the `config.vm.provision` method call. For example, the Vagrantfile\nbelow enables shell provisioning:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # ... other configuration\n\n  config.vm.provision \"shell\", inline: \"echo hello\"\nend\n```\n\nEvery provisioner has a type, such as `\"shell\"`, used as the first\nparameter to the provisioning configuration. Following that is basic key/value\nfor configuring that specific provisioner. Instead of basic key/value, you\ncan also use a Ruby block for a syntax that is more like variable assignment.\nThe following is effectively the same as the prior example:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # ... other configuration\n\n  config.vm.provision \"shell\" do |s|\n    s.inline = \"echo hello\"\n  end\nend\n```\n\nThe benefit of the block-based syntax is that with more than a couple options\nit can greatly improve readability. Additionally, some provisioners, like\nthe Chef provisioner, have special methods that can be called within that\nblock to ease configuration that cannot be done with the key/value approach,\nor you can use this syntax to pass arguments to a shell script.\n\nThe attributes that can be set in a single-line are the attributes that\nare set with the `=` style, such as `inline = \"echo hello\"` above. If the\nstyle is instead more of a function call, such as `add_recipe \"foo\"`, then\nthis cannot be specified in a single line.\n\nProvisioners can also be named (since 1.7.0). These names are used cosmetically for output\nas well as overriding provisioner settings (covered further below). An example\nof naming provisioners is shown below:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # ... other configuration\n\n  config.vm.provision \"bootstrap\", type: \"shell\" do |s|\n    s.inline = \"echo hello\"\n  end\nend\n```\n\nNaming provisioners is simple. The first argument to `config.vm.provision`\nbecomes the name, and then a `type` option is used to specify the provisioner\ntype, such as `type: \"shell\"` above.\n\n## Running Provisioners\n\nProvisioners are run in three cases: the initial `vagrant up`, `vagrant provision`, and `vagrant reload --provision`.\n\nA `--no-provision` flag can be passed to `up` and `reload` if you do not\nwant to run provisioners. Likewise, you can pass `--provision` to force\nprovisioning.\n\nThe `--provision-with` flag can be used if you only want to run a\nspecific provisioner if you have multiple provisioners specified. For\nexample, if you have a shell and Puppet provisioner and only want to\nrun the shell one, you can do `vagrant provision --provision-with shell`.\nThe arguments to `--provision-with` can be the provisioner type (such as\n\"shell\") or the provisioner name (such as \"bootstrap\" from above).\n\n## Run Once, Always or Never\n\nBy default, provisioners are only run once, during the first `vagrant up`\nsince the last `vagrant destroy`, unless the `--provision` flag is set,\nas noted above.\n\nOptionally, you can configure provisioners to run on every `up` or\n`reload`. They will only be not run if the `--no-provision` flag is\nexplicitly specified. To do this set the `run` option to \"always\",\nas shown below:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"shell\", inline: \"echo hello\",\n    run: \"always\"\nend\n```\n\nYou can also set `run:` to `\"never\"` if you have an optional provisioner\nthat you want to mention to the user in a \"post up message\" or that\nrequires some other configuration before it is possible, then call this\nwith `vagrant provision --provision-with bootstrap`.\n\nIf you are using the block format, you must specify it outside\nof the block, as shown below:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"bootstrap\", type: \"shell\", run: \"never\" do |s|\n    s.inline = \"echo hello\"\n  end\nend\n```\n\n## Multiple Provisioners\n\nMultiple `config.vm.provision` methods can be used to define multiple\nprovisioners. These provisioners will be run in the order they're defined.\nThis is useful for a variety of reasons, but most commonly it is used so\nthat a shell script can bootstrap some of the system so that another provisioner\ncan take over later.\n\nIf you define provisioners at multiple \"scope\" levels (such as globally\nin the configuration block, then in a\n[multi-machine](/vagrant/docs/multi-machine/) definition, then maybe\nin a [provider-specific override](/vagrant/docs/providers/configuration)),\nthen the outer scopes will always run _before_ any inner scopes. For\nexample, in the Vagrantfile below:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"shell\", inline: \"echo foo\"\n\n  config.vm.define \"web\" do |web|\n    web.vm.provision \"shell\", inline: \"echo bar\"\n  end\n\n  config.vm.provision \"shell\", inline: \"echo baz\"\nend\n```\n\nThe ordering of the provisioners will be to echo \"foo\", \"baz\", then\n\"bar\" (note the second one might not be what you expect!). Remember:\nordering is _outside in_.\n\nWith multiple provisioners, use the `--provision-with` setting along\nwith names to get more fine grained control over what is run and when.\n\n## Overriding Provisioner Settings\n\n~> **Warning: Advanced Topic!** Provisioner overriding is\nan advanced topic that really only becomes useful if you are already\nusing multi-machine and/or provider overrides. If you are just getting\nstarted with Vagrant, you can safely skip this.\n\nWhen using features such as [multi-machine](/vagrant/docs/multi-machine/)\nor [provider-specific overrides](/vagrant/docs/providers/configuration),\nyou may want to define common provisioners in the global configuration\nscope of a Vagrantfile, but override certain aspects of them internally.\nVagrant allows you to do this, but has some details to consider.\n\nTo override settings, you must assign a name to your provisioner.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"foo\", type: \"shell\",\n    inline: \"echo foo\"\n\n  config.vm.define \"web\" do |web|\n    web.vm.provision \"foo\", type: \"shell\",\n      inline: \"echo bar\"\n  end\nend\n```\n\nIn the above, only \"bar\" will be echoed, because the inline setting\noverloaded the outer provisioner. This overload is only effective\nwithin that scope: the \"web\" VM. If there were another VM defined,\nit would still echo \"foo\" unless it itself also overloaded the\nprovisioner.\n\n**Be careful with ordering.** When overriding a provisioner in\na sub-scope, the provisioner will run at _that point_. In the example\nbelow, the output would be \"foo\" then \"bar\":\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"foo\", type: \"shell\",\n    inline: \"echo ORIGINAL!\"\n\n  config.vm.define \"web\" do |web|\n    web.vm.provision \"shell\",\n      inline: \"echo foo\"\n    web.vm.provision \"foo\", type: \"shell\",\n      inline: \"echo bar\"\n  end\nend\n```\n\nIf you want to preserve the original ordering, you can specify\nthe `preserve_order: true` flag:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"do-this\",\n    type: \"shell\",\n    preserve_order: true,\n    inline: \"echo FIRST!\"\n  config.vm.provision \"then-this\",\n    type: \"shell\",\n    preserve_order: true,\n    inline: \"echo SECOND!\"\nend\n```\n\n## Dependency Provisioners\n\n~> **Warning: Advanced Topic!** Dependency provisioners are\nan advanced topic. If you are just getting started with Vagrant, you can\nsafely skip this.\n\nIf a provisioner has been configured using the `before` or `after` options, it\nis considered a _Dependency Provisioner_. This means it has been configured to\nrun before or after a _Root Provisioner_, which does not have the `before` or\n`after` options configured.\n\nDependency provisioners also have two valid shortcuts:\n`:each` and `:all`.\n\n**Note**: As of 2.2.6, dependency provisioners cannot rely on other dependency\nprovisioners and is considered a configuration state error in Vagrant. If you must\norder dependency provisioners, you can still order them by the order they are defined\ninside your Vagrantfile.\n\nAn example of these dependency provisioners can be seen below:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"C\", after: \"B\", type: \"shell\", inline:<<-SHELL\n  echo 'C'\n  SHELL\n  config.vm.provision \"B\", type: \"shell\", inline:<<-SHELL\n  echo 'B'\n  SHELL\n  config.vm.provision \"D\", type: \"shell\", inline:<<-SHELL\n  echo 'D'\n  SHELL\n  config.vm.provision \"A\", before: \"B\", type: \"shell\", inline:<<-SHELL\n  echo 'A'\n  SHELL\n  config.vm.provision \"Separate After\", after: :each, type: \"shell\", inline:<<-SHELL\n  echo '=============================='\n  SHELL\n  config.vm.provision \"Separate Before\", before: :each, type: \"shell\", inline:<<-SHELL\n  echo '++++++++++++++++++++++++++++++'\n  SHELL\n  config.vm.provision \"Hello\", before: :all, type: \"shell\", inline:<<-SHELL\n  echo 'HERE WE GO!!'\n  SHELL\n  config.vm.provision \"Goodbye\", after: :all, type: \"shell\", inline:<<-SHELL\n  echo 'The end'\n  SHELL\nend\n```\n\nThe result of running `vagrant provision` with a guest configured above:\n\n```\n==> default: Running provisioner: Hello (shell)...\n    default: Running: inline script\n    default: HERE WE GO!!\n==> default: Running provisioner: Separate Before (shell)...\n    default: Running: inline script\n    default: ++++++++++++++++++++++++++++++\n==> default: Running provisioner: A (shell)...\n    default: Running: inline script\n    default: A\n==> default: Running provisioner: Separate After (shell)...\n    default: Running: inline script\n    default: ==============================\n==> default: Running provisioner: Separate Before (shell)...\n    default: Running: inline script\n    default: ++++++++++++++++++++++++++++++\n==> default: Running provisioner: B (shell)...\n    default: Running: inline script\n    default: B\n==> default: Running provisioner: Separate After (shell)...\n    default: Running: inline script\n    default: ==============================\n==> default: Running provisioner: Separate Before (shell)...\n    default: Running: inline script\n    default: ++++++++++++++++++++++++++++++\n==> default: Running provisioner: C (shell)...\n    default: Running: inline script\n    default: C\n==> default: Running provisioner: Separate After (shell)...\n    default: Running: inline script\n    default: ==============================\n==> default: Running provisioner: Separate Before (shell)...\n    default: Running: inline script\n    default: ++++++++++++++++++++++++++++++\n==> default: Running provisioner: D (shell)...\n    default: Running: inline script\n    default: D\n==> default: Running provisioner: Separate After (shell)...\n    default: Running: inline script\n    default: ==============================\n==> default: Running provisioner: Goodbye (shell)...\n    default: Running: inline script\n    default: The end\n```\n"
  },
  {
    "path": "website/content/docs/provisioning/cfengine.mdx",
    "content": "---\nlayout: docs\npage_title: CFEngine Provisioner\ndescription: |-\n  The Vagrant CFEngine provisioner allows you to provision the guest using\n  CFEngine. It can set up both CFEngine policy servers and clients. You can\n  configure both the policy server and the clients in a single multi-machine\n  Vagrantfile.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# CFEngine Provisioner\n\n**Provisioner name: `cfengine`**\n\nThe Vagrant CFEngine provisioner allows you to provision the guest using\n[CFEngine](https://cfengine.com/). It can set up both CFEngine\npolicy servers and clients. You can configure both the policy server\nand the clients in a single\n[multi-machine `Vagrantfile`](/vagrant/docs/multi-machine/).\n\n~> **Warning:** If you are not familiar with CFEngine and Vagrant already,\nit is recommended to start with the [shell provisioner](/vagrant/docs/provisioning/shell).\nHowever, if you are comfortable with Vagrant already, Vagrant is the best way to learn CFEngine.\n\nLet us look at some common examples first. See the bottom of this\ndocument for a comprehensive list of options.\n\n## Setting up a CFEngine server and client\n\nThe CFEngine provisioner automatically installs the latest\n[CFEngine Community packages](https://cfengine.com/cfengine-linux-distros)\non the VM, then configures and starts CFEngine according to your\nspecification.\n\nConfiguring a VM as a CFEngine policy server is easy:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"cfengine\" do |cf|\n    cf.am_policy_hub = true\n  end\nend\n```\n\nThe host will automatically be\n[bootstrapped](https://cfengine.com/docs/3.5/manuals-architecture-networking.html#bootstrapping)\nto itself to become a policy server.\n\nIf you already have a working CFEngine policy server, you can get a\nCFEngine client installed and bootstrapped by specifying its IP\naddress:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"cfengine\" do |cf|\n    cf.policy_server_address = \"10.0.2.15\"\n  end\nend\n```\n\n## Copying files to the VM\n\nIf you have some policy or other files that you want to install by\ndefault on a VM, you can use the `files_path` attribute:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n   config.vm.provision \"cfengine\" do |cf|\n      cf.am_policy_hub = true\n      cf.files_path = \"cfengine_files\"\n    end\n  end\n```\n\nEverything under `cfengine_files/` in the Vagrant project directory\nwill be recursively copied under `/var/cfengine/` in the VM, on top of\nits default contents.\n\nA common use case is to add your own files to\n`/var/cfengine/masterfiles/` in the policy server. Assuming your extra\nfiles are stored under `cfengine_files/masterfiles/`, the line shown\nabove will add them to the VM after CFEngine is installed, but before\nit is bootstrapped.\n\n## Modes of operation\n\nThe default mode of operation is `:bootstrap`, which results in\nCFEngine being bootstrapped according to the information provided in\nthe `Vagrantfile`. You can also set `mode` to `:single_run`, which\nwill run `cf-agent` once on the host to execute the file specified in\nthe `run_file` parameter, but will not bootstrap it, so it will not be\nexecuted periodically.\n\nThe recommended mode of operation is `:bootstrap`, as you get the full\nbenefits of CFEngine when you have it running periodically.\n\n## Running a standalone file\n\nIf you want to run a standalone file, you can specify the `run_file`\nparameter. The file will be copied to the VM and executed on its own\nusing `cf-agent`. Note that the file needs to be a standalone policy,\nincluding its own\n[`body common control`](https://cfengine.com/docs/3.5/reference-components.html#common-control).\n\nThe `run_file` parameter is mandatory if `mode` is set to\n`:single_run`, but can also be specified when `mode` is set to\n`:bootstrap` - in this case the file will be executed after the host\nhas been bootstrapped.\n\n## Full Alphabetical List of Configuration Options\n\n- `am_policy_hub` (boolean, default `false`) determines whether the VM will be\n  configured as a CFEngine policy hub (automatically bootstrapped to\n  its own IP address). You can combine it with `policy_server_address`\n  if the VM has multiple network interfaces and you want to bootstrap\n  to a specific one.\n- `extra_agent_args` (string, default `nil`) can be used to pass\n  additional arguments to `cf-agent` when it is executed. For example,\n  you could use it to pass the `-I` or `-v` options to enable\n  additional output from the agent.\n- `classes` (array, default `nil`) can be used to define additional\n  classes during `cf-agent` runs. These classes will be defined using\n  the `-D` option to `cf-agent`.\n- `deb_repo_file` (string, default\n  `\"/etc/apt/sources.list.d/cfengine-community.list\"`) specifies the\n  file in which the CFEngine repository information will be stored in\n  Debian systems.\n- `deb_repo_line` (string, default `\"deb https://cfengine.com/pub/apt $(lsb_release -cs) main\"`) specifies the repository to use for\n  `.deb` packages.\n- `files_path` (string, default `nil`) specifies a directory that will\n  be copied to the VM on top of the default\n  `/var/cfengine/` (the contents of `/var/cfengine/` will not\n  be replaced, the files will added to it).\n- `force_bootstrap` (boolean, default `false`) specifies whether\n  CFEngine will be bootstrapped again even if the host has already\n  been bootstrapped.\n- `install` (boolean or `:force`, default `true`) specifies whether\n  CFEngine will be installed on the VM if needed. If you set this\n  parameter to `:force`, then CFEngine will be reinstalled even if\n  it is already present on the machine.\n- `mode` (`:bootstrap` or `:single_run`, default `:bootstrap`)\n  specifies whether CFEngine will be bootstrapped so that it executes\n  periodically, or will be run a single time. If `mode` is set to\n  `:single_run` you have to set `run_file`.\n- `policy_server_address` (string, no default) specifies the IP\n  address of the policy server to which CFEngine will be\n  bootstrapped. If `am_policy_hub` is set to `true`, this parameter\n  defaults to the VM's IP address, but can still be set (for\n  example, if the VM has more than one network interface).\n- `repo_gpg_key_url` (string, default\n  `\"https://cfengine.com/pub/gpg.key\"`) contains the URL to obtain the\n  GPG key used to verify the packages obtained from the repository.\n- `run_file` (string, default `nil`) can be used to specify a file\n  inside the Vagrant project directory that will be copied to the VM\n  and executed once using `cf-agent`. This parameter is mandatory if\n  `mode` is set to `:single_run`, but can also be specified when\n  `mode` is set to `:bootstrap` - in this case the file will be\n  executed after the host has been bootstrapped.\n- `upload_path` (string, default `\"/tmp/vagrant-cfengine-file\"`)\n  specifies the file to which `run_file` (if specified) will be copied\n  on the VM before being executed.\n- `yum_repo_file` (string, default\n  `\"/etc/yum.repos.d/cfengine-community.repo\"`) specifies the file in\n  which the CFEngine repository information will be stored in RedHat\n  systems.\n- `yum_repo_url` (string, default `\"https://cfengine.com/pub/yum/\"`)\n  specifies the URL of the repository to use for `.rpm` packages.\n- `package_name` (string, default `\"cfengine-community\"`) specifies\n  the name of the package used to install CFEngine.\n"
  },
  {
    "path": "website/content/docs/provisioning/chef_apply.mdx",
    "content": "---\nlayout: docs\npage_title: Chef Apply - Provisioning\ndescription: |-\n  The Vagrant Chef Apply provisioner allows you to provision the guest using\n  Chef with chef-apply.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Chef Apply Provisioner\n\n**Provisioner name: `chef_apply`**\n\nThe Vagrant Chef Apply provisioner allows you to provision the guest using\n[Chef](https://www.getchef.com/), specifically with\n[Chef Apply](https://docs.getchef.com/ctl_chef_apply.html).\n\nChef Apply is ideal for people who are already experienced with Chef and the\nChef ecosystem. Specifically, this documentation page does not cover how use\nChef or how to write Chef recipes.\n\n~> **Warning:** If you are not familiar with Chef and Vagrant already,\nwe recommend starting with the [shell provisioner](/vagrant/docs/provisioning/shell).\n\n## Options\n\nThis section lists the complete set of available options for the Chef Apply\nprovisioner. More detailed examples of how to use the provisioner are\navailable below this section.\n\n- `recipe` (string) - The raw recipe contents to execute using Chef Apply on\n  the guest.\n\n- `log_level` (string) - The log level to use while executing `chef-apply`. The\n  default value is \"info\".\n\n- `upload_path` (string) - **Advanced!** The location on the guest where the\n  generated recipe file should be stored. For most use cases, it is unlikely you\n  will need to customize this value. The default value is\n  `/tmp/vagrant-chef-apply-#` where `#` is a unique counter generated by\n  Vagrant to prevent collisions.\n\nIn addition to all the options listed above, the Chef Apply provisioner supports\nthe [common options for all Chef provisioners](/vagrant/docs/provisioning/chef_common).\n\n## Specifying a Recipe\n\nThe easiest way to get started with the Chef Apply provisioner is to just\nspecify an inline\n[Chef recipe](https://docs.chef.io/recipes.html). For\nexample:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_apply\" do |chef|\n    chef.recipe = \"package[apache2]\"\n  end\nend\n```\n\nThis causes Vagrant to run Chef Apply with the given recipe contents. If you are\nfamiliar with Chef, you know this will install the apache2 package from the\nsystem package provider.\n\nSince single-line Chef recipes are rare, you can also specify the recipe using a\n\"heredoc\":\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_apply\" do |chef|\n    chef.recipe = <<-RECIPE\n      package \"apache2\"\n\n      template \"/etc/apache2/my.config\" do\n        # ...\n      end\n    RECIPE\n  end\nend\n```\n\nFinally, if you would prefer to store the recipe as plain-text, you can set the\nrecipe to the contents of a file:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_apply\" do |chef|\n    chef.recipe = File.read(\"/path/to/my/recipe.rb\")\n  end\nend\n```\n\n## Roles\n\nThe Vagrant Chef Apply provisioner does not support roles. Please use a\ndifferent Vagrant Chef provisioner if you need support for roles.\n\n## Data Bags\n\nThe Vagrant Chef Apply provisioner does not support data_bags. Please use a\ndifferent Vagrant Chef provisioner if you need support for data_bags.\n"
  },
  {
    "path": "website/content/docs/provisioning/chef_client.mdx",
    "content": "---\nlayout: docs\npage_title: Chef Client - Provisioning\ndescription: |-\n  The Vagrant Chef Client provisioner allows you to provision the guest using\n  Chef, specifically by connecting to an existing Chef Server and registering\n  the Vagrant machine as a node within your infrastructure.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Chef Client Provisioner\n\n**Provisioner name: `chef_client`**\n\nThe Vagrant Chef Client provisioner allows you to provision the guest using\n[Chef](https://www.chef.io/chef/), specifically by connecting\nto an existing Chef Server and registering the Vagrant machine as a\nnode within your infrastructure.\n\nIf you are just learning Chef for the first time, you probably want\nto start with the [Chef Solo](/vagrant/docs/provisioning/chef_solo)\nprovisioner.\n\n~> **Warning:** If you are not familiar with Chef and Vagrant already,\nit is recommended to start with the [shell provisioner](/vagrant/docs/provisioning/shell).\n\n## Authenticating\n\nThe minimum required to use provision using Chef Client is to provide\na URL to the Chef Server as well as the path to the validation key so\nthat the node can register with the Chef Server:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_client\" do |chef|\n    chef.chef_server_url = \"http://example.com\"\n    chef.validation_key_path = \"validation.pem\"\n  end\nend\n```\n\nThe node will register with the Chef Server specified, download the\nproper run list for that node, and provision.\n\n## Specifying a Run List\n\nNormally, the Chef Server is responsible for specifying the run list\nfor the node. However, you can override what the Chef Server sends\ndown by manually specifying a run list:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_client\" do |chef|\n    # Add a recipe\n    chef.add_recipe \"apache\"\n\n    # Or maybe a role\n    chef.add_role \"web\"\n  end\nend\n```\n\nRemember, this will _override_ the run list specified on the Chef\nserver itself.\n\n## Environments\n\nYou can specify the [environment](https://docs.chef.io/environments.html)\nfor the node to come up in using the `environment` configuration option:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_client\" do |chef|\n    # ...\n\n    chef.environment = \"development\"\n  end\nend\n```\n\n## Other Configuration Options\n\nThere are a few more configuration options available. These generally do not\nneed to be modified but are available if your Chef Server requires customization\nof these variables.\n\n- `client_key_path`\n- `node_name`\n- `validation_client_name`\n\nIn addition to all the options listed above, the Chef Client provisioner supports\nthe [common options for all Chef provisioners](/vagrant/docs/provisioning/chef_common).\n\n## Cleanup\n\nWhen you provision your Vagrant virtual machine with Chef Server, it creates a\nnew Chef \"node\" entry and Chef \"client\" entry on the Chef Server, using the\nhostname of the machine. After you tear down your guest machine, Vagrant can be\nconfigured to do it automatically with the following settings:\n\n```ruby\nchef.delete_node = true\nchef.delete_client = true\n```\n\nIf you do not specify it or set it to `false`, you must explicitly delete these\nentries from the Chef Server before you provision a new one with Chef Server.\nFor example, using Chef's built-in `knife` tool:\n\n```shell-session\n$ knife node delete precise64\n$ knife client delete precise64\n```\n\nIf you fail to do so, you will get the following error when Vagrant\ntries to provision the machine with Chef Client:\n\n```text\nHTTP Request Returned 409 Conflict: Client already exists.\n```\n"
  },
  {
    "path": "website/content/docs/provisioning/chef_common.mdx",
    "content": "---\nlayout: docs\npage_title: Common Chef Options - Provisioning\ndescription: |-\n  The following options are available to all Vagrant Chef provisioners. Many of\n  these options are for advanced users only and should not be used unless you\n  understand their purpose.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Shared Chef Options\n\n## All Chef Provisioners\n\nThe following options are available to all Vagrant Chef provisioners. Many of\nthese options are for advanced users only and should not be used unless you\nunderstand their purpose.\n\n- `binary_path` (string) - The path to Chef's `bin/` directory on the guest\n  machine.\n\n- `binary_env` (string) - Arbitrary environment variables to set before running\n  the Chef provisioner command. This should be of the format `KEY=value` as a\n  string.\n\n- `install` (boolean, string) - Install Chef on the system if it does not exist.\n  The default value is \"true\", which will use the official Omnibus installer\n  from Chef. This is a trinary attribute (it can have three values):\n\n  - `true` (boolean) - install Chef\n  - `false` (boolean) - do not install Chef\n  - `\"force\"` (string) - install Chef, even if it is already installed at the\n    proper version on the guest\n\n- `installer_download_path` (string) - The path where the Chef installer will be\n  downloaded to. This option is only honored if the `install` attribute is\n  `true` or `\"force\"`. The default value is to use the path provided by Chef's\n  Omnibus installer, which varies between releases. This value has no effect on\n  Windows because Chef's omnibus installer lacks the option on Windows.\n\n- `log_level` (string) - The Chef log level. See the Chef docs for acceptable\n  values.\n\n- `product` (string) - The name of the Chef product to install. The default\n  value is \"chef\", which corresponds to the Chef Client. You can also specify\n  \"chefdk\", which will install the Chef Development Kit. At the time of this\n  writing, the ChefDK is only available through the \"current\" channel, so you\n  will need to update that value as well.\n\n- `channel` (string) - The release channel from which to pull the Chef Client\n  or the Chef Development Kit. The default value is `\"stable\"` which will pull\n  the latest stable version of the Chef Client. For newer versions, or if you\n  wish to install the Chef Development Kit, you may need to change the channel\n  to \"current\". Because Chef Software floats the versions that are contained in\n  the channel, they may change and Vagrant is unable to detect this.\n\n- `version` (string) - The version of Chef to install on the guest. If Chef is\n  already installed on the system, the installed version is compared with the\n  requested version. If they match, no action is taken. If they do not match,\n  the value specified in this attribute will be installed in favor of the\n  existing version (a message will be displayed).\n  You can also specify \"latest\" (default), which will install the latest\n  version of Chef on the system. In this case, Chef will use whatever\n  version is on the system. To force the newest version of Chef to be\n  installed on every provision, set the [`install`](#install) option to \"force\".\n\n- `omnibus_url` (string) - Location of Omnibus installation scripts.\n  This URL specifies the location of install.sh/install.ps1 for\n  Linux/Unix and Windows respectively.\n  It defaults to https://omnitruck.chef.io. The full URL is in this case:\n\n  - Linux/Unix: https://omnitruck.chef.io/install.sh\n  - Windows: https://omnitruck.chef.io/install.ps1\n\n  If you want to have https://example.com/install.sh as Omnibus script\n  for your Linux/Unix installations, you should set this option to\n  https://example.com\n\n## Runner Chef Provisioners\n\nThe following options are available to any of the Chef \"runner\" provisioners\nwhich include [Chef Solo](/vagrant/docs/provisioning/chef_solo), [Chef Zero](/vagrant/docs/provisioning/chef_zero), and [Chef Client](/vagrant/docs/provisioning/chef_client).\n\n- `arguments` (string) - A list of additional arguments to pass on the\n  command-line to Chef. Since these are passed in a shell-like environment,\n  be sure to properly quote and escape characters if necessary. By default,\n  no additional arguments are sent.\n\n- `attempts` (int) - The number of times Chef will be run if an error occurs.\n  This defaults to 1. This can be increased to a higher number if your Chef\n  runs take multiple runs to reach convergence.\n\n- `custom_config_path` (string) - A path to a custom Chef configuration local\n  on your machine that will be used as the Chef configuration. This Chef\n  configuration will be loaded _after_ the Chef configuration that Vagrant\n  generates, allowing you to override anything that Vagrant does. This is\n  also a great way to use new Chef features that may not be supported fully\n  by Vagrant's abstractions yet.\n\n- `encrypted_data_bag_secret_key_path` (string) - The path to the secret key\n  file to decrypt encrypted data bags. By default, this is not set.\n\n- `environment` (string) - The environment you want the Chef run to be\n  a part of.\n\n- `formatter` (string) - The formatter to use for output from Chef.\n\n- `http_proxy`, `http_proxy_user`, `http_proxy_pass`, `no_proxy` (string) - Settings\n  to configure HTTP and HTTPS proxies to use from Chef. These settings are\n  also available with `http` replaced with `https` to configure HTTPS proxies.\n\n- `json` (hash) - Custom node attributes to pass into the Chef run.\n\n- `log_level` (string) - The log level for Chef output. This defaults to\n  \"info\".\n\n- `node_name` (string) - The node name for the Chef Client. By default this\n  will be your hostname.\n\n- `provisioning_path` (string) - The path on the remote machine where Vagrant\n  will store all necessary files for provisioning such as cookbooks, configurations,\n  etc. This path must be world writable. By default this is\n  `/tmp/vagrant-chef-#` where \"#\" is replaced by a unique counter.\n\n- `run_list` (array) - The run list that will be executed on the node.\n\n- `file_cache_path` and `file_backup_path` (string) - Paths on the remote\n  machine where files will be cached and backed up. It is useful sometimes\n  to configure this to a synced folder address so that this can be shared\n  across many Vagrant runs.\n\n- `verbose_logging` (boolean) - Whether or not to enable the Chef\n  `verbose_logging` option. By default this is false.\n\n- `enable_reporting` (boolean) - Whether or not to enable the Chef\n  `enable_reporting` option. By default this is true.\n"
  },
  {
    "path": "website/content/docs/provisioning/chef_solo.mdx",
    "content": "---\nlayout: docs\npage_title: Chef Solo - Provisioning\ndescription: |-\n  The Vagrant Chef Solo provisioner allows you to provision the guest using\n  Chef, specifically with chef-solo.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Chef Solo Provisioner\n\n**Provisioner name: `chef_solo`**\n\nThe Vagrant Chef Solo provisioner allows you to provision the guest using\n[Chef](https://www.chef.io/chef/), specifically with\n[Chef Solo](https://docs.chef.io/chef_solo.html).\n\nChef Solo is ideal for people who are already experienced with Chef,\nalready have Chef cookbooks, or are looking to learn Chef. Specifically,\nthis documentation page will not go into how to use Chef or how to write\nChef cookbooks, since Chef is a complete system that is beyond the scope\nof a single page of documentation.\n\n~> **Warning:** If you are not familiar with Chef and Vagrant already,\nit is recommended to start with the [shell provisioner](/vagrant/docs/provisioning/shell).\nHowever, if you are comfortable with Vagrant already, Vagrant\nis the best way to learn Chef.\n\n## Options\n\nThis section lists the complete set of available options for the Chef Solo\nprovisioner. More detailed examples of how to use the provisioner are\navailable below this section.\n\n- `cookbooks_path` (string or array) - A list of paths to where cookbooks\n  are stored. By default this is \"cookbooks\", expecting a cookbooks folder\n  relative to the Vagrantfile location.\n\n- `data_bags_path` (string or array) - A path where data bags are stored. By\n  default, no data bag path is set. Chef 12 or higher is required to use the\n  array option. Chef 11 and lower only accept a string value.\n\n- `environments_path` (string) - A path where environment definitions are\n  located. By default, no environments folder is set.\n\n- `nodes_path` (string or array) - A list of paths where node objects (in JSON format) are stored. By default, no\n  nodes path is set.\n\n- `environment` (string) - The environment you want the Chef run to be\n  a part of. This requires Chef 11.6.0 or later, and that `environments_path`\n  is set.\n\n- `recipe_url` (string) - URL to an archive of cookbooks that Chef will download\n  and use.\n\n- `roles_path` (string or array) - A list of paths where roles are defined.\n  By default this is empty. Multiple role directories are only supported by\n  Chef 11.8.0 and later.\n\n- `synced_folder_type` (string) - The type of synced folders to use when\n  sharing the data required for the provisioner to work properly. By default\n  this will use the default synced folder type. For example, you can set this\n  to \"nfs\" to use NFS synced folders.\n\nIn addition to all the options listed above, the Chef Solo provisioner supports\nthe [common options for all Chef provisioners](/vagrant/docs/provisioning/chef_common).\n\n## Specifying a Run List\n\nThe easiest way to get started with the Chef Solo provisioner is to just\nspecify a [run list](https://docs.chef.io/nodes.html#about-run-lists). This looks like:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_solo\" do |chef|\n    chef.add_recipe \"apache\"\n  end\nend\n```\n\nThis causes Vagrant to run Chef Solo with the \"apache\" cookbook. The cookbooks\nby default are looked for in the \"cookbooks\" directory relative to your\nproject root. The directory structure ends up looking like this:\n\n```shell-session\n$ tree\n.\n|-- Vagrantfile\n|-- cookbooks\n|   |-- apache\n|       |-- recipes\n|           |-- default.rb\n```\n\nThe order of the calls to `add_recipe` will specify the order of the run list.\nEarlier recipes added with `add_recipe` are run before later recipes added.\n\n## Custom Cookbooks Path\n\nInstead of using the default \"cookbooks\" directory, a custom cookbooks\npath can also be set via the `cookbooks_path` configuration directive:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_solo\" do |chef|\n    chef.cookbooks_path = \"my_cookbooks\"\n  end\nend\n```\n\nThe path can be relative or absolute. If it is relative, it is relative\nto the project root.\n\nThe configuration value can also be an array of paths:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_solo\" do |chef|\n    chef.cookbooks_path = [\"cookbooks\", \"my_cookbooks\"]\n  end\nend\n```\n\n## Roles\n\nVagrant also supports provisioning with [Chef roles](https://docs.chef.io/roles.html).\nThis is done by specifying a path to a roles folder where roles are defined\nand by adding roles to your run list:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_solo\" do |chef|\n    chef.roles_path = \"roles\"\n    chef.add_role(\"web\")\n  end\nend\n```\n\nJust like the cookbooks path, the roles path is relative to the project\nroot if a relative path is given.\n\nThe configuration value can also be an array of paths on Chef 11.8.0 and newer.\nOn older Chef versions only the first path is used.\n\n**Note:** The name of the role file must be the same as the role name.\nFor example the `web` role must be in the `roles_path` as web.json or web.rb.\nThis is required by Chef itself, and is not a limitation imposed by\nVagrant.\n\n## Data Bags\n\n[Data bags](https://docs.chef.io/data_bags.html) are also\nsupported by the Chef Solo provisioner. This is done by specifying\na path to your data bags directory:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_solo\" do |chef|\n    chef.data_bags_path = \"data_bags\"\n  end\nend\n```\n\n## Custom JSON Data\n\nAdditional configuration data for Chef attributes can be passed in\nto Chef Solo. This is done by setting the `json` property with a Ruby\nhash (dictionary-like object), which is converted to JSON and passed\nin to Chef:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_solo\" do |chef|\n    # ...\n\n    chef.json = {\n      \"apache\" => {\n        \"listen_address\" => \"0.0.0.0\"\n      }\n    }\n  end\nend\n```\n\nHashes, arrays, etc. can be used with the JSON configuration object. Basically,\nanything that can be turned cleanly into JSON works.\n\n## Custom Node Name\n\nYou can specify a custom node name by setting the `node_name` property. This\nis useful for cookbooks that may depend on this being set to some sort\nof value. Example:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_solo\" do |chef|\n    chef.node_name = \"foo\"\n  end\nend\n```\n"
  },
  {
    "path": "website/content/docs/provisioning/chef_zero.mdx",
    "content": "---\nlayout: docs\npage_title: Chef Zero - Provisioning\ndescription: |-\n  The Vagrant Chef Zero provisioner allows you to provision the guest using\n  Chef, specifically with chef-zero.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Chef Zero Provisioner\n\n**Provisioner name: `chef_zero`**\n\nThe Vagrant Chef Zero provisioner allows you to provision the guest using\n[Chef](https://www.getchef.com/chef/), specifically with\n[Chef Zero/local mode](https://docs.getchef.com/ctl_chef_client.html#run-in-local-mode).\n\nThis new provisioner is a middle ground between running a full blown\nChef Server and using the limited [Chef Solo](/vagrant/docs/provisioning/chef_solo)\nprovisioner. It runs a local in-memory Chef Server and fakes the validation\nand client key registration.\n\n~> **Warning:** If you are not familiar with Chef and Vagrant already,\nwe recommend starting with the [shell provisioner](/vagrant/docs/provisioning/shell).\nHowever, if you are comfortable with Vagrant already, Vagrant\nis the best way to learn Chef.\n\n## Options\n\nThis section lists the complete set of available options for the Chef Zero\nprovisioner. More detailed examples of how to use the provisioner are\navailable below this section.\n\n- `cookbooks_path` (string or array) - A list of paths to where cookbooks\n  are stored. By default this is \"cookbooks\", expecting a cookbooks folder\n  relative to the Vagrantfile location.\n\n- `data_bags_path` (string or array) - A path where data bags are stored. By\n  default, no data bag path is set. Chef 12 or higher is required to use the\n  array option. Chef 11 and lower only accept a string value.\n\n- `environments_path` (string) - A path where environment definitions are\n  located. By default, no environments folder is set.\n\n- `nodes_path` (string or array) - A list of paths where node objects\n  (in JSON format) are stored. By default, no nodes path is set. This value is\n  required.\n\n- `environment` (string) - The environment you want the Chef run to be\n  a part of. This requires Chef 11.6.0 or later, and that `environments_path`\n  is set.\n\n- `roles_path` (string or array) - A list of paths where roles are defined.\n  By default this is empty. Multiple role directories are only supported by\n  Chef 11.8.0 and later.\n\n- `synced_folder_type` (string) - The type of synced folders to use when\n  sharing the data required for the provisioner to work properly. By default\n  this will use the default synced folder type. For example, you can set this\n  to \"nfs\" to use NFS synced folders.\n\nIn addition to all the options listed above, the Chef Zero provisioner supports\nthe [common options for all Chef provisioners](/vagrant/docs/provisioning/chef_common).\n\n## Usage\n\nThe Chef Zero provisioner is configured basically the same way as the Chef Solo\nprovisioner. See the [Chef Solo documentations](/vagrant/docs/provisioning/chef_solo)\nfor more information.\n\nA basic example could look like this:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"chef_zero\" do |chef|\n    # Specify the local paths where Chef data is stored\n    chef.cookbooks_path = \"cookbooks\"\n    chef.data_bags_path = \"data_bags\"\n    chef.nodes_path = \"nodes\"\n    chef.roles_path = \"roles\"\n\n    # Add a recipe\n    chef.add_recipe \"apache\"\n\n    # Or maybe a role\n    chef.add_role \"web\"\n  end\nend\n```\n"
  },
  {
    "path": "website/content/docs/provisioning/docker.mdx",
    "content": "---\nlayout: docs\npage_title: Docker - Provisioning\ndescription: |-\n  The Vagrant Docker provisioner can automatically install Docker, pull Docker\n  containers, and configure certain containers to run on boot.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Docker Provisioner\n\n**Provisioner name: `\"docker\"`**\n\nThe Vagrant Docker provisioner can automatically install\n[Docker](https://www.docker.io), pull Docker containers, and configure certain\ncontainers to run on boot.\n\nThe docker provisioner is ideal for organizations that are using\nDocker as a means to distribute things like their application or services.\nOr, if you are just getting started with Docker, the Docker provisioner\nprovides the easiest possible way to begin using Docker since the provisioner\nautomates installing Docker for you.\n\nAs with all provisioners, the Docker provisioner can be used along with\nall the other provisioners Vagrant has in order to setup your working\nenvironment the best way possible. For example, perhaps you use Puppet to\ninstall services like databases or web servers but use Docker to house\nyour application runtime. You can use the Puppet provisioner along\nwith the Docker provisioner.\n\n-> **Note:** This documentation is for the Docker\n_provisioner_. If you are looking for the Docker _provider_, visit the\n[Docker provider documentation](/vagrant/docs/providers/docker/).\n\n## Options\n\nThe docker provisioner takes various options. None are required. If\nno options are required, the Docker provisioner will only install Docker\nfor you (if it is not already installed).\n\n- `images` (array) - A list of images to pull using `docker pull`. You\n  can also use the `pull_images` function. See the example below this\n  section for more information.\n\nIn addition to the options that can be set, various functions are available\nand can be called to configure other aspects of the Docker provisioner. Most\nof these functions have examples in more detailed sections below.\n\n- `build_image` - Build an image from a Dockerfile.\n\n- `pull_images` - Pull the given images. This does not start these images.\n\n- `post_install_provisioner` - A [provisioner block](/vagrant/docs/provisioning) that runs post docker\n  installation.\n\n- `run` - Run a container and configure it to start on boot. This can\n  only be specified once.\n\n## Building Images\n\nThe provisioner can automatically build images. Images are built prior to\nany configured containers to run, so you can build an image before running it.\nBuilding an image is easy:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"hashicorp/bionic64\"\n  config.vm.provision \"docker\" do |d|\n    d.build_image \"/vagrant/app\"\n  end\nend\n```\n\nThe argument to build an image is the path to give to `docker build`. This\nmust be a path that exists within the guest machine. If you need to get data\nto the guest machine, use a synced folder.\n\nThe `build_image` function accepts options as a second parameter. Here\nare the available options:\n\n- `args` (string) - Additional arguments to pass to `docker build`. Use this\n  to pass in things like `-t \"foo\"` to tag the image.\n\n## Pulling Images\n\nThe docker provisioner can automatically pull images from the\nDocker registry for you. There are two ways to specify images to\npull. The first is as an array using `images`:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"hashicorp/bionic64\"\n  config.vm.provision \"docker\",\n    images: [\"ubuntu\"]\nend\n```\n\nThis will cause Vagrant to pull the \"ubuntu\" image from the registry\nfor you automatically.\n\nThe second way to pull images is to use the `pull_images` function.\nEach call to `pull_images` will _append_ the images to be pulled. The\n`images` variable, on the other hand, can only be used once.\n\nAdditionally, the `pull_images` function cannot be used with the\nsimple configuration method for provisioners (specifying it all in one line).\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"hashicorp/bionic64\"\n  config.vm.provision \"docker\" do |d|\n    d.pull_images \"ubuntu\"\n    d.pull_images \"vagrant\"\n  end\nend\n```\n\n## Running Containers\n\nIn addition to pulling images, the Docker provisioner can run and start\ncontainers for you. This lets you automatically start services as part of\n`vagrant up`.\n\nRunning containers can only be configured using the Ruby block syntax with\nthe `do...end` blocks. An example of running a container is shown below:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"hashicorp/bionic64\"\n  config.vm.provision \"docker\" do |d|\n    d.run \"rabbitmq\"\n  end\nend\n```\n\nThis will `docker run` a container with the \"rabbitmq\" image. Note that\nVagrant uses the first parameter (the image name by default) to override any\nsettings used in a previous `run` definition. Therefore, if you need to run\nmultiple containers from the same image then you must specify the `image`\noption (documented below) with a unique name.\n\nIn addition to the name, the `run` method accepts a set of options, all optional:\n\n- `image` (string) - The image to run. This defaults to the first argument\n  but can also be given here as an option.\n\n- `cmd` (string) - The command to start within the container. If not specified,\n  then the container's default command will be used, such as the\n  \"CMD\" command [specified in the `Dockerfile`](https://docs.docker.io/en/latest/use/builder/#cmd).\n\n- `args` (string) - Extra arguments for [`docker run`](https://docs.docker.io/en/latest/commandline/cli/#run)\n  on the command line. These are raw arguments that are passed directly to Docker.\n\n- `auto_assign_name` (boolean) - If true, the `--name` of the container will\n  be set to the first argument of the run. By default this is true. If the\n  name set contains a \"/\" or \":\" (because of the image name or version), it will be\n  replaced with \"-\". Therefore, if you do `d.run \"foo/bar:0.0.1\"`, then the name of the\n  container will be \"foo-bar-0.0.1\".\n\n- `daemonize` (boolean) - If true, the \"-d\" flag is given to `docker run` to\n  daemonize the containers. By default this is true.\n\n- `restart` (string) - The restart policy for the container. Defaults to\n  \"always\"\n\nFor example, here is how you would configure Docker to run a container\nwith the Vagrant shared directory mounted inside of it:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"hashicorp/bionic64\"\n  config.vm.provision \"docker\" do |d|\n    d.run \"ubuntu\",\n      cmd: \"bash -l\",\n      args: \"-v '/vagrant:/var/www'\"\n  end\nend\n```\n\nIn case you need to run multiple containers based off the same image, you can do\nso by providing different names and specifying the `image` parameter to it:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"hashicorp/bionic64\"\n  config.vm.provision \"docker\" do |d|\n    d.run \"db-1\", image: \"user/mysql\"\n    d.run \"db-2\", image: \"user/mysql\"\n  end\nend\n```\n\n## Other\n\nThis section documents some other things related to the Docker provisioner\nthat are generally useful to know if you are using this provisioner.\n\n### Customize `/etc/default/docker`\n\nTo customize this file, use the `post_install_provision` shell provisioner.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"hashicorp/bionic64\"\n  config.vm.provision \"docker\" do |d|\n    d.post_install_provision \"shell\", inline:\"echo export http_proxy='http://127.0.0.1:3128/' >> /etc/default/docker\"\n    d.run \"ubuntu\",\n      cmd: \"bash -l\",\n      args: \"-v '/vagrant:/var/www'\"\n  end\nend\n```\n"
  },
  {
    "path": "website/content/docs/provisioning/file.mdx",
    "content": "---\nlayout: docs\npage_title: File Uploads - Provisioning\ndescription: |-\n  The Vagrant file provisioner allows you to upload a file or directory from the\n  host machine to the guest machine.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# File Provisioner\n\n**Provisioner name: `\"file\"`**\n\nThe Vagrant file provisioner allows you to upload a file or directory from the\nhost machine to the guest machine.\n\nFile provisioning is a simple way to, for example, replicate your local\n~/.gitconfig to the vagrant user's home directory on the guest machine so\nyou will not have to run `git config --global` every time you provision a\nnew VM.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # ... other configuration\n\n  config.vm.provision \"file\", source: \"~/.gitconfig\", destination: \".gitconfig\"\nend\n```\n\nIf you want to upload a folder to your guest system, it can be accomplished by\nusing a file provisioner seen below. This will copy the your local `folder` \n(specified as the `source`) to the the `newfolder` on the guest machine\n(specified as the `destination`). Note that if you'd like the same folder name \non your guest machine, make sure that the destination path has the same name as \nthe folder on your host.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # ... other configuration\n\n  config.vm.provision \"file\", source: \"~/path/to/host/folder\", destination: \"$HOME/remote/newfolder\"\nend\n```\n\nPrior to copying `~/path/to/host/folder` to the guest machine:\n\n```text\nfolder\n├── script.sh\n├── otherfolder\n│   └── hello.sh\n├── goodbye.sh\n├── hello.sh\n└── woot.sh\n\n1 directory, 5 files\n```\n\nAfter to copying `~/path/to/host/folder` into `$HOME/remote/newfolder` to the guest machine:\n\n```text\nnewfolder\n├── script.sh\n├── otherfolder\n│   └── hello.sh\n├── goodbye.sh\n├── hello.sh\n└── woot.sh\n\n1 directory, 5 files\n```\n\nNote that, unlike with synced folders, files or directories that are uploaded\nwill not be kept in sync. Continuing with the example above, if you make\nfurther changes to your local ~/.gitconfig, they will not be immediately\nreflected in the copy you uploaded to the guest machine.\n\nThe file uploads by the file provisioner are done as the\n_SSH or PowerShell user_. This is important since these users generally\ndo not have elevated privileges on their own. If you want to upload files to\nlocations that require elevated privileges, we recommend uploading them\nto temporary locations and then using the\n[shell provisioner](/vagrant/docs/provisioning/shell)\nto move them into place.\n\n## Options\n\nThe file provisioner takes only two options, both of which are required:\n\n- `source` (string) - Is the local path of the file or directory to be\n  uploaded.\n\n- `destination` (string) - Is the remote path on the guest machine where\n  the source will be uploaded to. The file/folder is uploaded as the SSH user\n  over SCP, so this location must be writable to that user. The SSH user can be\n  determined by running `vagrant ssh-config`, and defaults to \"vagrant\". Both\n  forward and backward slash work for Windows guest. Variables like `$HOME` are\n  expanded by Vagrant, not by guest.\n\n## Caveats\n\nWhile the file provisioner does support trailing slashes or \"globing\", this can\nlead to some confusing results due to the underlying tool used to copy files and\nfolders between the host and guests. For example, if you have a source and\ndestination with a trailing slash defined below:\n\n```ruby\nconfig.vm.provision \"file\", source: \"~/pathfolder\", destination: \"/remote/newlocation/\"\n```\n\nYou are telling vagrant to upload `~/pathfolder` under the remote dir `/remote/newlocation`,\nwhich will look like:\n\n```text\nnewlocation\n├── pathfolder\n│   └── file.sh\n\n1 directory, 2 files\n```\n\nThis behavior can also be achieved by defining your file provisioner below:\n\n```ruby\nconfig.vm.provision \"file\", source: \"~/pathfolder\", destination: \"/remote/newlocation/pathfolder\"\n```\n\nAnother example is using globing on the host machine to grab all files within a\nfolder, but not the top level folder itself:\n\n```ruby\nconfig.vm.provision \"file\", source: \"~/otherfolder/.\", destination: \"/remote/otherlocation\"\n```\n\nThe file provisioner is defined to include all files under `~/otherfolder`\nto the new location `/remote/otherlocation`. This idea can be achieved by simply\nhaving your destination folder differ from the source folder:\n\n```ruby\nconfig.vm.provision \"file\", source: \"/otherfolder\", destination: \"/remote/otherlocation\"\n```\n"
  },
  {
    "path": "website/content/docs/provisioning/index.mdx",
    "content": "---\nlayout: docs\npage_title: Provisioning\ndescription: >-\n  Provisioners in Vagrant allow you to automatically install software, alter\n  configurations, and more on the machine as part of the `vagrant up` process.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Provisioning\n\nProvisioners in Vagrant allow you to automatically install software, alter configurations,\nand more on the machine as part of the `vagrant up` process.\n\nThis is useful since [boxes](/vagrant/docs/boxes) typically are not\nbuilt _perfectly_ for your use case. Of course, if you want to just use\n`vagrant ssh` and install the software by hand, that works. But by using\nthe provisioning systems built-in to Vagrant, it automates the process so\nthat it is repeatable. Most importantly, it requires no human interaction,\nso you can `vagrant destroy` and `vagrant up` and have a fully ready-to-go\nwork environment with a single command. Powerful.\n\nVagrant gives you multiple options for provisioning the machine, from\nsimple shell scripts to more complex, industry-standard configuration\nmanagement systems.\n\nIf you've never used a configuration management system before, it is\nrecommended you start with basic [shell scripts](/vagrant/docs/provisioning/shell)\nfor provisioning.\n\nYou can find the full list of built-in provisioners and usage of these\nprovisioners in the navigational area to the left.\n\n## When Provisioning Happens\n\nProvisioning happens at certain points during the lifetime of your\nVagrant environment:\n\n- On the first `vagrant up` that creates the environment, provisioning is run.\n  If the environment was already created and the up is just resuming a machine\n  or booting it up, they will not run unless the `--provision` flag is explicitly\n  provided.\n\n- When `vagrant provision` is used on a running environment.\n\n- When `vagrant reload --provision` is called. The `--provision` flag must\n  be present to force provisioning.\n\nYou can also bring up your environment and explicitly _not_ run provisioners\nby specifying `--no-provision`.\n"
  },
  {
    "path": "website/content/docs/provisioning/podman.mdx",
    "content": "---\nlayout: docs\npage_title: Podman - Provisioning\ndescription: >-\n  The Vagrant Podman provisioner can automatically install Podman, and run it as\n  a drop in replacement for Docker.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Podman Provisioner\n\n**Provisioner name: `\"podman\"`**\n\nThe Vagrant Podman provisioner can automatically install\n[Podman](https://www.podman.io) to be used as a drop in Docker replacement.\nThis includes the ability to pull Docker containers, and configure certain\ncontainers to run on boot. The podman provisioner is ideal for organizations\nthat are using Podman as a means to manage and run their OCI images.\n\nAs with all provisioners, the Podman provisioner can be used along with\nall the other provisioners Vagrant has in order to setup your working\nenvironment the best way possible. For example, perhaps you use Puppet to\ninstall services like databases or web servers but use Podman to house\nyour application runtime. You can use the Puppet provisioner along\nwith the Podman provisioner.\n\nNote, that in order to install Podman on RHEL systems, the system must\nbe registered.\n\n## Options\n\nThe podman provisioner takes various options. None are required. If\nno options are provided, the Podman provisioner will only install Podman\nfor you (if it is not already installed).\n\n- `images` (array) - A list of images to pull using `podman pull`. You\n  can also use the `pull_images` function. See the example below this\n  section for more information.\n\nIn addition to the options that can be set, various functions are available\nand can be called to configure other aspects of the Podman provisioner. Most\nof these functions have examples in more detailed sections below.\n\n- `build_image` - Build an image from a Dockerfile.\n\n- `kubic` - Boolean, install Podman from [Kubic project](https://build.opensuse.org/project/show/devel:kubic:libcontainers:stable)\n\n- `pull_images` - Pull the given images. This does not start these images.\n\n- `post_install_provisioner` - A [provisioner block](/vagrant/docs/provisioning) that runs post podman\n  installation.\n\n- `run` - Run a container and configure it to start on boot. This can\n  only be specified once.\n\n## Building Images\n\nThe provisioner can automatically build images. Images are built prior to\nany configured containers to run, so you can build an image before running it.\nBuilding an image is easy:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"centos/7\"\n  config.vm.provision \"podman\" do |d|\n    d.build_image \"/vagrant/app\"\n  end\nend\n```\n\nThe argument to build an image is the path to give to `podman build`. This\nmust be a path that exists within the guest machine. If you need to get data\nto the guest machine, use a synced folder.\n\nThe `build_image` function accepts options as a second parameter. Here\nare the available options:\n\n- `args` (string) - Additional arguments to pass to `podman build`. Use this\n  to pass in things like `-t \"foo\"` to tag the image.\n\n## Pulling Images\n\nThe podman provisioner can automatically pull images from the\nDocker registry for you. There are two ways to specify images to\npull. The first is as an array using `images`:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"centos/7\"\n  config.vm.provision \"podman\",\n    images: [\"ubuntu\"]\nend\n```\n\nThis will cause Vagrant to pull the \"ubuntu\" image from the registry\nfor you automatically.\n\nThe second way to pull images is to use the `pull_images` function.\nEach call to `pull_images` will _append_ the images to be pulled. The\n`images` variable, on the other hand, can only be used once.\n\nAdditionally, the `pull_images` function cannot be used with the\nsimple configuration method for provisioners (specifying it all in one line).\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"centos/7\"\n  config.vm.provision \"podman\" do |d|\n    d.pull_images \"ubuntu\"\n    d.pull_images \"vagrant\"\n  end\nend\n```\n\n## Running Containers\n\nIn addition to pulling images, the Podman provisioner can run and start\ncontainers for you. This lets you automatically start services as part of\n`vagrant up`.\n\nRunning containers can only be configured using the Ruby block syntax with\nthe `do...end` blocks. An example of running a container is shown below:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"centos/7\"\n  config.vm.provision \"podman\" do |d|\n    d.run \"rabbitmq\"\n  end\nend\n```\n\nThis will `podman run` a container with the \"rabbitmq\" image. Note that\nVagrant uses the first parameter (the image name by default) to override any\nsettings used in a previous `run` definition. Therefore, if you need to run\nmultiple containers from the same image then you must specify the `image`\noption (documented below) with a unique name.\n\nIn addition to the name, the `run` method accepts a set of options, all optional:\n\n- `image` (string) - The image to run. This defaults to the first argument\n  but can also be given here as an option.\n\n- `cmd` (string) - The command to start within the container. If not specified,\n  then the container's default command will be used, such as the\n  \"CMD\" command [specified in the `Dockerfile`](https://docs.docker.io/en/latest/use/builder/#cmd).\n\n- `args` (string) - Extra arguments for `podman run` (same as the extra arguments that can be specified for [`docker run`](https://docs.docker.io/en/latest/commandline/cli/#run))\n  on the command line. These are raw arguments that are passed directly to Podman.\n\n- `auto_assign_name` (boolean) - If true, the `--name` of the container will\n  be set to the first argument of the run. By default this is true. If the\n  name set contains a \"/\" (because of the image name), it will be replaced\n  with \"-\". Therefore, if you do `d.run \"foo/bar\"`, then the name of the\n  container will be \"foo-bar\".\n\n- `daemonize` (boolean) - If true, the \"-d\" flag is given to `podman run` to\n  daemonize the containers. By default this is true.\n\n- `restart` (string) - The restart policy for the container. Defaults to\n  \"always\"\n\nFor example, here is how you would configure Podman to run a container\nwith the Vagrant shared directory mounted inside of it:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"centos/7\"\n  config.vm.provision \"podman\" do |d|\n    d.run \"ubuntu\",\n      cmd: \"bash -l\",\n      args: \"-v '/vagrant:/var/www'\"\n  end\nend\n```\n\nIn case you need to run multiple containers based off the same image, you can do\nso by providing different names and specifying the `image` parameter to it:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"centos/7\"\n  config.vm.provision \"podman\" do |d|\n    d.run \"db-1\", image: \"user/mysql\"\n    d.run \"db-2\", image: \"user/mysql\"\n  end\nend\n```\n"
  },
  {
    "path": "website/content/docs/provisioning/puppet_agent.mdx",
    "content": "---\nlayout: docs\npage_title: Puppet Agent - Provisioning\ndescription: |-\n  The Vagrant Puppet agent provisioner allows you to provision the guest using\n  Puppet, specifically by calling \"puppet agent\", connecting to a Puppet master,\n  and retrieving the set of modules and manifests from there.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Puppet Agent Provisioner\n\n**Provisioner name: `puppet_server`**\n\nThe Vagrant Puppet agent provisioner allows you to provision the guest using\n[Puppet](https://www.puppetlabs.com/puppet), specifically by\ncalling `puppet agent`, connecting to a Puppet master, and retrieving\nthe set of modules and manifests from there.\n\n~> **Warning:** If you are not familiar with Puppet and Vagrant already,\nit is recommended to start with the [shell provisioner](/vagrant/docs/provisioning/shell).\nHowever, if you are comfortable with Vagrant already, Vagrant\nis the best way to learn Puppet.\n\n## Options\n\nThe `puppet_server` provisioner takes various options. None are strictly\nrequired. They are listed below:\n\n- `binary_path` (string) - Path on the guest to Puppet's `bin/` directory.\n\n- `client_cert_path` (string) - Path to the client certificate for the\n  node on your disk. This defaults to nothing, in which case a client\n  cert will not be uploaded.\n\n- `client_private_key_path` (string) - Path to the client private key for\n  the node on your disk. This defaults to nothing, in which case a client\n  private key will not be uploaded.\n\n- `facter` (hash) - Additional Facter facts to make available to the\n  Puppet run.\n\n- `options` (string or array) - Additional command line options to pass\n  to `puppet agent` when Puppet is ran.\n\n- `puppet_node` (string) - The name of the node. If this is not set,\n  this will attempt to use a hostname if set via `config.vm.hostname`.\n  Otherwise, the box name will be used.\n\n- `puppet_server` (string) - Hostname of the Puppet server. By default\n  \"puppet\" will be used.\n\n## Specifying the Puppet Master\n\nThe quickest way to get started with the Puppet agent provisioner is to just\nspecify the location of the Puppet master:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"puppet_server\" do |puppet|\n    puppet.puppet_server = \"puppet.example.com\"\n  end\nend\n```\n\nBy default, Vagrant will look for the host named \"puppet\" on the\nlocal domain of the guest machine.\n\n## Configuring the Node Name\n\nThe node name that the agent registers as can be customized. Remember\nthis is important because Puppet uses the node name as part of the process\nto compile the catalog the node will run.\n\nThe node name defaults to the hostname of the guest machine, but can\nbe customized using the Vagrantfile:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"puppet_server\" do |puppet|\n    puppet.puppet_node = \"node.example.com\"\n  end\nend\n```\n\n## Additional Options\n\nPuppet supports a lot of command-line flags. Basically any setting can\nbe overridden on the command line. To give you the most power and flexibility\npossible with Puppet, Vagrant allows you to specify custom command line\nflags to use:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"puppet_server\" do |puppet|\n    puppet.options = \"--verbose --debug\"\n  end\nend\n```\n"
  },
  {
    "path": "website/content/docs/provisioning/puppet_apply.mdx",
    "content": "---\nlayout: docs\npage_title: Puppet Apply - Provisioning\ndescription: |-\n  The Vagrant Puppet provisioner allows you to provision the guest using\n  Puppet, specifically by calling \"puppet apply\", without a Puppet Master.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Puppet Apply Provisioner\n\n**Provisioner name: `puppet`**\n\nThe Vagrant Puppet provisioner allows you to provision the guest using\n[Puppet](https://www.puppetlabs.com/puppet), specifically by\ncalling `puppet apply`, without a Puppet Master.\n\n~> **Warning:** If you are not familiar with Puppet and Vagrant already,\nit is recommended to start with the [shell provisioner](/vagrant/docs/provisioning/shell).\nHowever, if you are comfortable with Vagrant already, Vagrant\nis the best way to learn Puppet.\n\n## Options\n\nThis section lists the complete set of available options for the Puppet\nprovisioner. More detailed examples of how to use the provisioner are\navailable below this section.\n\n- `binary_path` (string) - Path on the guest to Puppet's `bin/` directory.\n\n- `facter` (hash) - A hash of data to set as available facter variables\n  within the Puppet run.\n\n- `hiera_config_path` (string) - Path to the Hiera configuration on\n  the host. Read the section below on how to use Hiera with Vagrant.\n\n- `manifest_file` (string) - The name of the manifest file that will serve\n  as the entrypoint for the Puppet run. This manifest file is expected to\n  exist in the configured `manifests_path` (see below). This defaults\n  to \"default.pp\"\n\n- `manifests_path` (string) - The path to the directory which contains the\n  manifest files. This defaults to \"manifests\"\n\n- `module_path` (string or array of strings) - Path or paths, on the host, to the directory which\n  contains Puppet modules, if any.\n\n- `environment` (string) - Name of the Puppet environment.\n\n- `environment_path` (string) - Path to the directory that contains environment\n  files on the host disk.\n\n- `environment_variables` (hash) - A hash of string key/value pairs to be set as\n  environment variables before the puppet apply run.\n\n- `options` (array of strings) - Additionally options to pass to the\n  Puppet executable when running Puppet.\n\n- `synced_folder_type` (string) - The type of synced folders to use when\n  sharing the data required for the provisioner to work properly. By default\n  this will use the default synced folder type. For example, you can set this\n  to \"nfs\" to use NFS synced folders.\n\n- `synced_folder_args` (array) - Arguments that are passed to the folder sync.\n  For example ['-a', '--delete', '--exclude=fixtures'] for the rsync sync\n  command.\n\n- `temp_dir` (string) - The directory where all the data associated with\n  the Puppet run (manifest files, modules, etc.) will be stored on the\n  guest machine.\n\n- `working_directory` (string) - Path in the guest that will be the working\n  directory when Puppet is executed. This is usually only set because relative\n  paths are used in the Hiera configuration.\n\n~> If only `environment` and `environment_path` are specified, it will parse\nand use the manifest specified in the `environment.conf` file. If\n`manifests_path` and `manifest_file` is specified along with the environment\noptions, the manifest from the environment will be overridden by the specified `manifest_file`. If `manifests_path` and `manifest_file` are specified without\nenvironments, the old non-environment mode will be used (which will fail on\nPuppet 4+).\n\n## Bare Minimum\n\nThe quickest way to get started with the Puppet provisioner is to just\nenable it:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"puppet\"\nend\n```\n\n~> `puppet` needs to be installed in the guest VM.\n\nBy default, Vagrant will configure Puppet to look for manifests in the\n\"manifests\" folder relative to the project root, and will use the\n\"default.pp\" manifest as an entry-point. This means, if your directory\ntree looks like the one below, you can get started with Puppet with\njust that one line in your Vagrantfile.\n\n```shell-session\n$ tree\n.\n|-- Vagrantfile\n|-- manifests\n|   |-- default.pp\n```\n\n## Custom Manifest Settings\n\nOf course, you are able to put and name your manifests whatever you would\nlike. You can override both the directory where Puppet looks for\nmanifests with `manifests_path`, and the manifest file used as the\nentry-point with `manifest_file`:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"puppet\" do |puppet|\n    puppet.manifests_path = \"my_manifests\"\n    puppet.manifest_file = \"default.pp\"\n  end\nend\n```\n\nThe path can be relative or absolute. If it is relative, it is relative\nto the project root.\n\nYou can also specify a manifests path that is on the remote machine\nalready, perhaps put in place by a shell provisioner. In this case, Vagrant\nwill not attempt to upload the manifests directory. To specify a remote\nmanifests path, use the following syntax:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"puppet\" do |puppet|\n    puppet.manifests_path = [\"vm\", \"/path/to/manifests\"]\n    puppet.manifest_file = \"default.pp\"\n  end\nend\n```\n\nIt is a somewhat odd syntax, but the tuple (two-element array) says\nthat the path is located in the \"vm\" at \"/path/to/manifests\".\n\n## Environments\n\nIf you are using Puppet 4 or higher, you can provision using\n[Puppet Environments](https://docs.puppetlabs.com/puppet/latest/environments.html) by specifying the name of the environment and the path on the\nlocal disk to the environment files:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"puppet\" do |puppet|\n    puppet.environment_path = \"../puppet/environments\"\n    puppet.environment = \"testenv\"\n  end\nend\n```\n\nThe default manifest is the environment's `manifests` directory.\nIf the environment has an `environment.conf` the manifest path is parsed\nfrom there. Relative paths are assumed to be relative to the directory of\nthe environment. If the manifest setting in `environment.conf` use\nthe Puppet variables `$codedir` or `$environment` they are resolved to\nthe parent directory of `environment_path` and `environment` respectively.\n\n## Modules\n\nVagrant also supports provisioning with [Puppet modules](https://www.puppet.com/docs/puppet/latest/modules.html).\nThis is done by specifying a path to a modules folder where modules are located.\nThe manifest file is still used as an entry-point.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"puppet\" do |puppet|\n    puppet.module_path = \"modules\"\n  end\nend\n```\n\nJust like the manifests path, the modules path is relative to the project\nroot if a relative path is given.\n\n## Custom Facts\n\nCustom facts to be exposed by [Facter](https://www.puppet.com/docs/puppet/latest/facter.html)\ncan be specified as well:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"puppet\" do |puppet|\n    puppet.facter = {\n      \"vagrant\" => \"1\"\n    }\n  end\nend\n```\n\nNow, the `$vagrant` variable in your Puppet manifests will equal \"1\".\n\n## Configuring Hiera\n\n[Hiera](https://www.puppet.com/docs/puppet/latest/hiera.html) configuration is also supported.\n`hiera_config_path` specifies the path to the Hiera configuration file stored on\nthe host. If the `:datadir` setting in the Hiera configuration file is a\nrelative path, `working_directory` should be used to specify the directory in\nthe guest that path is relative to.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"puppet\" do |puppet|\n    puppet.hiera_config_path = \"hiera.yaml\"\n    puppet.working_directory = \"/tmp/vagrant-puppet\"\n  end\nend\n```\n\n`hiera_config_path` can be relative or absolute. If it is relative, it is\nrelative to the project root. `working_directory` is an absolute path within the\nguest.\n\n## Additional Options\n\nPuppet supports a lot of command-line flags. Basically any setting can\nbe overridden on the command line. To give you the most power and flexibility\npossible with Puppet, Vagrant allows you to specify custom command line\nflags to use:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"puppet\" do |puppet|\n    puppet.options = \"--verbose --debug\"\n  end\nend\n```\n"
  },
  {
    "path": "website/content/docs/provisioning/salt.mdx",
    "content": "---\nlayout: docs\npage_title: Salt - Provisioning\ndescription: |-\n  The Vagrant Salt provisioner allows you to provision the guest using\n  Salt states.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Salt Provisioner\n\n**Provisioner name: `salt`**\n\nThe Vagrant Salt provisioner allows you to provision the guest using\n[Salt](http://saltstack.com/) states.\n\nSalt states are [YAML](https://en.wikipedia.org/wiki/YAML) documents\nthat describes the current state a machine should be in, e.g. what\npackages should be installed, which services are running, and the\ncontents of arbitrary files.\n\n_NOTE: The Salt provisioner is builtin to Vagrant. If the `vagrant-salt`\nplugin is installed, it should be uninstalled to ensure expected behavior._\n\n## Masterless Quickstart\n\nWhat follows is a basic Vagrantfile that will get salt working\non a single minion, without a master:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  ## Choose your base box\n  config.vm.box = \"bionic64\"\n\n  ## For masterless, mount your salt file root\n  config.vm.synced_folder \"salt/roots/\", \"/srv/salt/\"\n\n  ## Use all the defaults:\n  config.vm.provision :salt do |salt|\n\n    salt.masterless = true\n    salt.minion_config = \"salt/minion\"\n    salt.run_highstate = true\n\n  end\nend\n```\n\nThis sets up a shared folder for the salt root, and copies\nthe minion file over, then runs `state.highstate` on the\nmachine. Your minion file must contain the line\n`file_client: local` in order to work in a\nmasterless setup.\n\n## Install Options\n\nThe Salt provisioner uses the [Salt bootstrap script](https://github.com/saltstack/salt-bootstrap)\nfor installing Salt on your guest. These options build up the arguments used to\nfor the bootstrap script.\n\n- `install_master` (boolean) - Should vagrant install the salt-master\n  on this machine. Not supported on Windows guest machines.\n\n- `no_minion` (boolean) - Do not install the minion, default `false`. Not supported on Windows guest machines.\n\n- `install_syndic` (boolean) - Install the salt-syndic, default\n  `false`. Not supported on Windows guest machines.\n\n- `install_type` (stable | git | daily | testing) - Whether to install from a\n  distribution's stable package manager, git tree-ish, daily ppa, or testing repository. Not supported on Windows guest machines.\n\n- `install_args` (string, default: \"develop\") - When performing a git install, you can specify a branch, tag, or any treeish. Not supported on Windows.\n\n- `always_install` (boolean) - Installs salt binaries even\n  if they are already detected, default `false`\n\n- `bootstrap_script` (string) - Path to your customized salt-bootstrap.sh script (or bootstrap-salt.ps1 for Windows).\n\n- `bootstrap_options` (string) - Additional command-line options to\n  pass to the bootstrap script.\n\n- `version` (string) - Version of minion to be installed. Defaults to latest version. When specifying `version` you must also specify a `install_type`.\n\n- `python_version` (string, default: \"3\") - Major Python version of minion to be installed. Only valid for minion versions >= 2017.7.0. Only supported on Windows guest machines.\n\n## Minion Options\n\nThese only make sense when `no_minion` is `false`.\n\n- `minion_config` (string, default: \"salt/minion\") - Path to\n  a custom salt minion config file.\n\n- `minion_key` (string, default: \"salt/key/minion.key\") - Path to your minion key\n\n- `minion_id` (string) - Unique identifier for minion. Used for masterless and preseeding keys.\n\n- `minion_pub` (string, default: \"salt/key/minion.pub\") - Path to your minion\n  public key\n\n- `grains_config` (string) - Path to a custom salt grains file. On Windows, the minion needs `ipc_mode: tcp` set otherwise it will [fail to communicate](https://github.com/saltstack/salt/issues/22796) with the master.\n\n- `masterless` (boolean) - Calls state.highstate in local mode. Uses `minion_id` and `pillar_data` when provided.\n\n- `minion_json_config` (string) - Valid json for configuring the salt minion\n  (`-j` in bootstrap-salt.sh). Not supported on Windows.\n\n- `salt_call_args` (array) - An array of additional command line flag arguments to be passed to the `salt-call` command when provisioning with masterless.\n\n## Master Options\n\nThese only make sense when `install_master` is `true`. Not supported on Windows guest machines.\n\n- `master_config` (string, default: \"salt/master\")\n  Path to a custom salt master config file.\n\n- `master_key` (string, default: \"salt/key/master.pem\") - Path to your master key.\n\n- `master_pub` (string, default: \"salt/key/master.pub\") - Path to your master public key.\n\n- `seed_master` (dictionary) - Upload keys to master, thereby\n  pre-seeding it before use. Example: `{minion_name:/path/to/key.pub}`\n\n- `master_json_config` (string) - Valid json for configuring the salt master\n  (`-J` in bootstrap-salt.sh). Not supported on Windows.\n\n- `salt_args` (array) - An array of additional command line flag arguments to be passed to the `salt` command when provisioning with masterless.\n\n## Execute States\n\nEither of the following may be used to actually execute states\nduring provisioning.\n\n- `run_highstate` - (boolean) Executes `state.highstate` on\n  vagrant up. Can be applied to any machine.\n\n## Execute Runners\n\nEither of the following may be used to actually execute runners\nduring provisioning.\n\n- `run_overstate` - (boolean) Executes `state.over` on\n  vagrant up. Can be applied to the master only. This is superseded by\n  orchestrate. Not supported on Windows guest machines.\n\n- `orchestrations` - (array of strings) Executes `state.orchestrate` on\n  vagrant up. Can be applied to the master only. This is superseded by\n  run_overstate. Not supported on Windows guest machines.\n\n## Output Control\n\nThese may be used to control the output of state execution:\n\n- `colorize` (boolean) - If true, output is colorized. Defaults to false.\n\n- `log_level` (string) - The verbosity of the outputs. Defaults to \"debug\".\n  Can be one of \"all\", \"garbage\", \"trace\", \"debug\", \"info\", or\n  \"warning\". Requires `verbose` to be set to \"true\".\n\n- `verbose` (boolean) - The verbosity of the outputs. Defaults to \"false\".\n  Must be true for log_level taking effect and the output of the salt-commands being displayed.\n\n## Pillar Data\n\nYou can export pillar data for use during provisioning by using the `pillar`\ncommand. Each call will merge the data so you can safely call it multiple\ntimes. The data passed in should only be hashes and lists. Here is an example::\n\n```ruby\nconfig.vm.provision :salt do |salt|\n\n  # Export hostnames for webserver config\n  salt.pillar({\n    \"hostnames\" => {\n      \"www\" => \"www.example.com\",\n      \"intranet\" => \"intranet.example.com\"\n    }\n  })\n\n  # Export database credentials\n  salt.pillar({\n    \"database\" => {\n      \"user\" => \"jdoe\",\n      \"password\" => \"topsecret\"\n    }\n  })\n\n  salt.run_highstate = true\n\nend\n```\n\nOn Windows guests, this requires PowerShell 3.0 or higher.\n\n## Preseeding Keys\n\nPreseeding keys is the recommended way to handle provisioning\nusing a master.\nOn a machine with salt installed, run\n`salt-key --gen-keys=[minion_id]` to generate the necessary\n.pub and .pem files\n\nFor an example of a more advanced setup, look at the original\n[plugin](https://github.com/saltstack/salty-vagrant/tree/develop/example).\n"
  },
  {
    "path": "website/content/docs/provisioning/shell.mdx",
    "content": "---\nlayout: docs\npage_title: Shell Scripts - Provisioning\ndescription: |-\n  The Vagrant Shell provisioner allows you to upload and execute a script within\n  the guest machine.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Shell Provisioner\n\n**Provisioner name: `\"shell\"`**\n\nThe Vagrant Shell provisioner allows you to upload and execute a script within\nthe guest machine.\n\nShell provisioning is ideal for users new to Vagrant who want to get up\nand running quickly and provides a strong alternative for users who are not\ncomfortable with a full configuration management system such as Chef or\nPuppet.\n\nFor POSIX-like machines, the shell provisioner executes scripts with\nSSH. For Windows guest machines that are configured to use WinRM, the\nshell provisioner executes PowerShell and Batch scripts over WinRM.\n\n## Options\n\nThe shell provisioner takes various options. One of `inline` or `path`\nis required:\n\n- `inline` (string) - Specifies a shell command inline to execute on the\n  remote machine. See the [inline scripts](#inline-scripts) section below\n  for more information.\n\n- `path` (string) - Path to a shell script to upload and execute. It can be a\n  script relative to the project Vagrantfile or a remote script (like a [gist](https://gist.github.com)).\n\nThe remainder of the available options are optional:\n\n- `args` (string or array) - Arguments to pass to the shell script when executing it\n  as a single string. These arguments must be written as if they were typed\n  directly on the command line, so be sure to escape characters, quote,\n  etc. as needed. You may also pass the arguments in using an array. In this\n  case, Vagrant will handle quoting for you.\n\n- `binary` (boolean) - Vagrant automatically replaces Windows line endings with\n  Unix line endings. If this is false, then Vagrant will not do this. By default\n  this is \"false\". If the shell provisioner is communicating over WinRM, this\n  defaults to \"true\".\n\n- `env` (hash) - List of key-value pairs to pass in as environment variables to\n  the script. Vagrant will handle quoting for environment variable values, but\n  the keys remain untouched.\n\n- `keep_color` (boolean) - Vagrant automatically colors output in green and\n  red depending on whether the output is from stdout or stderr. If this is\n  true, Vagrant will not do this, allowing the native colors from the script\n  to be outputted.\n\n- `md5` (string) - MD5 checksum used to validate remotely downloaded shell files.\n\n- `name` (string) - This value will be displayed in the output so that\n  identification by the user is easier when many shell provisioners are present.\n\n- `powershell_args` (string) - Extra arguments to pass to `PowerShell`\n  if you are provisioning with PowerShell on Windows.\n\n- `powershell_elevated_interactive` (boolean) - Run an elevated script in interactive mode\n  on Windows. By default this is \"false\". Must also be `privileged`. Be sure to\n  enable auto-login for Windows as the user must be logged in for interactive\n  mode to work.\n\n- `privileged` (boolean) - Specifies whether to execute the shell script\n  as a privileged user or not (`sudo`). By default this is \"true\". Windows\n  guests use a scheduled task to run as a true administrator without the\n  WinRM limitations.\n\n- `reboot` (boolean) - Reboot the guest. This requires the guest to have a\n  reboot capability implemented.\n\n- `reset` (boolean) - Reset the communicator to the machine after completion. This\n  is useful when a shell may need to be reloaded.\n\n- `sha1` (string) - SHA1 checksum used to validate remotely downloaded shell files.\n\n- `sha256` (string) - SHA256 checksum used to validate remotely downloaded shell files.\n\n- `sha384` (string) - SHA384 checksum used to validate remotely downloaded shell files.\n\n- `sha512` (string) - SHA512 checksum used to validate remotely downloaded shell files.\n\n- `sensitive` (boolean) - Marks the Hash values used in the `env` option as sensitive\n  and hides them from output. By default this is \"false\".\n\n- `upload_path` (string) - Is the remote path where the shell script will\n  be uploaded to. The script is uploaded as the SSH user over SCP, so this\n  location must be writable to that user. By default this is\n  \"/tmp/vagrant-shell\". On Windows, this will default to\n  \"C:\\tmp\\vagrant-shell\".\n\n## Inline Scripts\n\nPerhaps the easiest way to get started is with an inline script. An\ninline script is a script that is given to Vagrant directly within\nthe Vagrantfile. An example is best:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"shell\",\n    inline: \"echo Hello, World\"\nend\n```\n\nThis causes `echo Hello, World` to be run within the guest machine when\nprovisioners are run.\n\nCombined with a little bit more Ruby, this makes it very easy to embed\nyour shell scripts directly within your Vagrantfile. Another example below:\n\n```ruby\n$script = <<-SCRIPT\necho I am provisioning...\ndate > /etc/vagrant_provisioned_at\nSCRIPT\n\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"shell\", inline: $script\nend\n```\n\nIn the code block above, the script block starts with `<<-SCRIPT` and ends with `SCRIPT`.\nThis is known as a \"Here Document\" or a \"heredoc\". Additionally, if your script\nrelies on quotes and you do not wish for Ruby to escape your quotes, you may\nwant to use this style of heredoc where `SCRIPT` is surrounded in single quotes:\n\n```ruby\n$script = <<-'SCRIPT'\necho \"These are my \\\"quotes\\\"! I am provisioning my guest.\"\ndate > /etc/vagrant_provisioned_at\nSCRIPT\n\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"shell\", inline: $script\nend\n```\n\nNow that our \"heredoc\" is quoted, our script will preserve the quotes in the string to `echo`:\n\n```\n==> default: Running provisioner: shell...\n    default: Running: inline script\n    default: These are my \"quotes\"! I am provisioning my guest.\n```\n\nFor more examples of how to use \"heredoc\" in Ruby, please refer to the\n[Ruby documentation](https://ruby-doc.org/core-2.5.0/doc/syntax/literals_rdoc.html#label-Here+Documents).\n\nIt is understandable that if you are not familiar with Ruby, the above may seem very\nadvanced or foreign. But do not fear, what it is doing is quite simple:\nthe script is assigned to a global variable `$script`. This global variable\ncontains a string which is then passed in as the inline script to the\nVagrant configuration.\n\nOf course, if any Ruby in your Vagrantfile outside of basic variable assignment\nmakes you uncomfortable, you can use an actual script file, documented in\nthe next section.\n\nFor Windows guest machines, the supported inline script is dependent upon the\ncommunicator in use. When using the `winrm` communicator, the inline script\n_must_ be PowerShell. Batch scripts are not allowed as inline scripts when\nusing the `winrm` communicator. When using the `winssh` communicator, the\ninline script will be run using the configured `shell` which defaults to\nPowerShell.\n\n## External Script\n\nThe shell provisioner can also take an option specifying a path to\na shell script on the host machine. Vagrant will then upload this script\ninto the guest and execute it. An example:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"shell\", path: \"script.sh\"\nend\n```\n\nRelative paths, such as above, are expanded relative to the location\nof the root Vagrantfile for your project. Absolute paths can also be used,\nas well as shortcuts such as `~` (home directory) and `..` (parent directory).\n\nIf you use a remote script as part of your provisioning process, you can pass in\nits URL as the `path` argument as well:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"shell\", path: \"https://example.com/provisioner.sh\"\nend\n```\n\nIf you are running a Batch or PowerShell script for Windows, make sure\nthat the external path has the proper extension (\".bat\" or \".ps1\"), because\nWindows uses this to determine what kind of file it is to execute. Additionally,\nif you are running Vagrant on something like Cygwin or WSL where bash is\navailable, then you should be able to use an extension like \".sh\".\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"shell\", path: \"scripts/PowershellScript.ps1\"\nend\n```\n\nTo run a script already available on the guest you can use an inline script to\ninvoke the remote script on the guest.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"shell\",\n    inline: \"/bin/sh /path/to/the/script/already/on/the/guest.sh\"\nend\n```\n\n## Script Arguments\n\nYou can parameterize your scripts as well like any normal shell script.\nThese arguments can be specified to the shell provisioner. They should\nbe specified as a string as they'd be typed on the command line, so\nbe sure to properly escape anything:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"shell\" do |s|\n    s.inline = \"echo $1\"\n    s.args   = \"'hello, world!'\"\n  end\nend\n```\n\nYou can also specify arguments as an array if you do not want to worry about\nquoting:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.provision \"shell\" do |s|\n    s.inline = \"echo $1\"\n    s.args   = [\"hello, world!\"]\n  end\nend\n```\n"
  },
  {
    "path": "website/content/docs/push/ftp.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Push - FTP & SFTP Strategy\ndescription: |-\n  Vagrant Push FTP and SFTP strategy pushes the code in your Vagrant development\n  environment to a remote FTP or SFTP server.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant Push\n\n## FTP & SFTP Strategy\n\nVagrant Push FTP and SFTP strategy pushes the code in your Vagrant development\nenvironment to a remote FTP or SFTP server.\n\nThe Vagrant Push FTP And SFTP strategy supports the following configuration\noptions:\n\n- `host` - The address of the remote (S)FTP server. If the (S)FTP server is\n  running on a non-standard port, you can specify the port after the address\n  (`host:port`).\n\n- `username` - The username to use for authentication with the (S)FTP server.\n\n- `password` - The password to use for authentication with the (S)FTP server.\n\n- `passive` - Use passive FTP (default is true).\n\n- `secure` - Use secure (SFTP) (default is false).\n\n- `destination` - The root destination on the target system to sync the files\n  (default is `/`).\n\n- `exclude` - Add a file or file pattern to exclude from the upload, relative to\n  the `dir`. This value may be specified multiple times and is additive.\n  `exclude` take precedence over `include` values.\n\n- `include` - Add a file or file pattern to include in the upload, relative to\n  the `dir`. This value may be specified multiple times and is additive.\n\n- `dir` - The base directory containing the files to upload. By default this is\n  the same directory as the Vagrantfile, but you can specify this if you have\n  a `src` folder or `bin` folder or some other folder you want to upload.\n\n### Usage\n\nThe Vagrant Push FTP and SFTP strategy is defined in the `Vagrantfile` using the\n`ftp` key:\n\n```ruby\nconfig.push.define \"ftp\" do |push|\n  push.host = \"ftp.example.com\"\n  push.username = \"username\"\n  push.password = \"password\"\nend\n```\n\nAnd then push the application to the FTP or SFTP server:\n\n```shell-session\n$ vagrant push\n```\n"
  },
  {
    "path": "website/content/docs/push/heroku.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Push - Heroku Strategy\ndescription: |-\n  The Vagrant Push Heroku strategy pushes your application's code to Heroku.\n  Only files which are committed to the Git repository are pushed to Heroku.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant Push\n\n## Heroku Strategy\n\n[Heroku][] is a public PAAS provider that makes it easy to deploy an\napplication. The Vagrant Push Heroku strategy pushes your application's code to\nHeroku.\n\n~> **Warning:** The Vagrant Push Heroku strategy requires you\nhave configured your Heroku credentials and created the Heroku application.\nThis documentation will not cover these prerequisites, but you can read more\nabout them in the [Heroku documentation](https://devcenter.heroku.com).\n\nOnly files which are committed to the Git repository will be pushed to Heroku.\nAdditionally, the current working branch is always pushed to the Heroku, even if\nit is not the \"master\" branch.\n\nThe Vagrant Push Heroku strategy supports the following configuration options:\n\n- `app` - The name of the Heroku application. If the Heroku application does not\n  exist, an exception will be raised. If this value is not specified, the\n  basename of the directory containing the `Vagrantfile` is assumed to be the\n  name of the Heroku application. Since this value can change between users, it\n  is highly recommended that you add the `app` setting to your `Vagrantfile`.\n\n- `dir` - The base directory containing the Git repository to upload to Heroku.\n  By default this is the same directory as the Vagrantfile, but you can specify\n  this if you have a nested Git directory.\n\n- `remote` - The name of the Git remote where Heroku is configured. The default\n  value is \"heroku\".\n\n### Usage\n\nThe Vagrant Push Heroku strategy is defined in the `Vagrantfile` using the\n`heroku` key:\n\n```ruby\nconfig.push.define \"heroku\" do |push|\n  push.app = \"my_application\"\nend\n```\n\nAnd then push the application to Heroku:\n\n```shell-session\n$ vagrant push\n```\n\n[heroku]: https://heroku.com/ 'Heroku'\n"
  },
  {
    "path": "website/content/docs/push/index.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Push\ndescription: |-\n  Vagrant Push is a revolutionary feature that allows users to push the code in\n  their Vagrant environment to a remote location.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant Push\n\nAs of version 1.7, Vagrant is capable of deploying or \"pushing\" application code\nin the same directory as your Vagrantfile to a remote such as an FTP server.\n\nPushes are defined in an application's `Vagrantfile` and are invoked using the\n`vagrant push` subcommand. Much like other components of Vagrant, each Vagrant\nPush plugin has its own configuration options. Please consult the documentation\nfor your Vagrant Push plugin for more information. Here is an example Vagrant\nPush configuration section in a `Vagrantfile`:\n\n```ruby\nconfig.push.define \"ftp\" do |push|\n  push.host = \"ftp.example.com\"\n  push.username = \"...\"\n  # ...\nend\n```\n\nWhen the application is ready to be deployed to the FTP server, just run a\nsingle command:\n\n```shell-session\n$ vagrant push\n```\n\nMuch like [Vagrant Providers][], Vagrant Push also supports multiple backend\ndeclarations. Consider the common scenario of a staging and QA environment:\n\n```ruby\nconfig.push.define \"staging\", strategy: \"ftp\" do |push|\n  # ...\nend\n\nconfig.push.define \"qa\", strategy: \"ftp\" do |push|\n  # ...\nend\n```\n\nIn this scenario, the user must pass the name of the Vagrant Push to the\nsubcommand:\n\n```shell-session\n$ vagrant push staging\n```\n\nVagrant Push is the easiest way to deploy your application. You can read more\nin the documentation links on the sidebar.\n\n[vagrant providers]: /vagrant/docs/providers/ 'Vagrant Providers'\n"
  },
  {
    "path": "website/content/docs/push/local-exec.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Push - Local Exec Strategy\ndescription: |-\n  The Vagrant Push Local Exec strategy pushes your application's code using a\n  user-defined script.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant Push\n\n## Local Exec Strategy\n\nThe Vagrant Push Local Exec strategy allows the user to invoke an arbitrary\nshell command or script as part of a push.\n\n~> **Warning:** The Vagrant Push Local Exec strategy does not\nperform any validation on the correctness of the shell script.\n\nThe Vagrant Push Local Exec strategy supports the following configuration\noptions:\n\n- `script` - The path to a script on disk (relative to the `Vagrantfile`) to\n  execute. Vagrant will attempt to convert this script to an executable, but an\n  exception will be raised if that fails.\n- `inline` - The inline script to execute (as a string).\n- `args` (string or array) - Optional arguments to pass to the shell script when executing it\n  as a single string. These arguments must be written as if they were typed\n  directly on the command line, so be sure to escape characters, quote,\n  etc. as needed. You may also pass the arguments in using an array. In this\n  case, Vagrant will handle quoting for you.\n\nPlease note - only one of the `script` and `inline` options may be specified in\na single push definition.\n\n### Usage\n\nThe Vagrant Push Local Exec strategy is defined in the `Vagrantfile` using the\n`local-exec` key:\n\nRemote path:\n\n```ruby\nconfig.push.define \"local-exec\" do |push|\n  push.inline = <<-SCRIPT\n    scp -r . server:/var/www/website\n  SCRIPT\nend\n```\n\nLocal path:\n\n```ruby\nconfig.push.define \"local-exec\" do |push|\n  push.inline = <<-SCRIPT\n    cp -r . /var/www/website\n  SCRIPT\nend\n```\n\nFor more complicated scripts, you may store them in a separate file and read\nthem from the `Vagrantfile` like so:\n\n```ruby\nconfig.push.define \"local-exec\" do |push|\n  push.script = \"my-script.sh\"\nend\n```\n\nAnd then invoke the push with Vagrant:\n\n```shell-session\n$ vagrant push\n```\n\n### Script Arguments\n\nRefer to [Shell Provisioner](/vagrant/docs/provisioning/shell).\n"
  },
  {
    "path": "website/content/docs/share/connect.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Connect - Vagrant Share\ndescription: |-\n  Vagrant can share any or every port to your Vagrant environment, not\n  just SSH and HTTP.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant Connect\n\nVagrant can share any or _every_ port to your Vagrant environment, not\njust SSH and HTTP. The `vagrant connect` command gives the connecting person\na static IP they can use to communicate to the shared Vagrant environment.\nAny TCP traffic sent to this IP is sent to the shared Vagrant environment.\n\n## Usage\n\nJust call `vagrant share --full`. This will automatically share as many ports as\npossible for remote connections. Please see\n[the Vagrant share security page](/vagrant/docs/share/security) for more\ninformation.\n\nNote the share name at the end of calling `vagrant share --full`, and give this to\nthe person who wants to connect to your machine. They simply have to call\n`vagrant connect NAME`. This will give them a static IP they can use to access\nyour Vagrant environment.\n\n## How does it work?\n\n`vagrant connect` works by doing what Vagrant does best: managing virtual\nmachines. `vagrant connect` creates a tiny virtual machine that takes up\nonly around 20 MB in RAM, using VirtualBox or VMware (more provider support\nis coming soon).\n\nAny traffic sent to this tiny virtual machine is then proxied through to\nthe shared Vagrant environment as if it were directed at it.\n\n## Beware: Vagrant Insecure Key\n\nIf the Vagrant environment or box you are using is protected with the\nVagrant insecure keypair (most public boxes are), then SSH will be easily\navailable to anyone who connects.\n\nWhile hopefully you are sharing with someone you trust, in certain environments\nyou might be sharing with a class, or a conference, and you do not want them\nto be able to SSH in.\n\nIn this case, we recommend changing or removing the insecure key from\nthe Vagrant machine.\n\nFinally, we want to note that we are working on making it so that when\nVagrant share is used, the Vagrant private key is actively rejected unless\nexplicitly allowed. This feature is not yet done, however.\n"
  },
  {
    "path": "website/content/docs/share/http.mdx",
    "content": "---\nlayout: docs\npage_title: HTTP Sharing - Vagrant Share\ndescription: |-\n  Vagrant Share can create a publicly accessible URL endpoint to access an\n  HTTP server running in your Vagrant environment. This is known as \"HTTP\n  sharing,\" and is enabled by default when \"vagrant share\" is used.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# HTTP Sharing\n\nVagrant Share can create a publicly accessible URL endpoint to access an\nHTTP server running in your Vagrant environment. This is known as \"HTTP\nsharing,\" and is enabled by default when `vagrant share` is used.\n\nBecause this mode of sharing creates a publicly accessible URL, the accessing\nparty does not need to have Vagrant installed in order to view your environment.\n\nThis has a number of useful use cases: you can test webhooks by exposing\nyour Vagrant environment to the internet, you can show your work to clients,\nteammates, or managers, etc.\n\n## Usage\n\nTo use HTTP sharing, simply run `vagrant share`:\n\n```shell-session\n$ vagrant share\n==> default: Detecting network information for machine...\ndefault: Local machine address: 192.168.84.130\ndefault: Local HTTP port: 9999\ndefault: Local HTTPS port: disabled\n==> default: Creating Vagrant Share session...\n==> default: HTTP URL: http://b1fb1f3f.ngrok.io\n```\n\nVagrant detects where your HTTP server is running in your Vagrant environment\nand outputs the endpoint that can be used to access this share. Just give\nthis URL to anyone you want to share it with, and they will be able to access\nyour Vagrant environment!\n\nIf Vagrant has trouble detecting the port of your servers in your environment,\nuse the `--http` and/or `--https` flags to be more explicit.\n\nThe share will be accessible for the duration that `vagrant share` is running.\nPress `Ctrl-C` to quit the sharing session.\n\n~> **Warning:** This URL is accessible by _anyone_\nwho knows it, so be careful if you are sharing sensitive information.\n\n## Disabling\n\nIf you want to disable the creation of the publicly accessible endpoint,\nrun `vagrant share` with the `--disable-http` flag. This will share your\nenvironment using one of the other methods available, and will not create\nthe URL endpoint.\n\n## Missing Assets\n\nShared web applications must use **relative paths** for loading any\nlocal assets such as images, stylesheets, javascript.\n\nThe web application under development will be accessed remotely. This means\nthat if you have any hardcoded asset (images, stylesheets, etc.) URLs\nsuch as `<img src=\"http://127.0.0.1/header.png\" />`, then they will not load\nfor people accessing your share.\n\nMost web frameworks or toolkits have settings or helpers to generate\nrelative paths. For example, if you are a WordPress developer, the\n[Root Relative URLs](http://wordpress.org/plugins/root-relative-urls/) plugin\nwill automatically do this for you.\n\nRelative URLs to assets is generally a best practice in general, so you\nshould do this anyways!\n\n## HTTPS (SSL)\n\nVagrant Share can also expose an SSL port that can be accessed over\nSSL. Creating an HTTPS share requires a non-free ngrok account.\n\n`vagrant share` by default looks for any SSL traffic on port 443 in your\ndevelopment environment. If it cannot find any, then SSL is disabled by\ndefault.\n\nThe HTTPS share can be explicitly disabled using the `--disable-https` flag.\n"
  },
  {
    "path": "website/content/docs/share/index.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Share\ndescription: |-\n  Vagrant Share allows you to  share your Vagrant environment with anyone in\n  the world, enabling collaboration directly in your Vagrant environment\n  in almost any network environment with just a single command- \"vagrant share\".\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant Share\n\nVagrant Share allows you to share your Vagrant environment with anyone in\nthe world, enabling collaboration directly in your Vagrant environment\nin almost any network environment with just a single command:\n`vagrant share`.\n\nVagrant share has three primary modes or features. These features are not\nmutually exclusive, meaning that any combination of them can be active\nat any given time:\n\n- **HTTP sharing** will create a URL that you can give to anyone. This\n  URL will route directly into your Vagrant environment. The person using\n  this URL does not need Vagrant installed, so it can be shared with anyone.\n  This is useful for testing webhooks or showing your work to clients,\n  teammates, managers, etc.\n\n- **SSH sharing** will allow instant SSH access to your Vagrant environment\n  by anyone by running `vagrant connect --ssh` on the remote side. This\n  is useful for pair programming, debugging ops problems, etc.\n\n- **General sharing** allows anyone to access any exposed port of your\n  Vagrant environment by running `vagrant connect` on the remote side.\n  This is useful if the remote side wants to access your Vagrant\n  environment as if it were a computer on the LAN.\n\nThe details of each are covered in their specific section in the sidebar\nto the left. We also have a section where we go into detail about the\nsecurity implications of this feature.\n\n## Installation\n\nVagrant Share is a Vagrant plugin that must be installed. It is not\nincluded with Vagrant system packages. To install the Vagrant Share\nplugin, run the following command:\n\n```shell-session\n$ vagrant plugin install vagrant-share\n```\n\nVagrant Share requires [ngrok](https://ngrok.com) to be used.\n"
  },
  {
    "path": "website/content/docs/share/provider.mdx",
    "content": "---\nlayout: docs\npage_title: Custom Provider - Vagrant Share\ndescription: |-\n  If you are developing a custom Vagrant provider, you will need to do a tiny\n  bit more work in order for it to work well with Vagrant Share.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Custom Provider\n\n~> **Warning: Advanced Topic!** This topic is related to\ndeveloping Vagrant plugins. If you are not interested in this or\nyou are just starting with Vagrant, it is safe to skip this page.\n\nIf you are developing a [custom Vagrant provider](/vagrant/docs/plugins/providers),\nyou will need to do a tiny bit more work in order for it to work well with\nVagrant Share.\n\nFor now, this is only one step:\n\n- `public_address` provider capability - You must implement this capability\n  to return a string that is an address that can be used to access the\n  guest from Vagrant. This does not need to be a globally routable address,\n  it only needs to be accessible from the machine running Vagrant. If you\n  cannot detect an address, return `nil`.\n"
  },
  {
    "path": "website/content/docs/share/security.mdx",
    "content": "---\nlayout: docs\npage_title: Security - Vagrant Share\ndescription: |-\n  Sharing your Vagrant environment understandably raises a number of security\n  concerns.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Security\n\nSharing your Vagrant environment understandably raises a number of security\nconcerns.\n\nThe primary security mechanism for Vagrant\nShare is security through obscurity along with an encryption key for SSH.\nAdditionally, there are several configuration options made available to\nhelp control access and manage security:\n\n- `--disable-http` will not create a publicly accessible HTTP URL. When\n  this is set, the only way to access the share is with `vagrant connect`.\n\nIn addition to these options, there are other features we've built to help:\n\n- Vagrant share uses end-to-end TLS for non-HTTP connections. So even unencrypted\n  TCP streams are encrypted through the various proxies and only unencrypted during\n  the final local communication between the local proxy and the Vagrant environment.\n\n- SSH keys are encrypted by default, using a password that is not transmitted\n  to our servers or across the network at all.\n\n- SSH is not shared by default, it must explicitly be shared with the\n  `--ssh` flag.\n\nMost importantly, you must understand that by running `vagrant share`,\nyou are making your Vagrant environment accessible by anyone who knows\nthe share name. When share is not running, it is not accessible.\n"
  },
  {
    "path": "website/content/docs/share/ssh.mdx",
    "content": "---\nlayout: docs\npage_title: SSH Sharing - Vagrant Share\ndescription: |-\n  Vagrant share makes it trivially easy to allow remote SSH access to your\n  Vagrant environment by supplying the \"--ssh\" flag to \"vagrant share\".\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# SSH Sharing\n\nVagrant share makes it trivially easy to allow remote SSH access to your\nVagrant environment by supplying the `--ssh` flag to `vagrant share`.\n\nEasy SSH sharing is incredibly useful if you want to give access to\na colleague for troubleshooting ops issues. Additionally, it enables\npair programming with a Vagrant environment, if you want!\n\nSSH sharing is disabled by default as a security measure. To enable\nSSH sharing, simply supply the `--ssh` flag when calling `vagrant share`.\n\n## Usage\n\nJust run `vagrant share --ssh`!\n\nWhen SSH sharing is enabled, Vagrant generates a brand new keypair for\nSSH access. The public key portion is automatically inserted\ninto the Vagrant machine, and the private key portion is provided to the\nuser connecting to the Vagrant share. This private key is encrypted using\na password that you will be prompted for. This password is _never_ transmitted\nacross the network by Vagrant, and is an extra layer of security preventing\nanyone who may know your share name from easily accessing your machine.\n\nAfter running `vagrant share --ssh`, it will output the name of your share:\n\n```shell-session\n$ vagrant share --ssh\n==> default: Detecting network information for machine...\ndefault: Local machine address: 192.168.84.130\n==> default: Generating new SSH key...\ndefault: Please enter a password to encrypt the key:\ndefault: Repeat the password to confirm:\ndefault: Inserting generated SSH key into machine...\ndefault: Local HTTP port: disabled\ndefault: Local HTTPS port: disabled\ndefault: SSH Port: 2200\n==> default: Creating Vagrant Share session...\nshare: Cloning VMware VM: 'hashicorp/vagrant-share'. This can take some time...\nshare: Verifying vmnet devices are healthy...\nshare: Preparing network adapters...\nshare: Starting the VMware VM...\nshare: Waiting for machine to boot. This may take a few minutes...\nshare: SSH address: 192.168.84.134:22\nshare: SSH username: tc\nshare: SSH auth method: password\nshare:\nshare: Inserting generated public key within guest...\nshare: Removing insecure key from the guest if it's present...\nshare: Key inserted! Disconnecting and reconnecting using new SSH key...\nshare: Machine booted and ready!\nshare: Forwarding ports...\nshare: -- 31338 => 65534\nshare: -- 22 => 2202\nshare: SSH address: 192.168.84.134:22\nshare: SSH username: tc\nshare: SSH auth method: password\nshare: Configuring network adapters within the VM...\n==> share:\n==> share: Your Vagrant Share is running! Name: bazaar_wolf:sultan_oasis\n==> share:\n==> share: You're sharing with SSH access. This means that another can SSH to\n==> share: your Vagrant machine by running:\n==> share:\n==> share:   vagrant connect --ssh bazaar_wolf:sultan_oasis\n==> share:\n```\n\nAnyone can then SSH directly to your Vagrant environment by running\n`vagrant connect --ssh NAME` where NAME is the name of the share outputted\npreviously.\n\n```shell-session\n$ vagrant connect --ssh bazaar_wolf:sultan_oasis\nLoading share 'bazaar_wolf:sultan_oasis'...\nThe SSH key to connect to this share is encrypted. You will\nrequire the password entered when creating the share to\ndecrypt it. Verify you have access to this password before\ncontinuing.\n\nPress enter to continue, or Ctrl-C to exit now.\nPassword for the private key:\nExecuting SSH...\nWelcome to Ubuntu 12.04.3 LTS (GNU/Linux 3.8.0-29-generic x86_64)\n\n * Documentation:  https://help.ubuntu.com/\nLast login: Fri Mar  7 17:44:50 2014 from 192.168.163.1\nvagrant@vagrant:~$\n```\n\nIf the private key is encrypted (the default behavior), then the connecting\nperson will be prompted for the password to decrypt the private key.\n"
  },
  {
    "path": "website/content/docs/synced-folders/basic_usage.mdx",
    "content": "---\nlayout: docs\npage_title: Basic Usage - Synced Folders\ndescription: |-\n  Synced folders are configured within your Vagrantfile using the\n  \"config.vm.synced_folder\" method.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Basic Usage\n\n## Configuration\n\nSynced folders are configured within your Vagrantfile using the\n`config.vm.synced_folder` method. Usage of the configuration directive\nis very simple:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # other config here\n\n  config.vm.synced_folder \"src/\", \"/srv/website\"\nend\n```\n\nThe first parameter is a path to a directory on the host machine. If\nthe path is relative, it is relative to the project root. The second\nparameter must be an absolute path of where to share the folder within\nthe guest machine. This folder will be created (recursively, if it must)\nif it does not exist. By default, Vagrant mounts the synced folders with\nthe owner/group set to the SSH user and any parent folders set to root.\n\n## Options\n\nYou may also specify additional optional parameters when configuring\nsynced folders. These options are listed below. More detailed examples of using\nsome of these options are shown below this section, note the owner/group example\nsupplies two additional options separated by commas.\n\nIn addition to these options, the specific synced folder type might\nallow more options. See the documentation for your specific synced folder\ntype for more details. The built-in synced folder types are documented\nin other pages available in the navigation for these docs.\n\n- `create` (boolean) - If true, the host path will be created if it\n  does not exist. Defaults to false.\n\n- `disabled` (boolean) - If true, this synced folder will be disabled and\n  will not be setup. This can be used to disable a previously defined synced\n  folder or to conditionally disable a definition based on some external\n  factor.\n\n- `group` (string) - The group that will own the synced folder. By default\n  this will be the SSH user. Some synced folder types do not support\n  modifying the group.\n\n- `mount_options` (array) - A list of additional mount options to pass\n  to the `mount` command.\n\n- `owner` (string) - The user who should be the owner of this synced folder.\n  By default this will be the SSH user. Some synced folder types do not\n  support modifying the owner.\n\n- `type` (string) - The type of synced folder. If this is not specified,\n  Vagrant will automatically choose the best synced folder option for your\n  environment. Otherwise, you can specify a specific type such as \"nfs\".\n\n- `id` (string) - The name for the mount point of this synced folder in the\n  guest machine. This shows up when you run `mount` in the guest machine.\n\n## Enabling\n\nSynced folders are automatically setup during `vagrant up` and\n`vagrant reload`.\n\n## Disabling\n\nSynced folders can be disabled by adding the `disabled` option to\nany definition:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.synced_folder \"src/\", \"/srv/website\", disabled: true\nend\n```\n\nDisabling the default `/vagrant` share can be done as follows:\n\n```ruby\nconfig.vm.synced_folder \".\", \"/vagrant\", disabled: true\n```\n\n## Modifying the Owner/Group\n\nSometimes it is preferable to mount folders with a different owner/group than\nthe default SSH user. Keep in mind that these options will only affect the\nsynced folder itself. If you want to modify the owner/group of the synced\nfolder's parent folders use a script. It is possible to set these options:\n\n```ruby\nconfig.vm.synced_folder \"src/\", \"/srv/website\",\n  owner: \"root\", group: \"root\"\n```\n\n_NOTE: Owner and group IDs defined within `mount_options` will have precedence\nover the `owner` and `group` options._\n\nFor example, given the following configuration:\n\n```ruby\nconfig.vm.synced_folder \".\", \"/vagrant\", owner: \"vagrant\",\n  group: \"vagrant\", mount_options: [\"uid=1234\", \"gid=1234\"]\n```\n\nthe mounted synced folder will be owned by the user with ID `1234` and the\ngroup with ID `1234`. The `owner` and `group` options will be ignored.\n\n## Symbolic Links\n\nSupport for symbolic links across synced folder implementations and\nhost/guest combinations is not consistent. Vagrant does its best to\nmake sure symbolic links work by configuring various hypervisors (such\nas VirtualBox), but some host/guest combinations still do not work\nproperly. This can affect some development environments that rely on\nsymbolic links.\n\nThe recommendation is to make sure to test symbolic links on all the\nhost/guest combinations you sync folders on if this is important to you.\n"
  },
  {
    "path": "website/content/docs/synced-folders/index.mdx",
    "content": "---\nlayout: docs\npage_title: Synced Folders\ndescription: |-\n  Synced folders enable Vagrant to sync a folder on the host machine to the\n  guest machine, allowing you to continue working on your project's files\n  on your host machine, but use the resources in the guest machine to\n  compile or run your project.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Synced Folders\n\nSynced folders enable Vagrant to sync a folder on the host machine to the\nguest machine, allowing you to continue working on your project's files\non your host machine, but use the resources in the guest machine to\ncompile or run your project.\n\nBy default, Vagrant will share your project directory (the directory\nwith the [Vagrantfile](/vagrant/docs/vagrantfile/)) to `/vagrant`.\n\nRead the [basic usage](/vagrant/docs/synced-folders/basic_usage) page to get started\nwith synced folders.\n"
  },
  {
    "path": "website/content/docs/synced-folders/nfs.mdx",
    "content": "---\nlayout: docs\npage_title: NFS - Synced Folders\ndescription: >-\n  In some cases the default shared folder implementations such as VirtualBox\n\n  shared folders have high performance penalties. If you are seeing less than\n\n  ideal performance with synced folders, NFS can offer a solution. Vagrant has\n\n  built-in support to orchestrate the configuration of the NFS server on the\n  host\n\n  and guest for you.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# NFS\n\nIn some cases the default shared folder implementations (such as VirtualBox\nshared folders) have high performance penalties. If you are seeing less\nthan ideal performance with synced folders, [NFS](https://en.wikipedia.org/wiki/Network_File_System_%28protocol%29)\ncan offer a solution. Vagrant has built-in support to orchestrate the\nconfiguration of the NFS server on the host and guest for you.\n\n~> **Windows users:** NFS folders do not work on Windows hosts. Vagrant will\nignore your request for NFS synced folders on Windows.\n\n## Prerequisites\n\nBefore using synced folders backed by NFS, the host machine must have\n`nfsd` installed, the NFS server daemon. This comes pre-installed on Mac\nOS X, and is typically a simple package install on Linux.\n\nAdditionally, the guest machine must have NFS support installed. This is\nalso usually a simple package installation away.\n\nIf you are using the VirtualBox provider, you will also need to make sure you\nhave a\n[private network set up](/vagrant/docs/networking/private_network). This is due to a limitation of VirtualBox's built-in networking. With\nVMware, you do not need this.\n\n## Enabling NFS Synced Folders\n\nTo enable NFS, just add the `type: \"nfs\"` flag onto your synced folder:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.synced_folder \".\", \"/vagrant\", type: \"nfs\"\nend\n```\n\nIf you add this to an existing Vagrantfile that has a running guest machine,\nbe sure to `vagrant reload` to see your changes.\n\n## NFS Synced Folder Options\n\nNFS synced folders have a set of options that can be specified that are\nunique to NFS. These are listed below. These options can be specified in\nthe final part of the `config.vm.synced_folder` definition, along with the\n`type` option.\n\n- `nfs_export` (boolean) - If this is false, then Vagrant will not modify\n  your `/etc/exports` automatically and assumes you've done so already.\n\n- `nfs_udp` (boolean) - Whether or not to use UDP as the transport. UDP\n  is faster but has some limitations (see the NFS documentation for more\n  details). This defaults to true.\n\n- `nfs_version` (string | integer) - The NFS protocol version to use when\n  mounting the folder on the guest. This defaults to 3.\n\n## NFS Global Options\n\nThere are also more global NFS options you can set with `config.nfs` in\nthe Vagrantfile. These are documented below:\n\n- `functional` (bool) - Defaults to true. If false, then NFS will not be used\n  as a synced folder type. If a synced folder specifically requests NFS,\n  it will error.\n\n- `map_uid` and `map_gid` (int) - The UID/GID, respectively, to map all\n  read/write requests too. This will not affect the owner/group within the\n  guest machine itself, but any writes will behave as if they were written\n  as this UID/GID on the host. This defaults to the current user running\n  Vagrant.\n\n- `verify_installed` (bool) - Defaults to true. If this is false, then\n  Vagrant will skip checking if NFS is installed.\n\n## Specifying NFS Arguments\n\nIn addition to the options specified above, it is possible for Vagrant to\nspecify alternate NFS arguments when mounting the NFS share by using the\n`mount_options` key. For example, to use the `actimeo=2` client mount option:\n\n```ruby\nconfig.vm.synced_folder \".\", \"/vagrant\",\n  type: \"nfs\",\n  mount_options: ['actimeo=2']\n```\n\nThis would result in the following `mount` command being executed on the guest:\n\n```\nmount -o 'actimeo=2' 172.28.128.1:'/path/to/vagrantfile' /vagrant\n```\n\nYou can also tweak the arguments specified in the `/etc/exports` template\nwhen the mount is added, by using the OS-specific `linux__nfs_options` or\n`bsd__nfs_options` keys. Note that these options completely override the default\narguments that are added by Vagrant automatically. For example, to make the\nNFS share asynchronous:\n\n```ruby\nconfig.vm.synced_folder \".\", \"/vagrant\",\n  type: \"nfs\",\n  linux__nfs_options: ['rw','no_subtree_check','all_squash','async']\n```\n\nThis would result in the following content in `/etc/exports` on the host (note\nthe added `async` flag):\n\n```\n# VAGRANT-BEGIN: 21171 5b8f0135-9e73-4166-9bfd-ac43d5f14261\n\"/path/to/vagrantfile\" 172.28.128.5(rw,no_subtree_check,all_squash,async,anonuid=21171,anongid=660,fsid=3382034405)\n# VAGRANT-END: 21171 5b8f0135-9e73-4166-9bfd-ac43d5f14261\n```\n\n## Root Privilege Requirement\n\nTo configure NFS, Vagrant must modify system files on the host. Therefore,\nat some point during the `vagrant up` sequence, you may be prompted for\nadministrative privileges (via the typical `sudo` program). These\nprivileges are used to modify `/etc/exports` as well as to start and\nstop the NFS server daemon.\n\nIf you do not want to type your password on every `vagrant up`, Vagrant\nuses thoughtfully crafted commands to make fine-grained sudoers modifications\npossible to avoid entering your password.\n\nBelow, we have a couple example sudoers entries. Note that you may\nhave to modify them _slightly_ on certain hosts because the way Vagrant\nmodifies `/etc/exports` changes a bit from OS to OS. If the commands below\nare located in non-standard paths, modify them as appropriate.\n\nAlso note that in the sudoer file format, entries are applied in order. If you've added the appropriate entries but still have to type in your password, make sure the entries aren't inserted too early. From the sudoers man page: \"When multiple entries match for a user, they are applied in order. Where there are multiple matches, the last match is used (which is not necessarily the most specific match).\"\n\nFor \\*nix users, make sure to edit your `/etc/sudoers` file with `visudo`. It protects you against syntax errors which could leave you without the ability to gain elevated privileges.\n\nAll of the snippets below require Vagrant version 1.7.3 or higher.\n\n~> **Use the appropriate group for your user** Depending on how your machine is\nconfigured, you might need to use a different group than the ones listed in the examples below.\n\nFor macOS, sudoers should have this entry:\n\n```\nCmnd_Alias VAGRANT_EXPORTS_ADD = /usr/bin/tee -a /etc/exports\nCmnd_Alias VAGRANT_NFSD = /sbin/nfsd ^(restart|status|update)$\nCmnd_Alias VAGRANT_EXPORTS_REMOVE = /usr/bin/sed -E -e /*/ d -ibak /etc/exports\n%admin ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD, VAGRANT_EXPORTS_REMOVE\n```\n\nFor Linux , sudoers should look like this:\n\n```\nCmnd_Alias VAGRANT_EXPORTS_CHOWN = /bin/chown 0\\:0 /tmp/vagrant-exports\nCmnd_Alias VAGRANT_EXPORTS_MV = /bin/mv -f /tmp/vagrant-exports /etc/exports\nCmnd_Alias VAGRANT_NFSD_CHECK = /etc/init.d/nfs-kernel-server status\nCmnd_Alias VAGRANT_NFSD_START = /etc/init.d/nfs-kernel-server start\nCmnd_Alias VAGRANT_NFSD_APPLY = /usr/sbin/exportfs -ar\n%sudo ALL=(root) NOPASSWD: VAGRANT_EXPORTS_CHOWN, VAGRANT_EXPORTS_MV, VAGRANT_NFSD_CHECK, VAGRANT_NFSD_START, VAGRANT_NFSD_APPLY\n```\n\nFor Fedora Linux, sudoers might look like this (given your user\nbelongs to the vagrant group):\n\n```\nCmnd_Alias VAGRANT_EXPORTS_CHOWN = /bin/chown 0\\:0 /tmp/vagrant-exports\nCmnd_Alias VAGRANT_EXPORTS_MV = /bin/mv -f /tmp/vagrant-exports /etc/exports\nCmnd_Alias VAGRANT_NFSD_CHECK = /usr/bin/systemctl status --no-pager nfs-server.service\nCmnd_Alias VAGRANT_NFSD_START = /usr/bin/systemctl start nfs-server.service\nCmnd_Alias VAGRANT_NFSD_APPLY = /usr/sbin/exportfs -ar\n%vagrant ALL=(root) NOPASSWD: VAGRANT_EXPORTS_CHOWN, VAGRANT_EXPORTS_MV, VAGRANT_NFSD_CHECK, VAGRANT_NFSD_START, VAGRANT_NFSD_APPLY\n```\n\nFor SUSE Linux, sudoers might look like this (given your user\nbelongs to the vagrant group):\n\n```\nCmnd_Alias VAGRANT_CHOWN = /usr/bin/chown 0\\:0 /tmp/vagrant-exports\nCmnd_Alias VAGRANT_MV = /usr/bin/mv -f /tmp/vagrant-exports /etc/exports\nCmnd_Alias VAGRANT_START = /usr/bin/systemctl start --no-pager nfs-server\nCmnd_Alias VAGRANT_STATUS = /usr/bin/systemctl status --no-pager nfs-server\nCmnd_Alias VAGRANT_APPLY = /usr/sbin/exportfs -ar\n%vagrant ALL=(root) NOPASSWD: VAGRANT_CHOWN, VAGRANT_MV, VAGRANT_START, VAGRANT_STATUS, VAGRANT_APPLY\n```\n\nIf you don't want to edit `/etc/sudoers` directly, you can create\n`/etc/sudoers.d/vagrant-syncedfolders` with the appropriate entries,\nassuming `/etc/sudoers.d` has been enabled.\n\n## Other Notes\n\n**Encrypted folders:** If you have an encrypted disk, then NFS very often\nwill refuse to export the filesystem. The error message given by NFS is\noften not clear. One error message seen is `<path> does not support NFS`.\nThere is no workaround for this other than sharing a directory which is not\nencrypted.\n\n**Version 4:** UDP is generally not a valid transport protocol for NFSv4.\nEarly implementations of NFS 4.0 still allowed UDP which allows the UDP\ntransport protocol to be used in rare cases. RFC5661 explicitly states\nUDP alone should not be used for the transport protocol in NFS 4.1. Errors\ndue to unsupported transport protocols for specific versions of NFS are\nnot always clear. A common error message when attempting to use UDP with\nNFSv4:\n\n```\nmount.nfs: an incorrect mount option was specified\n```\n\nWhen using NFSv4, ensure the `nfs_udp` option is set to false. For example:\n\n```ruby\nconfig.vm.synced_folder \".\", \"/vagrant\",\n  type: \"nfs\",\n  nfs_version: 4,\n  nfs_udp: false\n```\n\nFor more information about transport protocols and NFS version 4 see:\n\n- NFSv4.0 - [RFC7530](https://tools.ietf.org/html/rfc7530#section-3.1)\n- NFSv4.1 - [RFC5661](https://tools.ietf.org/html/rfc5661#section-2.9.1)\n\n## Troubleshooting NFS Issues\n\nNFS issues may arise for a variety of reasons. The following list\ndescribes how to possibly identify the root of the issue.\n\n- Ensure nfs server is running on the host. Check if it is running using\n  the command `ps aux | grep nfsd`. If the nfs service is not running,\n  then it may require a manual restart.\n\n- Check status of nfs-kernel-server `systemctl status nfs-kernel-server` for\n  errors like `exportfs: Failed to stat /path : No such file or directory`.\n  Then create the missing directory or remove the line from `/etc/exports`\n  and restart the nfs-kerne-server `sysctemctl start nfs-kernel-server`\n\n- If using Mac, ensure that `/sbin/nfsd` has been given Full Disk Access.\n\n- Ensure the synced folder is present in the hosts `/etc/exports` file.\n  If the target folder is not listed in `/etc/exports`, then ensure that\n  the synced_folder option `nfs_export` is set to `true`, or manually add\n  the entry.\n\n- Ensure that the contents of `/etc/exports` is valid. For example, if running\n  nfsd, this can be done by running `nfsd checkexports`.\n\n- Ensure guest machine has a nfs client installed. The client may differ\n  depending on the OS. If no nfs client is installed on the guest, then it may\n  need to be installed.\n\n- Ensure the guest has access to the mounts. This can be done using something\n  like the `rpcinfo` or `showmount` commands. For example `rpcinfo -u <ip> nfs`\n  or `showmount -e <ip>`.\n\n- Ensure a firewall is not blocking NFS.\n\n- Try manually mounting the folder, enabling verbose output:\n\n  ```\n  $ vagrant ssh\n  $ mount -v -t nfs -o <mount options> <ip address>:<path to folder on host> <mountpoint>\n  ```\n\n- If using a UDP connection: ensure UDP is enabled by the nfs server. This setting\n  can likely be changed in config file `/etc/nfs.conf`. Or, in Vagrant, set the\n  `nfs_udp` option for the synced folder to `false`.\n"
  },
  {
    "path": "website/content/docs/synced-folders/rsync.mdx",
    "content": "---\nlayout: docs\npage_title: RSync - Synced Folders\ndescription: |-\n  Vagrant can use rsync as a mechanism to sync a folder to the guest machine.\n  This synced folder type is useful primarily in situations where other synced\n  folder mechanisms are not available, such as when NFS or VirtualBox shared\n  folders are not available in the guest machine.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# RSync\n\n**Synced folder type:** `rsync`\n\nVagrant can use [rsync](https://en.wikipedia.org/wiki/Rsync) as a mechanism\nto sync a folder to the guest machine. This synced folder type is useful\nprimarily in situations where other synced folder mechanisms are not available,\nsuch as when NFS or VirtualBox shared folders are not available in the guest\nmachine.\n\nThe rsync synced folder does a one-time one-way sync from the machine running\nto the machine being started by Vagrant.\n\nThe [rsync](/vagrant/docs/cli/rsync) and [rsync-auto](/vagrant/docs/cli/rsync-auto)\ncommands can be used to force a resync and to automatically resync when\nchanges occur in the filesystem. Without running these commands, Vagrant\nonly syncs the folders on `vagrant up` or `vagrant reload`.\n\n## Prerequisites\n\nTo use the rsync synced folder type, the machine running Vagrant must have\n`rsync` (or `rsync.exe`) on the path. This executable is expected to behave\nlike the standard rsync tool.\n\nOn Windows, rsync installed with Cygwin or MinGW will be detected by\nVagrant and works well.\n\nThe destination machine must also have rsync installed, but Vagrant\ncan automatically install rsync into many operating systems. If Vagrant\nis unable to automatically install rsync for your operating system,\nit will tell you.\n\nThe destination folder will be created as the user initiating the connection,\nthis is `vagrant` by default. This user requires the appropriate permissions on\nthe destination folder.\n\n## Options\n\nThe rsync synced folder type accepts the following options:\n\n- `rsync__args` (array of strings) - A list of arguments to supply\n  to `rsync`. By default this is `[\"--verbose\", \"--archive\", \"--delete\", \"-z\", \"--copy-links\"]`.\n\n- `rsync__auto` (boolean) - If false, then `rsync-auto` will not\n  watch and automatically sync this folder. By default, this is true. **Note**: This\n  option will not automatically invoke the `rsync-auto` subcommand.\n\n- `rsync__chown` (boolean) - If false, then the\n  [`owner` and `group`](/vagrant/docs/synced-folders/basic_usage)\n  options for the synced folder are ignored and Vagrant will not execute\n  a recursive `chown`. This defaults to true. This option exists because\n  the `chown` causes issues for some development environments. Note that\n  any `rsync__args` options for ownership **will be overridden** by\n  `rsync__chown`.\n\n- `rsync__exclude` (string or array of strings) - A list of files or directories\n  to exclude from the sync. The values can be any acceptable rsync exclude\n  pattern. By default, the \".vagrant/\" directory is excluded. We recommend\n  excluding revision control directories such as \".git/\" as well.\n\n- `rsync__rsync_ownership` (boolean) - If true, and rsync executables in use\n  are >= 3.1.0, then rsync will be used to set the owner and group instead\n  of a separate call to modify ownership. By default, this is false.\n\n- `rsync__rsync_path` (string) - The path on the remote host where rsync\n  is and how it is executed. This is platform specific but defaults to\n  \"sudo rsync\" for many guests.\n\n- `rsync__verbose` (boolean) - If true, then the output from the rsync\n  process will be echoed to the console. The output of rsync is subject\n  to `rsync__args` of course. By default, this is false.\n\n## Example\n\nThe following is an example of using RSync to sync a folder:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.synced_folder \".\", \"/vagrant\", type: \"rsync\",\n    rsync__exclude: \".git/\"\nend\n```\n\n## Rsync to a restricted folder\n\nIf required to copy to a destination where `vagrant` user does not have\npermissions, use `\"--rsync-path='sudo rsync'\"` to run rsync with sudo on the guest\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.synced_folder \"bin\", \"/usr/local/bin\", type: \"rsync\",\n    rsync__exclude: \".git/\",\n    rsync__args: [\"--verbose\", \"--rsync-path='sudo rsync'\", \"--archive\", \"--delete\", \"-z\"]\nend\n```\n"
  },
  {
    "path": "website/content/docs/synced-folders/smb.mdx",
    "content": "---\nlayout: docs\npage_title: SMB - Synced Folders\ndescription: |-\n  Vagrant can use SMB as a mechanism to create a bi-directional synced folder\n  between the host machine and the Vagrant machine.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# SMB\n\n**Synced folder type:** `smb`\n\nVagrant can use [SMB](https://en.wikipedia.org/wiki/Server_Message_Block)\nas a mechanism to create a bi-directional synced folder between the host\nmachine and the Vagrant machine.\n\nSMB is built-in to Windows machines and provides a higher performance\nalternative to some other mechanisms such as VirtualBox shared folders.\n\n-> SMB is currently only supported when the host machine is Windows or\nmacOS. The guest machine can be Windows, Linux, or macOS.\n\n## Prerequisites\n\n### Windows Host\n\nTo use the SMB synced folder type on a Windows host, the machine must have\nPowerShell version 3 or later installed. In addition, when Vagrant attempts\nto create new SMB shares, or remove existing SMB shares, Administrator\nprivileges will be required. Vagrant will request these privileges using UAC.\n\n### macOS Host\n\nTo use the SMB synced folder type on a macOS host, file sharing must be enabled\nfor the local account. Enable SMB file sharing by following the instructions\nbelow:\n\n- Open \"System Preferences\"\n- Click \"Sharing\"\n- Check the \"On\" checkbox next to \"File Sharing\"\n- Click \"Options\"\n- Check \"Share files and folders using SMB\"\n- Check the \"On\" checkbox next to your username within \"Windows File Sharing\"\n- Click \"Done\"\n\nWhen Vagrant attempts to create new SMB shares, or remove existing SMB shares,\nroot access will be required. Vagrant will request these privileges using\n`sudo` to run the `/usr/sbin/sharing` command. Adding the following to\nthe system's `sudoers` configuration will allow Vagrant to manage SMB shares\nwithout requiring a password each time:\n\n```\nCmnd_Alias VAGRANT_SMB_ADD = /usr/sbin/sharing -a * -S * -s * -g * -n *\nCmnd_Alias VAGRANT_SMB_REMOVE = /usr/sbin/sharing -r *\nCmnd_Alias VAGRANT_SMB_LIST = /usr/sbin/sharing -l\nCmnd_Alias VAGRANT_SMB_PLOAD = /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.smb.preferences.plist\nCmnd_Alias VAGRANT_SMB_DLOAD = /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.smbd.plist\nCmnd_Alias VAGRANT_SMB_DSTART = /bin/launchctl start com.apple.smbd\n%admin ALL=(root) NOPASSWD: VAGRANT_SMB_ADD, VAGRANT_SMB_REMOVE, VAGRANT_SMB_LIST, VAGRANT_SMB_PLOAD, VAGRANT_SMB_DLOAD, VAGRANT_SMB_DSTART\n```\n\n### Guests\n\nThe destination machine must be able to mount SMB filesystems. On Linux\nthe package to do this is usually called `smbfs` or `cifs`. Vagrant knows\nhow to automatically install this for some operating systems.\n\n## Options\n\nThe SMB synced folder type has a variety of options it accepts:\n\n- `smb_host` (string) - The host IP where the SMB mount is located. If this\n  is not specified, Vagrant will attempt to determine this automatically.\n\n- `smb_password` (string) - The password used for authentication to mount\n  the SMB mount. This is the password for the username specified by\n  `smb_username`. If this is not specified, Vagrant will prompt you for it.\n  It is highly recommended that you do not set this, since it would expose\n  your password directly in your Vagrantfile.\n\n- `smb_username` (string) - The username used for authentication to mount\n  the SMB mount. This is the username to access the mount, _not_ the username\n  of the account where the folder is being mounted to. This is usually your\n  Windows username. If you sign into a domain, specify it as `user@domain`.\n  If this option is not specified, Vagrant will prompt you for it.\n\n## Example\n\nThe following is an example of using SMB to sync a folder:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.synced_folder \".\", \"/vagrant\", type: \"smb\"\nend\n```\n\n## Preventing Idle Disconnects\n\nOn Windows, if a file is not accessed for some period of time, it may\ndisconnect from the guest and prevent the guest from accessing the SMB-mounted\nshare. To prevent this, the following command can be used in a superuser\nshell. Note that you should research if this is the right option for you.\n\n```\nnet config server /autodisconnect:-1\n```\n\n## Common Issues\n\n### \"wrong fs type\" Error\n\nIf during mounting on Linux you are seeing an error message that includes\nthe words \"wrong fs type,\" this is because the SMB kernel extension needs to\nbe updated in the OS.\n\nIf updating the kernel extension is not an option, you can workaround the\nissue by specifying the following options on your synced folder:\n\n```ruby\nmount_options: [\"username=USERNAME\",\"password=PASSWORD\"]\n```\n\nReplace \"USERNAME\" and \"PASSWORD\" with your SMB username and password.\n\nVagrant 1.8 changed SMB mounting to use the more secure credential file\nmechanism. However, many operating systems ship with an outdated filesystem\ntype for SMB out of the box which does not support this. The above workaround\nreverts Vagrant to the insecure before, but causes it work.\n"
  },
  {
    "path": "website/content/docs/synced-folders/virtualbox.mdx",
    "content": "---\nlayout: docs\npage_title: VirtualBox Shared Folders - Synced Folders\ndescription: |-\n  If you are using the Vagrant VirtualBox provider, then VirtualBox shared\n  folders are the default synced folder type. These synced folders use the\n  VirtualBox shared folder system to sync file changes from the guest to the\n  host and vice versa.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# VirtualBox\n\nIf you are using the Vagrant VirtualBox [provider](/vagrant/docs/providers/), then\nVirtualBox shared folders are the default synced folder type. These synced\nfolders use the VirtualBox shared folder system to sync file changes from\nthe guest to the host and vice versa.\n\n## Options\n\n- `automount` (boolean) - If true, the `--automount` flag will be used when\n  using the VirtualBox tools to share the folder with the guest VM. Defaults to false\n  if not present.\n\n- `SharedFoldersEnableSymlinksCreate` (boolean) - If false, will disable the\n  ability to create symlinks with the given VirtualBox shared folder. Defaults to\n  true if the option is not present.\n\n## Caveats\n\nThere is a [VirtualBox bug][sendfile bug] related to `sendfile` which can result\nin corrupted or non-updating files. You should deactivate `sendfile` in any\nweb servers you may be running.\n\nIn Nginx:\n\n    sendfile off;\n\nIn Apache:\n\n    EnableSendfile Off\n\n[sendfile bug]: https://github.com/hashicorp/vagrant/issues/351#issuecomment-1339640\n"
  },
  {
    "path": "website/content/docs/triggers/configuration.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Triggers Configuration\ndescription: Documentation of various configuration options for Vagrant Triggers\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Configuration\n\nVagrant Triggers has a few options to define trigger behavior.\n\n## Execution Order\n\nThe trigger config block takes two different operations that determine when a trigger\nshould fire:\n\n- `before`\n- `after`\n\nThese define _how_ the trigger behaves and when it should fire off during\nthe Vagrant life cycle. A simple example of a _before_ operation could look like:\n\n```ruby\nconfig.trigger.before :up do |t|\n  t.info = \"Bringing up your Vagrant guest machine!\"\nend\n```\n\nTriggers can also be used with [_commands_](#commands), [_actions_](#actions), or [_hooks_](#hooks).\nBy default triggers will be defined to run before or after a Vagrant guest. For more\ndetailed examples of how to use triggers, check out the [usage section](/vagrant/docs/triggers/usage).\n\n## Trigger Options\n\nThe trigger class takes various options.\n\n- `action` (symbol, array) - Expected to be a single symbol value, an array of symbols, or a _splat_ of symbols. The first argument that comes after either **before** or **after** when defining a new trigger. Can be any valid Vagrant command. It also accepts a special value `:all` which will make the trigger fire for every action. An action can be ignored with the `ignore` setting if desired. These are the valid action commands for triggers:\n\n  - `destroy`\n  - `halt`\n  - `provision`\n  - `reload`\n  - `resume`\n  - `suspend`\n  - `up`\n\n- `ignore` (symbol, array) - Symbol or array of symbols corresponding to the action that a trigger should not fire on.\n\n- `info` (string) - A message that will be printed at the beginning of a trigger.\n\n- `name` (string) - The name of the trigger. If set, the name will be displayed when firing the trigger.\n\n- `on_error` (symbol) - Defines how the trigger should behave if it encounters an error. By default this will be `:halt`, but can be configured to ignore failures and continue on with `:continue`.\n\n- `only_on` (string, regex, array) - Limit the trigger to these guests. Values can be a string or regex that matches a guest name.\n\n- `ruby` (block) - A block of Ruby code to be executed on the host. The block accepts two arguments that can be used with your Ruby code: `env` and `machine`. These options correspond to the Vagrant environment used (note: these are not your shell's environment variables), and the Vagrant guest machine that the trigger is firing on. This option can only be a `Proc` type, which must be explicitly called out when using the hash syntax for a trigger.\n\n  ```ruby\n  ubuntu.trigger.after :up do |trigger|\n    trigger.info = \"More information\"\n    trigger.ruby do |env,machine|\n      greetings = \"hello there #{machine.id}!\"\n      puts greetings\n    end\n  end\n  ```\n\n- `run_remote` (hash) - A collection of settings to run a inline or remote script with on the guest. These settings correspond to the [shell provisioner](/vagrant/docs/provisioning/shell).\n\n- `run` (hash) - A collection of settings to run a inline or remote script on the host. These settings correspond to the [shell provisioner](/vagrant/docs/provisioning/shell). However, at the moment the only settings `run` takes advantage of are:\n\n  - `args`\n  - `inline`\n  - `path`\n\n    **Note:** The `run` option with `inline` is not entirely like a shell provisioner that runs bash.\n    It executes binaries on your machine rather than a bash script. For example:\n    If you wish you use bash to pipe some text to a file in your `run` option with `inline`, wrap\n    your inline script with _`bash -c \"<script goes here>\"`_.:\n\n    ```ruby\n    config.trigger.after :up do |trigger|\n      trigger.info = \"More information\"\n      trigger.run = {inline: \"bash -c 'echo \\\"hey there!!\\\" > file.txt'\"}\n    end\n    ```\n\n- `warn` (string) - A warning message that will be printed at the beginning of a trigger.\n\n- `exit_codes` (integer, array) - A set of acceptable exit codes to continue on. Defaults to `0` if option is absent. For now only valid with the `run` option.\n\n- `abort` (integer,boolean) - An option that will exit the running Vagrant process once the trigger fires. If set to `true`, Vagrant will use exit code 1. Otherwise, an integer can be provided and Vagrant will it as its exit code when aborting.\n\n## Trigger Types\n\nOptionally, it is possible to define a trigger that executes around Vagrant commands,\nhooks, and actions.\n\nA trigger can be one of three types:\n\n- `type` (symbol) - Optional\n  - `:action` - Action triggers run before or after a Vagrant action\n  - `:command` - Command triggers run before or after a Vagrant command\n  - `:hook` - Action hook triggers run before or after a Vagrant hook\n\nThese types determine when and where a defined trigger will execute.\n\n```ruby\nconfig.trigger.after :destroy, type: :command do |t|\n  t.warn = \"Destroy command completed\"\nend\n```\n\n#### Quick Note\n\nTriggers _without_ the type option will run before or after a Vagrant guest.\n\nOlder Vagrant versions will unfortunately not be able to properly parse the new\n`:type` option. If you are worried about older clients failing to parse your Vagrantfile,\nyou can guard the new trigger based on the version of Vagrant:\n\n```ruby\nif Vagrant.version?(\">= 2.3.0\")\n  config.trigger.before :status, type: :command do |t|\n    t.info = \"before action!!!!!!!\"\n  end\nend\n```\n\n### Commands\n\nCommand typed triggers can be defined for any valid Vagrant command. They will always\nrun before or after the command.\n\nThe difference between this and the default behavior is that these triggers are\nnot attached to any specific guest, and will always run before or after the given\ncommand. A simple example might be running a trigger before the up command to give\na simple message to the user:\n\n```ruby\nconfig.trigger.before :up, type: :command do |t|\n  t.info = \"Before command!\"\nend\n```\n\nFor a more detailed example, please check out the [examples](/vagrant/docs/triggers/usage#commands)\npage for more.\n\n### Hooks\n\n~> **Advanced topic!** This is an advanced topic for use only if\nyou want to execute triggers around Vagrant hooks. If you are just getting\nstarted with Vagrant and triggers, you may safely skip this section.\n\nHook typed triggers can be defined for any valid Vagrant action hook that is defined.\n\nA simple example would be running a trigger on a given hook called `action_hook_name`.\n\n```ruby\nconfig.trigger.after :action_hook_name, type: :hook do |t|\n  t.info = \"After action hook!\"\nend\n```\n\nFor a more detailed example, please check out the [examples](/vagrant/docs/triggers/usage#hooks)\npage for more.\n\n### Actions\n\n~> **Advanced topic!** This is an advanced topic for use only if\nyou want to execute triggers around Vagrant actions. If you are just getting\nstarted with Vagrant and triggers, you may safely skip this section.\n\nAction typed triggers can be defined for any valid Vagrant action class. Actions\nin this case refer to the Vagrant class `#Action`, which is used internally to\nVagrant and in every Vagrant plugin.\n\n```ruby\nconfig.trigger.before :\"Action::Class::Name\", type: :action do |t|\n  t.info = \"Before action class!\"\nend\n```\n\nFor a more detailed example, please check out the [examples](/vagrant/docs/triggers/usage#actions)\npage for more.\n"
  },
  {
    "path": "website/content/docs/triggers/index.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Triggers\ndescription: Introduction to Vagrant Triggers\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant Triggers\n\nAs of version 2.1.0, Vagrant is capable of executing machine triggers _before_ or\n_after_ Vagrant commands.\n\nEach trigger is expected to be given a command key for when it should be fired\nduring the Vagrant command lifecycle. These could be defined as a single key or\nan array which acts like a _whitelist_ for the defined trigger.\n\n```ruby\n# single command trigger\nconfig.trigger.after :up do |trigger|\n...\nend\n\n# multiple commands for this trigger\nconfig.trigger.before [:up, :destroy, :halt, :package] do |trigger|\n...\nend\n\n# or defined as a splat list\nconfig.trigger.before :up, :destroy, :halt, :package do |trigger|\n...\nend\n```\n\nAlternatively, the key `:all` could be given which would run the trigger before\nor after every Vagrant command. If there is a command you don't want the trigger\nto run on, you can ignore that command with the `ignore` option.\n\n```ruby\n# single command trigger\nconfig.trigger.before :all do |trigger|\n  trigger.info = \"Running a before trigger!\"\n  trigger.ignore = [:destroy, :halt]\nend\n```\n\n**Note:** _If a trigger is defined on a command that does not exist, a warning\nwill be displayed._\n\nTriggers can be defined as a block or hash in a Vagrantfile. The example below\nwill result in the same trigger:\n\n```ruby\nconfig.trigger.after :up do |trigger|\n  trigger.name = \"Finished Message\"\n  trigger.info = \"Machine is up!\"\nend\n\nconfig.trigger.after :up,\n  name: \"Finished Message\",\n  info: \"Machine is up!\"\n```\n\nTriggers can also be defined within the scope of guests in a Vagrantfile. These\ntriggers will only run on the configured guest. An example of a guest only trigger:\n\n```ruby\nconfig.vm.define \"ubuntu\" do |ubuntu|\n  ubuntu.vm.box = \"ubuntu\"\n  ubuntu.trigger.before :destroy do |trigger|\n    trigger.warn = \"Dumping database to /vagrant/outfile\"\n    trigger.run_remote = {inline: \"pg_dump dbname > /vagrant/outfile\"}\n  end\nend\n```\n\nGlobal and machine-scoped triggers will execute in the order that they are\ndefined within a Vagrantfile. Take for example an abstracted Vagrantfile:\n\n```\nVagrantfile\n  global trigger 1\n  global trigger 2\n  machine defined\n    machine trigger 3\n  global trigger 4\nend\n```\n\nIn this generic case, the triggers would fire in the order: 1 -> 2 -> 3 -> 4\n\nFor more information about what options are available for triggers, see the\n[configuration section](/vagrant/docs/triggers/configuration).\n"
  },
  {
    "path": "website/content/docs/triggers/usage.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrant Triggers Usage\ndescription: Various Vagrant Triggers examples\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Basic Usage\n\nBelow are some very simple examples of how to use Vagrant Triggers.\n\n## Examples\n\nThe following is a basic example of two global triggers. One that runs _before_\nthe `:up` command and one that runs _after_ the `:up` command:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.trigger.before :up do |trigger|\n    trigger.name = \"Hello world\"\n    trigger.info = \"I am running before vagrant up!!\"\n  end\n\n  config.trigger.after :up do |trigger|\n    trigger.name = \"Hello world\"\n    trigger.info = \"I am running after vagrant up!!\"\n  end\n\n  config.vm.define \"ubuntu\" do |ubuntu|\n    ubuntu.vm.box = \"ubuntu\"\n  end\nend\n```\n\nThese will run before and after each defined guest in the Vagrantfile.\n\nRunning a remote script to save a database on your host before **destroy**ing a\nguest:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.define \"ubuntu\" do |ubuntu|\n    ubuntu.vm.box = \"ubuntu\"\n\n    ubuntu.trigger.before :destroy do |trigger|\n      trigger.warn = \"Dumping database to /vagrant/outfile\"\n      trigger.run_remote = {inline: \"pg_dump dbname > /vagrant/outfile\"}\n    end\n  end\nend\n```\n\nNow that the trigger is defined, running the **destroy** command will fire off\nthe defined trigger before Vagrant destroys the machine.\n\n```shell-session\n$ vagrant destroy ubuntu\n```\n\nAn example of defining three triggers that start and stop tinyproxy on your host\nmachine using homebrew:\n\n```shell\n#/bin/bash\n# start-tinyproxy.sh\nbrew services start tinyproxy\n```\n\n```shell\n#/bin/bash\n# stop-tinyproxy.sh\nbrew services stop tinyproxy\n```\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.define \"ubuntu\" do |ubuntu|\n    ubuntu.vm.box = \"ubuntu\"\n\n    ubuntu.trigger.before :up do |trigger|\n      trigger.info = \"Starting tinyproxy...\"\n      trigger.run = {path: \"start-tinyproxy.sh\"}\n    end\n\n    ubuntu.trigger.after :destroy, :halt do |trigger|\n      trigger.info = \"Stopping tinyproxy...\"\n      trigger.run = {path: \"stop-tinyproxy.sh\"}\n    end\n  end\nend\n```\n\nRunning `vagrant up` would fire the before trigger to start tinyproxy, where as\nrunning either `vagrant destroy` or `vagrant halt` would stop tinyproxy.\n\n### Ruby Option\n\nTriggers can also be defined to run Ruby, rather than bash or PowerShell. An\nexample of this might be using a Ruby option to get more information from the `VBoxManage`\ntool. In this case, we are printing the `ostype` defined for thte guest after\nit has been brought up.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.define \"ubuntu\" do |ubuntu|\n    ubuntu.vm.box = \"ubuntu\"\n\n    ubuntu.trigger.after :up do |trigger|\n      trigger.info = \"More information with ruby magic\"\n      trigger.ruby do |env,machine|\n        puts `VBoxManage showvminfo #{machine.id} --machinereadable | grep ostype`\n      end\n    end\n  end\nend\n```\n\nIf you are defining your triggers using the hash syntax, you must use the `Proc`\ntype for defining a ruby trigger.\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  config.vm.define \"ubuntu\" do |ubuntu|\n    ubuntu.vm.box = \"ubuntu\"\n\n    ubuntu.trigger.after :up,\n      info: \"More information with ruby magic\",\n      ruby: proc{|env,machine| puts `VBoxManage showvminfo #{machine.id} --machinereadable | grep ostype`}\n  end\nend\n```\n\n### Typed Triggers\n\nBelow are some basic examples of using `:type` triggers. They cover commands, hooks,\nand actions.\n\nIt is important to note that while `command` triggers will be a fairly common use case,\nboth `action` and `hook` triggers are more complicated and are a more advanced use case.\n\n#### Commands\n\nThe most common use case for typed triggers are with `command`. These kinds of\ntriggers allow you to run something before or after a subcommand in Vagrant.\n\n```ruby\nconfig.trigger.after :status, type: :command do |t|\n  t.info = \"Showing status of all VMs!\"\nend\n```\n\nBecause they are specifically for subcommands, they do not work with any guest\noperations like `run_remote` or if you define the trigger as a guest trigger.\n\n#### Hooks\n\nBelow is an example of a Vagrant trigger that runs before and after each defined\nprovisioner:\n\n```ruby\nconfig.trigger.before :provisioner_run, type: :hook do |t|\n  t.info = \"Before the provision!\"\nend\n\nconfig.vm.provision \"file\", source: \"scripts/script.sh\", destination: \"/test/script.sh\"\n\nconfig.vm.provision \"shell\", inline: <<-SHELL\necho \"Provision the guest!\"\nSHELL\n\n```\n\nNotice how this trigger runs before _each_ provisioner defined for the guest:\n\n```shell\n==> guest: Running provisioner: Sandbox (file)...\n==> vagrant: Running hook triggers before provisioner_run ...\n==> vagrant: Running trigger...\n==> vagrant: Before the provision!\n    guest: /home/hashicorp/vagrant-sandbox/scripts/script.sh => /home/vagrant/test/script.sh\n==> guest: Running provisioner: shell...\n==> vagrant: Running hook triggers before provisioner_run ...\n==> vagrant: Running trigger...\n==> vagrant: Before the provision!\n    guest: Running: inline script\n    guest: Provision the guest!\n```\n\n#### Actions\n\nWith action typed triggers, you can fire off triggers before or after certain\nAction classes. A simple example of this might be warning the user when Vagrant\ninvokes the `GracefulHalt` action.\n\n```ruby\nconfig.trigger.before :\"Vagrant::Action::Builtin::GracefulHalt\", type: :action do |t|\n  t.warn = \"Vagrant is halting your guest...\"\nend\n```\n"
  },
  {
    "path": "website/content/docs/vagrantfile/index.mdx",
    "content": "---\nlayout: docs\npage_title: Vagrantfile\ndescription: |-\n  The primary function of the Vagrantfile is to describe the type\n  of machine required for a project, and how to configure and\n  provision these machines. Vagrantfiles are called Vagrantfiles because\n  the actual literal filename for the file is \"Vagrantfile\".\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrantfile\n\nThe primary function of the Vagrantfile is to describe the type\nof machine required for a project, and how to configure and\nprovision these machines. Vagrantfiles are called Vagrantfiles because\nthe actual literal filename for the file is `Vagrantfile` (casing does not\nmatter unless your file system is running in a strict case sensitive mode).\n\nVagrant is meant to run with one Vagrantfile per project, and the Vagrantfile\nis supposed to be committed to version control. This allows other developers\ninvolved in the project to check out the code, run `vagrant up`, and be on\ntheir way. Vagrantfiles are portable across every platform Vagrant supports.\n\nThe syntax of Vagrantfiles is [Ruby](http://www.ruby-lang.org), but knowledge\nof the Ruby programming language is not necessary to make modifications to the\nVagrantfile, since it is mostly simple variable assignment. In fact, Ruby is not\neven the most popular community Vagrant is used within, which should help show\nyou that despite not having Ruby knowledge, people are very successful with\nVagrant.\n\n## Lookup Path\n\nWhen you run any `vagrant` command, Vagrant climbs up the directory tree\nlooking for the first Vagrantfile it can find, starting first in the\ncurrent directory. So if you run `vagrant` in `/home/mitchellh/projects/foo`,\nit will search the following paths in order for a Vagrantfile, until it\nfinds one:\n\n```\n/home/mitchellh/projects/foo/Vagrantfile\n/home/mitchellh/projects/Vagrantfile\n/home/mitchellh/Vagrantfile\n/home/Vagrantfile\n/Vagrantfile\n```\n\nThis feature lets you run `vagrant` from any directory in your project.\n\nYou can change the starting directory where Vagrant looks for a Vagrantfile\nby setting the `VAGRANT_CWD` environmental variable to some other path.\n\n## Load Order and Merging ((#load-order))\n\nAn important concept to understand is how Vagrant loads Vagrantfiles. Vagrant\nactually loads a series of Vagrantfiles, merging the settings as it goes. This\nallows Vagrantfiles of varying level of specificity to override prior settings.\nVagrantfiles are loaded in the order shown below. Note that if a Vagrantfile\nis not found at any step, Vagrant continues with the next step.\n\n1. Vagrantfile packaged with the [box](/vagrant/docs/boxes) that is to be used\n   for a given machine.\n2. Vagrantfile in your Vagrant home directory (defaults to `~/.vagrant.d`).\n   This lets you specify some defaults for your system user.\n3. Vagrantfile from the project directory. This is the Vagrantfile that you will\n   be modifying most of the time.\n4. [Multi-machine overrides](/vagrant/docs/multi-machine/) if any.\n5. [Provider-specific overrides](/vagrant/docs/providers/configuration),\n   if any.\n\nAt each level, settings set will be merged with previous values. What this\nexactly means depends on the setting. For most settings, this means that\nthe newer setting overrides the older one. However, for things such as defining\nnetworks, the networks are actually appended to each other. By default, you\nshould assume that settings will override each other. If the behavior is\ndifferent, it will be noted in the relevant documentation section.\n\nWithin each Vagrantfile, you may specify multiple `Vagrant.configure` blocks.\nAll configurations will be merged within a single Vagrantfile in the order\nthey're defined.\n\n## Available Configuration Options\n\nYou can learn more about the available configuration options by clicking\nthe relevant section in the left navigational area.\n"
  },
  {
    "path": "website/content/docs/vagrantfile/machine_settings.mdx",
    "content": "---\nlayout: docs\npage_title: config.vm - Vagrantfile\ndescription: |-\n  The settings within \"config.vm\" modify the configuration of the\n  machine that Vagrant manages.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Machine Settings\n\n**Config namespace: `config.vm`**\n\nThe settings within `config.vm` modify the configuration of the\nmachine that Vagrant manages.\n\n## Available Settings\n\n- `config.vm.allow_fstab_modification` (boolean) - If true, will add fstab\n  entries for synced folders. If false, no modifications to fstab will be made\n  by Vagrant. Note, this may mean that folders will not be automatically mounted\n  on machine reboot. Defaults to true.\n\n- `config.vm.allow_hosts_modification` (boolean) - If false, will prevent Vagrant\n  from writing to `/etc/hosts`. Defaults to true.\n\n- `config.vm.allowed_synced_folder_types` (array of strings) - A list of allowed\nsynced folder plugins. This will restrict plugin selection when Vagrant is\ndetermining the default synced folder type. The elements of the array should be\nthe name of the synced folder plugin.\n\n- `config.vm.base_mac` (string) - The MAC address to be assigned to the default\n  NAT interface on the guest. _Support for this option is provider dependent._\n\n- `config.vm.base_address` (string) - The IP address to be assigned to the default\n  NAT interface on the guest. _Support for this option is provider dependent._\n\n- `config.vm.boot_timeout` (integer) - The time in seconds that Vagrant will wait\n  for the machine to boot and be accessible. By default this is 300 seconds.\n\n- `config.vm.box` (string) - This configures what [box](/vagrant/docs/boxes) the\n  machine will be brought up against. The value here should be the name\n  of an installed box or a shorthand name of a box in\n  [HashiCorp's Vagrant Cloud](/vagrant/vagrant-cloud).\n\n- `config.vm.box_architecture` (string) - The architecture of the box to be used.\n  Supported architecture values: \"i386\", \"amd64\", \"arm\", \"arm64\", \"ppc64le\", \"ppc64\",\n  \"mips64le\", \"mips64\", \"mipsle\", \"mips\", and \"s390x\". The special value `:auto` will\n  detect the host architecture and fetch the appropriate box, if available. When the\n  value is set to `nil`, it will fetch the box flagged as the default architecture.\n  Defaults to `:auto`.\n\n- `config.vm.box_check_update` (boolean) - If true, Vagrant will check for updates to\n  the configured box on every `vagrant up`. If an update is found, Vagrant\n  will tell the user. By default this is true. Updates will only be checked\n  for boxes that properly support updates (boxes from\n  [HashiCorp's Vagrant Cloud](/vagrant/vagrant-cloud)\n  or some other versioned box).\n\n- `config.vm.box_download_checksum` (string) - The checksum of the box specified by\n  `config.vm.box_url`. If not specified, no checksum comparison will be done.\n  If specified, Vagrant will compare the checksum of the downloaded box to\n  this value and error if they do not match. Checksum checking is only done\n  when Vagrant must download the box. If this is specified, then\n  `config.vm.box_download_checksum_type` must also be specified.\n\n- `config.vm.box_download_checksum_type` (string) - The type of checksum specified\n  by `config.vm.box_download_checksum` (if any). Supported values are\n  currently \"md5\", \"sha1\", \"sha256\", \"sha384\", and \"sha512\".\n\n- `config.vm.box_download_client_cert` (string) - Path to a client certificate to\n  use when downloading the box, if it is necessary. By default, no client\n  certificate is used to download the box.\n\n- `config.vm.box_download_ca_cert` (string) - Path to a CA cert bundle to use when\n  downloading a box directly. By default, Vagrant will use the Mozilla CA cert\n  bundle.\n\n- `config.vm.box_download_ca_path` (string) - Path to a directory containing\n  CA certificates for downloading a box directly. By default, Vagrant will\n  use the Mozilla CA cert bundle.\n\n- `config.vm.box_download_disable_ssl_revoke_best_effort` (boolean) - Disable SSL\n  revocation checking from being best effort. If an error is encountered when attempting\n  to check certificate revocation, enabling this option will halt the request. This\n  option is only applied on the Windows platform and defaults to `false`.\n\n- `config.vm.box_download_options` (map) - A map of extra download options\n  to pass to the downloader. For example, a path to a key that the downloader\n  should use could be specified as `{key: \"<path/to/key>\"}`. The keys should\n  be options supported by `curl` using the unshortened form of the flag. For\n  example, use `append` instead of `a`. To pass a curl option that does not\n  accept a value, include the option in the map with the value `true`. For\n  example specify the `--fail` flag as `{fail: true}`.\n\n- `config.vm.box_download_insecure` (boolean) - If true, then SSL certificates\n  from the server will not be verified. By default, if the URL is an HTTPS\n  URL, then SSL certs will be verified.\n\n- `config.vm.box_download_location_trusted` (boolean) - If true, then all HTTP redirects will be\n  treated as trusted. That means credentials used for initial URL will be used for\n  all subsequent redirects. By default, redirect locations are untrusted so credentials\n  (if specified) used only for initial HTTP request.\n\n- `config.vm.box_url` (string, array of strings) - The URL that the configured box can be found at.\n  If `config.vm.box` is a shorthand to a box in [HashiCorp's Vagrant Cloud](/vagrant/vagrant-cloud)\n  then this value does not need to be specified. Otherwise, it should\n  point to the proper place where the box can be found if it is not\n  installed. This can also be an array of multiple URLs. The URLs will be tried in\n  order.\n\n  Note that any client certificates, insecure download settings, and\n  so on will apply to all URLs in this list. The URLs can also be local files\n  by using the `file://` scheme. For example: `file://tmp/test.box`.\n\n- `config.vm.box_version` (string) - The version of the box to use. This defaults to\n  \">= 0\" (the latest version available). This can contain an arbitrary list\n  of constraints, separated by commas, such as: `>= 1.0, < 1.5`. When constraints\n  are given, Vagrant will use the latest available box satisfying these\n  constraints.\n\n- `config.vm.cloud_init` - Stores various [cloud_init](/vagrant/docs/cloud-init) configurations\n  on the machine.\n\n- `config.vm.cloud_init_first_boot_only` - (boolean) - If true then the cloud-init \n  configuration will only be generated and attached on the first successful boot of \n  the machine. Subsequent boots of the machine will not generate the cloud-init \n  configuration and the `cloud-init wait` command will not be executed. Defaults \n  to `true`.\n\n- `config.vm.communicator` (string) - The communicator type to use to connect to the\n  guest box. By default this is `\"ssh\"`, but should be changed to `\"winrm\"` for\n  Windows guests.\n\n- `config.vm.disk` - Stores various virtual [disk](/vagrant/docs/disks) configurations\n  on the machine.\n\n- `config.vm.graceful_halt_timeout` (integer) - The time in seconds that Vagrant will\n  wait for the machine to gracefully halt when `vagrant halt` is called.\n  Defaults to 60 seconds.\n\n- `config.vm.guest` (string, symbol) - The guest OS that will be running within this\n  machine. This defaults to `:linux`, and Vagrant will auto-detect the\n  proper distro. However, this should be changed to `:windows` for Windows guests.\n  Vagrant needs to know this information to perform some guest OS-specific things\n  such as mounting folders and configuring networks.\n\n- `config.vm.hostname` (string) - The hostname the machine should have. Defaults\n  to nil. If nil, Vagrant will not manage the hostname. If set to a string,\n  the hostname will be set on boot. If set, Vagrant will update `/etc/hosts`\n  on the guest with the configured hostname.\n\n- `config.vm.ignore_box_vagrantfile` (boolean) - If true, Vagrant will not load the\n  settings found inside a boxes Vagrantfile, if present. Defaults to `false`.\n\n- `config.vm.network` - Configures [networks](/vagrant/docs/networking/) on\n  the machine. Please see the networking page for more information.\n\n- `config.vm.post_up_message` (string) - A message to show after `vagrant up`. This\n  will be shown to the user and is useful for containing instructions\n  such as how to access various components of the development environment.\n\n- `config.vm.provider` - Configures [provider-specific configuration](/vagrant/docs/providers/configuration),\n  which is used to modify settings which are specific to a certain\n  [provider](/vagrant/docs/providers/). If the provider you are configuring\n  does not exist or is not setup on the system of the person who runs\n  `vagrant up`, Vagrant will ignore this configuration block. This allows\n  a Vagrantfile that is configured for many providers to be shared among\n  a group of people who may not have all the same providers installed.\n\n- `config.vm.provision` - Configures [provisioners](/vagrant/docs/provisioning/)\n  on the machine, so that software can be automatically installed and configured\n  when the machine is created. Please see the page on provisioners for more\n  information on how this setting works.\n\n- `config.vm.synced_folder` - Configures [synced folders](/vagrant/docs/synced-folders/)\n  on the machine, so that folders on your host machine can be synced to\n  and from the guest machine. Please see the page on synced folders for\n  more information on how this setting works.\n\n- `config.vm.usable_port_range` (range) - A range of ports Vagrant can use for\n  handling port collisions and such. Defaults to `2200..2250`.\n"
  },
  {
    "path": "website/content/docs/vagrantfile/ssh_settings.mdx",
    "content": "---\nlayout: docs\npage_title: config.ssh - Vagrantfile\ndescription: |-\n  The settings within \"config.ssh\" relate to configuring how Vagrant\n  will access your machine over SSH. As with most Vagrant settings, the\n  defaults are typically fine, but you can fine tune whatever you would like.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# SSH Settings\n\n**Config namespace: `config.ssh`**\n\nThe settings within `config.ssh` relate to configuring how Vagrant\nwill access your machine over SSH. As with most Vagrant settings, the\ndefaults are typically fine, but you can fine tune whatever you would like.\n\n## Available Settings\n\n- `config.ssh.compression` (boolean) - If `false`, this setting will not include the\n  compression setting when ssh'ing into a machine. If this is not set, it will\n  default to `true` and `Compression=yes` will be enabled with ssh.\n\n- `config.ssh.connect_retries` (integer) - Number of times to attempt to establish an\n  an SSH connection to the guest. Defaults to `5`.\n\n- `config.ssh.connect_retry_delay` (numeric) - Number of seconds to wait between\n  retries when attempting to establish an SSH connection to the guest. Defaults to `2`.\n\n- `config.ssh.connect_timeout` (integer) - Number of seconds to wait for establishing\n  an SSH connection to the guest. Defaults to `15`.\n\n- `config.ssh.config` (string) - Path to a custom ssh_config file to use for configuring\n  the SSH connections.\n\n- `config.ssh.disable_deprecated_algorithms` (boolean) - If `true`, this setting will\n  not configure the SSH client to allow connecting to a host using ssh-rsa key types\n  and host key algorithms. Defaults to false.\n\n- `config.ssh.dsa_authentication` (boolean) - If `false`, this setting will not include\n  `DSAAuthentication` when ssh'ing into a machine. If this is not set, it will\n  default to `true` and `DSAAuthentication=yes` will be used with ssh.\n\n- `config.ssh.export_command_template` (string) - The template used to generate\n  exported environment variables in the active session. This can be useful\n  when using a Bourne incompatible shell like C shell. The template supports\n  two variables which are replaced with the desired environment variable key and\n  environment variable value: `%ENV_KEY%` and `%ENV_VALUE%`. The default template\n  is:\n\n  ```ruby\n  config.ssh.export_command_template = 'export %ENV_KEY%=\"%ENV_VALUE%\"'\n  ```\n\n- `config.ssh.extra_args` (array of strings) - This settings value is passed directly\n  into the ssh executable. This allows you to pass any arbitrary commands to do things such\n  as reverse tunneling down into the SSH program. These options can either be\n  single flags set as strings such as `\"-6\"` for IPV6 or an array of arguments\n  such as `[\"-L\", \"8008:localhost:80\"]` for enabling a tunnel from host port 8008\n  to port 80 on guest. **Note:** This option only affects the `ssh` command or instances\n  where the SSH executable is invoked (non-interactive SSH connections use the internal\n  SSH communicator which is unaffected by this setting).\n\n- `config.ssh.forward_agent` (boolean) - If `true`, agent forwarding over SSH\n  connections is enabled. Defaults to false.\n\n- `config.ssh.forward_env` (array of strings) - An array of host environment variables to\n  forward to the guest. If you are familiar with OpenSSH, this corresponds to the `SendEnv`\n  parameter.\n\n  ```ruby\n  config.ssh.forward_env = [\"CUSTOM_VAR\"]\n  ```\n\n- `config.ssh.forward_x11` (boolean) - If `true`, X11 forwarding over SSH connections\n  is enabled. Defaults to false.\n\n- `config.ssh.guest_port` (integer) - The port on the guest that SSH is running on. This\n  is used by some providers to detect forwarded ports for SSH. For example, if\n  this is set to 22 (the default), and Vagrant detects a forwarded port to\n  port 22 on the guest from port 4567 on the host, Vagrant will attempt\n  to use port 4567 to talk to the guest if there is no other option.\n\n- `config.ssh.host` (string) - The hostname or IP to SSH into. By default this is\n  empty, because the provider usually figures this out for you.\n\n- `config.ssh.insert_key` (boolean) - By default or if set to `true`, Vagrant will automatically insert\n  a keypair to use for SSH, replacing Vagrant's default insecure key inside the machine\n  if detected. If you already use private keys for authentication to your guest, or are relying\n  on the default insecure key, this option will not be used. If set to `false`,\n  Vagrant will not automatically add a keypair to the guest.\n\n- `config.ssh.keep_alive` (boolean) - If `true`, this setting SSH will send keep-alive packets\n  every 5 seconds by default to keep connections alive.\n\n- `config.ssh.keys_only` (boolean) - Only use Vagrant-provided SSH private keys (do not use\n  any keys stored in ssh-agent). The default value is `true`.\n\n- `config.ssh.key_type` (string, symbol) - The SSH key type that should be used when generating\n  a new key to replace the default insecure key. Supported values are: `:ed25519`, `:ecdsa256`,\n  `:ecdsa384`, `:ecdsa521`, `:rsa`, and `:auto`. When the value is set to `:auto`, Vagrant will\n  automatically pick a type based on what is supported by the guest SSH server. The default\n  value is `:auto`.\n\n- `config.ssh.paranoid` (boolean) - Perform strict host-key verification. The default value is\n  `false`.\n\n  !> **Deprecation:** The `config.ssh.paranoid` option is deprecated and will be removed\n  in a future release. Please use the `config.ssh.verify_host_key` option instead.\n\n- `config.ssh.password` (string) - This sets a password that Vagrant will use to\n  authenticate the SSH user. Note that Vagrant recommends you use key-based\n  authentication rather than a password (see `private_key_path`) below. If\n  you use a password, Vagrant will automatically insert a keypair if\n  `insert_key` is true.\n\n- `config.ssh.port` (integer) - The port to SSH into. By default this is port 22.\n\n- `config.ssh.private_key_path` (string, array of strings) - The path to the private\n  key to use to SSH into the guest machine. By default this is the insecure private key\n  that ships with Vagrant, since that is what public boxes use. If you make\n  your own custom box with a custom SSH key, this should point to that\n  private key. You can also specify multiple private keys by setting this to be an array.\n  This is useful, for example, if you use the default private key to bootstrap\n  the machine, but replace it with perhaps a more secure key later.\n\n- `config.ssh.proxy_command` (string) - A command-line command to execute that receives\n  the data to send to SSH on stdin. This can be used to proxy the SSH connection.\n  `%h` in the command is replaced with the host and `%p` is replaced with\n  the port.\n\n- `config.ssh.pty` (boolean) - If `true`, pty will be used for provisioning. Defaults to false.\n\n  This setting is an _advanced feature_ that should not be enabled unless\n  absolutely necessary. It breaks some other features of Vagrant, and is\n  really only exposed for cases where it is absolutely necessary. If you can find\n  a way to not use a pty, that is recommended instead.\n\n  When pty is enabled, it is important to note that command output will _not_ be\n  streamed to the UI. Instead, the output will be delivered in full to the UI\n  once the command has completed.\n\n- `config.ssh.remote_user` (string) - The \"remote user\" value used to replace the `%r`\n  character(s) used within a configured `ProxyCommand`. This value is only used by the\n  net-ssh library (ignored by the `ssh` executable) and should not be used in general.\n  This defaults to the value of `config.ssh.username`.\n\n- `config.ssh.shell` (string) - The shell to use when executing SSH commands from\n  Vagrant. By default this is `bash -l`.\n\n- `config.ssh.sudo_command` (string) - The command to use when executing a command\n  with `sudo`. This defaults to `sudo -E -H %c`. The `%c` will be replaced by\n  the command that is being executed.\n\n- `config.ssh.username` (string) - This sets the username that Vagrant will SSH\n  as by default. Providers are free to override this if they detect a more\n  appropriate user. By default this is \"vagrant\", since that is what most\n  public boxes are made as.\n\n- `config.ssh.verify_host_key` (string, symbol) - Perform strict host-key verification. The\n  default value is `:never`. The other options are `:accept_new_or_local_tunnel`,\n  `:accept_new`, or `:always`, which each refer to one of\n  [net-ssh](https://net-ssh.github.io/net-ssh/)'s `Net::SSH::Verifiers` subclasses.\n"
  },
  {
    "path": "website/content/docs/vagrantfile/tips.mdx",
    "content": "---\nlayout: docs\npage_title: Tips & Tricks - Vagrantfile\ndescription: |-\n  The Vagrantfile is a very flexible configuration format. Since it is just\n  Ruby, there is a lot you can do with it. However, in that same vein, since\n  it is Ruby, there are a lot of ways you can shoot yourself in the foot. When\n  using some of the tips and tricks on this page, please take care to use them\n  correctly.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Tips & Tricks\n\nThe Vagrantfile is a very flexible configuration format. Since it is just\nRuby, there is a lot you can do with it. However, in that same vein, since\nit is Ruby, there are a lot of ways you can shoot yourself in the foot. When\nusing some of the tips and tricks on this page, please take care to use them\ncorrectly.\n\n## Loop Over VM Definitions\n\nIf you want to apply a slightly different configuration to many\nmulti-machine machines, you can use a loop to do this. For example, if\nyou wanted to create three machines:\n\n```ruby\n(1..3).each do |i|\n  config.vm.define \"node-#{i}\" do |node|\n    node.vm.provision \"shell\",\n      inline: \"echo hello from node #{i}\"\n  end\nend\n```\n\n~> **Warning:** The inner portion of multi-machine definitions\nand provider overrides are lazy-loaded. This can cause issues if you change\nthe value of a variable used within the configs. For example, the loop below\n_does not work_:\n\n```ruby\n# THIS DOES NOT WORK!\nfor i in 1..3 do\n  config.vm.define \"node-#{i}\" do |node|\n    node.vm.provision \"shell\",\n      inline: \"echo hello from node #{i}\"\n  end\nend\n```\n\nThe `for i in ...` construct in Ruby actually modifies the value of `i`\nfor each iteration, rather than making a copy. Therefore, when you run this,\nevery node will actually provision with the same text.\n\nThis is an easy mistake to make, and Vagrant cannot really protect against it,\nso the best we can do is mention it here.\n\n## Overwrite host locale in SSH session\n\nUsually, host locale environment variables are passed to guest. It may cause\nfailures if the guest software do not support host locale. One possible solution\nis override locale in the `Vagrantfile`:\n\n```ruby\nENV[\"LC_ALL\"] = \"en_US.UTF-8\"\n\nVagrant.configure(\"2\") do |config|\n  # ...\nend\n```\n\nThe change is only visible within the `Vagrantfile`.\n"
  },
  {
    "path": "website/content/docs/vagrantfile/vagrant_settings.mdx",
    "content": "---\nlayout: docs\npage_title: config.vagrant - Vagrantfile\ndescription: |-\n  The settings within \"config.vagrant\" modify the behavior of Vagrant\n  itself.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant Settings\n\n**Config namespace: `config.vagrant`**\n\nThe settings within `config.vagrant` modify the behavior of Vagrant\nitself.\n\n## Available Settings\n\n- `config.vagrant.host` (string, symbol) - This sets the type of host machine\n  that is running Vagrant. By default this is `:detect`, which causes Vagrant to\n  auto-detect the host. Vagrant needs to know this information in order to perform\n  some host-specific things, such as preparing NFS folders if they're enabled.\n  You should only manually set this if auto-detection fails.\n\n- `config.vagrant.plugins` - (string, array, hash) - Define plugin, list of\n  plugins, or definition of plugins to install for the local project. Vagrant\n  will require these plugins be installed and available for the project. If\n  the plugins are not available, it will attempt to automatically install\n  them into the local project. When requiring a single plugin, a string can\n  be provided:\n\n  ```ruby\n  config.vagrant.plugins = \"vagrant-plugin\"\n  ```\n\n  If multiple plugins are required, they can be provided as an array:\n\n  ```ruby\n  config.vagrant.plugins = [\"vagrant-plugin\", \"vagrant-other-plugin\"]\n  ```\n\n  Plugins can also be defined as a Hash, which supports setting extra options\n  for the plugins. When a Hash is used, the key is the name of the plugin, and\n  the value is a Hash of options for the plugin. For example, to set an explicit\n  version of a plugin to install:\n\n  ```ruby\n  config.vagrant.plugins = {\"vagrant-scp\" => {\"version\" => \"1.0.0\"}}\n  ```\n\n  Supported options are:\n\n  - `entry_point` - Path for Vagrant to load plugin\n  - `sources` - Custom sources for downloading plugin\n  - `version` - Version constraint for plugin\n\n- `config.vagrant.sensitive` - (string, array) - Value or list of values that\n  should not be displayed in Vagrant's output. Value(s) will be removed from\n  Vagrant's normal UI output as well as logger output.\n\n  ```ruby\n  config.vagrant.sensitive = [\"MySecretPassword\", ENV[\"MY_TOKEN\"]]\n  ```\n"
  },
  {
    "path": "website/content/docs/vagrantfile/vagrant_version.mdx",
    "content": "---\nlayout: docs\npage_title: Minimum Vagrant Version - Vagrantfile\ndescription: |-\n  A set of Vagrant version requirements can be specified in the Vagrantfile\n  to enforce that people use a specific version of Vagrant with a Vagrantfile.\n  This can help with compatibility issues that may otherwise arise from using\n  a too old or too new Vagrant version with a Vagrantfile.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Minimum Vagrant Version\n\nA set of Vagrant version requirements can be specified in the Vagrantfile\nto enforce that people use a specific version of Vagrant with a Vagrantfile.\nThis can help with compatibility issues that may otherwise arise from using\na too old or too new Vagrant version with a Vagrantfile.\n\nVagrant version requirements should be specified at the top of a Vagrantfile\nwith the `Vagrant.require_version` helper:\n\n```ruby\nVagrant.require_version \">= 1.3.5\"\n```\n\nIn the case above, the Vagrantfile will only load if the version loading it\nis Vagrant 1.3.5 or greater.\n\nMultiple requirements can be specified as well:\n\n```ruby\nVagrant.require_version \">= 1.3.5\", \"< 1.4.0\"\n```\n"
  },
  {
    "path": "website/content/docs/vagrantfile/version.mdx",
    "content": "---\nlayout: docs\npage_title: Configuration Version - Vagrantfile\ndescription: |-\n  Configuration versions are the mechanism by which Vagrant 1.1+ is able to\n  remain backwards compatible with Vagrant 1.0.x Vagrantfiles, while introducing\n  dramatically new features and configuration options.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Configuration Version\n\nConfiguration versions are the mechanism by which Vagrant 1.1+ is able\nto remain [backwards compatible](/vagrant/docs/installation/backwards-compatibility)\nwith Vagrant 1.0.x Vagrantfiles, while introducing dramatically new features\nand configuration options.\n\nIf you run `vagrant init` today, the Vagrantfile will be in roughly the\nfollowing format:\n\n```ruby\nVagrant.configure(\"2\") do |config|\n  # ...\nend\n```\n\nThe `\"2\"` in the first line above represents the version of the configuration\nobject `config` that will be used for configuration for that block (the\nsection between the `do` and the `end`). This object can be very\ndifferent from version to version.\n\nCurrently, there are only two supported versions: \"1\" and \"2\". Version 1\nrepresents the configuration from Vagrant 1.0.x. \"2\" represents the configuration\nfor 1.1+ leading up to 2.0.x.\n\nWhen loading Vagrantfiles, Vagrant uses the proper configuration object\nfor each version, and properly merges them, just like any other configuration.\n\nThe important thing to understand as a general user of Vagrant is that\n_within a single configuration section_, only a single version can be used.\nYou cannot use the new `config.vm.provider` configurations in a version 1\nconfiguration section. Likewise, `config.vm.forward_port` will not work\nin a version 2 configuration section (it was renamed).\n\nIf you want, you can mix and match multiple configuration versions in the\nsame Vagrantfile. This is useful if you found some useful configuration\nsnippet or something that you want to use. Example:\n\n```ruby\nVagrant.configure(\"1\") do |config|\n  # v1 configs...\nend\n\nVagrant.configure(\"2\") do |config|\n  # v2 configs...\nend\n```\n\n-> **What is `Vagrant::Config.run`?** You may see this in Vagrantfiles. This was actually how Vagrant 1.0.x\ndid configuration. In Vagrant 1.1+, this is synonymous with `Vagrant.configure(\"1\")`.\n"
  },
  {
    "path": "website/content/docs/vagrantfile/winrm_settings.mdx",
    "content": "---\nlayout: docs\npage_title: config.winrm - Vagrantfile\ndescription: |-\n  The settings within \"config.winrm\" relate to configuring how Vagrant\n  will access your Windows guest over WinRM. As with most Vagrant settings, the\n  defaults are typically fine, but you can fine tune whatever you would like.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# WinRM Settings\n\n**Config namespace: `config.winrm`**\n\nThe settings within `config.winrm` relate to configuring how Vagrant\nwill access your Windows guest over WinRM. As with most Vagrant settings, the\ndefaults are typically fine, but you can fine tune whatever you would like.\n\nThese settings are only used if you've set your communicator type to `:winrm`.\n\n## Available Settings\n\n- `config.winrm.username` (string) - This sets the username that Vagrant will use\n  to login to the WinRM web service by default. Providers are free to override\n  this if they detect a more appropriate user. By default this is \"vagrant,\"\n  since that is what most public boxes are made as.\n\n- `config.winrm.password` (string) - This sets a password that Vagrant will use to\n  authenticate the WinRM user. By default this is \"vagrant,\" since that is\n  what most public boxes are made as.\n\n- `config.winrm.host` (string) - The hostname or IP to connect to the WinRM service.\n  By default this is empty, because the provider usually figures this out for\n  you.\n\n- `config.winrm.port` (integer) - The WinRM port to connect to, by default 5985. If ssl\n  is enabled, the default port is 5986.\n\n- `config.winrm.guest_port` (integer) - The port on the guest that WinRM is running on.\n  This is used by some providers to detect forwarded ports for WinRM. For\n  example, if this is set to 5985 (the default), and Vagrant detects a forwarded\n  port to port 5985 on the guest from port 4567 on the host, Vagrant will attempt\n  to use port 4567 to talk to the guest if there is no other option.\n\n- `config.winrm.transport` (symbol)- The transport used for WinRM communication.\n  Valid settings include: `:negotiate`, `:ssl`, and `:plaintext`. The default is `:negotiate`.\n\n- `config.winrm.basic_auth_only` (boolean) - Whether to use Basic Authentication. Defaults\n  to `false`. If set to `true` you should also use the `:plaintext` transport setting and\n  the Windows machine must be configured appropriately.\n\n  -> **Note:** It is strongly recommended that you only use basic authentication for\n  debugging purposes. Credentials will be transferred in plain text.\n\n- `config.winrm.execution_time_limit` (string) - The amount of time that is allowed to\n  complete task. This defaults to \"PT2H\", that is 2 hours.\n\n- `config.winrm.ssl_peer_verification` (boolean) - When set to `false` ssl certificate\n  validation is not performed. By default this is true.\n\n- `config.winrm.timeout` (integer) - The maximum amount of time to wait for a response\n  from the endpoint. This defaults to 1800 seconds. Note that this will not \"timeout\"\n  commands that exceed this amount of time to process, it just requires the endpoint to\n  report the status of the command before the given amount of time passes.\n\n- `config.winrm.max_tries` (integer) - The maximum number of times to retry opening\n  a shell after failure. This defaults to 20.\n\n- `config.winrm.retry_delay` (integer) - The amount of time to wait between retries and\n  defaults to 2 seconds.\n\n- `config.winrm.codepage` (string) - The WINRS_CODEPAGE which is the client's console\n  output code page. By default this is not set by Vagrant.\n\n  -> **Note:** Versions of Windows older than Windows 7/Server 2008 R2 may exhibit\n  undesirable behavior using the default UTF-8 codepage. When using these older\n  versions of Windows, its best to use the native code page of the server's locale.\n  For example, en-US servers will have a codepage of 437. The Windows `chcp` command\n  can be used to determine the value of the native codepage.\n"
  },
  {
    "path": "website/content/docs/vagrantfile/winssh_settings.mdx",
    "content": "---\nlayout: docs\npage_title: config.winssh - Vagrantfile\ndescription: >-\n  The settings within \"config.winssh\" relate to configuring how Vagrant\n\n  will access your machine over Windows OpenSSH. As with most Vagrant settings,\n  the\n\n  defaults are typically fine, but you can fine tune whatever you would like.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# WinSSH\n\nThe WinSSH communicator is built specifically for the Windows native\nport of OpenSSH. It does not rely on a POSIX-like environment which\nremoves the requirement of extra software installation (like cygwin)\nfor proper functionality.\n\nFor more information, see the [Win32-OpenSSH project page](https://github.com/PowerShell/Win32-OpenSSH/).\n\n# WinSSH Settings\n\nThe WinSSH communicator uses the same connection configuration options\nas the SSH communicator. These settings provide the information for the\ncommunicator to establish a connection to the VM.\n\nThe configuration options below are specific to the WinSSH communicator.\n\n**Config namespace: `config.winssh`**\n\n## Available Settings\n\n- `config.winssh.forward_agent` (boolean) - If `true`, agent forwarding over SSH\n  connections is enabled. Defaults to false.\n\n- `config.winssh.forward_env` (array of strings) - An array of host environment\n  variables to forward to the guest. If you are familiar with OpenSSH, this corresponds\n  to the `SendEnv` parameter.\n\n  ```ruby\n  config.winssh.forward_env = [\"CUSTOM_VAR\"]\n  ```\n\n- `config.winssh.proxy_command` (string) - A command-line command to execute that\n  receives the data to send to SSH on stdin. This can be used to proxy the SSH connection.\n  `%h` in the command is replaced with the host and `%p` is replaced with the port.\n\n- `config.winssh.keep_alive` (boolean) - If `true`, this setting SSH will send keep-alive\n  packets every 5 seconds by default to keep connections alive.\n\n- `config.winssh.shell` (string) - The shell to use when executing SSH commands from\n  Vagrant. By default this is `powershell`. Valid values are `\"cmd\"` or `\"powershell\"`.\n  When the WinSSH provider is enabled, this shell will only be used internally. When\n  running `vagrant ssh` you will be provided the shell configured by the guest.\n\n- `config.winssh.export_command_template` (string) - The template used to generate\n  exported environment variables in the active session. This can be useful\n  when using a Bourne incompatible shell like C shell. The template supports\n  two variables which are replaced with the desired environment variable key and\n  environment variable value: `%ENV_KEY%` and `%ENV_VALUE%`. The default template\n  for a `cmd` configured shell is:\n\n  ```ruby\n  config.winssh.export_command_template = 'set %ENV_KEY%=\"%ENV_VALUE%\"'\n  ```\n\n  The default template for a `powershell` configured shell is:\n\n  ```ruby\n  config.winssh.export_command_template = '$env:%ENV_KEY%=\"%ENV_VALUE%\"'\n  ```\n\n- `config.winssh.sudo_command` (string) - The command to use when executing a command\n  with `sudo`. This defaults to `%c` (assumes vagrant user is an administrator\n  and needs no escalation). The `%c` will be replaced by the command that is\n  being executed.\n\n- `config.winssh.upload_directory` (string) - The upload directory used on the guest\n  to store scripts for execute. This is set to `C:\\Windows\\Temp` by default.\n"
  },
  {
    "path": "website/content/intro/contributing-guide.mdx",
    "content": "---\nlayout: intro\npage_title: Contributing Guide\ndescription: |-\n  Vagrant is a community tool. Contributing ideas as well as code to Vagrant\n  is a great way to be involved in the community. Follow the Contributing\n  Style Guide to ensure the Vagrant project stays healthy.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Contributing to Vagrant During Hacktoberfest\n\nHashiCorp has some [community guidelines](https://www.hashicorp.com/community-guidelines)\nto ensure our public forums are a safe space for everyone. When contributing\nto Vagrant, please respect the same guidelines.\n\n## Getting started with contributions\n\nThere are plenty of things to work on in Vagrant, not limited to writing\nsoftware. This includes:\n\n- Fixing documentation for accuracy\n- Fixing documentation for grammar and punctuation\n- Finding bugs and submitting detailed issues\n- Fixing or adding plugins\n- Squashing bugs!\n\nWhen looking through the [Vagrant issue tracker](https://github.com/hashicorp/vagrant/issues)\nfor a good first issue, it may be useful to filter by tags. For example, the\n[`needs-community-help`](https://github.com/hashicorp/vagrant/labels/needs-community-help),\n[`needs-repro`](https://github.com/hashicorp/vagrant/labels/needs-repro), or\n[`good-first-issue`](https://github.com/hashicorp/vagrant/labels/good-first-issue)\ntags.\n\nOnce you have found a good issue, you might want to get a development installation\nworking. Follow the instructions for [installing from source](/vagrant/docs/installation/source)\nin order to setup a development environment.\n\n## Submitting your first pull request\n\nCheck out the [Vagrant contribution guide](https://github.com/hashicorp/vagrant/blob/main/.github/CONTRIBUTING.md)\nfor instructions on what can be expected for the pull request lifecycle.\n\nWhen submitting your changes, please ensure you have done the following:\n\n- You have written tests for the changes made\n- You have run the test suite using `bundle exec rake` and all tests pass\n- You have written a descriptive commit message\n- Reference the issue the pull requests resolves if applicable\n"
  },
  {
    "path": "website/content/intro/index.mdx",
    "content": "---\nlayout: intro\npage_title: Introduction\ndescription: |-\n  Vagrant is a tool for building complete development environments. With an\n  easy-to-use workflow and focus on automation, Vagrant lowers development\n  environment setup time, increases development/production parity, and makes\n  the \"it works on my machine\" excuse a relic of the past.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Introduction to Vagrant\n\nVagrant is a tool for building and managing virtual machine environments in a\nsingle workflow. With an easy-to-use workflow and focus on automation, Vagrant\nlowers development environment setup time, increases production parity, and\nmakes the \"works on my machine\" excuse a relic of the past.\n\nIf you are already familiar with the basics of Vagrant, the\n[documentation](/vagrant/docs) provides a better reference build for all\navailable features and internals.\n\n## Why Vagrant?\n\nVagrant provides easy to configure, reproducible, and portable work environments\nbuilt on top of industry-standard technology and controlled by a single\nconsistent workflow to help maximize the productivity and flexibility of you and\nyour team.\n\nTo achieve its magic, Vagrant stands on the shoulders of giants. Machines\nare provisioned on top of VirtualBox, VMware, AWS, or\n[any other provider](/vagrant/docs/providers/). Then, industry-standard\n[provisioning tools](/vagrant/docs/provisioning/)\nsuch as shell scripts, Chef, or Puppet can automatically install\nand configure software on the virtual machine.\n\n### For Developers\n\nIf you are a **developer**, Vagrant will isolate dependencies and their\nconfiguration within a single disposable, consistent environment, without\nsacrificing any of the tools you are used to working with (editors, browsers,\ndebuggers, etc.). Once you or someone else creates a single\n[Vagrantfile](/vagrant/docs/vagrantfile/), you just need to `vagrant up` and everything\nis installed and configured for you to work. Other members of your team create\ntheir development environments from the same configuration, so whether you are\nworking on Linux, Mac OS X, or Windows, all your team members are running code\nin the same environment, against the same dependencies, all configured the same\nway. Say goodbye to \"works on my machine\" bugs.\n\n### For Operators\n\nIf you are an **operations engineer** or **DevOps engineer**, Vagrant gives you a disposable\nenvironment and consistent workflow for developing and testing infrastructure\nmanagement scripts. You can quickly test things like shell scripts, Chef\ncookbooks, Puppet modules, and more using local virtualization such as\nVirtualBox or VMware. Then, with the _same configuration_, you can test these\nscripts on remote clouds such as AWS or RackSpace with the _same workflow_.\nDitch your custom scripts to recycle EC2 instances, stop juggling SSH prompts to\nvarious machines, and start using Vagrant to bring sanity to your life.\n\n### For Designers\n\nIf you are a **designer**, Vagrant will automatically set everything up that is\nrequired for that web app in order for you to focus on doing what you do best:\ndesign. Once a developer configures Vagrant, you do not need to worry about how\nto get that app running ever again. No more bothering other developers to help\nyou fix your environment so you can test designs. Just check out the code,\n`vagrant up`, and start designing.\n\n### For Everyone\n\nVagrant is designed for everyone as the easiest and fastest way to create a\nvirtualized environment!\n"
  },
  {
    "path": "website/content/intro/support.mdx",
    "content": "---\nlayout: intro\npage_title: Vagrant Support\ndescription: \"Find Vagrant & HCP Vagrant Support\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Contact Support\n\nTo submit a support ticket:\n1.  Visit the HashiCorp [Support Page](https://support.hashicorp.com/hc/en-us) to explore our support knowledge base for answers to your questions. If you do not find what you need, click `Open a new ticket` to get in touch with us directly.\n2. Submit a new support ticket by selecting `Cloud Customer` and choose `HCP Vagrant` from the dropdown menu.\n\n### Free Support\n\nWe do not currently publish support SLAs for free accounts but aim to respond as quickly as possible."
  },
  {
    "path": "website/content/intro/vs/cli-tools.mdx",
    "content": "---\nlayout: intro\npage_title: Vagrant vs. CLI Tools\ndescription: |-\n  Virtualization software like VirtualBox and VMware comes with command line\n  utilities for managing the lifecycle of machines on their platform. Vagrant\n  actually uses many of these utilities internally. The difference between these\n  CLI tools and Vagrant is that Vagrant provides a declarative, reproducible,\n  idempotent workflow.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant vs. CLI Tools\n\nVirtualization software like VirtualBox and VMware come with command line\nutilities for managing the lifecycle of machines on their platform. Many\npeople make use of these utilities to write their own automation. Vagrant\nactually uses many of these utilities internally.\n\nThe difference between these CLI tools and Vagrant is that Vagrant builds\non top of these utilities in a number of ways while still providing a\nconsistent workflow. Vagrant supports multiple synced folder types, multiple\nprovisioners to set up the machine, automatic SSH setup, creating HTTP tunnels\ninto your development environment and more. All of these can be configured\nusing a single simple configuration file.\n\nVagrant still has a number of improvements over manual scripting even if you\nignore all the higher-level features Vagrant provides. The command line\nutilities provided by virtualization software often change each version\nor have subtle bugs with workarounds. Vagrant automatically detects the\nversion, uses the correct flags, and can work around known issues. So if\nyou're using one version of VirtualBox and a co-worker is using a different\nversion, Vagrant will still work consistently.\n\nFor highly specific workflows that don't change often, it can still be\nbeneficial to maintain custom scripts. Vagrant is targeted at building\ndevelopment environments but some advanced users still use the CLI tools\nunderneath to do other manual things.\n"
  },
  {
    "path": "website/content/intro/vs/docker.mdx",
    "content": "---\nlayout: intro\npage_title: Vagrant vs. Docker\ndescription: |-\n  Vagrant and Docker both provide isolation primitives. This page details the\n  differences between them.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant vs. Docker\n\nVagrant is a tool focused on providing a consistent development environment\nworkflow across multiple operating systems. Docker is a container management\nthat can consistently run software as long as a containerization system exists.\n\nContainers are generally more lightweight than virtual machines, so starting\nand stopping containers is extremely fast. Docker uses the native\ncontainerization functionality on macOS, Linux, and Windows.\n\nCurrently, Docker lacks support for certain operating systems (such as\nBSD). If your target deployment is one of these operating systems,\nDocker will not provide the same production parity as a tool like Vagrant.\nVagrant will allow you to run a Windows development environment on Mac or Linux,\nas well.\n\nFor microservice heavy environments, Docker can be attractive because you\ncan easily start a single Docker VM and start many containers above that\nvery quickly. This is a good use case for Docker. Vagrant can do this as well\nwith the Docker provider. A primary benefit for Vagrant is a consistent workflow\nbut there are many cases where a pure-Docker workflow does make sense.\n\nBoth Vagrant and Docker have a vast library of community-contributed \"images\"\nor \"boxes\" to choose from.\n"
  },
  {
    "path": "website/content/intro/vs/index.mdx",
    "content": "---\nlayout: intro\npage_title: Vagrant vs. Other Software\ndescription: |-\n  Vagrant is not the only tool to manage virtual machines and development\n  environments. These pages detail similar tools to Vagrant.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant vs. Other Software\n\nVagrant is not the only tool to manage virtual machines and development\nenvironments. This section compares Vagrant to these other software choices.\n\nDue to the bias of the comparisons, we attempt to only use facts. If you find\nsomething that is invalid or out of date in the comparisons, please [open an\nissue](https://github.com/hashicorp/vagrant/issues) and we'll address it as soon\nas possible.\n\nUse the navigation on the left to read comparisons of Vagrant versus similar\nsoftware.\n"
  },
  {
    "path": "website/content/intro/vs/terraform.mdx",
    "content": "---\nlayout: intro\npage_title: Vagrant vs. Terraform\ndescription: |-\n  Vagrant is a tool for managing virtual machines. Terraform is another open\n  source tool from HashiCorp which enables infrastructure as code.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant vs. Terraform\n\nVagrant and [Terraform][terraform] are both projects from [HashiCorp][hashicorp].\nVagrant is a tool focused for managing development environments and\nTerraform is a tool for building infrastructure.\n\nTerraform can describe complex sets of infrastructure that exist\nlocally or remotely. It is focused on building and changing that infrastructure\nover time. The minimal aspects of virtual machine lifecycle can be reproduced\nin Terraform, sometimes leading to confusion with Vagrant.\n\nVagrant provides a number of higher level features that Terraform doesn't.\nSynced folders, automatic networking, HTTP tunneling, and more are features\nprovided by Vagrant to ease development environment usage. Because Terraform\nis focused on infrastructure management and not development environments,\nthese features are out of scope for that project.\n\nThe primary usage of Terraform is for managing remote resources in cloud\nproviders such as AWS. Terraform is designed to be able to manage extremely\nlarge infrastructures that span multiple cloud providers. Vagrant is designed\nprimarily for local development environments that use only a handful of\nvirtual machines at most.\n\nVagrant is for development environments. Terraform is for more general\ninfrastructure management.\n\n[hashicorp]: https://www.hashicorp.com\n[terraform]: https://www.terraform.io/\n"
  },
  {
    "path": "website/content/vagrant-cloud/api/v1.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Vagrant Cloud API (Version 1)\ndescription: \"Vagrant Cloud provides an API for users to interact with Vagrant Cloud for experimentation, automation, or building new features and tools on top of our existing application.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant Cloud API (Version 1)\n\n## Using the API\n\nVagrant Cloud provides an API for users to interact with Vagrant Cloud for experimentation, automation, or building new features and tools on top of our existing application.\n\n### Authentication\n\nSome API endpoints require authentication to create new resources, update or delete existing resources, or to read a private resource.\n\nClients can authenticate using an authentication token.\nThe token can be passed to Vagrant Cloud one of two ways:\n\n1. (Preferred) Set the `Authorization` header to `\"Bearer \"` and the value of the authentication token.\n2. Pass the authentication token as an `access_token` URL parameter (_NOTE_: deprecated).\n\nExamples below will set the header, but feel free to use whichever method is easier for your implementation.\n\n-> The `X-Atlas-Token` header is also supported for backwards-compatibility.\n\n### Request and Response Format\n\nRequests to Vagrant Cloud which include data attributes (`POST` or `PUT`/`PATCH`) should set the `Content-Type` header to `\"application/json\"`, and include a valid JSON body with the request.\n\nJSON responses may include an `errors` key, which will contain an array of error strings, as well as a `success` key.\nFor example:\n\n```json\n{\n  \"errors\": [\"Resource not found!\"],\n  \"success\": false\n}\n```\n\n### Response Codes\n\nVagrant Cloud may respond with the following response codes, depending on the status of the request and context:\n\n#### Success\n\n##### **200** OK\n\n##### **201** Created\n\n##### **204** No Content\n\n#### Client Errors\n\n##### **401** Unauthorized\n\nYou do not have authorization to access the requested resource.\n\n##### **402** Payment Required\n\nYou are trying to access a resource which is delinquent on billing.\nPlease contact the owner of the resource so that they can update their billing information.\n\n##### **403** Forbidden\n\nYou are attempting to use the system in a way which is not allowed.\nThere could be required request parameters that are missing, or one of the parameters is invalid.\nPlease check the response `errors` key, and double-check the examples below for any discrepancies.\n\n##### **404** Not Found\n\nThe resource you are trying to access does not exist. This may also be returned if you attempt to access a private resource that you don't have authorization to view.\n\n##### **422** Unprocessable Entity\n\n##### **429** Too Many Requests\n\nYou are currently being rate-limited. Please decrease your frequency of usage or [contact support](/vagrant/intro/support) with a description of your use case so that we can consider creating an exception.\n\n#### Server Errors\n\n##### **500** Internal Server Error\n\nThe server failed to respond to the request for an unknown reason.\nPlease [contact support](/vagrant/intro/support) with a description of the problem so that we can investigate.\n\n##### **503** Service Unavailable\n\nVagrant Cloud is temporarily in maintenance mode.\nPlease check the [HashiCorp Status Site](http://status.hashicorp.com) for more information.\n\n## Creating a usable box from scratch\n\n-> This assumes that you have a valid Vagrant Cloud authentication token. You can [create one via the API](#create-a-token), or [create one on the Vagrant Cloud website](https://app.vagrantup.com/settings/security).\n\nIn order to create a usable box on Vagrant Cloud, perform the following steps:\n\n1. [Create a new box](#create-a-box)\n1. [Create a new version](#create-a-version)\n1. [Create a new provider](#create-a-provider)\n1. [Upload a box image for that provider](#upload-a-provider)\n1. [Release the version](#release-a-version)\n\n#### Example Requests\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\n# Create a new box\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/boxes \\\n  --data '{ \"box\": { \"username\": \"myuser\", \"name\": \"test\" } }'\n\n# Create a new version\n\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/versions \\\n  --data '{ \"version\": { \"version\": \"1.2.3\" } }'\n\n# Create a new provider\n\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/providers \\\n  --data '{ \"provider\": { \"name\": \"virtualbox\" } }'\n\n# Prepare the provider for upload/get an upload URL\n\nresponse=$(curl \\\n    --request GET \\\n    --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n    https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/provider/virtualbox/upload)\n\n# Extract the upload URL from the response (requires the jq command)\n\nupload_path=$(echo \"$response\" | jq .upload_path)\n\n# Perform the upload\n\ncurl --request PUT \"${upload_path}\" --upload-file virtualbox-1.2.3.box\n\n# Release the version\n\ncurl \\\n  --request PUT \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/release\n\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\n# Create a new box\n\napi.post \"/api/v1/boxes\",\njson: { box: { username: \"myuser\", name: \"test\" } }\n\n# Create a new version\n\napi.post \"/api/v1/box/myuser/test/versions\",\njson: { version: { version: \"1.2.3\" } }\n\n# Create a new provider\n\napi.post \"/api/v1/box/myuser/test/version/1.2.3/providers\",\njson: { provider: { name: \"virtualbox\" } }\n\n# Prepare the provider for upload\n\nresponse = api.get(\"/api/v1/box/myuser/test/version/1.2.3/provider/virtualbox/upload\")\n\n# Extract the upload URL\n\nupload_path = response.parse['upload_path']\n\n# Upload the box image\n\nHTTP.put upload_path, body: File.open(\"virtualbox-1.2.3.box\")\n\n# Release the version\n\napi.put(\"/api/v1/box/myuser/test/version/1.2.3/release\")\n```\n\n</Tab>\n</Tabs>\n\n## Authentication\n\n### Create a token\n\n`POST /api/v1/authenticate`\n\nCreates a new token for the given user.\n\n#### Arguments\n\n- `token`\n- `description` (Optional) - A description of the token.\n- `two_factor`\n- `code` - A two-factor authentication code. Required to use this API method if 2FA is enabled. See [Request a 2FA code](#request-a-2fa-code) if not using a TOTP application.\n- `user`\n- `login` - Username or email address of the user authenticating.\n- `password` - The user's password.\n\n#### Example Request\n\n<Tabs>\n\n<Tab heading='cURL'>\n\n```shell\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  https://app.vagrantup.com/api/v1/authenticate \\\n  --data '\n    {\n      \"token\": {\n        \"description\": \"Login from cURL\"\n      },\n      \"user\": {\n        \"login\": \"myuser\",\n        \"password\": \"secretpassword\"\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading='Ruby'>\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n\"Content-Type\" => \"application/json\"\n)\n\nresponse = api.post(\"/api/v1/authenticate\", json: {\n  token: { description: \"Login from Ruby\" },\n  user: { login: \"myuser\", password: \"secretpassword\" }\n})\n\nif response.status.success? # Success, the response attributes are available here.\n  p response.parse\nelse # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"description\": \"Login from cURL\",\n  \"token\": \"qwlIE1qBVUafsg.atlasv1.FLwfJSSYkl49i4qZIu8R31GBnI9r8DrW4IQKMppkGq5rD264lRksTqaIN0zY9Bmy0zs\",\n  \"token_hash\": \"7598236a879ecb42cb0f25399d6f25d1d2cfbbc6333392131bbdfba325eb352795c169daa4a61a8094d44afe817a857e0e5fc7dc72a1401eb434577337d1246c\",\n  \"created_at\": \"2017-10-18T19:16:24.956Z\"\n}\n```\n\n### Validate a token\n\n`GET /api/v1/authenticate`\n\nResponds [`200 OK`](#200-ok) if the authentication request was successful, otherwise responds [`401 Unauthorized`](#401-unauthorized).\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/authenticate\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v1/authenticate\")\n\nif response.status.success? # Success, the response attributes are available here.\n  p response.parse\nelse # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n### Delete a token\n\n`DELETE /api/v1/authenticate`\n\nResponds [`204 OK`](#204-no-content) if the deletion request was successful, otherwise responds [`401 Unauthorized`](#401-unauthorized).\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request DELETE \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/authenticate\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.delete(\"/api/v1/authenticate\")\n\nif response.status.success? # Success, the response attributes are available here.\n  p response.parse\nelse # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n### Request a 2FA code\n\n`POST /api/v1/two-factor/request-code`\n\nSends a 2FA code to the requested delivery method.\n\n#### Arguments\n\n- `two_factor`\n- `delivery_method` - A valid 2FA delivery method. Currently only `sms` is supported.\n- `user`\n- `login` - Username or email address of the user authenticating.\n- `password` - The user's password.\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  https://app.vagrantup.com/api/v1/two-factor/request-code \\\n  --data '\n    {\n      \"two_factor\": {\n        \"delivery_method\": \"sms\"\n      },\n      \"user\": {\n        \"login\": \"myuser\",\n        \"password\": \"secretpassword\"\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\"\n)\n\nresponse = api.post(\"/api/v1/two-factor/request-code\", json: {\n  two_factor: { delivery_method: \"sms\" },\n  user: { login: \"myuser\", password: \"secretpassword\" }\n})\n\nif response.status.success? # Success, the response attributes are available here.\n  p response.parse\nelse # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"two_factor\": {\n    \"obfuscated_destination\": \"SMS number ending in 7890\"\n  }\n}\n```\n\n## Organizations\n\n### Read an organization\n\n`GET /api/v1/user/:username`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/user/myuser\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v1/user/myuser\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"username\": \"myuser\",\n  \"avatar_url\": \"https://www.gravatar.com/avatar/130a640278870c3dada38b3d912ee022?s=460&d=mm\",\n  \"profile_html\": \"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>\\n\",\n  \"profile_markdown\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\",\n  \"boxes\": []\n}\n```\n\n## Search\n\n### Search for boxes\n\n`GET /api/v1/search`\n\n#### Arguments\n\n- `q` - (Optional) The search query. Results will match the `username`, `name`, or `short_description` fields for a box. If omitted, the top boxes based on `sort` and `order` will be returned (defaults to \"downloads desc\").\n- `provider` - (Optional) Filter results to boxes supporting for a specific provider.\n- `sort` - (Optional, default: `\"downloads\"`) The field to sort results on. Can be one of `\"downloads\"`, `\"created\"`, or `\"updated\"`.\n- `order` - (Optional, default: `\"desc\"`) The order to return the sorted field in. Can be `\"desc\"` os `\"asc\"`.\n- `limit` - (Optional, default: `10`) The number of results to return (max of 100).\n- `page` - (Optional, default: `1`)\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  \"https://app.vagrantup.com/api/v1/search?q=test&provider=virtualbox\"\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v1/search\", params: {\n  q: \"test\",\n  provider: \"virtualbox\"\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"boxes\": [\n    {\n      \"created_at\": \"2017-10-20T14:19:59.842Z\",\n      \"updated_at\": \"2017-10-20T15:23:53.363Z\",\n      \"tag\": \"myuser/test\",\n      \"name\": \"test\",\n      \"short_description\": \"My dev box\",\n      \"description_html\": \"<p>My development Vagrant box</p>\\n\",\n      \"username\": \"myuser\",\n      \"description_markdown\": \"My development Vagrant box\",\n      \"private\": true,\n      \"downloads\": 123,\n      \"current_version\": {\n        \"version\": \"1.2.3\",\n        \"status\": \"active\",\n        \"description_html\": \"<p>A new version</p>\\n\",\n        \"description_markdown\": \"A new version\",\n        \"created_at\": \"2017-10-20T15:23:17.184Z\",\n        \"updated_at\": \"2017-10-20T15:23:53.355Z\",\n        \"number\": \"1.2.3\",\n        \"release_url\": \"https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/release\",\n        \"revoke_url\": \"https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/revoke\",\n        \"providers\": [\n          {\n            \"name\": \"virtualbox\",\n            \"hosted\": false,\n            \"hosted_token\": null,\n            \"original_url\": \"https://example.com/virtualbox-1.2.3.box\",\n            \"created_at\": \"2017-10-20T15:23:35.718Z\",\n            \"updated_at\": \"2017-10-20T15:23:35.718Z\",\n            \"download_url\": \"https://vagrantcloud.com/myuser/boxes/test/versions/1.2.3/providers/virtualbox.box\"\n          }\n        ]\n      }\n    }\n  ]\n}\n```\n\n## Boxes\n\n### Read a box\n\n`GET /api/v1/box/:username/:name`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v1/box/myuser/test\")\n\nif response.status.success? # Success, the response attributes are available here.\n  p response.parse\nelse # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"created_at\": \"2017-10-20T14:19:59.842Z\",\n  \"updated_at\": \"2017-10-20T15:23:53.363Z\",\n  \"tag\": \"myuser/test\",\n  \"name\": \"test\",\n  \"short_description\": \"My dev box\",\n  \"description_html\": \"<p>My development Vagrant box</p>\\n\",\n  \"username\": \"myuser\",\n  \"description_markdown\": \"My development Vagrant box\",\n  \"private\": true,\n  \"downloads\": 123,\n  \"current_version\": {\n    \"version\": \"1.2.3\",\n    \"status\": \"active\",\n    \"description_html\": \"<p>A new version</p>\\n\",\n    \"description_markdown\": \"A new version\",\n    \"created_at\": \"2017-10-20T15:23:17.184Z\",\n    \"updated_at\": \"2017-10-20T15:23:53.355Z\",\n    \"number\": \"1.2.3\",\n    \"release_url\": \"https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/release\",\n    \"revoke_url\": \"https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/revoke\",\n    \"providers\": [\n      {\n        \"name\": \"virtualbox\",\n        \"hosted\": false,\n        \"hosted_token\": null,\n        \"original_url\": \"https://example.com/virtualbox-1.2.3.box\",\n        \"created_at\": \"2017-10-20T15:23:35.718Z\",\n        \"updated_at\": \"2017-10-20T15:23:35.718Z\",\n        \"download_url\": \"https://vagrantcloud.com/myuser/boxes/test/versions/1.2.3/providers/virtualbox.box\"\n      }\n    ]\n  },\n  \"versions\": [\n    {\n      \"version\": \"1.2.3\",\n      \"status\": \"active\",\n      \"description_html\": \"<p>A new version</p>\\n\",\n      \"description_markdown\": \"A new version\",\n      \"created_at\": \"2017-10-20T15:23:17.184Z\",\n      \"updated_at\": \"2017-10-20T15:23:53.355Z\",\n      \"number\": \"1.2.3\",\n      \"release_url\": \"https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/release\",\n      \"revoke_url\": \"https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/revoke\",\n      \"providers\": [\n        {\n          \"name\": \"virtualbox\",\n          \"hosted\": false,\n          \"hosted_token\": null,\n          \"original_url\": \"https://example.com/virtualbox-1.2.3.box\",\n          \"created_at\": \"2017-10-20T15:23:35.718Z\",\n          \"updated_at\": \"2017-10-20T15:23:35.718Z\",\n          \"download_url\": \"https://vagrantcloud.com/myuser/boxes/test/versions/1.2.3/providers/virtualbox.box\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### Create a box\n\n`POST /api/v1/boxes`\n\n#### Arguments\n\n- `box`\n  - `username` - The username of the organization that will own this box.\n  - `name` - The name of the box.\n  - `short_description` - A short summary of the box.\n  - `description` - A longer description of the box. Can be formatted with [Markdown][markdown].\n  - `is_private` (Optional, default: `true`) - Whether or not this box is private.\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/boxes \\\n  --data '\n    {\n      \"box\": {\n        \"username\": \"myuser\",\n        \"name\": \"test\",\n        \"short_description\": \"My dev box\",\n        \"description\": \"My development Vagrant box\",\n        \"is_private\": true\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.post(\"/api/v1/boxes\", json: {\n  box: {\n    username: \"myuser\",\n    name: \"test\",\n    short_description: \"My dev box\",\n    description: \"My development Vagrant box\",\n    is_private: true\n  }\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a box](#read-a-box).\n\n### Update a box\n\n`PUT /api/v1/box/:username/:name`\n\n#### Arguments\n\n- `box`\n  - `name` - The name of the box.\n  - `short_description` - A short summary of the box.\n  - `description` - A longer description of the box. Can be formatted with [Markdown](https://daringfireball.net/projects/markdown/syntax).\n  - `is_private` (Optional, default: `true`) - Whether or not this box is private.\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request PUT \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test \\\n  --data '\n    {\n      \"box\": {\n        \"name\": \"test\",\n        \"short_description\": \"My dev box\",\n        \"description\": \"My development Vagrant box\",\n        \"is_private\": true\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.put(\"/api/v1/box/myuser/test\", json: {\n  box: {\n    name: \"test\",\n    short_description: \"My dev box\",\n    description: \"My development Vagrant box\",\n    is_private: true\n  }\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n### Delete a box\n\n`DELETE /api/v1/box/:username/:name`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request DELETE \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.delete(\"/api/v1/box/myuser/test\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a box](#read-a-box).\n\n## Versions\n\n### Read a version\n\n`GET /api/v1/box/:username/:name/version/:version`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v1/box/myuser/test/version/1.2.3\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"version\": \"1.2.3\",\n  \"status\": \"active\",\n  \"description_html\": \"<p>A new version</p>\\n\",\n  \"description_markdown\": \"A new version\",\n  \"created_at\": \"2017-10-20T15:23:17.184Z\",\n  \"updated_at\": \"2017-10-20T15:23:53.355Z\",\n  \"number\": \"1.2.3\",\n  \"release_url\": \"https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/release\",\n  \"revoke_url\": \"https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/revoke\",\n  \"providers\": [\n    {\n      \"name\": \"virtualbox\",\n      \"hosted\": false,\n      \"hosted_token\": null,\n      \"original_url\": \"https://example.com/virtualbox-1.2.3.box\",\n      \"created_at\": \"2017-10-20T15:23:35.718Z\",\n      \"updated_at\": \"2017-10-20T15:23:35.718Z\",\n      \"download_url\": \"https://vagrantcloud.com/myuser/boxes/test/versions/1.2.3/providers/virtualbox.box\"\n    }\n  ]\n}\n```\n\n### Create a version\n\n`POST /api/v1/box/:username/:name/versions`\n\n-> New versions start as `unreleased`. You must create a valid provider before releasing a new version.\n\n#### Arguments\n\n- `version`\n  - `version` - The version number of this version.\n  - `description` - A description for this version. Can be formatted with [Markdown](https://daringfireball.net/projects/markdown/syntax).\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/versions \\\n  --data '\n    {\n      \"version\": {\n        \"version\": \"1.2.3\",\n        \"description\": \"A new version\"\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.post(\"/api/v1/box/myuser/test/versions\", json: {\n  version: {\n    version: \"1.2.3\",\n    description: \"A new version\"\n  }\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a version](#read-a-version).\n\n### Update a version\n\n`PUT /api/v1/box/:username/:name/version/1.2.3`\n\n#### Arguments\n\n- `version`\n  - `version` - The version number of this version.\n  - `description` - A description for this version. Can be formatted with [Markdown][markdown].\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request PUT \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3 \\\n  --data '\n    {\n      \"version\": {\n        \"version\": \"1.2.3\",\n        \"description\": \"A new version\"\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.put(\"/api/v1/box/myuser/test/version/1.2.3\", json: {\n  version: {\n    name: \"1.2.3\",\n    description: \"A new version\"\n  }\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a version](#read-a-version).\n\n### Delete a version\n\n`DELETE /api/v1/box/:username/:name/version/:version`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request DELETE \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.delete(\"/api/v1/box/myuser/test/version/1.2.3\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a version](#read-a-version).\n\n### Release a version\n\n`PUT /api/v1/box/:username/:name/version/1.2.3/release`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request PUT \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/release\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.put(\"/api/v1/box/myuser/test/version/1.2.3/release\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a version](#read-a-version).\n\n### Revoke a version\n\n`PUT /api/v1/box/:username/:name/version/1.2.3/revoke`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request PUT \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/revoke\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.put(\"/api/v1/box/myuser/test/version/1.2.3/revoke\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a version](#read-a-version).\n\n## Providers\n\n### Read a provider\n\n`GET /api/v1/box/:username/:name/version/:version/provider/:provider`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/provider/virtualbox\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v1/box/myuser/test/version/1.2.3/provider/virtualbox\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"name\": \"virtualbox\",\n  \"hosted\": false,\n  \"hosted_token\": null,\n  \"original_url\": \"https://example.com/virtualbox-1.2.3.box\",\n  \"created_at\": \"2017-10-20T15:23:35.718Z\",\n  \"updated_at\": \"2017-10-20T15:23:35.718Z\",\n  \"download_url\": \"https://vagrantcloud.com/myuser/boxes/test/versions/1.2.3/providers/virtualbox.box\",\n  \"checksum\": \"a59e7332e8bbe896f11f478fc61fa8a6\",\n  \"checksum_type\": \"md5\"\n}\n```\n\n### Create a provider\n\n`POST /api/v1/box/:username/:name/version/:version/providers`\n\n#### Arguments\n\n- `provider`\n  - `name` - The name of the provider.\n  - `url` - A valid URL to download this provider. If omitted, you must [upload](#upload-a-provider) the Vagrant box image for this provider to Vagrant Cloud before the provider can be used.\n  - `checksum` - Computed checksum of the box assets. When set, Vagrant will compute the checksum of the downloaded box asset and validate it matches this value.\n  - `checksum_type` - Type of checksum used. Currently supported values: md5, sha1, sha256, sha384, and sha512\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/providers \\\n  --data '\n    {\n      \"provider\": {\n        \"checksum\": \"a59e7332e8bbe896f11f478fc61fa8a6\",\n        \"checksum_type\": \"md5\",\n        \"name\": \"virtualbox\",\n        \"url\": \"https://example.com/virtualbox-1.2.3.box\"\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.post(\"/api/v1/box/myuser/test/version/1.2.3/providers\", json: {\n  provider: {\n    name: \"virtualbox\",\n    url: \"https://example.com/virtualbox-1.2.3.box\"\n  }\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a provider](#read-a-provider).\n\n### Update a provider\n\n`PUT /api/v1/box/:username/:name/version/:version/provider/:provider`\n\n#### Arguments\n\n- `provider`\n  - `name` - The name of the provider.\n  - `url` - A valid URL to download this provider. If omitted, you must [upload](#upload-a-provider) the Vagrant box image for this provider to Vagrant Cloud before the provider can be used.\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request PUT \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/provider/virtualbox \\\n  --data '\n    {\n      \"provider\": {\n        \"checksum\": \"a59e7332e8bbe896f11f478fc61fa8a6\",\n        \"checksum_type\": \"md5\",\n        \"name\": \"virtualbox\",\n        \"url\": \"https://example.com/virtualbox-1.2.3.box\"\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.put(\"/api/v1/box/myuser/test/version/1.2.3/provider/virtualbox\", json: {\n  provider: {\n    name: \"virtualbox\",\n    url: \"https://example.com/virtualbox-1.2.3.box\"\n  }\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a provider](#read-a-provider).\n\n### Delete a provider\n\n`DELETE /api/v1/box/:username/:name/version/:version/provider/:provider`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request DELETE \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/provider/virtualbox\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.delete(\"/api/v1/box/myuser/test/version/1.2.3/provider/virtualbox\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a provider](#read-a-provider).\n\n### Upload a provider\n\n`GET /api/v1/box/:username/:name/version/:version/provider/:provider/upload`\n\nPrepares the provider for upload, and returns a JSON blob containing an `upload_path`.\n\n~> The upload must begin shortly after the response is returned, otherwise the URL will expire. If the URL expires, you can request this same API method again for a new upload URL.\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\nresponse=$(curl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/provider/virtualbox/upload)\n\n# Requires the jq command\nupload_path=$(echo \"$response\" | jq .upload_path)\n\ncurl \\\n  --request PUT \\\n  --upload-file virtualbox-1.2.3.box \\\n  \"${upload_path}\"\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v1/box/myuser/test/version/1.2.3/provider/virtualbox/upload\")\n\nif response.status.success?\n  # Success, you can now upload the box image to the returned URL\n  upload_path = response.parse['upload_path']\n  HTTP.post upload_path, body: File.open(\"virtualbox-1.2.3.box\")\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"upload_path\": \"https://archivist.hashicorp.com/v1/object/630e42d9-2364-2412-4121-18266770468e\"\n}\n```\n\n### Upload a provider directly to backend storage\n\n`GET /api/v1/box/:username/:name/version/:version/provider/:provider/upload/direct`\n\nPrepares the provider for upload. This version of the upload API allows uploading the box asset directly to the backend storage. It requires\na two step process for uploading the box assets. First uploading the asset to storage and then finalizing the upload within Vagrant Cloud\nvia a provided callback.\n\nThe request returns a JSON blob containing two fields:\n\n- `upload_path` - URL to `PUT` the box asset\n- `callback` - Vagrant Cloud callback URL to finalize upload\n\nThe box asset is uploaded directly to the URL provided by the `upload_path` via a `PUT` request. Once complete, a `PUT` request to the URL\nprovided in the `callback` field (complete with authentication header) finalizes the upload.\n\n~> The upload must begin shortly after the response is returned, otherwise the URL will expire. If the URL expires, you can request this same API method again for a new upload URL.\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\nresponse=$(curl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/provider/virtualbox/upload/direct)\n\n# Requires the jq command\nupload_path=$(echo \"$response\" | jq .upload_path)\ncallback=$(echo \"$response\" | jq .callback)\n\ncurl \\\n  --request PUT \\\n  --upload-file virtualbox-1.2.3.box \\\n  \"${upload_path}\"\n\ncurl\n  --request PUT \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  \"${callback}\"\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\nrequire \"uri\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v1/box/myuser/test/version/1.2.3/provider/virtualbox/upload\")\n\nif response.status.success?\n  # Success, you can now upload the box image to the returned URL\n  upload_path = response.parse['upload_path']\n  callback = response.parse['callback']\n  # Upload the box asset\n  HTTP.post upload_path, body: File.open(\"virtualbox-1.2.3.box\")\n  # Finalize the upload\n  api.put(URI.parse(callback).path)\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"upload_path\": \"https://remote-storage.example.com/bucket/630e42d9-2364-2412-4121-18266770468e?auth=9023wqfda\",\n  \"callback\": \"https://app.vagrantup.com/api/v1/box/myuser/test/version/1.2.3/provider/virtualbox/upload/direct/630e42d9-2364-2412-4121-18266770468e\"\n}\n```\n\n[markdown]: https://daringfireball.net/projects/markdown/syntax\n"
  },
  {
    "path": "website/content/vagrant-cloud/api/v2.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Vagrant Cloud API (Version 2)\ndescription: \"Vagrant Cloud provides an API for users to interact with Vagrant Cloud for experimentation, automation, or building new features and tools on top of our existing application.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant Cloud API (Version 2)\n\n## Using the API\n\nVagrant Cloud provides an API for users to interact with Vagrant Cloud for experimentation, automation, or building new features and tools on top of our existing application.\n\n### Authentication\n\nSome API endpoints require authentication to create new resources, update or delete existing resources, or to read a private resource.\n\nClients can authenticate using an authentication token.\nThe token can be passed to Vagrant Cloud one of two ways:\n\n1. (Preferred) Set the `Authorization` header to `\"Bearer \"` and the value of the authentication token.\n2. Pass the authentication token as an `access_token` URL parameter (_NOTE_: deprecated).\n\nExamples below will set the header, but feel free to use whichever method is easier for your implementation.\n\n-> The `X-Atlas-Token` header is also supported for backwards-compatibility.\n\n### Request and Response Format\n\nRequests to Vagrant Cloud which include data attributes (`POST` or `PUT`/`PATCH`) should set the `Content-Type` header to `\"application/json\"`, and include a valid JSON body with the request.\n\nJSON responses may include an `errors` key, which will contain an array of error strings, as well as a `success` key.\nFor example:\n\n```json\n{\n  \"errors\": [\"Resource not found!\"],\n  \"success\": false\n}\n```\n\n### Response Codes\n\nVagrant Cloud may respond with the following response codes, depending on the status of the request and context:\n\n#### Success\n\n##### **200** OK\n\n##### **201** Created\n\n##### **204** No Content\n\n#### Client Errors\n\n##### **401** Unauthorized\n\nYou do not have authorization to access the requested resource.\n\n##### **402** Payment Required\n\nYou are trying to access a resource which is delinquent on billing.\nPlease contact the owner of the resource so that they can update their billing information.\n\n##### **403** Forbidden\n\nYou are attempting to use the system in a way which is not allowed.\nThere could be required request parameters that are missing, or one of the parameters is invalid.\nPlease check the response `errors` key, and double-check the examples below for any discrepancies.\n\n##### **404** Not Found\n\nThe resource you are trying to access does not exist. This may also be returned if you attempt to access a private resource that you don't have authorization to view.\n\n##### **422** Unprocessable Entity\n\n##### **429** Too Many Requests\n\nYou are currently being rate-limited. Please decrease your frequency of usage or [contact support](/vagrant/intro/support) with a description of your use case so that we can consider creating an exception.\n\n#### Server Errors\n\n##### **500** Internal Server Error\n\nThe server failed to respond to the request for an unknown reason.\nPlease [contact support](/vagrant/intro/support) with a description of the problem so that we can investigate.\n\n##### **503** Service Unavailable\n\nVagrant Cloud is temporarily in maintenance mode.\nPlease check the [HashiCorp Status Site](http://status.hashicorp.com) for more information.\n\n## Creating a usable box from scratch\n\n-> This assumes that you have a valid Vagrant Cloud authentication token. You can [create one via the API](#create-a-token), or [create one on the Vagrant Cloud website](https://app.vagrantup.com/settings/security).\n\nIn order to create a usable box on Vagrant Cloud, perform the following steps:\n\n1. [Create a new box](#create-a-box)\n1. [Create a new version](#create-a-version)\n1. [Create a new provider](#create-a-provider)\n1. [Upload a box image for that provider](#upload-a-provider)\n1. [Release the version](#release-a-version)\n\n#### Example Requests\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\n# Create a new box\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/boxes \\\n  --data '{ \"box\": { \"username\": \"myuser\", \"name\": \"test\" } }'\n\n# Create a new version\n\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/versions \\\n  --data '{ \"version\": { \"version\": \"1.2.3\" } }'\n\n# Create a new provider\n\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/providers \\\n  --data '{ \"provider\": { \"name\": \"virtualbox\" } }'\n\n# Prepare the provider for upload/get an upload URL\n\nresponse=$(curl \\\n    --request GET \\\n    --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n    https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/provider/virtualbox/upload)\n\n# Extract the upload URL from the response (requires the jq command)\n\nupload_path=$(echo \"$response\" | jq .upload_path)\n\n# Perform the upload\n\ncurl --request PUT \"${upload_path}\" --upload-file virtualbox-1.2.3.box\n\n# Release the version\n\ncurl \\\n  --request PUT \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/release\n\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\n# Create a new box\n\napi.post \"/api/v2/boxes\",\njson: { box: { username: \"myuser\", name: \"test\" } }\n\n# Create a new version\n\napi.post \"/api/v2/box/myuser/test/versions\",\njson: { version: { version: \"1.2.3\" } }\n\n# Create a new provider\n\napi.post \"/api/v2/box/myuser/test/version/1.2.3/providers\",\njson: { provider: { name: \"virtualbox\" } }\n\n# Prepare the provider for upload\n\nresponse = api.get(\"/api/v2/box/myuser/test/version/1.2.3/provider/virtualbox/upload\")\n\n# Extract the upload URL\n\nupload_path = response.parse['upload_path']\n\n# Upload the box image\n\nHTTP.put upload_path, body: File.open(\"virtualbox-1.2.3.box\")\n\n# Release the version\n\napi.put(\"/api/v2/box/myuser/test/version/1.2.3/release\")\n```\n\n</Tab>\n</Tabs>\n\n## Authentication\n\n### Validate a token\n\n`GET /api/v2/authenticate`\n\nResponds [`200 OK`](#200-ok) if the authentication request was successful, otherwise responds [`401 Unauthorized`](#401-unauthorized).\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/authenticate\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v2/authenticate\")\n\nif response.status.success? # Success, the response attributes are available here.\n  p response.parse\nelse # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n## Organizations\n\n### Read an organization\n\n`GET /api/v2/user/:username`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/user/myuser\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v2/user/myuser\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"username\": \"myuser\",\n  \"avatar_url\": \"https://www.gravatar.com/avatar/130a640278870c3dada38b3d912ee022?s=460&d=mm\",\n  \"profile_html\": \"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>\\n\",\n  \"profile_markdown\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\",\n  \"boxes\": []\n}\n```\n\n## Search\n\n### Search for boxes\n\n`GET /api/v2/search`\n\n#### Arguments\n\n- `q` - (Optional) The search query. Results will match the `username`, `name`, or `short_description` fields for a box. If omitted, the top boxes based on `sort` and `order` will be returned (defaults to \"downloads desc\").\n- `architcture` (Optional)  Filter results to boxes supporting a specific architecture.\n- `provider` - (Optional) Filter results to boxes supporting for a specific provider.\n- `sort` - (Optional, default: `\"downloads\"`) The field to sort results on. Can be one of `\"downloads\"`, `\"created\"`, or `\"updated\"`.\n- `order` - (Optional, default: `\"desc\"`) The order to return the sorted field in. Can be `\"desc\"` os `\"asc\"`.\n- `limit` - (Optional, default: `10`) The number of results to return (max of 100).\n- `page` - (Optional, default: `1`)\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  \"https://app.vagrantup.com/api/v2/search?q=test&provider=virtualbox\"\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v2/search\", params: {\n  q: \"test\",\n  provider: \"virtualbox\"\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"boxes\": [\n    {\n      \"created_at\": \"2017-10-20T14:19:59.842Z\",\n      \"updated_at\": \"2017-10-20T15:23:53.363Z\",\n      \"tag\": \"myuser/test\",\n      \"name\": \"test\",\n      \"short_description\": \"My dev box\",\n      \"description_html\": \"<p>My development Vagrant box</p>\\n\",\n      \"username\": \"myuser\",\n      \"description_markdown\": \"My development Vagrant box\",\n      \"private\": true,\n      \"downloads\": 123,\n      \"current_version\": {\n        \"version\": \"1.2.3\",\n        \"status\": \"active\",\n        \"description_html\": \"<p>A new version</p>\\n\",\n        \"description_markdown\": \"A new version\",\n        \"created_at\": \"2017-10-20T15:23:17.184Z\",\n        \"updated_at\": \"2017-10-20T15:23:53.355Z\",\n        \"number\": \"1.2.3\",\n        \"release_url\": \"https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/release\",\n        \"revoke_url\": \"https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/revoke\",\n        \"providers\": [\n          {\n            \"name\": \"virtualbox\",\n            \"hosted\": false,\n            \"hosted_token\": null,\n            \"original_url\": \"https://example.com/virtualbox-1.2.3.box\",\n            \"created_at\": \"2017-10-20T15:23:35.718Z\",\n            \"updated_at\": \"2017-10-20T15:23:35.718Z\",\n            \"download_url\": \"https://vagrantcloud.com/myuser/boxes/test/versions/1.2.3/providers/virtualbox.box\"\n          }\n        ]\n      }\n    }\n  ]\n}\n```\n\n## Boxes\n\n### Read a box\n\n`GET /api/v2/box/:username/:name`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v2/box/myuser/test\")\n\nif response.status.success? # Success, the response attributes are available here.\n  p response.parse\nelse # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"created_at\": \"2017-10-20T14:19:59.842Z\",\n  \"updated_at\": \"2017-10-20T15:23:53.363Z\",\n  \"tag\": \"myuser/test\",\n  \"name\": \"test\",\n  \"short_description\": \"My dev box\",\n  \"description_html\": \"<p>My development Vagrant box</p>\\n\",\n  \"username\": \"myuser\",\n  \"description_markdown\": \"My development Vagrant box\",\n  \"private\": true,\n  \"downloads\": 123,\n  \"current_version\": {\n    \"version\": \"1.2.3\",\n    \"status\": \"active\",\n    \"description_html\": \"<p>A new version</p>\\n\",\n    \"description_markdown\": \"A new version\",\n    \"created_at\": \"2017-10-20T15:23:17.184Z\",\n    \"updated_at\": \"2017-10-20T15:23:53.355Z\",\n    \"number\": \"1.2.3\",\n    \"release_url\": \"https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/release\",\n    \"revoke_url\": \"https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/revoke\",\n    \"providers\": [\n      {\n        \"name\": \"virtualbox\",\n        \"hosted\": false,\n        \"hosted_token\": null,\n        \"original_url\": \"https://example.com/virtualbox-1.2.3.box\",\n        \"created_at\": \"2017-10-20T15:23:35.718Z\",\n        \"updated_at\": \"2017-10-20T15:23:35.718Z\",\n        \"download_url\": \"https://vagrantcloud.com/myuser/boxes/test/versions/1.2.3/providers/virtualbox.box\"\n      }\n    ]\n  },\n  \"versions\": [\n    {\n      \"version\": \"1.2.3\",\n      \"status\": \"active\",\n      \"description_html\": \"<p>A new version</p>\\n\",\n      \"description_markdown\": \"A new version\",\n      \"created_at\": \"2017-10-20T15:23:17.184Z\",\n      \"updated_at\": \"2017-10-20T15:23:53.355Z\",\n      \"number\": \"1.2.3\",\n      \"release_url\": \"https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/release\",\n      \"revoke_url\": \"https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/revoke\",\n      \"providers\": [\n        {\n          \"name\": \"virtualbox\",\n          \"hosted\": false,\n          \"hosted_token\": null,\n          \"original_url\": \"https://example.com/virtualbox-1.2.3.box\",\n          \"created_at\": \"2017-10-20T15:23:35.718Z\",\n          \"updated_at\": \"2017-10-20T15:23:35.718Z\",\n          \"download_url\": \"https://vagrantcloud.com/myuser/boxes/test/versions/1.2.3/providers/virtualbox.box\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### Create a box\n\n`POST /api/v2/boxes`\n\n#### Arguments\n\n- `box`\n  - `username` - The username of the organization that will own this box.\n  - `name` - The name of the box.\n  - `short_description` - A short summary of the box.\n  - `description` - A longer description of the box. Can be formatted with [Markdown][markdown].\n  - `is_private` (Optional, default: `true`) - Whether or not this box is private.\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/boxes \\\n  --data '\n    {\n      \"box\": {\n        \"username\": \"myuser\",\n        \"name\": \"test\",\n        \"short_description\": \"My dev box\",\n        \"description\": \"My development Vagrant box\",\n        \"is_private\": true\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.post(\"/api/v2/boxes\", json: {\n  box: {\n    username: \"myuser\",\n    name: \"test\",\n    short_description: \"My dev box\",\n    description: \"My development Vagrant box\",\n    is_private: true\n  }\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a box](#read-a-box).\n\n### Update a box\n\n`PUT /api/v2/box/:username/:name`\n\n#### Arguments\n\n- `box`\n  - `name` - The name of the box.\n  - `short_description` - A short summary of the box.\n  - `description` - A longer description of the box. Can be formatted with [Markdown](https://daringfireball.net/projects/markdown/syntax).\n  - `is_private` (Optional, default: `true`) - Whether or not this box is private.\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request PUT \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test \\\n  --data '\n    {\n      \"box\": {\n        \"name\": \"test\",\n        \"short_description\": \"My dev box\",\n        \"description\": \"My development Vagrant box\",\n        \"is_private\": true\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.put(\"/api/v2/box/myuser/test\", json: {\n  box: {\n    name: \"test\",\n    short_description: \"My dev box\",\n    description: \"My development Vagrant box\",\n    is_private: true\n  }\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n### Delete a box\n\n`DELETE /api/v2/box/:username/:name`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request DELETE \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.delete(\"/api/v2/box/myuser/test\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a box](#read-a-box).\n\n## Versions\n\n### Read a version\n\n`GET /api/v2/box/:username/:name/version/:version`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v2/box/myuser/test/version/1.2.3\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"version\": \"1.2.3\",\n  \"status\": \"active\",\n  \"description_html\": \"<p>A new version</p>\\n\",\n  \"description_markdown\": \"A new version\",\n  \"created_at\": \"2017-10-20T15:23:17.184Z\",\n  \"updated_at\": \"2017-10-20T15:23:53.355Z\",\n  \"number\": \"1.2.3\",\n  \"release_url\": \"https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/release\",\n  \"revoke_url\": \"https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/revoke\",\n  \"providers\": [\n    {\n      \"name\": \"virtualbox\",\n      \"hosted\": false,\n      \"hosted_token\": null,\n      \"original_url\": \"https://example.com/virtualbox-1.2.3.box\",\n      \"created_at\": \"2017-10-20T15:23:35.718Z\",\n      \"updated_at\": \"2017-10-20T15:23:35.718Z\",\n      \"download_url\": \"https://vagrantcloud.com/myuser/boxes/test/versions/1.2.3/providers/virtualbox.box\",\n      \"architecture\": \"amd64\",\n      \"default_architecture\": true\n    }\n  ]\n}\n```\n\n### Create a version\n\n`POST /api/v2/box/:username/:name/versions`\n\n-> New versions start as `unreleased`. You must create a valid provider before releasing a new version.\n\n#### Arguments\n\n- `version`\n  - `version` - The version number of this version.\n  - `description` - A description for this version. Can be formatted with [Markdown](https://daringfireball.net/projects/markdown/syntax).\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/versions \\\n  --data '\n    {\n      \"version\": {\n        \"version\": \"1.2.3\",\n        \"description\": \"A new version\"\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.post(\"/api/v2/box/myuser/test/versions\", json: {\n  version: {\n    version: \"1.2.3\",\n    description: \"A new version\"\n  }\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a version](#read-a-version).\n\n### Update a version\n\n`PUT /api/v2/box/:username/:name/version/1.2.3`\n\n#### Arguments\n\n- `version`\n  - `version` - The version number of this version.\n  - `description` - A description for this version. Can be formatted with [Markdown][markdown].\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request PUT \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3 \\\n  --data '\n    {\n      \"version\": {\n        \"version\": \"1.2.3\",\n        \"description\": \"A new version\"\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.put(\"/api/v2/box/myuser/test/version/1.2.3\", json: {\n  version: {\n    name: \"1.2.3\",\n    description: \"A new version\"\n  }\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a version](#read-a-version).\n\n### Delete a version\n\n`DELETE /api/v2/box/:username/:name/version/:version`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request DELETE \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.delete(\"/api/v2/box/myuser/test/version/1.2.3\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a version](#read-a-version).\n\n### Release a version\n\n`PUT /api/v2/box/:username/:name/version/1.2.3/release`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request PUT \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/release\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.put(\"/api/v2/box/myuser/test/version/1.2.3/release\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a version](#read-a-version).\n\n### Revoke a version\n\n`PUT /api/v2/box/:username/:name/version/1.2.3/revoke`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request PUT \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/revoke\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.put(\"/api/v2/box/myuser/test/version/1.2.3/revoke\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a version](#read-a-version).\n\n## Providers\n\n### Read a provider\n\n`GET /api/v2/box/:username/:name/version/:version/provider/:provider/:architecture`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/provider/virtualbox/amd64\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v2/box/myuser/test/version/1.2.3/provider/virtualbox/amd64\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"name\": \"virtualbox\",\n  \"hosted\": false,\n  \"hosted_token\": null,\n  \"original_url\": \"https://example.com/virtualbox-1.2.3.box\",\n  \"created_at\": \"2017-10-20T15:23:35.718Z\",\n  \"updated_at\": \"2017-10-20T15:23:35.718Z\",\n  \"download_url\": \"https://vagrantcloud.com/myuser/boxes/test/versions/1.2.3/providers/virtualbox.box\",\n  \"checksum\": \"a59e7332e8bbe896f11f478fc61fa8a6\",\n  \"checksum_type\": \"md5\",\n  \"architecture\": \"amd64\",\n  \"default_architecture\": true\n}\n```\n\n### Create a provider\n\n`POST /api/v2/box/:username/:name/version/:version/providers`\n\n#### Arguments\n\n- `provider`\n  - `name` - The name of the provider.\n  - `url` - A valid URL to download this provider. If omitted, you must [upload](#upload-a-provider) the Vagrant box image for this provider to Vagrant Cloud before the provider can be used.\n  - `checksum` - Computed checksum of the box assets. When set, Vagrant will compute the checksum of the downloaded box asset and validate it matches this value.\n  - `checksum_type` - Type of checksum used. Currently supported values: md5, sha1, sha256, sha384, and sha512\n  - `architecture` - Architecture of the Vagrant box image. Currently supported values: amd64, i386, arm, arm64, ppc64le, ppc64, mips64le, mips64, mipsle, mips, and s390x\n  - `default_architecture` - Set as the default architecture to be used when no architecture information is provided\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request POST \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/providers \\\n  --data '\n    {\n      \"provider\": {\n        \"checksum\": \"a59e7332e8bbe896f11f478fc61fa8a6\",\n        \"checksum_type\": \"md5\",\n        \"name\": \"virtualbox\",\n        \"url\": \"https://example.com/virtualbox-1.2.3.box\",\n        \"architecture\": \"amd64\",\n        \"default_architecture\": true\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.post(\"/api/v2/box/myuser/test/version/1.2.3/providers\", json: {\n  provider: {\n    name: \"virtualbox\",\n    url: \"https://example.com/virtualbox-1.2.3.box\",\n    architecture: \"amd64\",\n    default_architecture: true\n  }\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a provider](#read-a-provider).\n\n### Update a provider\n\n`PUT /api/v2/box/:username/:name/version/:version/provider/:provider/:architecture`\n\n#### Arguments\n\n- `provider`\n  - `name` - The name of the provider.\n  - `url` - A valid URL to download this provider. If omitted, you must [upload](#upload-a-provider) the Vagrant box image for this provider to Vagrant Cloud before the provider can be used.\n  - `checksum` - Computed checksum of the box assets. When set, Vagrant will compute the checksum of the downloaded box asset and validate it matches this value.\n  - `checksum_type` - Type of checksum used. Currently supported values: md5, sha1, sha256, sha384, and sha512\n  - `architecture` - Architecture of the Vagrant box image. Currently supported values: amd64, i386, arm, arm64, ppc64le, ppc64, mips64le, mips64, mipsle, mips, and s390x\n  - `default_architecture` - Set as the default architecture to be used when no architecture information is provided\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request PUT \\\n  --header \"Content-Type: application/json\" \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/provider/virtualbox/amd64 \\\n  --data '\n    {\n      \"provider\": {\n        \"checksum\": \"a59e7332e8bbe896f11f478fc61fa8a6\",\n        \"checksum_type\": \"md5\",\n        \"name\": \"virtualbox\",\n        \"url\": \"https://example.com/virtualbox-1.2.3.box\"\n      }\n    }\n  '\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Content-Type\" => \"application/json\",\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.put(\"/api/v2/box/myuser/test/version/1.2.3/provider/virtualbox/amd64\", json: {\n  provider: {\n    name: \"virtualbox\",\n    url: \"https://example.com/virtualbox-1.2.3.box\"\n  }\n})\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a provider](#read-a-provider).\n\n### Delete a provider\n\n`DELETE /api/v2/box/:username/:name/version/:version/provider/:provider/:architecture`\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\ncurl \\\n  --request DELETE \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/provider/virtualbox/amd64\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.delete(\"/api/v2/box/myuser/test/version/1.2.3/provider/virtualbox/amd64\")\n\nif response.status.success?\n  # Success, the response attributes are available here.\n  p response.parse\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\nResponse body is identical to [Reading a provider](#read-a-provider).\n\n### Upload a provider\n\n`GET /api/v2/box/:username/:name/version/:version/provider/:provider/:architecture/upload`\n\nPrepares the provider for upload, and returns a JSON blob containing an `upload_path`.\n\n~> The upload must begin shortly after the response is returned, otherwise the URL will expire. If the URL expires, you can request this same API method again for a new upload URL.\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\nresponse=$(curl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/provider/virtualbox/amd64/upload)\n\n# Requires the jq command\nupload_path=$(echo \"$response\" | jq .upload_path)\n\ncurl \\\n  --request PUT \\\n  --upload-file virtualbox-1.2.3.box \\\n  \"${upload_path}\"\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v2/box/myuser/test/version/1.2.3/provider/virtualbox/amd64/upload\")\n\nif response.status.success?\n  # Success, you can now upload the box image to the returned URL\n  upload_path = response.parse['upload_path']\n  HTTP.post upload_path, body: File.open(\"virtualbox-1.2.3.box\")\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"upload_path\": \"https://archivist.hashicorp.com/v2/object/630e42d9-2364-2412-4121-18266770468e\"\n}\n```\n\n### Upload a provider directly to backend storage\n\n`GET /api/v2/box/:username/:name/version/:version/provider/:provider/:architecture/upload/direct`\n\nPrepares the provider for upload. This version of the upload API allows uploading the box asset directly to the backend storage. It requires\na two step process for uploading the box assets. First uploading the asset to storage and then finalizing the upload within Vagrant Cloud\nvia a provided callback.\n\nThe request returns a JSON blob containing two fields:\n\n- `upload_path` - URL to `PUT` the box asset\n- `callback` - Vagrant Cloud callback URL to finalize upload\n\nThe box asset is uploaded directly to the URL provided by the `upload_path` via a `PUT` request. Once complete, a `PUT` request to the URL\nprovided in the `callback` field (complete with authentication header) finalizes the upload.\n\n~> The upload must begin shortly after the response is returned, otherwise the URL will expire. If the URL expires, you can request this same API method again for a new upload URL.\n\n#### Example Request\n\n<Tabs>\n<Tab heading=\"cURL\">\n\n```shell\nresponse=$(curl \\\n  --request GET \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/provider/virtualbox/amd64/upload/direct)\n\n# Requires the jq command\nupload_path=$(echo \"$response\" | jq .upload_path)\ncallback=$(echo \"$response\" | jq .callback)\n\ncurl \\\n  --request PUT \\\n  --upload-file virtualbox-1.2.3.box \\\n  \"${upload_path}\"\n\ncurl\n  --request PUT \\\n  --header \"Authorization: Bearer $VAGRANT_CLOUD_TOKEN\" \\\n  \"${callback}\"\n```\n\n</Tab>\n<Tab heading=\"Ruby\">\n\n```ruby\n# gem install http, or add `gem \"http\"` to your Gemfile\nrequire \"http\"\nrequire \"uri\"\n\napi = HTTP.persistent(\"https://app.vagrantup.com\").headers(\n  \"Authorization\" => \"Bearer #{ENV['VAGRANT_CLOUD_TOKEN']}\"\n)\n\nresponse = api.get(\"/api/v2/box/myuser/test/version/1.2.3/provider/virtualbox/amd64/upload\")\n\nif response.status.success?\n  # Success, you can now upload the box image to the returned URL\n  upload_path = response.parse['upload_path']\n  callback = response.parse['callback']\n  # Upload the box asset\n  HTTP.post upload_path, body: File.open(\"virtualbox-1.2.3.box\")\n  # Finalize the upload\n  api.put(URI.parse(callback).path)\nelse\n  # Error, inspect the `errors` key for more information.\n  p response.code, response.body\nend\n```\n\n</Tab>\n</Tabs>\n\n#### Example Response\n\n```json\n{\n  \"upload_path\": \"https://remote-storage.example.com/bucket/630e42d9-2364-2412-4121-18266770468e?auth=9023wqfda\",\n  \"callback\": \"https://app.vagrantup.com/api/v2/box/myuser/test/version/1.2.3/provider/virtualbox/amd64/upload/direct/630e42d9-2364-2412-4121-18266770468e\"\n}\n```\n\n[markdown]: https://daringfireball.net/projects/markdown/syntax\n"
  },
  {
    "path": "website/content/vagrant-cloud/boxes/architecture.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Vagrant Box Architecture\ndescription: \"Vagrant box architecture and default architecture.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Architecture for Vagrant boxes\n\nProviders for Vagrant boxes, in Vagrant version 2.4.0 and newer, can include \nmultiple architecture options. This allows you to have multiple instances \nof one specific provider. The specific provider shares a common name for\nartifacts of different architectures.\n\nFor example, the `hashicorp/precise32` and `hashicorp/precise64` boxes each\ninclude a provider for `virtualbox`.\n\n```\nhashicorp/precise32\n  v1.0.0\n    provider: virtualbox\n    \nhashicorp/precise64\n  v1.0.0\n    provider: virtualbox\n```\n\nThe addition of architecture now allows these to be combined into a single\nbox. Instead of having a `hashicorp/precise32` box for a 32-bit guest, and \na `hashicorp/precise64` box for a 64-bit guest, a single `hashicorp/precise`\nbox can provide both.\n\n```\nhashicorp/precise\n  v1.0.0\n    provider: virtualbox, architecture: amd64\n    provider: virtualbox, architecture: i386\n```\n\nThe Vagrant CLI will automatically match the provider architecture using the\ndetected local host architecture. If the Vagrant CLI cannot find a matching \narchitecture, it will attempt a\n[special case match](/vagrant/vagrant-cloud/boxes/architecture#unknown-architecture).\n\n## Default architecture\n\nVagrant Cloud allows you to optionally specify one architecture as the\n\"default architecture\" for a provider. This enables the box owner to \ncontrol which architecture for the provider the Vagrant CLI uses\nwhen architecture filtering is not available. By default, the first \nprovider in the configuration for a box version is the default architecture.\n\nNote, this functionality enables backwards compatiblity\nwith previous versions of the Vagrant CLI that do not support architecture\nfiltering when matching an appropriate provider.\n\nFor example, the `2.0.0` version of the `hashicorp/precise` box is created and \na new `virtualbox` provider is created with `amd64` architecture.\n\n```\nhashicorp/precise\n  v2.0.0\n    provider: virtualbox, architecture: amd64, default_architecture: true\n```\n\nAdding another `virtualbox` provider with `i386` architecture.\n\n```\nhashicorp/precise\n  v2.0.0\n    provider: virtualbox, architecture: amd64, default_architecture: true\n    provider: virtualbox, architecture: i386, default_architecture: false\n```\n\nWhen the Vagrant CLI, prior to version 2.4.0, requests the `hashicorp/precise`\nbox with the `virtualbox` provider, it will receive the information from \nthe `virtualbox` provider with the `amd64` architecture because it is flagged \nas being the default architecture. If, instead, the provider with `i386`\narchitecture should be returned, the `virtualbox` provider for the `i386` \narchitecture can be updated to be the default architecture.\n\n```\nhashicorp/precise\n  v2.0.0\n    provider: virtualbox, architecture: amd64, default_architecture: false\n    provider: virtualbox, architecture: i386, default_architecture: true\n```\n\nNow the provider with the `i386` architecture will be returned.\n\n## Special cases\n\nThere are two cases where Vagrant CLI versions with architecture support\nwill use the default architecture flag.\n\n### User requested\n\nIf the user sets the [config.vm.box_architecture](/vagrant/docs/vagrantfile/machine_settings#config-vm-box_architecture)\noption in their Vagrantfile to `nil`, the Vagrant CLI will use the \nprovider which has been flagged as the default architecture.\n\n### Unknown architecture\n\nThe architecture value `unknown` combined with the default architecture\nflag provides a special matching case for the Vagrant CLI. If the \n[config.vm.box_architecture](/vagrant/docs/vagrantfile/machine_settings#config-vm-box_architecture)\noption in the local Vagrantfile is configured with the default `:auto`\nvalue, and no architecture matching the host platform can be found \nfor the desired provider, the Vagrant CLI will check for a matching \nprovider that is flagged as the default architecture and has an\narchitecture value of `unknown`. If that match is found, the Vagrant\nCLI will use that provider.\n\nThis special case matching was included so the Vagrant CLI would be\nable to use boxes published to Vagrant Cloud prior to the introduction \nof architecture metadata. All previously existing providers have a \ndefault architecture value of `unknown` and are flagged as the default \narchitecture since they are the only provider to exist for a given name.\n"
  },
  {
    "path": "website/content/vagrant-cloud/boxes/catalog.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Discovering Vagrant Boxes\ndescription: \"Vagrant Cloud serves a public, searchable index of Vagrant boxes.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Discovering Vagrant Boxes\n\nVagrant Cloud serves a public, searchable index of Vagrant boxes. It's easy to find\nboxes you can use with Vagrant that contain the technologies you need\nfor a Vagrant environment.\n\nYou don't need a Vagrant Cloud account to use public boxes.\n\n1. Go to the [Box search page](https://vagrantcloud.com/boxes/search).\n\n1. Once you find a box, click its name to learn more about it.\n\n1. When you're ready to use it, copy the name, such as \"hashicorp/bionic64\"\n   and initialize your Vagrant project with `vagrant init hashicorp/bionic64`.\n   Or, if you already have a Vagrant project created, modify the Vagrantfile\n   to use the box: `config.vm.box = \"hashicorp/bionic64\"`.\n\n## Provider Support\n\nNot all boxes are available for all providers. You may need\nto sort by a provider that you have on your local system\nto narrow down your search.\n\n## Choosing the Right Box\n\nAs with all software and configuration used from a public source,\nit's important to keep in mind whose box you're using. Here\nare some things to note when you're choosing a box:\n\n- **The username of the user**. If it's `bento` or `ubuntu`, you can likely\n  trust the box more than an anonymous user.\n- **The number of downloads of the box**. Heavily downloaded boxes\n  are likely vetted more often by other members of the community. HashiCorp\n  responds to reports of malicious software distributed via Vagrant Cloud\n  by disabling and/or removing boxes. If you find a box which includes\n  malicious software, please [contact support](/vagrant/intro/support) to make a report.\n- **The latest release date**. Boxes which are updated periodically or which\n  have recent release dates will generally contain more up-to-date software.\n- **Availability of the box download**. Vagrant Cloud periodically checks if a box\n  which is externally hosted (hosted by the box author, not Vagrant Cloud) is publicly\n  accessible. You can see this information on the box page next to the provider.\n"
  },
  {
    "path": "website/content/vagrant-cloud/boxes/create-version.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Create a New Box Version\ndescription: \"Create a new box version in the Vagrant Cloud UI.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Create a New Box Version\n\nTo release a new version of a box to the public or to your team:\n\n1. Click on the name of the box you want to release a new version for.\n\n2. To the right of the box name, there is a dropdown of all the available\n   versions. Click this, and click \"Create a New Version\"\n\n3. Enter details for your new version and click \"Create Version.\" Note that\n   after clicking create version, the version is not yet _released_.\n\n4. Click \"Create new provider\" on this next page to add at least one\n   provider to the version. Specify the name of the provider (this is the\n   same value you specify to `--provider` when using Vagrant). Then\n   enter the URL to a self-hosted box file or upload a box to us.\n\n5. Once the provider is created, you now have the option to release the\n   version by clicking \"Release now,\" or you can add more providers.\n\nOnce you click \"Release now,\" that version will be available for installation\nwith Vagrant. Before clicking this, Vagrant does not know the version even\nexists.\n\n## Note About Public Boxes\n\nBe aware, when you create and upload a new version artifact on a public box in\nVagrant Cloud, even if that specific version has not been marked for \"release\",\nit can be accessible if a user knows the box URL with the new version. Releasing\na box simply makes it publicly accessible through search, or through reference\nusing the `organization/box-name` syntax.\n"
  },
  {
    "path": "website/content/vagrant-cloud/boxes/create.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Creating a New Vagrant Box\ndescription: \"Create and distribute new boxes to users.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Creating a New Vagrant Box\n\nThis page will cover creating a new box in Vagrant Cloud and how to distribute\nit to users. Boxes can be distributed without Vagrant Cloud, but\nmiss out on several [important features](/vagrant/vagrant-cloud/boxes).\n\nThere are **three ways to create and upload Vagrant Boxes to Vagrant Cloud**. All\nthree options are outlined below.\n\nWe recommend using Packer, as it is fully repeatable and keeps a strong\nhistory of changes within Vagrant Cloud. However, for some situations, including\nlegacy workflows, the Web UI or API will work well.\n\nAll three options require you [sign up for Vagrant Cloud](https://vagrantcloud.com/account/new).\n\n## Creating Boxes with Packer\n\nUsing Packer requires more up front effort, but the repeatable and\nautomated builds will end any manual management of boxes. Additionally,\nall boxes will be stored and served from Vagrant Cloud, keeping a history along\nthe way.\n\n## Creating Boxes via the Vagrant Cloud Web Interface\n\nYou'll first need to create a box file. This can be done via\nthe [vagrant `package` command](/vagrant/docs/boxes/base)\nor with Packer locally.\n\nAfter you've created the `.box` file, this guide can be followed.\n\n1. Go to the [Create Box](https://vagrantcloud.com/boxes/new) page.\n\n1. Name the box and give it a simple description\n\n1. Create your first version for the box. This version\n   must match the format `[0-9].[0-9].[0-9]`\n\n1. Create a provider for the box, matching the provider you need\n   locally in Vagrant. `virtualbox` is the most common provider.\n\n1. Upload the `.box` file for each provider, or use a url to the `.box`\n   file that is publicly accessible\n\nYou can find all of your boxes in the [Vagrant section](https://vagrantcloud.com/vagrant) of Vagrant Cloud.\n\nOnce you've created and released a box, you can release new versions of\nthe box by clicking \"Create New Version\" under the versions sidebar on\na box page. For more information on the release lifecycle of boxes, see\nthe [help page dedicated to box lifecycle](/vagrant/vagrant-cloud/boxes/lifecycle).\n\n## Creating Boxes with the API\n\nThis example uses the API to upload boxes with `curl`. To get started, you'll\nneed to get an [access token](https://vagrantcloud.com/settings/security).\n\nThen, prepare the upload:\n\n```shell-session\n$ curl 'https://vagrantcloud.com/api/v2/box/USERNAME/BOX_NAME/version/VERSION/provider/PROVIDER_NAME/ARCHITECTURE_NAME/upload?access_token=ACCESS_TOKEN'\n```\n\nThis should return something like this:\n\n```json\n{\n  \"upload_path\": \"https://archivist.hashicorp.com/v1/object/630e42d9-2364-2412-4121-18266770468e\"\n}\n```\n\nThen, upload your box with the following command, with the filename in this case being `foo.box`:\n\n```shell-session\n$ curl -X PUT --upload-file foo.box https://archivist.hashicorp.com/v1/object/630e42d9-2364-2412-4121-18266770468e\n```\n\nWhen the upload finishes, you can verify it worked by making this request and matching the `hosted_token` it returns to the previously retrieved upload token.\n\n```shell-session\n$ curl 'https://vagrantcloud.com/api/v2/box/USERNAME/BOX_NAME/version/VERSION_NUMBER/provider/PROVIDER_NAME/ARCHITECTURE_NAME?access_token=ACCESS_TOKEN'\n```\n\nYour box should then be available for download.\n"
  },
  {
    "path": "website/content/vagrant-cloud/boxes/distributing.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Distributing Boxes\ndescription: \"Configure both public and private boxes for distribution or to add collaborators.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n## Distributing Boxes\n\nTo distribute the box to your team, update your Vagrantfile to reference the\nbox on Vagrant Cloud.\n\n```ruby\nVagrant.configure(2) do |config|\n  config.vm.box = \"username/example-box\"\nend\n```\n\nNow when a team member runs `vagrant up`, the box will be downloaded from Vagrant Cloud.\nIf the box is private, the team member will be prompted to authorize access. Users\nare granted access to private resources by logging in with a Vagrant Cloud username and\npassword or by using a shared Vagrant Cloud token.\n[Learn more about authorization options here](/vagrant/vagrant-cloud/users/authentication).\n\n## Private Boxes\n\nIf you create a private box, only you (the owner) and collaborators\nwill be able to access it. This is valuable if you\nhave information, data or provisioning in your box\nthat cannot be public.\n\nPrivate boxes will be excluded from the box catalog.\n\n### Collaborators\n\nCollaborators can be both teams in\norganizations or individual users.\n\nTo add a collaborator:\n\n1. Go to the \"Access\" page of a box via the sidebar\n2. Enter the username or team name and submit the form\n3. You'll now see the user or team in the list of collaborators,\n   and if necessary a collaborator can be removed\n\n### Vagrant Login\n\nIn order to access these private boxes from Vagrant, you'll need to first\nauthenticate with your Vagrant Cloud account.\n\n1. Run `vagrant login`\n2. Enter your credentials\n\nYou should now be logged in. We use these credentials to request\na unique authentication token that is stored locally by Vagrant. Your\nusername or password is never stored on your machine.\n\n### 404 Not Found\n\nIf you don't authenticate, you will likely receive a `404 Not Found`\nerror in Vagrant. We return a 404 for security reasons, so a potential\nattacker could not verify if a private box exists.\n"
  },
  {
    "path": "website/content/vagrant-cloud/boxes/index.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: About Vagrant Boxes\ndescription: \"Boxes are the package format for environments.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# About Vagrant Boxes\n\nBoxes are the package format for [Vagrant](https://vagrantup.com/) environments.\nA box can be used on any Vagrant-supported platform to bring up an identical\nworking environment across a development team.\n\n## Vagrant Box Creation and Versioning\n\nBoxes support versioning so that members of your team using Vagrant can update\nthe underlying box easily, and the people who create boxes can push fixes and\ncommunicate these fixes efficiently.\n\nVagrant Cloud makes it easy to manage the versioning of boxes. Versioning boxes in\nVagrant Cloud allows for easy updates, transparent fixes, and clear communication in\nchanges made. Learn more about the [box release lifecycle\nhere](/vagrant/vagrant-cloud/boxes/lifecycle).\n\n## Vagrant Box Catalog and Discovery\n\nVagrant Cloud hosts a catalog of publicly available Vagrant Boxes. These are boxes\ncreated by both HashiCorp and community contributions. You can find an owner of\na box by selecting their username in the URL or on the box page.\n\nPublic Vagrant boxes let you get up-and-running quickly in unfamiliar\nenvironments. This is a popular way to set a base development environment\nlaunchable in a single command within an organization or community.\n"
  },
  {
    "path": "website/content/vagrant-cloud/boxes/lifecycle.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Box Versioning and Lifecycle\ndescription: \"Boxes support versioning so that members of your team can update the underlying box efficiently.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Box Versioning and Lifecycle\n\nBoxes support versioning so that members of your team using Vagrant can\nupdate the underlying box easily, and the people who create boxes can\npush fixes and communicate these fixes efficiently.\n\nThere are multiple components of a box:\n\n- The box itself, comprised of the box name and description.\n- One or more box versions.\n- One or more providers for each box version.\n\n## Vagrant Messaging\n\nUpon `vagrant up` or `vagrant box outdated`, an out-of-date box\nuser will see the following message in Vagrant:\n\n    Bringing machine 'default' up with 'virtualbox' provider...\n    ==> default: Checking if box 'hashicorp/example' is up to date...\n    ==> default: A newer version of the box 'hashicorp/example' is available! You currently\n    ==> default: have version '0.0.5'. The latest is version '0.0.6'. Run\n    ==> default: `vagrant box update` to update.\n    ...\n\n## Box Version Release States\n\nVagrant Cloud lets you create new versions of boxes without\nreleasing them or without Vagrant seeing the update. This lets you prepare\na box for release slowly. Box versions have three states:\n\n- `unreleased`: Vagrant cannot see this version yet, so it needs\n  to be released. Versions can be released by editing them and clicking\n  the release button at the top of the page\n- `active`: Vagrant is able to add and use this box version\n- `revoked`: Vagrant cannot see this version, and it cannot be re-released.\n  You must create the version again\n\n### Release Requirements\n\nA box can only be released if it has at least one of each component: a\nbox, a version, and a provider.\n"
  },
  {
    "path": "website/content/vagrant-cloud/boxes/private.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Private Boxes\ndescription: \"Private boxes are only accessible by you and any collaborators you add.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Private Boxes\n\nIf you create a private box, only you (the owner) and collaborators\nwill be able to access it.\n\n## Collaborators\n\nTo add a collaborator:\n\n1. Click the gear setting beside the boxes name in order to edit it.\n2. Under the \"Add Collaborator\" section, enter their username and\n   submit the form.\n3. You'll now see a list of collaborators, and if necessary a collaborator\n   can be removed.\n\nCollaborators can edit the box, versions and providers. The only\nthings they cannot do are:\n\n- Add another collaborator\n- Delete the box\n\n## Vagrant Login\n\nIn order to access these boxes from Vagrant, you'll need to first\nauthenticate with your Vagrant Cloud account.\n\n1. Run `vagrant login`\n2. Enter your credentials\n\nYou should now be logged in. We use these credentials to request\na unique authentication token that is stored locally by Vagrant. Your\nusername or password is never stored on your machine.\n\n## 404 Not Found\n\nIf you don't authenticate, you will likely receive a `404 Not Found`\nerror in Vagrant. We return a 404 for security reasons, so a potential\nattacker could not verify if a private box exists.\n"
  },
  {
    "path": "website/content/vagrant-cloud/boxes/release-workflow.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: API Release Workflow\ndescription: \"Automate box creation with the Vagrant Cloud API.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# API Release Workflow\n\nCreating new boxes through their [release lifecycle](/vagrant/vagrant-cloud/boxes/lifecycle)\nis possible through the Vagrant Cloud website, but you can also automate\nthe task via the Vagrant Cloud API.\n\n1. Create box, or locate a boxes `tag`, like `hashicorp/bionic64`\n2. After some event, like the end of a CI build, you may want to\n   release a new version of the box. To do this, first use the API to\n   create a new version with a version number and a short description\n   of the changes\n3. Then, create any providers associated with the version, like\n   `virtualbox`\n4. Once your system has made the necessary requests to the API and the\n   version is ready, make a request to the `release` endpoint on the version\n5. The version should now be available to users of the box via\n   the command `vagrant box outdated` or via the automated checks on\n   `vagrant up`\n"
  },
  {
    "path": "website/content/vagrant-cloud/boxes/using.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Find and Use Boxes\ndescription: \"Discover Vagrant boxes that contain the technologies you need for a Vagrant environment.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Finding and Using Boxes\n\nA primary use case of Vagrant Cloud by HashiCorp is to be able to easily find\nboxes you can use with Vagrant that contain the technologies you need\nfor a Vagrant environment. We've made it extremely easy to do that:\n\n1. Go to the [Discover page](https://app.vagrantup.com/boxes/search), and search for\n   any box you want.\n\n1. Once you find a box, click its name to learn more about it.\n\n1. When you're ready to use it, copy the name, such as \"hashicorp/bionic64\"\n   and initialize your Vagrant project with `vagrant init hashicorp/bionic64`.\n   Or, if you already have a Vagrant project created, modify the Vagrantfile\n   to use the box: `config.vm.box = \"hashicorp/bionic64\"`\n"
  },
  {
    "path": "website/content/vagrant-cloud/hcp-vagrant/migration-guide.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Migrate to HCP Vagrant Registry\ndescription: \"Use Vagrant Cloud's migration tools to move all your artifacts to HashiCorp Cloud Platform (HCP).\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Migrate to HCP Vagrant Registry\n\nYour Vagrant Cloud Organization will be put into a read-only state and marked `Migrating` until the migration is complete. During this time boxes will be available in searches and to download, but write functionality like uploading new boxes, releasing versions, and managing settings for your Vagrant Cloud Organization will be unavailable. Your new HCP Registry will not be available until the migration is complete.\n\nWhen you migrate your default organization your Profile Settings (user name, gravatar url, etc) will no longer be available to manage in Vagrant Cloud and should be managed from your [HCP Settings](https://portal.cloud.hashicorp.com/account-settings).\n\n## Overview\n\nHCP [Vagrant Registry](https://portal.cloud.hashicorp.com/vagrant/discover), like Vagrant Cloud, is a public, searchable index of Vagrant boxes that allows box owners to host and share artifacts.\n\nTo take advantage of shared infrastructure and platform investments available on the Hashicorp Cloud Platform (HCP), Hashicorp is migrating all existing boxes to HCP Vagrant Registry and retiring the current Vagrant Cloud application at the end of July 2024.\n\nMigrated organizations will be permanently accessible at their original Vagrant Cloud URLs and won’t require changes to user workflows. Migrated registries will have access to the modern HCP Vagrant Registry UI, an improved search experience, and free private boxes.\n\nThose who take no action will have their boxes migrated as part of the site wide migration. The Vagrant team will maintain redirects to ensure users can expect all existing URLs and workflows to operate as usual after migration regardless of how boxes are migrated.\n\n## Prerequisites\n\nBefore you continue with this guide, you should familiarize yourself with a few new resource management concepts in HCP and HCP Vagrant Registry.\n\n###### Registries\n\nThe collection that holds a user’s boxes, the registry name is incorporated into the first half of resource names. For example, `hashicorp/atlas`.\n\n~> Note: An HCP registry is analogous to an Organization in Vagrant Cloud. A given HCP project may have an unlimited number of registries.\n\n##### HCP Organizations\n\n[HCP organizations](/hcp/docs/hcp/admin/orgs) are parent-level entities that can have up to ten projects. A single HCP account can be a member of multiple organizations, as an invited user, but can only create and own a single organization.\n\n##### HCP Projects\n\n[HCP projects](/hcp/docs/hcp/admin/orgs) allow organization owners to segment resources by team, environment, etc. Resources such as HCP Vagrant Registries, Hashicorp Virtual Private Networks (HPN). Each organization is assigned a `default-project` at creation and may have up to 10 projects.\n\n#### Create an HCP Vagrant Registry account\n\nTo use the Vagrant Cloud migration tool, you must define an organization and project within an HCP account and be signed into an active session on that account.\n\nYour HCP account must use the email address associated with your Vagrant Cloud account. Check under `Settings → Profile` if you are unsure what email address you use for Vagrant Cloud.\n\n1. Sign up for a (free) [HCP account](https://portal.cloud.hashicorp.com/orgs/create), with your primary Vagrant Cloud organization email address. You must verify your account and sign into HCP.\n2. At first log-in, use the wizard to create an HCP organization.\n3. On creation of your organization, you will be directed to the dashboard for your `default-project`. You can create an additional project from the Projects page.\n4. Now that you have a project in an HCP Account that uses the same email address as your Vagrant Cloud account, you can begin the migration process.\n\n## Migrate to HCP Vagrant Registry\n\n#### 1. Sign in to both accounts and open the migration tool\n\nOnce you have signed into both your HCP and Vagrant Cloud accounts, you can access the [migration tool](https://app.vagrantup.com/migration).\n\n#### 2. Select a Vagrant Cloud organization to migrate\n\nSelect a single organization from the dropdown to migrate or select the checkbox `Migrate all organizations` to migrate them all at once to a single HCP Project.\n\n##### 3. Select a destination project in HCP\n\nIf you have multiple projects in HCP, select the project you want from the HCP Project dropdown. If you have not created a second project the `default-project` will be auto-selected.\n\nWhen preparing for migration it is worth noting the following:\n\n- Your Vagrant Cloud Organization will be put into a read-only state and marked `Migrating` until the migration is complete. During this time boxes will be available in searches and to download, but write functionality like uploading new boxes, releasing versions, and managing settings for your Vagrant Cloud Organization will be unavailable.\n- Your new HCP Registry will not be available until the migration is complete.\n- When you migrate your default organization your Profile Settings (user name, gravatar url, etc) will no longer be available to manage in Vagrant Cloud and should be managed from your [HCP Settings](https://portal.cloud.hashicorp.com/account-settings).\n\n#### 4. Start the migration\n\nAfter you press the `Migrate` button you will be redirected to a status page that will keep you updated on the progress of your migration.\n\n#### 5. Visit your Registry in HCP\n\nOnce your migration is complete you can view your new Registry in HCP.\n\nTo view your registries , click on [Vagrant Registry](https://portal.cloud.hashicorp.com/services/vagrant/registries) in the side panel or by visiting [https://portal.cloud.hashicorp.com/services/vagrant/registries](https://portal.cloud.hashicorp.com/services/vagrant/registries).\n\nIt is important to note that while a migrated organization will no longer be available in Vagrant Cloud the status of the migration will be visible from the `Status` tab on the [Migration](https://app.vagrantup.com/migration) page.\n\n## Conclusion\n\nIf for some reason you are unable to access your new HCP Registry or your migration does not complete successfully, review the [Migration Troubleshooting](/vagrant/vagrant-cloud/hcp-vagrant/troubleshooting) guide or [contact support](/vagrant/intro/support) . Our team will be happy to help you complete your migration or reset your organization state.\n"
  },
  {
    "path": "website/content/vagrant-cloud/hcp-vagrant/post-migration-guide.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Usage and Behavior Post Migration to HCP\ndescription: \"Learn about how Vagrant and Vagrant Cloud will behave after migrating to HashiCorp Cloud Platform (HCP).\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Migrating to HCP Vagrant Registry\n\nThis document describes the behavior of Vagrant Cloud after an organization is migrated to HCP Vagrant Registry. For information on migrating to HCP Vagrant Registry please see the [migration guide](/vagrant/vagrant-cloud/hcp-vagrant/migration-guide).\n\n# Vagrant Cloud Redirects\n\nAfter an organization has been migrated to HCP Vagrant Registry any requests Vagrant Cloud receives for that organization, or any boxes within that organization, will be automatically redirected to the proper location on HCP Vagrant Registry.\n\n# API \n\nAPI requests to Vagrant Cloud for organizations that have been migrated to HCP Vagrant Registry will be automatically proxied to HCP. \n\n## Authenticated Requests\n\nAuthenticated API requests for migrated organizations will be proxied to HCP Vagrant Registry. This will require an HCP access token to be available to Vagrant Cloud to complete the request. Vagrant Cloud supports a composite access token comprised of the Vagrant Cloud access token and the HCP access token allowing seamless interactions with organizations that still remain on Vagrant Cloud as well as organizations that have been migrated to HCP Vagrant Registry.\n\nThe format of the composite access token is:\n\n```\n<VAGRANT_CLOUD_TOKEN>;<HCP_TOKEN>\n```\n\nIf all organizations have been migrated to HCP Vagrant Registry the Vagrant Cloud access token will not be needed. The format of the access token is simply the access token:\n\n```\n<HCP_TOKEN>\n```\n\n### HCP Access Token\n\nThe following prerequisites are required for generating an HCP access token:\n\n* [HCP Service Principal](/hcp/docs/hcp/admin/iam/service-principals)\n* [HCP CLI](/hcp/docs/cli)\n\nUsing the `CLIENT_ID` and `CLIENT_SECRET` from the HCP service principal login to HCP using the `hcp` command:\n\n```\nhcp auth login --client-id=CLIENT_ID --client-secret=CLIENT_SECRET\n```\n\nOnce authenticated the access token can be printed using the following command:\n\n```\nhcp auth print-access-token\n```\n\n### Vagrant Cloud Composite Token\n\nThe Vagrant CLI supports using the `VAGRANT_CLOUD_TOKEN` environment variable to hold the access token used for authentication. This environment variable can be set with both access tokens to allow request for migrated organization and unmigrated organizations to both work as expected. As noted above, the format of the composite token will consist of: the Vagrant Cloud access token, a semi-colon, and the HCP access token. An example of setting the environment variable would be:\n\n```\nexport VAGRANT_CLOUD_TOKEN=\"<VAGRANT_CLOUD_TOKEN>;<HCP_TOKEN>\"\n```\n\nThe `hcp` command can also be used to provide the HCP access token to reduce the need for copying and pasting the access token:\n\n```\nexport VAGRANT_CLOUD_TOKEN=\"<VAGRANT_CLOUD_TOKEN>;$(hcp auth print-access-token)\"\n```\n\nIf all organizations have been migrated to HCP Vagrant Registry, only authentication with HCP will be required. This can be done by providing the HCP access token in the `VAGRANT_CLOUD_TOKEN` environment variable:\n\n```\nexport VAGRANT_CLOUD_TOKEN=\"$(hcp auth print-access-token)\"\n```\n\nOr, if using Vagrant v2.4.3 or later, by setting the `HCP_CLIENT_ID` and `HCP_CLIENT_SECRET` environment variables:\n\n```\nexport HCP_CLIENT_ID=\"<CLIENT_ID>\"`\nexport HCP_CLIENT_SECRET=\"<CLIENT_SECRET>\"`\n```\n\nIf these variables are set, and the `VAGRANT_CLOUD_TOKEN` environment variable is unset, access tokens will be generated as needed when performing requests.\n\n# Packer\n\nAfter migrating to HCP, box uploads may fail when using the [vagrant-cloud](/packer/integrations/hashicorp/vagrant/latest/components/post-processor/vagrant-cloud) post-processor. This is due to the HCP access token used in the composite token expiring prior to the post-processor being executed.\n\nTo resolve this issue, a new Packer post-processor has been introduced: [vagrant-registry](/packer/integrations/hashicorp/vagrant/latest/components/post-processor/vagrant-registry). This post-processor iteracts directly with the HCP Vagrant Box Registry and removes the requirement of a composite access token. The configuration of the `vagrant-registry` post-processor matches the configuration of the `vagrant-cloud` post-processor with the exception of the [authentication settings](/packer/integrations/hashicorp/vagrant/latest/components/post-processor/vagrant-registry#required) which requires a `client_id` and `client_secret` value instead of the `access_token` used by the `vagrant-cloud` post-processor.\n"
  },
  {
    "path": "website/content/vagrant-cloud/hcp-vagrant/troubleshooting.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Migration Troubleshooting\ndescription: 'HCP Migration troubleshooting guide'\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Troubleshooting\n\n### Inactive HCP Session\n\nYou may see a warning in the Migration Wizard if your HCP Session is inactive. The wizard will attempt to reload your page automatically within 5 seconds or you may attempt to refresh your session by reloading manually. The error will persist if you are not logged into HCP, log in from another window and refresh.\n\n### Missing Boxes or Registries\n\nIn the rare case that an inoperable box was successfully uploaded (e.g legacy box improperly ported from Atlas) that box will not be migrated to HCP Vagrant. Any failed boxes will be listed in the Migration Status page.\n\n### Retrying Migrations\n\nIf a migration fails, a `retry` icon will appear beside the name of that organization on the [migration status](https://app.vagrantup.com/migration/status) page. Failed migrations can be retried once.\nIn the event of a second failure, please [contact support](/vagrant/intro/support) with a description of your issue. Our team will be happy to help you complete your migration or reset your organization state.\n"
  },
  {
    "path": "website/content/vagrant-cloud/index.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Vagrant Cloud\ndescription: \"Vagrant Cloud serves a public, searchable index of Vagrant boxes\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Vagrant Cloud\n\n## Support\n\nFor Vagrant Cloud questions, feedback, or feature requests, please submit a ticket to\nHashiCorp Support.\n\n[Click here](/vagrant/vagrant-cloud/support) for instructions on submitting a support ticket.\n\n## Features\n\nVagrant Cloud provides the following features for Vagrant:\n\n- [Vagrant Box Catalog](/vagrant/vagrant-cloud/boxes/catalog)\n- [Vagrant Box Creation](/vagrant/vagrant-cloud/boxes/create)\n- [Vagrant Box Versioning](/vagrant/vagrant-cloud/boxes/lifecycle)\n"
  },
  {
    "path": "website/content/vagrant-cloud/organizations/authentication-policy.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Organization Authentication Policy\ndescription: \"Secure sensitive resources with organization-wide authentication policy in Vagrant Enterprise.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Set an Organization Authentication Policy\n\nBecause organization membership affords members access to potentially sensitive\nresources, owners can set organization-wide authentication policy in Vagrant\nEnterprise.\n\n## Requiring Two-Factor Authentication\n\nOrganization owners can require that all organization team members use\n[two-factor authentication](/vagrant/vagrant-cloud/users/authentication).\nThose that lack two-factor authentication will be locked out of the web\ninterface until they enable it or leave the organization.\n\nVisit your organization's configuration page to enable this feature. All\norganization owners must have two-factor authentication enabled to require the\npractice organization-wide. Note: locked-out users are still be able to interact\nwith Vagrant Cloud using their `ATLAS_TOKEN`.\n\n## Disabling Two-Factor Authentication Requirement\n\nOrganization owners can disable the two-factor authentication requirement from\ntheir organization's configuration page. Locked-out team members (those who have\nnot enabled two-factor authentication) will have their memberships reinstated.\n"
  },
  {
    "path": "website/content/vagrant-cloud/organizations/create.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Create an Organization\ndescription: \"Organizations enable you to share ownership of Vagrant resources.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Create an Organization\n\nTo create an organization:\n\n1. Create a personal account. You'll use this to create and administrate the\n   organization. You'll be able to add other users as owners of the organization,\n   so it won't be tied solely to your account.\n\n1. Visit your new organization page to create the organization.\n"
  },
  {
    "path": "website/content/vagrant-cloud/organizations/index.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Organizations in Vagrant Cloud\ndescription: \"Organizations are a group of users in Vagrant Cloud that have access and ownership over shared resources.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n## Organizations in Vagrant Cloud\n\nOrganizations are a group of users in Vagrant Cloud that have access and\nownership over shared resources. When operating within a team, we recommend\ncreating an organization to manage access control, auditing, billing and\nauthorization.\n\nEach individual member of your organization should have their own account.\n"
  },
  {
    "path": "website/content/vagrant-cloud/organizations/migrate.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Migrate User to Organization\ndescription: \"Step-by-step instructions to migrate existing users to an organization.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Migrate User to Organization\n\nTo migrate an existing user account to an organization:\n\n1. Create or retrieve the username of a new personal account. You'll add this\n   account as an \"owner\" for the new organization during the migration process. If\n   you already have another account, write down your username.\n\n2. Sign in as the account you wish to migrate.\n\n3. Go to [the account migration page](https://app.vagrantup.com/account/migrate).\n\n4. Put the username of the personal account you wish to make an owner of the\n   organization into the username text field and press \"Migrate\".\n\n5. You should now be logged out and receive a confirmation email with the\n   personal account you migrated to.\n\n6. Now, sign in with your personal account. If you visit you settings page, you\n   should see your migrated organization available to administrate.\n"
  },
  {
    "path": "website/content/vagrant-cloud/request-limits.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Request Limits\ndescription: \"Rate limits restrict the number of requests a client may send to services over a set time interval\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Request Limits\n\n## What is Rate Limiting?\n\nRate limiting is a process to protect our quality of service we provide to you. Rate limiting will limit the number of requests a client may send to services over a set time interval. We track requests by IP address origin of the request.\n\n## How many requests can I make?\n\nYou can make a set amount of requests per minute from a single IP address. This rate can vary per resource but the current value is returned in the HTTP headers of your request. For the most up to date rate limiting for your requests please view the information in the headers of your response.\n\n## Why are you rate limiting my requests?\n\nWe have detected a request rate in excess of our current threshold. To provide a high quality of services to all users, your request may have been rate limited.\n\n## How do I know if I have been rate limited?\n\nIf you have received a 429 HTTP status code in the response to your request, your request has likely been rate limited. There is some additional information in the headers that will help you determine if this is the case. Each request response will include the headers: X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset.\n\n- **X-RateLimit-Limit**: The current maximum number of requests allowed from your client.\n- **X-RateLimit-Remaining**: How many requests you have remaining in the time window.\n- **X-RateLimit-Reset**: The Unix timestamp for when the window resets.\n\n## My use case requires more requests. What do I do?\nPlease [contact support](/vagrant/intro/support) with a description of your use case so that we can consider creating an exception..\n"
  },
  {
    "path": "website/content/vagrant-cloud/support.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Vagrant Cloud Support\ndescription: \"Find support for Vagrant Cloud\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n# Contacting Support\nFor Vagrant Cloud questions, feedback, or feature requests, please submit a ticket to\n[HashiCorp Support](/vagrant/intro/support).\n\n### Free Support\n\nWe do not currently publish support SLAs for free accounts, but endeavor to\nrespond as quickly as possible. We respond to most requests within less than 24\nhours.\n\n## HashiCorp Tools Support\n\nIt's often the case that Vagrant Cloud questions or feedback relates to\nthe HashiCorp tooling. We encourage all Vagrant Cloud users to search for\nrelated issues and problems in the open source repositories and mailing lists\nprior to contacting us to help make our support more efficient and to help\nresolve problems faster.\n\nVisit the updating tools section for a list of our tools and their project\nwebsites.\n\n## Documentation Feedback\n\nDue to the dynamic nature of Vagrant Cloud and the broad set of features\nit provides, there may be information lacking in the documentation.\n\nVagrant Cloud documentation is open source.\nIf you'd like to improve or correct the documentation,\nplease submit a pull-request to the\n[Vagrant project on GitHub](https://github.com/hashicorp/vagrant/tree/main/website/content/vagrant-cloud/).\nThe Vagrant Cloud documentation can be found in the `/website/pages/vagrant-cloud/` directory.\n\nOtherwise, to make a suggestion or report an error in documentation, please [contact support](/vagrant/intro/support).\n"
  },
  {
    "path": "website/content/vagrant-cloud/users/authentication.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Authentication\ndescription: \"Authenticate your account with a username and password, tokens, or two-factor auth.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Authentication\n\nVagrant Cloud requires a username and password to sign up and login.\nHowever, there are several ways to authenticate with your account.\n\n### Authentication Tokens\n\nAuthentication tokens are keys used to access your account via tools or over the\nvarious APIs used in Vagrant Cloud.\n\nYou can create new tokens in the token section of your account settings. It's\nimportant to keep tokens secure, as they are essentially a password and can be\nused to access your account or resources. Additionally, token authentication\nbypasses two factor authentication.\n\n### Authenticating Tools\n\nAll HashiCorp tools look for the `ATLAS_TOKEN` environment variable:\n\n```shell-session\n$ export ATLAS_TOKEN=TOKEN\n```\n\nThis will automatically authenticate all requests against this token. This is\nthe recommended way to authenticate with our various tools. Care should be given\nto how this token is stored, as it is as good as a password.\n\n### Two Factor Authentication\n\nYou can optionally enable Two Factor authentication, requiring an SMS or TOTP\none-time code every time you log in, after entering your username and password.\n\nYou can enable Two Factor authentication in the security section of your account\nsettings.\n\nBe sure to save the generated recovery codes. Each backup code can be used once\nto sign in if you do not have access to your two-factor authentication device.\n\n### Sudo Mode\n\nWhen accessing certain admin-level pages (adjusting your user profile, for\nexample), you may notice that you're prompted for your password, even though\nyou're already logged in. This is by design, and aims to help guard protect you\nif your screen is unlocked and unattended.\n\n### Session Management\n\nYou can see a list of your active sessions on your security settings page. From\nhere, you can revoke sessions, in case you have lost access to a machine from\nwhich you were accessing.\n"
  },
  {
    "path": "website/content/vagrant-cloud/users/index.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: User Accounts\ndescription: \"Users are the main identity system in Vagrant Cloud.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# User Accounts\n\nUsers are the main identity system in Vagrant Cloud. A user can be a\nmember of multiple [organizations](/vagrant/vagrant-cloud/organizations),\nas well as individually collaborate on various resources.\n"
  },
  {
    "path": "website/content/vagrant-cloud/users/recovery.mdx",
    "content": "---\nlayout: vagrant-cloud\npage_title: Account Recovery\ndescription: \"Reset your Vagrant Cloud password with the reset link in the UI.\"\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\n# Account Recovery\n\nIf you have lost access to your Vagrant Cloud account, use the reset\npassword form on the login page to send yourself a link to reset your password.\n\nIf an email is unknown, [contact support](/vagrant/intro/support)\nfor further help.\n"
  },
  {
    "path": "website/content/vmware/index.mdx",
    "content": "---\nlayout: vmware\npage_title: VMware\ndescription: This is a placeholder page.\n---\n\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n> [!IMPORTANT]  \n> **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com.\n⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️\n\nSee [/docs/providers/vmware](/vagrant/docs/providers/vmware)\n"
  },
  {
    "path": "website/data/alert-banner.js",
    "content": "/**\n * Copyright IBM Corp. 2010, 2025\n * SPDX-License-Identifier: BUSL-1.1\n */\n\nexport const ALERT_BANNER_ACTIVE = false\n\n// https://github.com/hashicorp/web-components/tree/master/packages/alert-banner\nexport default {\n  tag: 'Blog post',\n  url: 'https://www.hashicorp.com/blog/a-new-chapter-for-hashicorp',\n  text: 'HashiCorp shares have begun trading on the Nasdaq. Read the blog from our founders, Mitchell Hashimoto and Armon Dadgar.',\n  linkText: 'Read the post',\n  // Set the expirationDate prop with a datetime string (e.g. '2020-01-31T12:00:00-07:00')\n  // if you'd like the component to stop showing at or after a certain date\n  expirationDate: '2021-12-17T23:00:00-07:00',\n}\n"
  },
  {
    "path": "website/data/docs-nav-data.json",
    "content": "[\n  {\n    \"title\": \"Overview\",\n    \"path\": \"\"\n  },\n  {\n    \"title\": \"Installation\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"installation\"\n      },\n      {\n        \"title\": \"Backwards Compatibility\",\n        \"path\": \"installation/backwards-compatibility\"\n      },\n      {\n        \"title\": \"Upgrading\",\n        \"path\": \"installation/upgrading\"\n      },\n      {\n        \"title\": \"Upgrading from 1.0.x\",\n        \"path\": \"installation/upgrading-from-1-0\"\n      },\n      {\n        \"title\": \"From Source\",\n        \"path\": \"installation/source\"\n      },\n      {\n        \"title\": \"Uninstallation\",\n        \"path\": \"installation/uninstallation\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Commands (CLI)\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"cli\"\n      },\n      {\n        \"title\": \"box\",\n        \"path\": \"cli/box\"\n      },\n      {\n        \"title\": \"cloud\",\n        \"path\": \"cli/cloud\"\n      },\n      {\n        \"title\": \"connect\",\n        \"path\": \"cli/connect\"\n      },\n      {\n        \"title\": \"destroy\",\n        \"path\": \"cli/destroy\"\n      },\n      {\n        \"title\": \"global-status\",\n        \"path\": \"cli/global-status\"\n      },\n      {\n        \"title\": \"halt\",\n        \"path\": \"cli/halt\"\n      },\n      {\n        \"title\": \"init\",\n        \"path\": \"cli/init\"\n      },\n      {\n        \"title\": \"login\",\n        \"path\": \"cli/login\"\n      },\n      {\n        \"title\": \"package\",\n        \"path\": \"cli/package\"\n      },\n      {\n        \"title\": \"plugin\",\n        \"path\": \"cli/plugin\"\n      },\n      {\n        \"title\": \"port\",\n        \"path\": \"cli/port\"\n      },\n      {\n        \"title\": \"powershell\",\n        \"path\": \"cli/powershell\"\n      },\n      {\n        \"title\": \"provision\",\n        \"path\": \"cli/provision\"\n      },\n      {\n        \"title\": \"rdp\",\n        \"path\": \"cli/rdp\"\n      },\n      {\n        \"title\": \"reload\",\n        \"path\": \"cli/reload\"\n      },\n      {\n        \"title\": \"resume\",\n        \"path\": \"cli/resume\"\n      },\n      {\n        \"title\": \"share\",\n        \"path\": \"cli/share\"\n      },\n      {\n        \"title\": \"snapshot\",\n        \"path\": \"cli/snapshot\"\n      },\n      {\n        \"title\": \"ssh\",\n        \"path\": \"cli/ssh\"\n      },\n      {\n        \"title\": \"ssh-config\",\n        \"path\": \"cli/ssh_config\"\n      },\n      {\n        \"title\": \"status\",\n        \"path\": \"cli/status\"\n      },\n      {\n        \"title\": \"suspend\",\n        \"path\": \"cli/suspend\"\n      },\n      {\n        \"title\": \"up\",\n        \"path\": \"cli/up\"\n      },\n      {\n        \"title\": \"upload\",\n        \"path\": \"cli/upload\"\n      },\n      {\n        \"title\": \"validate\",\n        \"path\": \"cli/validate\"\n      },\n      {\n        \"title\": \"version\",\n        \"path\": \"cli/version\"\n      },\n      {\n        \"title\": \"More Commands\",\n        \"path\": \"cli/non-primary\"\n      },\n      {\n        \"title\": \"Aliases\",\n        \"path\": \"cli/aliases\"\n      },\n      {\n        \"title\": \"Machine Readable Output\",\n        \"path\": \"cli/machine-readable\"\n      },\n      {\n        \"title\": \"rsync\",\n        \"path\": \"cli/rsync\",\n        \"hidden\": true\n      },\n      {\n        \"title\": \"rsync-auto\",\n        \"path\": \"cli/rsync-auto\",\n        \"hidden\": true\n      },\n      {\n        \"title\": \"winrm\",\n        \"path\": \"cli/winrm\",\n        \"hidden\": true\n      },\n      {\n        \"title\": \"winrm_config\",\n        \"path\": \"cli/winrm_config\",\n        \"hidden\": true\n      }\n    ]\n  },\n  {\n    \"title\": \"Vagrant Share\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"share\"\n      },\n      {\n        \"title\": \"HTTP Sharing\",\n        \"path\": \"share/http\"\n      },\n      {\n        \"title\": \"SSH Sharing\",\n        \"path\": \"share/ssh\"\n      },\n      {\n        \"title\": \"Connect\",\n        \"path\": \"share/connect\"\n      },\n      {\n        \"title\": \"Security\",\n        \"path\": \"share/security\"\n      },\n      {\n        \"title\": \"Custom Provider\",\n        \"path\": \"share/provider\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Vagrantfile\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"vagrantfile\"\n      },\n      {\n        \"title\": \"Configuration Version\",\n        \"path\": \"vagrantfile/version\"\n      },\n      {\n        \"title\": \"Minimum Vagrant Version\",\n        \"path\": \"vagrantfile/vagrant_version\"\n      },\n      {\n        \"title\": \"Tips &amp; Tricks\",\n        \"path\": \"vagrantfile/tips\"\n      },\n      {\n        \"title\": \"<code>config.vm</code>\",\n        \"path\": \"vagrantfile/machine_settings\"\n      },\n      {\n        \"title\": \"<code>config.ssh</code>\",\n        \"path\": \"vagrantfile/ssh_settings\"\n      },\n      {\n        \"title\": \"<code>config.winrm</code>\",\n        \"path\": \"vagrantfile/winrm_settings\"\n      },\n      {\n        \"title\": \"<code>config.winssh</code>\",\n        \"path\": \"vagrantfile/winssh_settings\"\n      },\n      {\n        \"title\": \"<code>config.vagrant</code>\",\n        \"path\": \"vagrantfile/vagrant_settings\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Boxes\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"boxes\"\n      },\n      {\n        \"title\": \"Box Versioning\",\n        \"path\": \"boxes/versioning\"\n      },\n      {\n        \"title\": \"Creating a Base Box\",\n        \"path\": \"boxes/base\"\n      },\n      {\n        \"title\": \"Box File Format\",\n        \"path\": \"boxes/format\"\n      },\n      {\n        \"title\": \"Box Repository\",\n        \"path\": \"boxes/box_repository\"\n      },\n      {\n        \"title\": \"Box Info Format\",\n        \"path\": \"boxes/info\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Provisioning\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"provisioning\"\n      },\n      {\n        \"title\": \"Basic Usage\",\n        \"path\": \"provisioning/basic_usage\"\n      },\n      {\n        \"title\": \"File\",\n        \"path\": \"provisioning/file\"\n      },\n      {\n        \"title\": \"Shell\",\n        \"path\": \"provisioning/shell\"\n      },\n      {\n        \"title\": \"Ansible Intro\",\n        \"path\": \"provisioning/ansible_intro\"\n      },\n      {\n        \"title\": \"Ansible\",\n        \"path\": \"provisioning/ansible\"\n      },\n      {\n        \"title\": \"Ansible Local\",\n        \"path\": \"provisioning/ansible_local\"\n      },\n      {\n        \"title\": \"Common Ansible Options\",\n        \"path\": \"provisioning/ansible_common\"\n      },\n      {\n        \"title\": \"CFEngine\",\n        \"path\": \"provisioning/cfengine\"\n      },\n      {\n        \"title\": \"Chef Common Configuration\",\n        \"path\": \"provisioning/chef_common\"\n      },\n      {\n        \"title\": \"Chef Solo\",\n        \"path\": \"provisioning/chef_solo\"\n      },\n      {\n        \"title\": \"Chef Zero\",\n        \"path\": \"provisioning/chef_zero\"\n      },\n      {\n        \"title\": \"Chef Client\",\n        \"path\": \"provisioning/chef_client\"\n      },\n      {\n        \"title\": \"Chef Apply\",\n        \"path\": \"provisioning/chef_apply\"\n      },\n      {\n        \"title\": \"Docker\",\n        \"path\": \"provisioning/docker\"\n      },\n      {\n        \"title\": \"Podman\",\n        \"path\": \"provisioning/podman\"\n      },\n      {\n        \"title\": \"Puppet Apply\",\n        \"path\": \"provisioning/puppet_apply\"\n      },\n      {\n        \"title\": \"Puppet Agent\",\n        \"path\": \"provisioning/puppet_agent\"\n      },\n      {\n        \"title\": \"Salt\",\n        \"path\": \"provisioning/salt\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Networking\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"networking\"\n      },\n      {\n        \"title\": \"Basic Usage\",\n        \"path\": \"networking/basic_usage\"\n      },\n      {\n        \"title\": \"Forwarded Ports\",\n        \"path\": \"networking/forwarded_ports\"\n      },\n      {\n        \"title\": \"Private Network\",\n        \"path\": \"networking/private_network\"\n      },\n      {\n        \"title\": \"Public Network\",\n        \"path\": \"networking/public_network\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Synced Folders\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"synced-folders\"\n      },\n      {\n        \"title\": \"Basic Usage\",\n        \"path\": \"synced-folders/basic_usage\"\n      },\n      {\n        \"title\": \"NFS\",\n        \"path\": \"synced-folders/nfs\"\n      },\n      {\n        \"title\": \"RSync\",\n        \"path\": \"synced-folders/rsync\"\n      },\n      {\n        \"title\": \"SMB\",\n        \"path\": \"synced-folders/smb\"\n      },\n      {\n        \"title\": \"VirtualBox\",\n        \"path\": \"synced-folders/virtualbox\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Cloud-Init\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"cloud-init\"\n      },\n      {\n        \"title\": \"Configuration\",\n        \"path\": \"cloud-init/configuration\"\n      },\n      {\n        \"title\": \"Usage\",\n        \"path\": \"cloud-init/usage\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Disks\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"disks\"\n      },\n      {\n        \"title\": \"Configuration\",\n        \"path\": \"disks/configuration\"\n      },\n      {\n        \"title\": \"Usage\",\n        \"path\": \"disks/usage\"\n      },\n      {\n        \"title\": \"VirtualBox\",\n        \"routes\": [\n          {\n            \"title\": \"Overview\",\n            \"path\": \"disks/virtualbox\"\n          },\n          {\n            \"title\": \"Usage\",\n            \"path\": \"disks/virtualbox/usage\"\n          },\n          {\n            \"title\": \"Common Issues\",\n            \"path\": \"disks/virtualbox/common-issues\"\n          }\n        ]\n      },\n      {\n        \"title\": \"Hyper-V\",\n        \"routes\": [\n          {\n            \"title\": \"Overview\",\n            \"path\": \"disks/hyperv\"\n          },\n          {\n            \"title\": \"Usage\",\n            \"path\": \"disks/hyperv/usage\"\n          },\n          {\n            \"title\": \"Common Issues\",\n            \"path\": \"disks/hyperv/common-issues\"\n          }\n        ]\n      },\n      {\n        \"title\": \"VMware\",\n        \"routes\": [\n          {\n            \"title\": \"Overview\",\n            \"path\": \"disks/vmware\"\n          },\n          {\n            \"title\": \"Usage\",\n            \"path\": \"disks/vmware/usage\"\n          },\n          {\n            \"title\": \"Common Issues\",\n            \"path\": \"disks/vmware/common-issues\"\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"title\": \"Multi-Machine\",\n    \"path\": \"multi-machine\"\n  },\n  {\n    \"title\": \"Providers\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"providers\"\n      },\n      {\n        \"title\": \"Installation\",\n        \"path\": \"providers/installation\"\n      },\n      {\n        \"title\": \"Basic Usage\",\n        \"path\": \"providers/basic_usage\"\n      },\n      {\n        \"title\": \"Configuration\",\n        \"path\": \"providers/configuration\"\n      },\n      {\n        \"title\": \"Default Provider\",\n        \"path\": \"providers/default\"\n      },\n      {\n        \"title\": \"VirtualBox\",\n        \"routes\": [\n          {\n            \"title\": \"Overview\",\n            \"path\": \"providers/virtualbox\"\n          },\n          {\n            \"title\": \"Usage\",\n            \"path\": \"providers/virtualbox/usage\"\n          },\n          {\n            \"title\": \"Creating a Base Box\",\n            \"path\": \"providers/virtualbox/boxes\"\n          },\n          {\n            \"title\": \"Configuration\",\n            \"path\": \"providers/virtualbox/configuration\"\n          },\n          {\n            \"title\": \"Networking\",\n            \"path\": \"providers/virtualbox/networking\"\n          },\n          {\n            \"title\": \"Common Issues\",\n            \"path\": \"providers/virtualbox/common-issues\"\n          }\n        ]\n      },\n      {\n        \"title\": \"VMware\",\n        \"routes\": [\n          {\n            \"title\": \"Overview\",\n            \"path\": \"providers/vmware\"\n          },\n          {\n            \"title\": \"Installation\",\n            \"path\": \"providers/vmware/installation\"\n          },\n          {\n            \"title\": \"VMware Utility\",\n            \"path\": \"providers/vmware/vagrant-vmware-utility\"\n          },\n          {\n            \"title\": \"Usage\",\n            \"path\": \"providers/vmware/usage\"\n          },\n          {\n            \"title\": \"Boxes\",\n            \"path\": \"providers/vmware/boxes\"\n          },\n          {\n            \"title\": \"Configuration\",\n            \"path\": \"providers/vmware/configuration\"\n          },\n          {\n            \"title\": \"Known Issues\",\n            \"path\": \"providers/vmware/known-issues\"\n          },\n          {\n            \"title\": \"FAQ\",\n            \"path\": \"providers/vmware/faq\"\n          }\n        ]\n      },\n      {\n        \"title\": \"Docker\",\n        \"routes\": [\n          {\n            \"title\": \"Overview\",\n            \"path\": \"providers/docker\"\n          },\n          {\n            \"title\": \"Basic Usage\",\n            \"path\": \"providers/docker/basics\"\n          },\n          {\n            \"title\": \"Commands\",\n            \"path\": \"providers/docker/commands\"\n          },\n          {\n            \"title\": \"Boxes\",\n            \"path\": \"providers/docker/boxes\"\n          },\n          {\n            \"title\": \"Configuration\",\n            \"path\": \"providers/docker/configuration\"\n          },\n          {\n            \"title\": \"Networking\",\n            \"path\": \"providers/docker/networking\"\n          }\n        ]\n      },\n      {\n        \"title\": \"Hyper-V\",\n        \"routes\": [\n          {\n            \"title\": \"Overview\",\n            \"path\": \"providers/hyperv\"\n          },\n          {\n            \"title\": \"Usage\",\n            \"path\": \"providers/hyperv/usage\"\n          },\n          {\n            \"title\": \"Creating a Base Box\",\n            \"path\": \"providers/hyperv/boxes\"\n          },\n          {\n            \"title\": \"Configuration\",\n            \"path\": \"providers/hyperv/configuration\"\n          },\n          {\n            \"title\": \"Limitations\",\n            \"path\": \"providers/hyperv/limitations\"\n          }\n        ]\n      },\n      {\n        \"title\": \"Custom Provider\",\n        \"path\": \"providers/custom\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Plugins\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"plugins\"\n      },\n      {\n        \"title\": \"Usage\",\n        \"path\": \"plugins/usage\"\n      },\n      {\n        \"title\": \"Plugin Development Basics\",\n        \"path\": \"plugins/development-basics\"\n      },\n      {\n        \"title\": \"Action Hooks\",\n        \"path\": \"plugins/action-hooks\"\n      },\n      {\n        \"title\": \"Commands\",\n        \"path\": \"plugins/commands\"\n      },\n      {\n        \"title\": \"Configuration\",\n        \"path\": \"plugins/configuration\"\n      },\n      {\n        \"title\": \"Guests\",\n        \"path\": \"plugins/guests\"\n      },\n      {\n        \"title\": \"Guest Capabilities\",\n        \"path\": \"plugins/guest-capabilities\"\n      },\n      {\n        \"title\": \"Hosts\",\n        \"path\": \"plugins/hosts\"\n      },\n      {\n        \"title\": \"Host Capabilities\",\n        \"path\": \"plugins/host-capabilities\"\n      },\n      {\n        \"title\": \"Providers\",\n        \"path\": \"plugins/providers\"\n      },\n      {\n        \"title\": \"Provisioners\",\n        \"path\": \"plugins/provisioners\"\n      },\n      {\n        \"title\": \"Packaging &amp; Distribution\",\n        \"path\": \"plugins/packaging\"\n      },\n      {\n        \"title\": \"Go Plugins\",\n        \"routes\": [\n          {\n            \"title\": \"Overview\",\n            \"path\": \"plugins/go-plugins\"\n          },\n          {\n            \"title\": \"Guests\",\n            \"path\": \"plugins/go-plugins/guests\"\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"title\": \"Push\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"push\"\n      },\n      {\n        \"title\": \"FTP / SFTP\",\n        \"path\": \"push/ftp\"\n      },\n      {\n        \"title\": \"Heroku\",\n        \"path\": \"push/heroku\"\n      },\n      {\n        \"title\": \"Local Exec\",\n        \"path\": \"push/local-exec\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Triggers\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"triggers\"\n      },\n      {\n        \"title\": \"Configuration\",\n        \"path\": \"triggers/configuration\"\n      },\n      {\n        \"title\": \"Usage\",\n        \"path\": \"triggers/usage\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Experimental\",\n    \"routes\": [\n      {\n        \"title\": \"Feature Flags\",\n        \"path\": \"experimental\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Other\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"other\"\n      },\n      {\n        \"title\": \"Debugging\",\n        \"path\": \"other/debugging\"\n      },\n      {\n        \"title\": \"Environmental Variables\",\n        \"path\": \"other/environmental-variables\"\n      },\n      {\n        \"title\": \"WSL\",\n        \"path\": \"other/wsl\"\n      },\n      {\n        \"title\": \"macOS Catalina\",\n        \"path\": \"other/macos-catalina\",\n        \"hidden\": true\n      }\n    ]\n  },\n  {\n    \"divider\": true\n  },\n  {\n    \"title\": \"Vagrant Cloud\",\n    \"href\": \"/vagrant-cloud\"\n  }\n]\n"
  },
  {
    "path": "website/data/intro-nav-data.json",
    "content": "[\n  {\n    \"title\": \"What is Vagrant?\",\n    \"path\": \"\"\n  },\n  {\n    \"title\": \"Vagrant vs. Other Software\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"vs\"\n      },\n      {\n        \"title\": \"CLI Tools\",\n        \"path\": \"vs/cli-tools\"\n      },\n      {\n        \"title\": \"Docker\",\n        \"path\": \"vs/docker\"\n      },\n      {\n        \"title\": \"Terraform\",\n        \"path\": \"vs/terraform\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Getting Started\",\n    \"href\": \"https://developer.hashicorp.com/vagrant/tutorials/get-started\"\n  },\n  {\n    \"title\": \"Contributing\",\n    \"path\": \"contributing-guide\"\n  },\n  {\n    \"title\": \"Support\",\n    \"path\": \"support\"\n  }\n]\n"
  },
  {
    "path": "website/data/metadata.js",
    "content": "/**\n * Copyright IBM Corp. 2010, 2025\n * SPDX-License-Identifier: BUSL-1.1\n */\n\nexport const productName = 'Vagrant'\nexport const productSlug = 'vagrant'\n"
  },
  {
    "path": "website/data/subnav.js",
    "content": "/**\n * Copyright IBM Corp. 2010, 2025\n * SPDX-License-Identifier: BUSL-1.1\n */\n\nexport default [\n  {\n    text: 'Intro',\n    url: '/intro',\n    type: 'inbound',\n  },\n  {\n    text: 'Docs',\n    url: '/docs',\n    type: 'inbound',\n  },\n  {\n    text: 'Community',\n    url: '/community',\n    type: 'inbound',\n  },\n]\n"
  },
  {
    "path": "website/data/vagrant-cloud-nav-data.json",
    "content": "[\n  {\n    \"title\": \"HCP Vagrant\",\n    \"routes\": [\n      {\n        \"title\": \"HCP Vagrant Registry Migration Guide\",\n        \"path\": \"hcp-vagrant/migration-guide\"\n      },\n      {\n        \"title\": \"Migration Troubleshooting\",\n        \"path\": \"hcp-vagrant/troubleshooting\"\n      },\n      {\n          \"title\": \"Post Migration\",\n          \"path\": \"hcp-vagrant/post-migration-guide\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Boxes\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"boxes\"\n      },\n      {\n        \"title\": \"Catalog\",\n        \"path\": \"boxes/catalog\"\n      },\n      {\n        \"title\": \"Creating a New Box\",\n        \"path\": \"boxes/create\"\n      },\n      {\n        \"title\": \"Creating a New Version\",\n        \"path\": \"boxes/create-version\"\n      },\n      {\n        \"title\": \"Box Provider Architecture\",\n        \"path\": \"boxes/architecture\"\n      },\n      {\n        \"title\": \"Distributing\",\n        \"path\": \"boxes/distributing\"\n      },\n      {\n        \"title\": \"Lifecycle\",\n        \"path\": \"boxes/lifecycle\"\n      },\n      {\n        \"title\": \"Private Boxes\",\n        \"path\": \"boxes/private\"\n      },\n      {\n        \"title\": \"Release Workflow\",\n        \"path\": \"boxes/release-workflow\"\n      },\n      {\n        \"title\": \"Finding & Using Boxes\",\n        \"path\": \"boxes/using\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Organizations\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"organizations\"\n      },\n      {\n        \"title\": \"Create an Organization\",\n        \"path\": \"organizations/create\"\n      },\n      {\n        \"title\": \"Migrate User to Organization\",\n        \"path\": \"organizations/migrate\"\n      },\n      {\n        \"title\": \"Authentication Policy\",\n        \"path\": \"organizations/authentication-policy\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Users\",\n    \"routes\": [\n      {\n        \"title\": \"Overview\",\n        \"path\": \"users\"\n      },\n      {\n        \"title\": \"Authentication\",\n        \"path\": \"users/authentication\"\n      },\n      {\n        \"title\": \"Account Recovery\",\n        \"path\": \"users/recovery\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Request Limits\",\n    \"path\": \"request-limits\"\n  },\n  {\n    \"title\": \"Support\",\n    \"path\": \"support\"\n  },\n  {\n    \"title\": \"API\",\n    \"routes\": [\n      {\n        \"title\": \"v2\",\n        \"path\": \"api/v2\"\n      },\n      {\n        \"title\": \"v1\",\n        \"path\": \"api/v1\"\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "website/data/version.json",
    "content": "{\n  \"VERSION\": \"2.4.9\",\n  \"VMWARE_UTILITY_VERSION\": \"1.0.22\"\n}\n"
  },
  {
    "path": "website/data/vmware-nav-data.json",
    "content": "[\n  {\n    \"title\": \"Privacy Policy\",\n    \"href\": \"https://www.hashicorp.com/privacy\"\n  }\n]\n"
  },
  {
    "path": "website/jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\"\n  },\n  \"exclude\": [\"node_modules\", \".next\", \"out\"]\n}\n"
  },
  {
    "path": "website/package.json",
    "content": "{\n  \"name\": \"vagrant-docs\",\n  \"description\": \"Documentation website for HashiCorp Vagrant\",\n  \"version\": \"1.0.0\",\n  \"author\": \"HashiCorp\",\n  \"devDependencies\": {\n    \"@hashicorp/platform-cli\": \"^2.6.0\",\n    \"@hashicorp/platform-content-conformance\": \"^0.0.9\",\n    \"husky\": \"4.3.8\",\n    \"next\": \"^12.3.1\",\n    \"prettier\": \"2.2.1\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"next-hashicorp precommit\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"./scripts/website-build.sh\",\n    \"format\": \"next-hashicorp format\",\n    \"generate:readme\": \"next-hashicorp markdown-blocks README.md\",\n    \"lint\": \"next-hashicorp lint\",\n    \"linkcheck\": \"linkcheck https://vagrantup.com\",\n    \"start\": \"./scripts/website-start.sh\",\n    \"content-check\": \"hc-content --config base-docs\"\n  },\n  \"engines\": {\n    \"npm\": \">=7.0.0\"\n  }\n}\n"
  },
  {
    "path": "website/prettier.config.js",
    "content": "/**\n * Copyright IBM Corp. 2010, 2025\n * SPDX-License-Identifier: BUSL-1.1\n */\n\nmodule.exports = {\n  ...require('@hashicorp/platform-cli/config/prettier.config'),\n}\n"
  },
  {
    "path": "website/public/ie-warning.js",
    "content": "/**\n * Copyright IBM Corp. 2010, 2025\n * SPDX-License-Identifier: BUSL-1.1\n */\n\n!(function () {\n  'use strict'\n\n  const el = document.createElement('div');\n  el.innerHTML =\n    '<div class=\"ie-warning\">' +\n    '  <p class=\"ie-warning-description\">' +\n    '    Internet Explorer is no longer supported.' +\n    '    <a href=\"https://support.hashicorp.com/hc/en-us/articles/4416485547795\">' +\n    '      Learn more.' +\n    '    </a>' +\n    '  </p>' +\n    '</div>' +\n    '<style>' +\n    '  .ie-warning {' +\n    '    background-color: #FCF0F2;' +\n    '    border-bottom: 1px solid #FFD4D6;' +\n    '    color: #BA2226;' +\n    '    text-align: center;' +\n    '    font-family: \"Segoe UI\", sans-serif;' +\n    '    font-weight: bold;' +\n    '  }' +\n    '  .ie-warning-description {' +\n    '    padding: 16px 0;' +\n    '    margin: 0;' +\n    '    color: #BA2226;' +\n    '  }' +\n    '</style>';\n\n  document.body.insertBefore(el, document.body.childNodes[0]);\n})();\n"
  },
  {
    "path": "website/redirects.js",
    "content": "/**\n * Copyright IBM Corp. 2010, 2025\n * SPDX-License-Identifier: BUSL-1.1\n */\n\n/**\n * Define your custom redirects within this file.\n *\n * See the README file in this directory for documentation. Please do not\n * modify or delete existing redirects without first verifying internally.\n *\n * Vercel's redirect documentation:\n * https://nextjs.org/docs/api-reference/next.config.js/redirects\n *\n * Relative paths with fragments (#) are not supported.\n * For destinations with fragments, use an absolute URL.\n *\n * Playground for testing url pattern matching: https://npm.runkit.com/path-to-regexp\n *\n * Note that redirects defined in a product's redirects file are applied to\n * the developer.hashicorp.com domain, which is where the documentation content\n * is rendered. Redirect sources should be prefixed with the product slug\n * to ensure they are scoped to the product's section. Any redirects that are\n * not prefixed with a product slug will be ignored.\n */\n module.exports = [\n  /*\n  Example redirect:\n  {\n    source: '/vagrant/docs/internal-docs/my-page',\n    destination: '/vagrant/docs/internals/my-page',\n    permanent: true,\n  },\n  */\n]\n"
  },
  {
    "path": "website/scripts/should-build.sh",
    "content": "#!/usr/bin/env bash\n# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n\n######################################################\n# NOTE: This file is managed by the Digital Team's   #\n# Terraform configuration @ hashicorp/mktg-terraform #\n######################################################\n\n# This is run during the website build step to determine if we should skip the build or not.\n# More information: https://vercel.com/docs/platform/projects#ignored-build-step\n\nif [[ \"$VERCEL_GIT_COMMIT_REF\" == \"stable-website\"  ]] ; then\n  # Proceed with the build if the branch is stable-website\n  echo \"✅ - Build can proceed\"\n  exit 1;\nelse\n  # Check for differences in the website directory\n  git diff --quiet HEAD^ HEAD ./\nfi"
  },
  {
    "path": "website/scripts/website-build.sh",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n######################################################\n# NOTE: This file is managed by the Digital Team's   #\n# Terraform configuration @ hashicorp/mktg-terraform #\n######################################################\n\n# Repo which we are cloning and executing npm run build:deploy-preview within\nREPO_TO_CLONE=dev-portal\n# Set the subdirectory name for the base project\nPREVIEW_DIR=website-preview\n# The directory we want to clone the project into\nCLONE_DIR=website-preview\n# The product for which we are building the deploy preview\nPRODUCT=vagrant\n# Preview mode, controls the UI rendered (either the product site or developer). Can be `io` or `developer`\nPREVIEW_MODE=developer\n\n# Get the git branch of the commit that triggered the deploy preview\n# This will power remote image assets in local and deploy previews\nCURRENT_GIT_BRANCH=$VERCEL_GIT_COMMIT_REF\n\n# This is where content files live, relative to the website-preview dir. If omitted, \"../content\" will be used\nLOCAL_CONTENT_DIR=\n\nfrom_cache=false\n\nif [ -d \"$PREVIEW_DIR\" ]; then\n  echo \"$PREVIEW_DIR found\"\n  CLONE_DIR=\"$PREVIEW_DIR-tmp\"\n  from_cache=true\nfi\n\n# Clone the base project, if needed\necho \"⏳ Cloning the $REPO_TO_CLONE repo, this might take a while...\"\ngit clone --depth=1 \"https://github.com/hashicorp/$REPO_TO_CLONE.git\" \"$CLONE_DIR\"\n\nif [ \"$from_cache\" = true ]; then\n  echo \"Setting up $PREVIEW_DIR\"\n  cp -R \"./$CLONE_DIR/.\" \"./$PREVIEW_DIR\"\nfi\n\n# cd into the preview directory project\ncd \"$PREVIEW_DIR\"\n\n# Run the build:deploy-preview start script\nPREVIEW_FROM_REPO=$PRODUCT \\\nIS_CONTENT_PREVIEW=true \\\nPREVIEW_MODE=$PREVIEW_MODE \\\nREPO=$PRODUCT \\\nHASHI_ENV=project-preview \\\nLOCAL_CONTENT_DIR=$LOCAL_CONTENT_DIR \\\nCURRENT_GIT_BRANCH=$CURRENT_GIT_BRANCH \\\nnpm run build:deploy-preview"
  },
  {
    "path": "website/scripts/website-start.sh",
    "content": "# Copyright IBM Corp. 2010, 2025\n# SPDX-License-Identifier: BUSL-1.1\n\n######################################################\n# NOTE: This file is managed by the Digital Team's   #\n# Terraform configuration @ hashicorp/mktg-terraform #\n######################################################\n\n# Repo which we are cloning and executing npm run build:deploy-preview within\nREPO_TO_CLONE=dev-portal\n# Set the subdirectory name for the dev-portal app\nPREVIEW_DIR=website-preview\n# The product for which we are building the deploy preview\nPRODUCT=vagrant\n# Preview mode, controls the UI rendered (either the product site or developer). Can be `io` or `developer`\nPREVIEW_MODE=developer\n\n# Get the git branch of the commit that triggered the deploy preview\n# This will power remote image assets in local and deploy previews\nCURRENT_GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)\n\n# This is where content files live, relative to the website-preview dir. If omitted, \"../content\" will be used\nLOCAL_CONTENT_DIR=\n\nshould_pull=true\n\n# Clone the dev-portal project, if needed\nif [ ! -d \"$PREVIEW_DIR\" ]; then\n    echo \"⏳ Cloning the $REPO_TO_CLONE repo, this might take a while...\"\n    git clone --depth=1 https://github.com/hashicorp/$REPO_TO_CLONE.git \"$PREVIEW_DIR\"\n    should_pull=false\nfi\n\ncd \"$PREVIEW_DIR\"\n\n# If the directory already existed, pull to ensure the clone is fresh\nif [ \"$should_pull\" = true ]; then\n    git pull origin main\nfi\n\n# Run the dev-portal content-repo start script\nREPO=$PRODUCT \\\nPREVIEW_FROM_REPO=$PRODUCT \\\nLOCAL_CONTENT_DIR=$LOCAL_CONTENT_DIR \\\nCURRENT_GIT_BRANCH=$CURRENT_GIT_BRANCH \\\nPREVIEW_MODE=$PREVIEW_MODE \\\nnpm run start:local-preview"
  },
  {
    "path": "website/vercel.json",
    "content": "{\n  \"version\": 2,\n  \"public\": true,\n  \"github\": {\n    \"silent\": true\n  }\n}\n"
  }
]