# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

import json
import logging
import os.path
from collections import namedtuple
from collections.abc import Callable
from textwrap import dedent
from typing import ContextManager, cast

import pytest

from pants.backend.docker.goals.package_image import (
    DockerBuildTargetStageError,
    DockerImageOptionValueError,
    DockerImageTagValueError,
    DockerInfoV1,
    DockerPackageFieldSet,
    DockerRepositoryNameError,
    ImageRefRegistry,
    ImageRefTag,
    build_docker_image,
    parse_image_id_from_docker_build_output,
    rules,
)
from pants.backend.docker.registries import DockerRegistries, DockerRegistryOptions
from pants.backend.docker.subsystems.docker_options import DockerOptions
from pants.backend.docker.subsystems.dockerfile_parser import DockerfileInfo
from pants.backend.docker.target_types import (
    DockerImageTags,
    DockerImageTagsRequest,
    DockerImageTarget,
)
from pants.backend.docker.util_rules.docker_binary import DockerBinary
from pants.backend.docker.util_rules.docker_build_args import (
    DockerBuildArgs,
    DockerBuildArgsRequest,
)
from pants.backend.docker.util_rules.docker_build_args import rules as build_args_rules
from pants.backend.docker.util_rules.docker_build_context import (
    DockerBuildContext,
    DockerBuildContextRequest,
)
from pants.backend.docker.util_rules.docker_build_env import (
    DockerBuildEnvironment,
    DockerBuildEnvironmentRequest,
)
from pants.backend.docker.util_rules.docker_build_env import rules as build_env_rules
from pants.engine.addresses import Address
from pants.engine.fs import (
    EMPTY_DIGEST,
    EMPTY_FILE_DIGEST,
    EMPTY_SNAPSHOT,
    CreateDigest,
    Digest,
    FileContent,
    Snapshot,
)
from pants.engine.platform import Platform
from pants.engine.process import (
    FallibleProcessResult,
    Process,
    ProcessExecutionEnvironment,
    ProcessExecutionFailure,
    ProcessResultMetadata,
)
from pants.engine.target import InvalidFieldException, WrappedTarget
from pants.engine.unions import UnionMembership, UnionRule
from pants.option.global_options import GlobalOptions, KeepSandboxes
from pants.testutil.option_util import create_subsystem
from pants.testutil.pytest_util import assert_logged, no_exception
from pants.testutil.rule_runner import QueryRule, RuleRunner, run_rule_with_mocks
from pants.util.frozendict import FrozenDict
from pants.util.value_interpolation import InterpolationContext, InterpolationError


@pytest.fixture
def rule_runner() -> RuleRunner:
    return RuleRunner(
        rules=[
            *rules(),
            *build_args_rules(),
            *build_env_rules(),
            QueryRule(GlobalOptions, []),
            QueryRule(DockerOptions, []),
            QueryRule(DockerBuildArgs, [DockerBuildArgsRequest]),
            QueryRule(DockerBuildEnvironment, [DockerBuildEnvironmentRequest]),
        ],
        target_types=[DockerImageTarget],
    )


class DockerImageTagsRequestPlugin(DockerImageTagsRequest):
    pass


def assert_build(
    rule_runner: RuleRunner,
    address: Address,
    *extra_log_lines: str,
    options: dict | None = None,
    process_assertions: Callable[[Process], None] | None = None,
    exit_code: int = 0,
    copy_sources: tuple[str, ...] = (),
    copy_build_args=(),
    build_context_snapshot: Snapshot = EMPTY_SNAPSHOT,
    version_tags: tuple[str, ...] = (),
    plugin_tags: tuple[str, ...] = (),
    expected_registries_metadata: None | list = None,
) -> None:
    tgt = rule_runner.get_target(address)
    metadata_file_path: list[str] = []
    metadata_file_contents: list[bytes] = []

    def build_context_mock(request: DockerBuildContextRequest) -> DockerBuildContext:
        return DockerBuildContext.create(
            snapshot=build_context_snapshot,
            upstream_image_ids=[],
            dockerfile_info=DockerfileInfo(
                request.address,
                digest=EMPTY_DIGEST,
                source=os.path.join(address.spec_path, "Dockerfile"),
                copy_source_paths=copy_sources,
                copy_build_args=copy_build_args,
                version_tags=version_tags,
            ),
            build_args=rule_runner.request(DockerBuildArgs, [DockerBuildArgsRequest(tgt)]),
            build_env=rule_runner.request(
                DockerBuildEnvironment, [DockerBuildEnvironmentRequest(tgt)]
            ),
        )

    def run_process_mock(process: Process) -> FallibleProcessResult:
        if process_assertions:
            process_assertions(process)

        return FallibleProcessResult(
            exit_code=exit_code,
            stdout=b"stdout",
            stdout_digest=EMPTY_FILE_DIGEST,
            stderr=b"stderr",
            stderr_digest=EMPTY_FILE_DIGEST,
            output_digest=EMPTY_DIGEST,
            metadata=ProcessResultMetadata(
                0,
                ProcessExecutionEnvironment(
                    environment_name=None,
                    platform=Platform.create_for_localhost().value,
                    docker_image=None,
                    remote_execution=False,
                    remote_execution_extra_platform_properties=[],
                    execute_in_workspace=False,
                    keep_sandboxes="never",
                ),
                "ran_locally",
                0,
            ),
        )

    def mock_get_info_file(request: CreateDigest) -> Digest:
        assert len(request) == 1
        assert isinstance(request[0], FileContent)
        metadata_file_path.append(request[0].path)
        metadata_file_contents.append(request[0].content)
        return EMPTY_DIGEST

    if options:
        opts = options or {}
        opts.setdefault("registries", {})
        opts.setdefault("default_repository", "{name}")
        opts.setdefault("default_context_root", "")
        opts.setdefault("build_args", [])
        opts.setdefault("build_target_stage", None)
        opts.setdefault("build_hosts", None)
        opts.setdefault("build_verbose", False)
        opts.setdefault("build_no_cache", False)
        opts.setdefault("use_buildx", False)
        opts.setdefault("env_vars", [])
        opts.setdefault("suggest_renames", True)

        docker_options = create_subsystem(
            DockerOptions,
            **opts,
        )
    else:
        docker_options = rule_runner.request(DockerOptions, [])

    global_options = rule_runner.request(GlobalOptions, [])

    union_membership = UnionMembership.from_rules(
        [UnionRule(DockerImageTagsRequest, DockerImageTagsRequestPlugin)]
    )
    result = run_rule_with_mocks(
        build_docker_image,
        rule_args=[
            DockerPackageFieldSet.create(tgt),
            docker_options,
            global_options,
            DockerBinary("/dummy/docker"),
            KeepSandboxes.never,
            union_membership,
        ],
        mock_calls={
            "pants.backend.docker.util_rules.docker_build_context.create_docker_build_context": build_context_mock,
            "pants.engine.internals.graph.resolve_target": lambda _: WrappedTarget(tgt),
            "pants.backend.docker.target_types.get_docker_image_tags": lambda __implicitly: DockerImageTags(
                plugin_tags
            ),
            "pants.engine.intrinsics.execute_process": run_process_mock,
            "pants.engine.intrinsics.create_digest": mock_get_info_file,
        },
        union_membership=union_membership,
        show_warnings=False,
    )

    assert result.digest == EMPTY_DIGEST
    assert len(result.artifacts) == 1
    assert len(metadata_file_path) == len(metadata_file_contents) == 1
    assert result.artifacts[0].relpath == metadata_file_path[0]

    metadata = json.loads(metadata_file_contents[0])
    # basic checks that we can always do
    assert metadata["version"] == 1
    assert metadata["image_id"] == "<unknown>"
    assert isinstance(metadata["registries"], list)
    # detailed checks, if the test opts in
    if expected_registries_metadata is not None:
        assert metadata["registries"] == expected_registries_metadata

    for log_line in extra_log_lines:
        assert log_line in result.artifacts[0].extra_log_lines


def test_build_docker_image(rule_runner: RuleRunner) -> None:
    rule_runner.write_files(
        {
            "docker/test/BUILD": dedent(
                """\

                docker_image(
                  name="test1",
                  image_tags=["1.2.3"],
                  repository="{directory}/{name}",
                )
                docker_image(
                  name="test2",
                  image_tags=["1.2.3"],
                )
                docker_image(
                  name="test3",
                  image_tags=["1.2.3"],
                  repository="{parent_directory}/{directory}/{name}",
                )
                docker_image(
                  name="test4",
                  image_tags=["1.2.3"],
                  repository="{directory}/four/test-four",
                )
                docker_image(
                  name="test5",
                  image_tags=["latest", "alpha-1.0", "alpha-1"],
                )
                docker_image(
                  name="test6",
                  image_tags=["1.2.3"],
                  repository="xyz/{full_directory}/{name}",
                )
                docker_image(
                  name="err1",
                  repository="{bad_template}",
                )
                """
            ),
            "docker/test/Dockerfile": "FROM python:3.8",
        }
    )

    assert_build(
        rule_runner,
        Address("docker/test", target_name="test1"),
        "Built docker image: test/test1:1.2.3",
        expected_registries_metadata=[
            dict(
                alias=None,
                address=None,
                repository="test/test1",
                tags=[
                    dict(
                        template="1.2.3",
                        tag="1.2.3",
                        uses_local_alias=False,
                        name="test/test1:1.2.3",
                    )
                ],
            )
        ],
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="test2"),
        "Built docker image: test2:1.2.3",
        expected_registries_metadata=[
            dict(
                alias=None,
                address=None,
                repository="test2",
                tags=[
                    dict(template="1.2.3", tag="1.2.3", uses_local_alias=False, name="test2:1.2.3")
                ],
            )
        ],
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="test3"),
        "Built docker image: docker/test/test3:1.2.3",
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="test4"),
        "Built docker image: test/four/test-four:1.2.3",
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="test5"),
        (
            "Built docker images: \n"
            "  * test/test5:latest\n"
            "  * test/test5:alpha-1.0\n"
            "  * test/test5:alpha-1"
        ),
        options=dict(default_repository="{directory}/{name}"),
        expected_registries_metadata=[
            dict(
                alias=None,
                address=None,
                repository="test/test5",
                tags=[
                    dict(
                        template="alpha-1",
                        tag="alpha-1",
                        uses_local_alias=False,
                        name="test/test5:alpha-1",
                    ),
                    dict(
                        template="alpha-1.0",
                        tag="alpha-1.0",
                        uses_local_alias=False,
                        name="test/test5:alpha-1.0",
                    ),
                    dict(
                        template="latest",
                        tag="latest",
                        uses_local_alias=False,
                        name="test/test5:latest",
                    ),
                ],
            )
        ],
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="test6"),
        "Built docker image: xyz/docker/test/test6:1.2.3",
    )

    err1 = (
        r"Invalid value for the `repository` field of the `docker_image` target at "
        r"docker/test:err1: '{bad_template}'\.\n\nThe placeholder 'bad_template' is unknown\. "
        r"Try with one of: build_args, default_repository, directory, full_directory, name, "
        r"pants, parent_directory, tags, target_repository\."
    )
    with pytest.raises(DockerRepositoryNameError, match=err1):
        assert_build(
            rule_runner,
            Address("docker/test", target_name="err1"),
        )


def test_build_image_with_registries(rule_runner: RuleRunner) -> None:
    rule_runner.write_files(
        {
            "docker/test/BUILD": dedent(
                """\
                docker_image(name="addr1", image_tags=["1.2.3"], registries=["myregistry1domain:port"])
                docker_image(name="addr2", image_tags=["1.2.3"], registries=["myregistry2domain:port"])
                docker_image(name="addr3", image_tags=["1.2.3"], registries=["myregistry3domain:port"])
                docker_image(name="alias1", image_tags=["1.2.3"], registries=["@reg1"])
                docker_image(name="alias2", image_tags=["1.2.3"], registries=["@reg2"])
                docker_image(name="alias3", image_tags=["1.2.3"], registries=["reg3"])
                docker_image(name="unreg", image_tags=["1.2.3"], registries=[])
                docker_image(name="def", image_tags=["1.2.3"])
                docker_image(name="multi", image_tags=["1.2.3"], registries=["@reg2", "@reg1"])
                docker_image(name="extra_tags", image_tags=["1.2.3"], registries=["@reg1", "@extra"])
                """
            ),
            "docker/test/Dockerfile": "FROM python:3.8",
        }
    )

    options = {
        "default_repository": "{name}",
        "registries": {
            "reg1": {"address": "myregistry1domain:port"},
            "reg2": {"address": "myregistry2domain:port", "default": "true"},
            "extra": {"address": "extra", "extra_image_tags": ["latest"]},
        },
    }

    assert_build(
        rule_runner,
        Address("docker/test", target_name="addr1"),
        "Built docker image: myregistry1domain:port/addr1:1.2.3",
        options=options,
        expected_registries_metadata=[
            dict(
                alias="reg1",
                address="myregistry1domain:port",
                repository="addr1",
                tags=[
                    dict(
                        template="1.2.3",
                        tag="1.2.3",
                        uses_local_alias=False,
                        name="myregistry1domain:port/addr1:1.2.3",
                    )
                ],
            )
        ],
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="addr2"),
        "Built docker image: myregistry2domain:port/addr2:1.2.3",
        options=options,
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="addr3"),
        "Built docker image: myregistry3domain:port/addr3:1.2.3",
        options=options,
        expected_registries_metadata=[
            dict(
                alias=None,
                address="myregistry3domain:port",
                repository="addr3",
                tags=[
                    dict(
                        template="1.2.3",
                        tag="1.2.3",
                        uses_local_alias=False,
                        name="myregistry3domain:port/addr3:1.2.3",
                    )
                ],
            )
        ],
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="alias1"),
        "Built docker image: myregistry1domain:port/alias1:1.2.3",
        options=options,
        expected_registries_metadata=[
            dict(
                alias="reg1",
                address="myregistry1domain:port",
                repository="alias1",
                tags=[
                    dict(
                        template="1.2.3",
                        tag="1.2.3",
                        uses_local_alias=False,
                        name="myregistry1domain:port/alias1:1.2.3",
                    )
                ],
            )
        ],
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="alias2"),
        "Built docker image: myregistry2domain:port/alias2:1.2.3",
        options=options,
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="alias3"),
        "Built docker image: reg3/alias3:1.2.3",
        options=options,
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="unreg"),
        "Built docker image: unreg:1.2.3",
        options=options,
        expected_registries_metadata=[
            dict(
                alias=None,
                address=None,
                repository="unreg",
                tags=[
                    dict(template="1.2.3", tag="1.2.3", uses_local_alias=False, name="unreg:1.2.3")
                ],
            )
        ],
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="def"),
        "Built docker image: myregistry2domain:port/def:1.2.3",
        options=options,
        expected_registries_metadata=[
            dict(
                alias="reg2",
                address="myregistry2domain:port",
                repository="def",
                tags=[
                    dict(
                        template="1.2.3",
                        tag="1.2.3",
                        uses_local_alias=False,
                        name="myregistry2domain:port/def:1.2.3",
                    )
                ],
            )
        ],
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="multi"),
        (
            "Built docker images: \n"
            "  * myregistry2domain:port/multi:1.2.3\n"
            "  * myregistry1domain:port/multi:1.2.3"
        ),
        options=options,
        expected_registries_metadata=[
            dict(
                alias="reg1",
                address="myregistry1domain:port",
                repository="multi",
                tags=[
                    dict(
                        template="1.2.3",
                        tag="1.2.3",
                        uses_local_alias=False,
                        name="myregistry1domain:port/multi:1.2.3",
                    )
                ],
            ),
            dict(
                alias="reg2",
                address="myregistry2domain:port",
                repository="multi",
                tags=[
                    dict(
                        template="1.2.3",
                        tag="1.2.3",
                        uses_local_alias=False,
                        name="myregistry2domain:port/multi:1.2.3",
                    )
                ],
            ),
        ],
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="extra_tags"),
        (
            "Built docker images: \n"
            "  * myregistry1domain:port/extra_tags:1.2.3\n"
            "  * extra/extra_tags:1.2.3\n"
            "  * extra/extra_tags:latest"
        ),
        options=options,
        expected_registries_metadata=[
            dict(
                alias="extra",
                address="extra",
                repository="extra_tags",
                tags=[
                    dict(
                        template="1.2.3",
                        tag="1.2.3",
                        uses_local_alias=False,
                        name="extra/extra_tags:1.2.3",
                    ),
                    dict(
                        template="latest",
                        tag="latest",
                        uses_local_alias=False,
                        name="extra/extra_tags:latest",
                    ),
                ],
            ),
            dict(
                alias="reg1",
                address="myregistry1domain:port",
                repository="extra_tags",
                tags=[
                    dict(
                        template="1.2.3",
                        tag="1.2.3",
                        uses_local_alias=False,
                        name="myregistry1domain:port/extra_tags:1.2.3",
                    )
                ],
            ),
        ],
    )


def test_dynamic_image_version(rule_runner: RuleRunner) -> None:
    interpolation_context = InterpolationContext.from_dict(
        {
            "baseimage": {"tag": "3.8"},
            "stage0": {"tag": "3.8"},
            "interim": {"tag": "latest"},
            "stage2": {"tag": "latest"},
            "output": {"tag": "1-1"},
        }
    )

    def assert_tags(name: str, *expect_tags: str) -> None:
        tgt = rule_runner.get_target(Address("docker/test", target_name=name))
        fs = DockerPackageFieldSet.create(tgt)
        image_refs = fs.image_refs(
            "image",
            DockerRegistries.from_dict({}),
            interpolation_context,
        )
        tags = tuple(t.full_name for r in image_refs for t in r.tags)
        assert expect_tags == tags

    rule_runner.write_files(
        {
            "docker/test/BUILD": dedent(
                """\
                docker_image(name="ver_1")
                docker_image(
                  name="ver_2",
                  image_tags=["{baseimage.tag}-{stage2.tag}", "beta"]
                )
                docker_image(name="err_1", image_tags=["{unknown_stage}"])
                docker_image(name="err_2", image_tags=["{stage0.unknown_value}"])
                """
            ),
        }
    )

    assert_tags("ver_1", "image:latest")
    assert_tags("ver_2", "image:3.8-latest", "image:beta")

    err_1 = (
        r"Invalid value for the `image_tags` field of the `docker_image` target at "
        r"docker/test:err_1: '{unknown_stage}'\.\n\n"
        r"The placeholder 'unknown_stage' is unknown\. Try with one of: baseimage, interim, "
        r"output, stage0, stage2\."
    )
    with pytest.raises(DockerImageTagValueError, match=err_1):
        assert_tags("err_1")

    err_2 = (
        r"Invalid value for the `image_tags` field of the `docker_image` target at "
        r"docker/test:err_2: '{stage0.unknown_value}'\.\n\n"
        r"The placeholder 'unknown_value' is unknown\. Try with one of: tag\."
    )
    with pytest.raises(DockerImageTagValueError, match=err_2):
        assert_tags("err_2")


def test_docker_build_process_environment(rule_runner: RuleRunner) -> None:
    rule_runner.write_files(
        {"docker/test/BUILD": 'docker_image(name="env1", image_tags=["1.2.3"])'}
    )
    rule_runner.set_options(
        [],
        env={
            "INHERIT": "from Pants env",
            "PANTS_DOCKER_ENV_VARS": '["VAR=value", "INHERIT"]',
        },
    )

    def check_docker_proc(process: Process):
        assert process.argv == (
            "/dummy/docker",
            "build",
            "--pull=False",
            "--tag",
            "env1:1.2.3",
            "--file",
            "docker/test/Dockerfile",
            ".",
        )
        assert process.env == FrozenDict(
            {
                "INHERIT": "from Pants env",
                "VAR": "value",
                "__UPSTREAM_IMAGE_IDS": "",
            }
        )

    assert_build(
        rule_runner,
        Address("docker/test", target_name="env1"),
        process_assertions=check_docker_proc,
    )


def test_docker_build_pull(rule_runner: RuleRunner) -> None:
    rule_runner.write_files({"docker/test/BUILD": 'docker_image(name="args1", pull=True)'})

    def check_docker_proc(process: Process):
        assert process.argv == (
            "/dummy/docker",
            "build",
            "--pull=True",
            "--tag",
            "args1:latest",
            "--file",
            "docker/test/Dockerfile",
            ".",
        )

    assert_build(
        rule_runner,
        Address("docker/test", target_name="args1"),
        process_assertions=check_docker_proc,
    )


def test_docker_build_squash(rule_runner: RuleRunner) -> None:
    rule_runner.write_files(
        {
            "docker/test/BUILD": dedent(
                """\
            docker_image(name="args1", squash=True)
            docker_image(name="args2", squash=False)
            """
            )
        }
    )

    def check_docker_proc(process: Process):
        assert process.argv == (
            "/dummy/docker",
            "build",
            "--pull=False",
            "--squash",
            "--tag",
            "args1:latest",
            "--file",
            "docker/test/Dockerfile",
            ".",
        )

    def check_docker_proc_no_squash(process: Process):
        assert process.argv == (
            "/dummy/docker",
            "build",
            "--pull=False",
            "--tag",
            "args2:latest",
            "--file",
            "docker/test/Dockerfile",
            ".",
        )

    assert_build(
        rule_runner,
        Address("docker/test", target_name="args1"),
        process_assertions=check_docker_proc,
    )
    assert_build(
        rule_runner,
        Address("docker/test", target_name="args2"),
        process_assertions=check_docker_proc_no_squash,
    )


def test_docker_build_args(rule_runner: RuleRunner) -> None:
    rule_runner.write_files(
        {"docker/test/BUILD": 'docker_image(name="args1", image_tags=["1.2.3"])'}
    )
    rule_runner.set_options(
        [],
        env={
            "INHERIT": "from Pants env",
            "PANTS_DOCKER_BUILD_ARGS": '["VAR=value", "INHERIT"]',
        },
    )

    def check_docker_proc(process: Process):
        assert process.argv == (
            "/dummy/docker",
            "build",
            "--pull=False",
            "--tag",
            "args1:1.2.3",
            "--build-arg",
            "INHERIT",
            "--build-arg",
            "VAR=value",
            "--file",
            "docker/test/Dockerfile",
            ".",
        )

        # Check that we pull in name only args via env.
        assert process.env == FrozenDict(
            {
                "INHERIT": "from Pants env",
                "__UPSTREAM_IMAGE_IDS": "",
            }
        )

    assert_build(
        rule_runner,
        Address("docker/test", target_name="args1"),
        process_assertions=check_docker_proc,
    )


def test_docker_image_version_from_build_arg(rule_runner: RuleRunner) -> None:
    rule_runner.write_files(
        {"docker/test/BUILD": 'docker_image(name="ver1", image_tags=["{build_args.VERSION}"])'}
    )
    rule_runner.set_options(
        [],
        env={
            "PANTS_DOCKER_BUILD_ARGS": '["VERSION=1.2.3"]',
        },
    )

    assert_build(
        rule_runner,
        Address("docker/test", target_name="ver1"),
        "Built docker image: ver1:1.2.3",
        expected_registries_metadata=[
            dict(
                alias=None,
                address=None,
                repository="ver1",
                tags=[
                    dict(
                        template="{build_args.VERSION}",
                        tag="1.2.3",
                        uses_local_alias=False,
                        name="ver1:1.2.3",
                    )
                ],
            )
        ],
    )


def test_docker_repository_from_build_arg(rule_runner: RuleRunner) -> None:
    rule_runner.write_files(
        {"docker/test/BUILD": 'docker_image(name="image", repository="{build_args.REPO}")'}
    )
    rule_runner.set_options(
        [],
        env={
            "PANTS_DOCKER_BUILD_ARGS": '["REPO=test/image"]',
        },
    )

    assert_build(
        rule_runner,
        Address("docker/test", target_name="image"),
        "Built docker image: test/image:latest",
    )


def test_docker_extra_build_args_field(rule_runner: RuleRunner) -> None:
    rule_runner.write_files(
        {
            "docker/test/BUILD": dedent(
                """\
                docker_image(
                  name="img1",
                  extra_build_args=[
                    "FROM_ENV",
                    "SET=value",
                    "DEFAULT2=overridden",
                  ]
                )
                """
            ),
        }
    )
    rule_runner.set_options(
        [
            "--docker-build-args=DEFAULT1=global1",
            "--docker-build-args=DEFAULT2=global2",
        ],
        env={
            "FROM_ENV": "env value",
            "SET": "no care",
        },
    )

    def check_docker_proc(process: Process):
        assert process.argv == (
            "/dummy/docker",
            "build",
            "--pull=False",
            "--tag",
            "img1:latest",
            "--build-arg",
            "DEFAULT1=global1",
            "--build-arg",
            "DEFAULT2=overridden",
            "--build-arg",
            "FROM_ENV",
            "--build-arg",
            "SET=value",
            "--file",
            "docker/test/Dockerfile",
            ".",
        )

        assert process.env == FrozenDict(
            {
                "FROM_ENV": "env value",
                "__UPSTREAM_IMAGE_IDS": "",
            }
        )

    assert_build(
        rule_runner,
        Address("docker/test", target_name="img1"),
        process_assertions=check_docker_proc,
    )


def test_docker_build_secrets_option(rule_runner: RuleRunner) -> None:
    rule_runner.write_files(
        {
            "docker/test/BUILD": dedent(
                """\
                docker_image(
                  name="img1",
                  secrets={
                    "system-secret": "/var/run/secrets/mysecret",
                    "project-secret": "secrets/mysecret",
                    "target-secret": "./mysecret",
                  }
                )
                """
            ),
        }
    )

    def check_docker_proc(process: Process):
        assert process.argv == (
            "/dummy/docker",
            "build",
            "--pull=False",
            "--secret",
            "id=system-secret,src=/var/run/secrets/mysecret",
            "--secret",
            f"id=project-secret,src={rule_runner.build_root}/secrets/mysecret",
            "--secret",
            f"id=target-secret,src={rule_runner.build_root}/docker/test/mysecret",
            "--tag",
            "img1:latest",
            "--file",
            "docker/test/Dockerfile",
            ".",
        )

    assert_build(
        rule_runner,
        Address("docker/test", target_name="img1"),
        process_assertions=check_docker_proc,
    )


def test_docker_build_ssh_option(rule_runner: RuleRunner) -> None:
    rule_runner.write_files(
        {
            "docker/test/BUILD": dedent(
                """\
                docker_image(
                  name="img1",
                  ssh=["default"],
                )
                """
            ),
        }
    )

    def check_docker_proc(process: Process):
        assert process.argv == (
            "/dummy/docker",
            "build",
            "--pull=False",
            "--ssh",
            "default",
            "--tag",
            "img1:latest",
            "--file",
            "docker/test/Dockerfile",
            ".",
        )

    assert_build(
        rule_runner,
        Address("docker/test", target_name="img1"),
        process_assertions=check_docker_proc,
    )


def test_docker_build_no_cache_option(rule_runner: RuleRunner) -> None:
    rule_runner.set_options(
        [],
        env={
            "PANTS_DOCKER_BUILD_NO_CACHE": "true",
        },
    )
    rule_runner.write_files(
        {
            "docker/test/BUILD": dedent(
                """\
                docker_image(
                  name="img1",
                )
                """
            ),
        }
    )

    def check_docker_proc(process: Process):
        assert process.argv == (
            "/dummy/docker",
            "build",
            "--pull=False",
            "--no-cache",
            "--tag",
            "img1:latest",
            "--file",
