Repository: pantsbuild/pants

Issue:
docker_image source=None not working
**Describe the bug**
I am trying to use the instructions field for docker_image. I have a docker_image target that looks like

```
docker_image(
    name="srcs",
    dependencies=['...'],
    registries=['...'],
    repository='...',
    instructions=['FROM ...',
    source=None,
    image_tags=['...'],
)
```
and I get the following error when i do a pants peek on srcs:
```
InvalidFieldException: The `docker_image` ... provides both a Dockerfile with the `source` field, and Dockerfile contents with the `instructions` field, which is not supported.
To fix, please either set `source=None` or `instructions=None
```

Setting source='' seems to solve it, but the docs say setting to None should work. And i would also assume the ideal behavior would be that setting `instructions` would automatically override the source arg?

Thank 

**Pants version**
2.14.0

**OS**
Only have tested on MacOS Monterey 12.0.1, Intel




Discussion:
Thank you for the report!

I agree, setting `source=None` should disable the default value for that field. I am not sure how that message survived in its current form..

Regarding setting instructions, is that there's very little interplay between fields, so the alternative would've been to not have a default value for the `source` field, then you would have to provide either one of them, but that would remove the easy `docker_image()` to work with a `Dockerfile` by default.

The plan going forward, is to provide the `instructions` to the `source` field, and this will be a non-issue. Thank you for providing a clear motivation to why that will be a good idea.

Resolving patch:
diff --git a/src/python/pants/backend/docker/target_types.py b/src/python/pants/backend/docker/target_types.py
--- a/src/python/pants/backend/docker/target_types.py
+++ b/src/python/pants/backend/docker/target_types.py
@@ -96,6 +96,7 @@ def compute_value(cls, raw_value: Optional[str], address: Address) -> Optional[s
 
 
 class DockerImageSourceField(OptionalSingleSourceField):
+    none_is_valid_value = True
     default = "Dockerfile"
 
     # When the default glob value is in effect, we don't want the normal glob match error behavior


Test patch:
diff --git a/src/python/pants/backend/docker/util_rules/docker_build_context_test.py b/src/python/pants/backend/docker/util_rules/docker_build_context_test.py
--- a/src/python/pants/backend/docker/util_rules/docker_build_context_test.py
+++ b/src/python/pants/backend/docker/util_rules/docker_build_context_test.py
@@ -673,3 +673,34 @@ def test_pex_custom_output_path_issue14031(rule_runner: RuleRunner) -> None:
         Address("project/test", target_name="test-image"),
         expected_files=["project/test/Dockerfile", "project/test.pex"],
     )
+
+
+def test_dockerfile_instructions_issue_17571(rule_runner: RuleRunner) -> None:
+    rule_runner.write_files(
+        {
+            "src/docker/Dockerfile": "do not use this file",
+            "src/docker/BUILD": dedent(
+                """\
+                docker_image(
+                  source=None,
+                  instructions=[
+                    "FROM python:3.8",
+                  ]
+                )
+                """
+            ),
+        }
+    )
+
+    assert_build_context(
+        rule_runner,
+        Address("src/docker"),
+        expected_files=["src/docker/Dockerfile.docker"],
+        expected_interpolation_context={
+            "tags": {
+                "baseimage": "3.8",
+                "stage0": "3.8",
+            },
+            "build_args": {},
+        },
+    )