Repository: kubeflow/pipelines

Issue:
[feature] Allow users to skip `docker build` in `kfp components build`
### Feature Area

<!-- Uncomment the labels below which are relevant to this feature: -->
<!-- /area frontend -->
<!-- /area backend -->
/area sdk
<!-- /area samples -->
<!-- /area components -->


### What feature would you like to see?

<!-- Provide a description of this feature and the user experience. -->

Building containerised components using the Python DSL, and packaging them using `kfp components build` is a really nice user experience - you get the benefits of the Python DSL without some of the drawbacks of "lightweight" components (e.g. having to install dependencies at runtime).

However, there are some scenarios where building container images using Docker is not an option, for example:

- Some organisations do not allow Docker to be installed on Data Scientists' laptops (very common for enterprise users)
- Some CI/CD platforms do not support docker-in-docker, and use other mechanisms (such as kaniko) for building container images

This feature would allow the user to skip the actual container image build, but still perform the other functions in `kfp components build` (generation of the different files). The user would then be responsible for building the container images using their preferred mechanism (e.g. Cloud Build, Kaniko)

### What is the use case or pain point?

<!-- It helps us understand the benefit of this feature for your use case. -->
Currently not all users are able to use the `kfp components build` command as they cannot run Docker (locally or in CI/CD)


### Is there a workaround currently?

<!-- Without this feature, how do you accomplish your task today? -->
Current workarounds:

- Roll your own containerised components without the convenience of the `kfp components build` command
- I suppose you could mock the `docker` library as is done in the tests? But this is quite hacky!

---

<!-- Don't delete message below to encourage users to support your feature request! -->
Love this idea? Give it a 👍. 


Discussion:

Resolving patch:
diff --git a/sdk/python/kfp/cli/components.py b/sdk/python/kfp/cli/components.py
--- a/sdk/python/kfp/cli/components.py
+++ b/sdk/python/kfp/cli/components.py
@@ -369,6 +369,9 @@ def build(components_directory: pathlib.Path = typer.Argument(
               False,
               help="Set this to true to always generate a Dockerfile"
               " as part of the build process"),
+          build_image: bool = typer.Option(
+            True, help="Build the container image."
+          ),
           push_image: bool = typer.Option(
               True, help="Push the built image to its remote repository.")):
     """
@@ -380,11 +383,11 @@ def build(components_directory: pathlib.Path = typer.Argument(
             components_directory))
         raise typer.Exit(1)
 
-    if engine != _Engine.DOCKER:
+    if build_image and (engine != _Engine.DOCKER):
         _error('Currently, only `docker` is supported for --engine.')
         raise typer.Exit(1)
 
-    if engine == _Engine.DOCKER:
+    if build_image and (engine == _Engine.DOCKER):
         if not _DOCKER_IS_PRESENT:
             _error(
                 'The `docker` Python package was not found in the current'
@@ -404,7 +407,9 @@ def build(components_directory: pathlib.Path = typer.Argument(
     builder.maybe_generate_requirements_txt()
     builder.maybe_generate_dockerignore()
     builder.maybe_generate_dockerfile(overwrite_dockerfile=overwrite_dockerfile)
-    builder.build_image(push_image=push_image)
+
+    if build_image:
+        builder.build_image(push_image=push_image)
 
 
 if __name__ == '__main__':


Test patch:
diff --git a/sdk/python/kfp/cli/components_test.py b/sdk/python/kfp/cli/components_test.py
--- a/sdk/python/kfp/cli/components_test.py
+++ b/sdk/python/kfp/cli/components_test.py
@@ -386,6 +386,20 @@ def testDockerClientIsCalledToBuildButSkipsPushing(self):
         self._docker_client.api.build.assert_called_once()
         self._docker_client.images.push.assert_not_called()
 
+    def testDockerClientIsNotCalledToBuild(self):
+        component = _make_component(
+            func_name='train', target_image='custom-image')
+        _write_components('components.py', component)
+
+        result = self._runner.invoke(
+            self._app,
+            ['build', str(self._working_dir), '--no-build-image'],
+        )
+        self.assertEqual(result.exit_code, 0)
+
+        self._docker_client.api.build.assert_not_called()
+        self._docker_client.images.push.assert_not_called()
+
     @mock.patch('kfp.__version__', '1.2.3')
     def testDockerfileIsCreatedCorrectly(self):
         component = _make_component(