Repository: kubernetes/kompose
Pull request #1544 (2022-12-01 14:49:24)

Feat support external traffic policy
# Description
This pull request solves #1543.
Since the `externalTrafficPolicy` is only specified when we have a `Service` of type `LoadBalancer` or `NodePort`, we will output a warning message when the user specifies another type of `Service`.
This feature is implemented for both: Kubernetes and Openshift.

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/kompose/pull/1544#" title="Author self-approved">AhmedGrati</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fkompose).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes/kompose/blob/master/OWNERS)~~ [AhmedGrati]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
ZakariaNaaija: Hi @AhmedGrati,
Thanks for handling the issue I have mentioned.

> Since the externalTrafficPolicy is only specified when we have a Service of type LoadBalancer, we will output a warning message when the user specifies another type of Service.

externalTrafficPolicy can be used along with a service of type NodePort.  
> Local preserves the client source IP and avoids a second hop for LoadBalancer and NodePort type Services, but risks potentially imbalanced traffic spreading.

[Source](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip)


AhmedGrati: > Hi @AhmedGrati, Thanks for handling the issue I have mentioned.
> 
> > Since the externalTrafficPolicy is only specified when we have a Service of type LoadBalancer, we will output a warning message when the user specifies another type of Service.
> 
> externalTrafficPolicy can be used along with a service of type NodePort.
> 
> > Local preserves the client source IP and avoids a second hop for LoadBalancer and NodePort type Services, but risks potentially imbalanced traffic spreading.
> 
> [Source](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip)

Thank you for raising it, much appreciated. I will update it.
k8s-ci-robot: @AhmedGrati: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
mrsangjunboon2018: FUCK!
k8s-ci-robot: @AhmedGrati: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
cdrage: This will need a huge rebase, sorry for the work @AhmedGrati !
k8s-ci-robot: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/kompose/pull/1544#" title="Author self-approved">AhmedGrati</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fkompose).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes/kompose/blob/main/OWNERS)~~ [AhmedGrati]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
AhmedGrati: @cdrage It's ready to be reviewed.
AhmedGrati: > The code LGTM but can you add some documentation as well for the user guide / under labels as this is a new label?

Yes makes sense 👌. Done!
cdrage: Thank you! Can you show all the options? I think it's cluster, local and blank right?

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
deleted file mode 100644
index fff477c6..00000000
--- a/.pre-commit-config.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-repos:
-  - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.3.0
-    hooks:
-      - id: trailing-whitespace
-      - id: end-of-file-fixer
-      - id: check-added-large-files
-  - repo: https://github.com/dnephin/pre-commit-golang
-    rev: v0.5.0
-    hooks:
-      - id: go-fmt
-      - id: go-imports
-      - id: golangci-lint
-      - id: go-unit-tests
-  - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
-    rev: v8.0.0
-    hooks:
-      - id: commitlint
-        stages: [commit-msg]
-        additional_dependencies: ["@commitlint/config-conventional"]
diff --git a/docs/user-guide.md b/docs/user-guide.md
index 4c261e5a..b7bb1f06 100644
--- a/docs/user-guide.md
+++ b/docs/user-guide.md
@@ -200,6 +200,7 @@ The currently supported options are:
 | kompose.service.healthcheck.liveness.http_get_path  | kubernetes liveness httpGet path                                                     |
 | kompose.service.healthcheck.liveness.http_get_port  | kubernetes liveness httpGet port                                                     |
 | kompose.service.healthcheck.liveness.tcp_port       | kubernetes liveness tcpSocket port                                                   |
+| kompose.service.external-traffic-policy       | 'cluster', 'local', ''                                                   |                                                |
 
 **Note**: `kompose.service.type` label should be defined with `ports` only (except for headless service), otherwise `kompose` will fail.
 
@@ -411,6 +412,25 @@ services:
 
 - `kompose.service.healthcheck.readiness` defines Kubernetes [readiness](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes)
 
+- `kompose.service.external-traffic-policy` defines Kubernetes Service [external traffic policy.](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip).
+
+For example:
+
+```yaml
+version: "3.3"
+
+services:
+  front-end:
+    image: gcr.io/google-samples/gb-frontend:v4
+    environment:
+      - GET_HOSTS_FROM=dns
+    ports:
+      - 80:80
+    labels:
+      kompose.service.expose: lb
+      kompose.service.external-traffic-policy: local
+      kompose.service.type: loadbalancer
+```
 ## Restart
 
 If you want to create normal pods without controller you can use `restart` construct of docker-compose to define that. Follow table below to see what happens on the `restart` value.
diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go
index 9237255c..79e074be 100644
--- a/pkg/kobject/kobject.go
+++ b/pkg/kobject/kobject.go
@@ -126,6 +126,7 @@ type ServiceConfig struct {
 	User                          string             `compose:"user"`
 	VolumesFrom                   []string           `compose:"volumes_from"`
 	ServiceType                   string             `compose:"kompose.service.type"`
+	ServiceExternalTrafficPolicy  string             `compose:"kompose.service.external-traffic-policy"`
 	NodePortPort                  int32              `compose:"kompose.service.nodeport.port"`
 	StopGracePeriod               string             `compose:"stop_grace_period"`
 	Build                         string             `compose:"build"`
diff --git a/pkg/loader/compose/compose.go b/pkg/loader/compose/compose.go
index fec0c01f..511ce339 100644
--- a/pkg/loader/compose/compose.go
+++ b/pkg/loader/compose/compose.go
@@ -698,6 +698,13 @@ func parseKomposeLabels(labels map[string]string, serviceConfig *kobject.Service
 			}
 
 			serviceConfig.ServiceType = serviceType
+		case LabelServiceExternalTrafficPolicy:
+			serviceExternalTypeTrafficPolicy, err := handleServiceExternalTrafficPolicy(value)
+			if err != nil {
+				return errors.Wrap(err, "handleServiceExternalTrafficPolicy failed")
+			}
+
+			serviceConfig.ServiceExternalTrafficPolicy = serviceExternalTypeTrafficPolicy
 		case LabelServiceExpose:
 			serviceConfig.ExposeService = strings.Trim(strings.ToLower(value), " ,")
 		case LabelNodePortPort:
diff --git a/pkg/loader/compose/utils.go b/pkg/loader/compose/utils.go
index 42aff189..b7977114 100644
--- a/pkg/loader/compose/utils.go
+++ b/pkg/loader/compose/utils.go
@@ -32,6 +32,8 @@ import (
 const (
 	// LabelServiceType defines the type of service to be created
 	LabelServiceType = "kompose.service.type"
+	// LabelServiceExternalTrafficPolicy defines the external policy traffic of service to be created
+	LabelServiceExternalTrafficPolicy = "kompose.service.external-traffic-policy"
 	// LabelServiceGroup defines the group of services in a single pod
 	LabelServiceGroup = "kompose.service.group"
 	// LabelNodePortPort defines the port value for NodePort service
@@ -151,6 +153,17 @@ func handleServiceType(ServiceType string) (string, error) {
 	}
 }
 
+func handleServiceExternalTrafficPolicy(ServiceExternalTrafficPolicyType string) (string, error) {
+	switch strings.ToLower(ServiceExternalTrafficPolicyType) {
+	case "", "cluster":
+		return string(api.ServiceExternalTrafficPolicyTypeCluster), nil
+	case "local":
+		return string(api.ServiceExternalTrafficPolicyTypeLocal), nil
+	default:
+		return "", errors.New("Unknown value " + ServiceExternalTrafficPolicyType + " , supported values are 'local, cluster'")
+	}
+}
+
 func normalizeContainerNames(svcName string) string {
 	return strings.ToLower(svcName)
 }
diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go
index caba2d5c..11201184 100644
--- a/pkg/transformer/kubernetes/kubernetes.go
+++ b/pkg/transformer/kubernetes/kubernetes.go
@@ -1357,6 +1357,7 @@ func (k *Kubernetes) configKubeServiceAndIngressForService(service kobject.Servi
 		if service.ServiceType == "LoadBalancer" {
 			svcs := k.CreateLBService(name, service)
 			for _, svc := range svcs {
+				svc.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyType(service.ServiceExternalTrafficPolicy)
 				*objects = append(*objects, svc)
 			}
 			if len(svcs) > 1 {
@@ -1368,11 +1369,17 @@ func (k *Kubernetes) configKubeServiceAndIngressForService(service kobject.Servi
 			if service.ExposeService != "" {
 				*objects = append(*objects, k.initIngress(name, service, svc.Spec.Ports[0].Port))
 			}
+			if service.ServiceExternalTrafficPolicy != "" && svc.Spec.Type != api.ServiceTypeNodePort {
+				log.Warningf("External Traffic Policy is ignored for the service %v of type %v", name, service.ServiceType)
+			}
 		}
 	} else {
 		if service.ServiceType == "Headless" {
 			svc := k.CreateHeadlessService(name, service)
 			*objects = append(*objects, svc)
+			if service.ServiceExternalTrafficPolicy != "" {
+				log.Warningf("External Traffic Policy is ignored for the service %v of type Headless", name)
+			}
 		} else {
 			log.Warnf("Service %q won't be created because 'ports' is not specified", service.Name)
 		}
diff --git a/pkg/transformer/openshift/openshift.go b/pkg/transformer/openshift/openshift.go
index 86e094bb..56224b2b 100644
--- a/pkg/transformer/openshift/openshift.go
+++ b/pkg/transformer/openshift/openshift.go
@@ -386,6 +386,7 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C
 			if service.ServiceType == "LoadBalancer" {
 				svcs := o.CreateLBService(name, service)
 				for _, svc := range svcs {
+					svc.Spec.ExternalTrafficPolicy = corev1.ServiceExternalTrafficPolicyType(service.ServiceExternalTrafficPolicy)
 					objects = append(objects, svc)
 				}
 				if len(svcs) > 1 {
@@ -398,10 +399,16 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C
 				if service.ExposeService != "" {
 					objects = append(objects, o.initRoute(name, service, svc.Spec.Ports[0].Port))
 				}
+				if service.ServiceExternalTrafficPolicy != "" && svc.Spec.Type != corev1.ServiceTypeNodePort {
+					log.Warningf("External Traffic Policy is ignored for the service %v of type %v", name, service.ServiceType)
+				}
 			}
 		} else if service.ServiceType == "Headless" {
 			svc := o.CreateHeadlessService(name, service)
 			objects = append(objects, svc)
+			if service.ServiceExternalTrafficPolicy != "" {
+				log.Warningf("External Traffic Policy is ignored for the service %v of type Headless", name)
+			}
 		}
 
 		err := o.UpdateKubernetesObjects(name, service, opt, &objects)
diff --git a/pkg/utils/docker/build.go b/pkg/utils/docker/build.go
index 832cd75b..70746fa6 100644
--- a/pkg/utils/docker/build.go
+++ b/pkg/utils/docker/build.go
@@ -18,11 +18,8 @@ package docker
 
 import (
 	"bytes"
-	"fmt"
 	"os"
-	"os/exec"
 	"path"
-	"strconv"
 	"strings"
 
 	dockerlib "github.com/fsouza/go-dockerclient"
@@ -37,36 +34,13 @@ type Build struct {
 }
 
 /*
-BuildImage builds a Docker image via the Docker API or Docker CLI.
-Takes the source directory and image name and then builds the appropriate image. Tarball is utilized
+BuildImage builds a Docker image via the Docker API. Takes the source directory
+and image name and then builds the appropriate image. Tarball is utilized
 in order to make building easier.
-
-if the DOCKER_BUILDKIT is '1', then we will use the docker CLI to build the image
 */
 func (c *Build) BuildImage(source string, image string, dockerfile string, buildargs []dockerlib.BuildArg) error {
 	log.Infof("Building image '%s' from directory '%s'", image, path.Base(source))
 
-	outputBuffer := bytes.NewBuffer(nil)
-	var err error
-
-	if usecli, _ := strconv.ParseBool(os.Getenv("DOCKER_BUILDKIT")); usecli {
-		err = buildDockerCli(source, image, dockerfile, buildargs, outputBuffer)
-	} else {
-		err = c.buildDockerClient(source, image, dockerfile, buildargs, outputBuffer)
-	}
-
-	log.Debugf("Image %s build output:\n%s", image, outputBuffer)
-
-	if err != nil {
-		return errors.Wrap(err, "Unable to build image. For more output, use -v or --verbose when converting.")
-	}
-
-	log.Infof("Image '%s' from directory '%s' built successfully", image, path.Base(source))
-
-	return nil
-}
-
-func (c *Build) buildDockerClient(source string, image string, dockerfile string, buildargs []dockerlib.BuildArg, outputBuffer *bytes.Buffer) error {
 	// Create a temporary file for tarball image packaging
 	tmpFile, err := os.CreateTemp(os.TempDir(), "kompose-image-build-")
 	if err != nil {
@@ -87,6 +61,7 @@ func (c *Build) buildDockerClient(source string, image string, dockerfile string
 	}
 
 	// Let's create all the options for the image building.
+	outputBuffer := bytes.NewBuffer(nil)
 	opts := dockerlib.BuildImageOptions{
 		Name:         image,
 		InputStream:  tarballSource,
@@ -96,27 +71,14 @@ func (c *Build) buildDockerClient(source string, image string, dockerfile string
 	}
 
 	// Build it!
-	return c.Client.BuildImage(opts)
-}
-
-func buildDockerCli(source string, image string, dockerfile string, buildargs []dockerlib.BuildArg, outputBuffer *bytes.Buffer) error {
-	args := []string{"build", "-t", image}
-
-	if dockerfile != "" {
-		args = append(args, "-f", dockerfile)
-	}
+	err = c.Client.BuildImage(opts)
+	log.Debugf("Image %s build output:\n%s", image, outputBuffer)
 
-	for _, buildarg := range buildargs {
-		args = append(args, "--build-arg", fmt.Sprintf("%s=%s", buildarg.Name, buildarg.Value))
+	if err != nil {
+		return errors.Wrap(err, "Unable to build image. For more output, use -v or --verbose when converting.")
 	}
 
-	args = append(args, source)
-
-	cmd := exec.Command("docker", args...)
-	cmd.Stdout = outputBuffer
-	cmd.Stderr = outputBuffer
-
-	log.Debugf("Image %s build calling command %v", image, cmd)
+	log.Infof("Image '%s' from directory '%s' built successfully", image, path.Base(source))
 
-	return cmd.Run()
+	return nil
 }


Test patch:
diff --git a/pkg/transformer/kubernetes/kubernetes_test.go b/pkg/transformer/kubernetes/kubernetes_test.go
index 99e9c53e..6b86d025 100644
--- a/pkg/transformer/kubernetes/kubernetes_test.go
+++ b/pkg/transformer/kubernetes/kubernetes_test.go
@@ -93,6 +93,16 @@ func newKomposeObjectHostPortProtocolConfig() kobject.ServiceConfig {
 	}
 }
 
+func newServiceConfigWithExternalTrafficPolicy() kobject.ServiceConfig {
+	loadBalancerServiceType := string(api.ServiceTypeLoadBalancer)
+	return kobject.ServiceConfig{
+		Name:                         "app",
+		Port:                         []kobject.Ports{{HostPort: 123, ContainerPort: 456}},
+		ServiceType:                  loadBalancerServiceType,
+		ServiceExternalTrafficPolicy: "local",
+	}
+}
+
 func equalStringSlice(s1, s2 []string) bool {
 	if len(s1) != len(s2) {
 		return false
@@ -985,3 +995,27 @@ func TestCreateHostPortAndProtocol(t *testing.T) {
 		}
 	}
 }
+
+func TestServiceExternalTrafficPolicy(t *testing.T) {
+	groupName := "pod_group"
+	komposeObject := kobject.KomposeObject{
+		ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfigWithExternalTrafficPolicy()},
+	}
+	k := Kubernetes{}
+	objs, err := k.Transform(komposeObject, kobject.ConvertOptions{ServiceGroupMode: groupName})
+	if err != nil {
+		t.Error(errors.Wrap(err, "k.Transform failed"))
+	}
+	for _, obj := range objs {
+		if service, ok := obj.(*api.Service); ok {
+			serviceExternalTrafficPolicy := string(service.Spec.ExternalTrafficPolicy)
+			if serviceExternalTrafficPolicy != strings.ToLower(string(api.ServiceExternalTrafficPolicyTypeLocal)) {
+				t.Errorf("Expected Local as external lifecycle policy, got %v", serviceExternalTrafficPolicy)
+			}
+			serviceType := service.Spec.Type
+			if serviceType != api.ServiceTypeLoadBalancer {
+				t.Errorf("Expected LoadBalancer as service type, got %v", serviceType)
+			}
+		}
+	}
+}
diff --git a/pkg/transformer/openshift/openshift_test.go b/pkg/transformer/openshift/openshift_test.go
index 9af2dbb2..60fe8bce 100644
--- a/pkg/transformer/openshift/openshift_test.go
+++ b/pkg/transformer/openshift/openshift_test.go
@@ -20,6 +20,7 @@ import (
 	"os"
 	"path/filepath"
 	"reflect"
+	"strings"
 	"testing"
 
 	"github.com/kubernetes/kompose/pkg/kobject"
@@ -56,6 +57,16 @@ func newServiceConfig() kobject.ServiceConfig {
 	}
 }
 
+func newServiceConfigWithExternalTrafficPolicy() kobject.ServiceConfig {
+	loadBalancerServiceType := string(corev1.ServiceTypeLoadBalancer)
+	return kobject.ServiceConfig{
+		Name:                         "app",
+		Port:                         []kobject.Ports{{HostPort: 123, ContainerPort: 456}},
+		ServiceType:                  loadBalancerServiceType,
+		ServiceExternalTrafficPolicy: "local",
+	}
+}
+
 func TestOpenShiftUpdateKubernetesObjects(t *testing.T) {
 	t.Log("Test case: Testing o.UpdateKubernetesObjects()")
 	var object []runtime.Object
@@ -425,3 +436,27 @@ func TestRecreateStrategyWithVolumesPresent(t *testing.T) {
 		}
 	}
 }
+
+func TestServiceExternalTrafficPolicy(t *testing.T) {
+	groupName := "pod_group"
+	komposeObject := kobject.KomposeObject{
+		ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfigWithExternalTrafficPolicy()},
+	}
+	o := OpenShift{}
+	objs, err := o.Transform(komposeObject, kobject.ConvertOptions{ServiceGroupMode: groupName})
+	if err != nil {
+		t.Error(errors.Wrap(err, "k.Transform failed"))
+	}
+	for _, obj := range objs {
+		if service, ok := obj.(*corev1.Service); ok {
+			serviceExternalTrafficPolicy := string(service.Spec.ExternalTrafficPolicy)
+			if serviceExternalTrafficPolicy != strings.ToLower(string(corev1.ServiceExternalTrafficPolicyTypeLocal)) {
+				t.Errorf("Expected Local as external lifecycle policy, got %v", serviceExternalTrafficPolicy)
+			}
+			serviceType := service.Spec.Type
+			if serviceType != corev1.ServiceTypeLoadBalancer {
+				t.Errorf("Expected LoadBalancer as service type, got %v", serviceType)
+			}
+		}
+	}
+}
diff --git a/script/test/cmd/tests_new.sh b/script/test/cmd/tests_new.sh
index 09f9cfd9..32432d7c 100755
--- a/script/test/cmd/tests_new.sh
+++ b/script/test/cmd/tests_new.sh
@@ -224,3 +224,18 @@ k8s_output="$KOMPOSE_ROOT/script/test/fixtures/host-port-protocol/output-k8s.yam
 os_output="$KOMPOSE_ROOT/script/test/fixtures/host-port-protocol/output-os.yaml"
 convert::expect_success_and_warning "$k8s_cmd" "$k8s_output"
 convert::expect_success "$os_cmd" "$os_output"
+
+# Test external traffic policy feature with valid configuration, warning is coming from the network policy.
+k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/docker-compose-v1.yaml convert --stdout --with-kompose-annotation=false"
+k8s_output="$KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-k8s-v1.yaml"
+os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/docker-compose-v1.yaml convert --stdout --with-kompose-annotation=false"
+os_output="$KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-os-v1.yaml"
+convert::expect_success_and_warning "$k8s_cmd" "$k8s_output"
+convert::expect_success "$os_cmd" "$os_output"
+# Test external traffic policy feature with warning, because we have nodeport with external traffic policy
+k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/docker-compose-v2.yaml convert --stdout --with-kompose-annotation=false"
+k8s_output="$KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-k8s-v2.yaml"
+os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/docker-compose-v2.yaml convert --stdout --with-kompose-annotation=false"
+os_output="$KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-os-v2.yaml"
+convert::expect_success_and_warning "$k8s_cmd" "$k8s_output"
+convert::expect_success_and_warning "$os_cmd" "$os_output"
diff --git a/script/test/fixtures/change-in-volume/output-k8s-empty-vols-template.yaml b/script/test/fixtures/change-in-volume/output-k8s-empty-vols-template.yaml
index 1691599d..905b4d8e 100644
--- a/script/test/fixtures/change-in-volume/output-k8s-empty-vols-template.yaml
+++ b/script/test/fixtures/change-in-volume/output-k8s-empty-vols-template.yaml
@@ -115,8 +115,6 @@ spec:
           name: web
           ports:
             - containerPort: 5000
-              hostPort: 5000
-              protocol: TCP
           resources: {}
           volumeMounts:
             - mountPath: /code
diff --git a/script/test/fixtures/change-in-volume/output-os-empty-vols-template.yaml b/script/test/fixtures/change-in-volume/output-os-empty-vols-template.yaml
index 5fe99044..142fabe2 100644
--- a/script/test/fixtures/change-in-volume/output-os-empty-vols-template.yaml
+++ b/script/test/fixtures/change-in-volume/output-os-empty-vols-template.yaml
@@ -138,8 +138,6 @@ spec:
           name: web
           ports:
             - containerPort: 5000
-              hostPort: 5000
-              protocol: TCP
           resources: {}
           volumeMounts:
             - mountPath: /code
diff --git a/script/test/fixtures/deploy/placement/output-placement-k8s.yaml b/script/test/fixtures/deploy/placement/output-placement-k8s.yaml
index 3e259159..777a2f0e 100644
--- a/script/test/fixtures/deploy/placement/output-placement-k8s.yaml
+++ b/script/test/fixtures/deploy/placement/output-placement-k8s.yaml
@@ -59,7 +59,6 @@ spec:
           name: redis
           ports:
             - containerPort: 6379
-              protocol: TCP
           resources: {}
       restartPolicy: Always
       topologySpreadConstraints:
diff --git a/script/test/fixtures/deploy/placement/output-placement-os.yaml b/script/test/fixtures/deploy/placement/output-placement-os.yaml
index 39a9a797..44583be4 100644
--- a/script/test/fixtures/deploy/placement/output-placement-os.yaml
+++ b/script/test/fixtures/deploy/placement/output-placement-os.yaml
@@ -59,7 +59,6 @@ spec:
           name: redis
           ports:
             - containerPort: 6379
-              protocol: TCP
           resources: {}
       restartPolicy: Always
       topologySpreadConstraints:
diff --git a/script/test/fixtures/expose/output-k8s.yaml b/script/test/fixtures/expose/output-k8s.yaml
index f3d48c3a..4148a28f 100644
--- a/script/test/fixtures/expose/output-k8s.yaml
+++ b/script/test/fixtures/expose/output-k8s.yaml
@@ -64,7 +64,6 @@ spec:
           name: redis
           ports:
             - containerPort: 6379
-              protocol: TCP
           resources: {}
       restartPolicy: Always
 status: {}
@@ -119,8 +118,6 @@ spec:
           name: web
           ports:
             - containerPort: 5000
-              hostPort: 5000
-              protocol: TCP
           resources: {}
       restartPolicy: Always
 status: {}
diff --git a/script/test/fixtures/expose/output-os.yaml b/script/test/fixtures/expose/output-os.yaml
index 59a63e79..08dd59bc 100644
--- a/script/test/fixtures/expose/output-os.yaml
+++ b/script/test/fixtures/expose/output-os.yaml
@@ -64,7 +64,6 @@ spec:
           name: redis
           ports:
             - containerPort: 6379
-              protocol: TCP
           resources: {}
       restartPolicy: Always
   test: false
@@ -140,8 +139,6 @@ spec:
           name: web
           ports:
             - containerPort: 5000
-              hostPort: 5000
-              protocol: TCP
           resources: {}
       restartPolicy: Always
   test: false
diff --git a/script/test/fixtures/external-traffic-policy/docker-compose-v1.yaml b/script/test/fixtures/external-traffic-policy/docker-compose-v1.yaml
new file mode 100644
index 00000000..c67c228c
--- /dev/null
+++ b/script/test/fixtures/external-traffic-policy/docker-compose-v1.yaml
@@ -0,0 +1,14 @@
+version: "3.3"
+
+services:
+  front-end:
+    image: gcr.io/google-samples/gb-frontend:v4
+    environment:
+      - GET_HOSTS_FROM=dns
+    ports:
+      - 80:80
+    labels:
+      kompose.service.expose: lb
+      kompose.service.expose.ingress-class-name: nginx
+      kompose.service.external-traffic-policy: local
+      kompose.service.type: loadbalancer
diff --git a/script/test/fixtures/external-traffic-policy/docker-compose-v2.yaml b/script/test/fixtures/external-traffic-policy/docker-compose-v2.yaml
new file mode 100644
index 00000000..997cad62
--- /dev/null
+++ b/script/test/fixtures/external-traffic-policy/docker-compose-v2.yaml
@@ -0,0 +1,14 @@
+version: "3.3"
+
+services:
+  front-end:
+    image: gcr.io/google-samples/gb-frontend:v4
+    environment:
+      - GET_HOSTS_FROM=dns
+    ports:
+      - 80:80
+    labels:
+      kompose.service.expose: lb
+      kompose.service.expose.ingress-class-name: nginx
+      kompose.service.external-traffic-policy: local
+      kompose.service.type: headless
diff --git a/script/test/fixtures/external-traffic-policy/output-k8s-v1.yaml b/script/test/fixtures/external-traffic-policy/output-k8s-v1.yaml
new file mode 100644
index 00000000..9a15d201
--- /dev/null
+++ b/script/test/fixtures/external-traffic-policy/output-k8s-v1.yaml
@@ -0,0 +1,85 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+  annotations:
+    kompose.service.expose: lb
+    kompose.service.expose.ingress-class-name: nginx
+    kompose.service.external-traffic-policy: local
+    kompose.service.type: loadbalancer
+  creationTimestamp: null
+  labels:
+    io.kompose.service: front-end-tcp
+  name: front-end-tcp
+spec:
+  externalTrafficPolicy: Local
+  ports:
+    - name: "80"
+      port: 80
+      targetPort: 80
+  selector:
+    io.kompose.service: front-end
+  type: LoadBalancer
+status:
+  loadBalancer: {}
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  annotations:
+    kompose.service.expose: lb
+    kompose.service.expose.ingress-class-name: nginx
+    kompose.service.external-traffic-policy: local
+    kompose.service.type: loadbalancer
+  creationTimestamp: null
+  labels:
+    io.kompose.service: front-end
+  name: front-end
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      io.kompose.service: front-end
+  strategy: {}
+  template:
+    metadata:
+      annotations:
+        kompose.service.expose: lb
+        kompose.service.expose.ingress-class-name: nginx
+        kompose.service.external-traffic-policy: local
+        kompose.service.type: loadbalancer
+      creationTimestamp: null
+      labels:
+        io.kompose.network/external-traffic-policy-default: "true"
+        io.kompose.service: front-end
+    spec:
+      containers:
+        - env:
+            - name: GET_HOSTS_FROM
+              value: dns
+          image: gcr.io/google-samples/gb-frontend:v4
+          name: front-end
+          ports:
+            - containerPort: 80
+              hostPort: 80
+              protocol: TCP
+          resources: {}
+      restartPolicy: Always
+status: {}
+
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  creationTimestamp: null
+  name: external-traffic-policy-default
+spec:
+  ingress:
+    - from:
+        - podSelector:
+            matchLabels:
+              io.kompose.network/external-traffic-policy-default: "true"
+  podSelector:
+    matchLabels:
+      io.kompose.network/external-traffic-policy-default: "true"
diff --git a/script/test/fixtures/external-traffic-policy/output-k8s-v2.yaml b/script/test/fixtures/external-traffic-policy/output-k8s-v2.yaml
new file mode 100644
index 00000000..d1d71e8d
--- /dev/null
+++ b/script/test/fixtures/external-traffic-policy/output-k8s-v2.yaml
@@ -0,0 +1,115 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+  annotations:
+    kompose.service.expose: lb
+    kompose.service.expose.ingress-class-name: nginx
+    kompose.service.external-traffic-policy: local
+    kompose.service.type: headless
+  creationTimestamp: null
+  labels:
+    io.kompose.service: front-end
+  name: front-end
+spec:
+  clusterIP: None
+  ports:
+    - name: "80"
+      port: 80
+      targetPort: 80
+  selector:
+    io.kompose.service: front-end
+  type: ClusterIP
+status:
+  loadBalancer: {}
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  annotations:
+    kompose.service.expose: lb
+    kompose.service.expose.ingress-class-name: nginx
+    kompose.service.external-traffic-policy: local
+    kompose.service.type: headless
+  creationTimestamp: null
+  labels:
+    io.kompose.service: front-end
+  name: front-end
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      io.kompose.service: front-end
+  strategy: {}
+  template:
+    metadata:
+      annotations:
+        kompose.service.expose: lb
+        kompose.service.expose.ingress-class-name: nginx
+        kompose.service.external-traffic-policy: local
+        kompose.service.type: headless
+      creationTimestamp: null
+      labels:
+        io.kompose.network/external-traffic-policy-default: "true"
+        io.kompose.service: front-end
+    spec:
+      containers:
+        - env:
+            - name: GET_HOSTS_FROM
+              value: dns
+          image: gcr.io/google-samples/gb-frontend:v4
+          name: front-end
+          ports:
+            - containerPort: 80
+              hostPort: 80
+              protocol: TCP
+          resources: {}
+      restartPolicy: Always
+status: {}
+
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  annotations:
+    kompose.service.expose: lb
+    kompose.service.expose.ingress-class-name: nginx
+    kompose.service.external-traffic-policy: local
+    kompose.service.type: headless
+  creationTimestamp: null
+  labels:
+    io.kompose.service: front-end
+  name: front-end
+spec:
+  ingressClassName: nginx
+  rules:
+    - host: lb
+      http:
+        paths:
+          - backend:
+              service:
+                name: front-end
+                port:
+                  number: 80
+            path: /
+            pathType: Prefix
+status:
+  loadBalancer: {}
+
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  creationTimestamp: null
+  name: external-traffic-policy-default
+spec:
+  ingress:
+    - from:
+        - podSelector:
+            matchLabels:
+              io.kompose.network/external-traffic-policy-default: "true"
+  podSelector:
+    matchLabels:
+      io.kompose.network/external-traffic-policy-default: "true"
+
diff --git a/script/test/fixtures/external-traffic-policy/output-os-v1.yaml b/script/test/fixtures/external-traffic-policy/output-os-v1.yaml
new file mode 100644
index 00000000..e7226313
--- /dev/null
+++ b/script/test/fixtures/external-traffic-policy/output-os-v1.yaml
@@ -0,0 +1,106 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+  annotations:
+    kompose.service.expose: lb
+    kompose.service.expose.ingress-class-name: nginx
+    kompose.service.external-traffic-policy: local
+    kompose.service.type: loadbalancer
+  creationTimestamp: null
+  labels:
+    io.kompose.service: front-end-tcp
+  name: front-end-tcp
+spec:
+  externalTrafficPolicy: Local
+  ports:
+    - name: "80"
+      port: 80
+      targetPort: 80
+  selector:
+    io.kompose.service: front-end
+  type: LoadBalancer
+status:
+  loadBalancer: {}
+
+---
+apiVersion: v1
+kind: DeploymentConfig
+metadata:
+  annotations:
+    kompose.service.expose: lb
+    kompose.service.expose.ingress-class-name: nginx
+    kompose.service.external-traffic-policy: local
+    kompose.service.type: loadbalancer
+  creationTimestamp: null
+  labels:
+    io.kompose.service: front-end
+  name: front-end
+spec:
+  replicas: 1
+  selector:
+    io.kompose.service: front-end
+  strategy:
+    resources: {}
+  template:
+    metadata:
+      creationTimestamp: null
+      labels:
+        io.kompose.network/external-traffic-policy-default: "true"
+        io.kompose.service: front-end
+    spec:
+      containers:
+        - env:
+            - name: GET_HOSTS_FROM
+              value: dns
+          image: ' '
+          name: front-end
+          ports:
+            - containerPort: 80
+              hostPort: 80
+              protocol: TCP
+          resources: {}
+      restartPolicy: Always
+  test: false
+  triggers:
+    - type: ConfigChange
+    - imageChangeParams:
+        automatic: true
+        containerNames:
+          - front-end
+        from:
+          kind: ImageStreamTag
+          name: front-end:v4
+      type: ImageChange
+status:
+  availableReplicas: 0
+  latestVersion: 0
+  observedGeneration: 0
+  replicas: 0
+  unavailableReplicas: 0
+  updatedReplicas: 0
+
+---
+apiVersion: v1
+kind: ImageStream
+metadata:
+  creationTimestamp: null
+  labels:
+    io.kompose.service: front-end
+  name: front-end
+spec:
+  lookupPolicy:
+    local: false
+  tags:
+    - annotations: null
+      from:
+        kind: DockerImage
+        name: gcr.io/google-samples/gb-frontend:v4
+      generation: null
+      importPolicy: {}
+      name: v4
+      referencePolicy:
+        type: ""
+status:
+  dockerImageRepository: ""
+
diff --git a/script/test/fixtures/external-traffic-policy/output-os-v2.yaml b/script/test/fixtures/external-traffic-policy/output-os-v2.yaml
new file mode 100644
index 00000000..159116b6
--- /dev/null
+++ b/script/test/fixtures/external-traffic-policy/output-os-v2.yaml
@@ -0,0 +1,125 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+  annotations:
+    kompose.service.expose: lb
+    kompose.service.expose.ingress-class-name: nginx
+    kompose.service.external-traffic-policy: local
+    kompose.service.type: headless
+  creationTimestamp: null
+  labels:
+    io.kompose.service: front-end
+  name: front-end
+spec:
+  clusterIP: None
+  ports:
+    - name: "80"
+      port: 80
+      targetPort: 80
+  selector:
+    io.kompose.service: front-end
+  type: ClusterIP
+status:
+  loadBalancer: {}
+
+---
+apiVersion: v1
+kind: DeploymentConfig
+metadata:
+  annotations:
+    kompose.service.expose: lb
+    kompose.service.expose.ingress-class-name: nginx
+    kompose.service.external-traffic-policy: local
+    kompose.service.type: headless
+  creationTimestamp: null
+  labels:
+    io.kompose.service: front-end
+  name: front-end
+spec:
+  replicas: 1
+  selector:
+    io.kompose.service: front-end
+  strategy:
+    resources: {}
+  template:
+    metadata:
+      creationTimestamp: null
+      labels:
+        io.kompose.network/external-traffic-policy-default: "true"
+        io.kompose.service: front-end
+    spec:
+      containers:
+        - env:
+            - name: GET_HOSTS_FROM
+              value: dns
+          image: ' '
+          name: front-end
+          ports:
+            - containerPort: 80
+              hostPort: 80
+              protocol: TCP
+          resources: {}
+      restartPolicy: Always
+  test: false
+  triggers:
+    - type: ConfigChange
+    - imageChangeParams:
+        automatic: true
+        containerNames:
+          - front-end
+        from:
+          kind: ImageStreamTag
+          name: front-end:v4
+      type: ImageChange
+status:
+  availableReplicas: 0
+  latestVersion: 0
+  observedGeneration: 0
+  replicas: 0
+  unavailableReplicas: 0
+  updatedReplicas: 0
+
+---
+apiVersion: v1
+kind: ImageStream
+metadata:
+  creationTimestamp: null
+  labels:
+    io.kompose.service: front-end
+  name: front-end
+spec:
+  lookupPolicy:
+    local: false
+  tags:
+    - annotations: null
+      from:
+        kind: DockerImage
+        name: gcr.io/google-samples/gb-frontend:v4
+      generation: null
+      importPolicy: {}
+      name: v4
+      referencePolicy:
+        type: ""
+status:
+  dockerImageRepository: ""
+
+---
+apiVersion: v1
+kind: Route
+metadata:
+  creationTimestamp: null
+  labels:
+    io.kompose.service: front-end
+  name: front-end
+spec:
+  host: lb
+  port:
+    targetPort: 80
+  to:
+    kind: Service
+    name: front-end
+    weight: null
+status:
+  ingress: null
+
diff --git a/script/test/fixtures/healthcheck/output-healthcheck-k8s.yaml b/script/test/fixtures/healthcheck/output-healthcheck-k8s.yaml
index e0a1cf13..4acf7f6a 100644
--- a/script/test/fixtures/healthcheck/output-healthcheck-k8s.yaml
+++ b/script/test/fixtures/healthcheck/output-healthcheck-k8s.yaml
@@ -4,20 +4,20 @@ kind: Service
 metadata:
   annotations:
     kompose.service.group: my-group
-    kompose.service.healthcheck.liveness.tcp_port: "8080"
-    kompose.service.healthcheck.readiness.interval: 10s
-    kompose.service.healthcheck.readiness.retries: "5"
-    kompose.service.healthcheck.readiness.tcp_port: "9090"
-    kompose.service.healthcheck.readiness.timeout: 1s
+    kompose.service.healthcheck.liveness.tcp_port: "8081"
+    kompose.service.healthcheck.readiness.interval: 11s
+    kompose.service.healthcheck.readiness.retries: "6"
+    kompose.service.healthcheck.readiness.tcp_port: "9091"
+    kompose.service.healthcheck.readiness.timeout: 2s
   creationTimestamp: null
   labels:
     io.kompose.service: my-group
-  name: mongo
+  name: mysql
 spec:
   ports:
-    - name: "27017"
-      port: 27017
-      targetPort: 27017
+    - name: "3306"
+      port: 3306
+      targetPort: 3306
   selector:
     io.kompose.service: my-group
 status:
@@ -29,20 +29,20 @@ kind: Service
 metadata:
   annotations:
     kompose.service.group: my-group
-    kompose.service.healthcheck.liveness.tcp_port: "8081"
-    kompose.service.healthcheck.readiness.interval: 11s
-    kompose.service.healthcheck.readiness.retries: "6"
-    kompose.service.healthcheck.readiness.tcp_port: "9091"
-    kompose.service.healthcheck.readiness.timeout: 2s
+    kompose.service.healthcheck.liveness.tcp_port: "8080"
+    kompose.service.healthcheck.readiness.interval: 10s
+    kompose.service.healthcheck.readiness.retries: "5"
+    kompose.service.healthcheck.readiness.tcp_port: "9090"
+    kompose.service.healthcheck.readiness.timeout: 1s
   creationTimestamp: null
   labels:
     io.kompose.service: my-group
-  name: mysql
+  name: mongo
 spec:
   ports:
-    - name: "3306"
-      port: 3306
-      targetPort: 3306
+    - name: "27017"
+      port: 27017
+      targetPort: 27017
   selector:
     io.kompose.service: my-group
 status:
@@ -103,11 +103,11 @@ kind: Deployment
 metadata:
   annotations:
     kompose.service.group: my-group
-    kompose.service.healthcheck.liveness.tcp_port: "8081"
-    kompose.service.healthcheck.readiness.interval: 11s
-    kompose.service.healthcheck.readiness.retries: "6"
-    kompose.service.healthcheck.readiness.tcp_port: "9091"
-    kompose.service.healthcheck.readiness.timeout: 2s
+    kompose.service.healthcheck.liveness.tcp_port: "8080"
+    kompose.service.healthcheck.readiness.interval: 10s
+    kompose.service.healthcheck.readiness.retries: "5"
+    kompose.service.healthcheck.readiness.tcp_port: "9090"
+    kompose.service.healthcheck.readiness.timeout: 1s
   creationTimestamp: null
   labels:
     io.kompose.service: my-group
@@ -122,35 +122,17 @@ spec:
     metadata:
       annotations:
         kompose.service.group: my-group
-        kompose.service.healthcheck.liveness.tcp_port: "8080"
-        kompose.service.healthcheck.readiness.interval: 10s
-        kompose.service.healthcheck.readiness.retries: "5"
-        kompose.service.healthcheck.readiness.tcp_port: "9090"
-        kompose.service.healthcheck.readiness.timeout: 1s
+        kompose.service.healthcheck.liveness.tcp_port: "8081"
+        kompose.service.healthcheck.readiness.interval: 11s
+        kompose.service.healthcheck.readiness.retries: "6"
+        kompose.service.healthcheck.readiness.tcp_port: "9091"
+        kompose.service.healthcheck.readiness.timeout: 2s
       creationTimestamp: null
       labels:
         io.kompose.network/healthcheck-default: "true"
         io.kompose.service: my-group
     spec:
       containers:
-        - image: mongo
-          livenessProbe:
-            failureThreshold: 5
-            periodSeconds: 10
-            tcpSocket:
-              port: 8080
-            timeoutSeconds: 1
-          name: mongo
-          ports:
-            - containerPort: 27017
-              protocol: TCP
-          readinessProbe:
-            failureThreshold: 5
-            periodSeconds: 10
-            tcpSocket:
-              port: 9090
-            timeoutSeconds: 1
-          resources: {}
         - image: mysql
           livenessProbe:
             failureThreshold: 6
@@ -161,7 +143,6 @@ spec:
           name: mysql
           ports:
             - containerPort: 3306
-              protocol: TCP
           readinessProbe:
             failureThreshold: 6
             periodSeconds: 11
@@ -169,6 +150,23 @@ spec:
               port: 9091
             timeoutSeconds: 2
           resources: {}
+        - image: mongo
+          livenessProbe:
+            failureThreshold: 5
+            periodSeconds: 10
+            tcpSocket:
+              port: 8080
+            timeoutSeconds: 1
+          name: mongo
+          ports:
+            - containerPort: 27017
+          readinessProbe:
+            failureThreshold: 5
+            periodSeconds: 10
+            tcpSocket:
+              port: 9090
+            timeoutSeconds: 1
+          resources: {}
       restartPolicy: Always
 status: {}
 
@@ -237,7 +235,6 @@ spec:
           name: postgresql
           ports:
             - containerPort: 5432
-              protocol: TCP
           readinessProbe:
             failureThreshold: 5
             httpGet:
@@ -292,7 +289,6 @@ spec:
           name: redis
           ports:
             - containerPort: 6379
-              protocol: TCP
           readinessProbe:
             exec:
               command:
diff --git a/script/test/fixtures/healthcheck/output-healthcheck-os.yaml b/script/test/fixtures/healthcheck/output-healthcheck-os.yaml
index 54479474..46e2f57b 100644
--- a/script/test/fixtures/healthcheck/output-healthcheck-os.yaml
+++ b/script/test/fixtures/healthcheck/output-healthcheck-os.yaml
@@ -136,7 +136,6 @@ spec:
           name: mongo
           ports:
             - containerPort: 27017
-              protocol: TCP
           readinessProbe:
             failureThreshold: 5
             periodSeconds: 10
@@ -227,7 +226,6 @@ spec:
           name: mysql
           ports:
             - containerPort: 3306
-              protocol: TCP
           readinessProbe:
             failureThreshold: 6
             periodSeconds: 11
@@ -320,7 +318,6 @@ spec:
           name: postgresql
           ports:
             - containerPort: 5432
-              protocol: TCP
           readinessProbe:
             failureThreshold: 5
             httpGet:
@@ -411,7 +408,6 @@ spec:
           name: redis
           ports:
             - containerPort: 6379
-              protocol: TCP
           readinessProbe:
             exec:
               command:
diff --git a/script/test/fixtures/service-group/output-k8s.yaml b/script/test/fixtures/service-group/output-k8s.yaml
index 290b1ac1..34fe6511 100644
--- a/script/test/fixtures/service-group/output-k8s.yaml
+++ b/script/test/fixtures/service-group/output-k8s.yaml
@@ -46,8 +46,6 @@ spec:
           name: librenms
           ports:
             - containerPort: 8000
-              hostPort: 8000
-              protocol: TCP
           resources: {}
           volumeMounts:
             - mountPath: /data
diff --git a/script/test/fixtures/single-file-output/output-k8s.yaml b/script/test/fixtures/single-file-output/output-k8s.yaml
index bbd8d09d..370fd087 100644
--- a/script/test/fixtures/single-file-output/output-k8s.yaml
+++ b/script/test/fixtures/single-file-output/output-k8s.yaml
@@ -54,8 +54,6 @@ spec:
           name: front-end
           ports:
             - containerPort: 80
-              hostPort: 80
-              protocol: TCP
           resources: {}
       restartPolicy: Always
 status: {}
diff --git a/script/test/fixtures/statefulset/output-k8s.yaml b/script/test/fixtures/statefulset/output-k8s.yaml
index 6a231ce7..7985de88 100644
--- a/script/test/fixtures/statefulset/output-k8s.yaml
+++ b/script/test/fixtures/statefulset/output-k8s.yaml
@@ -73,8 +73,6 @@ spec:
           name: db
           ports:
             - containerPort: 3306
-              hostPort: 3306
-              protocol: TCP
           resources: {}
           volumeMounts:
             - mountPath: /var/lib/mysql
@@ -148,8 +146,6 @@ spec:
           name: wordpress
           ports:
             - containerPort: 80
-              hostPort: 8000
-              protocol: TCP
           resources: {}
           volumeMounts:
             - mountPath: /var/www/html
diff --git a/script/test/fixtures/statefulset/output-os.yaml b/script/test/fixtures/statefulset/output-os.yaml
index f52e7b3c..2c9fbfd8 100644
--- a/script/test/fixtures/statefulset/output-os.yaml
+++ b/script/test/fixtures/statefulset/output-os.yaml
@@ -69,8 +69,6 @@ spec:
           name: db
           ports:
             - containerPort: 3306
-              hostPort: 3306
-              protocol: TCP
           resources: {}
           volumeMounts:
             - mountPath: /var/lib/mysql
@@ -129,8 +127,6 @@ spec:
           name: db
           ports:
             - containerPort: 3306
-              hostPort: 3306
-              protocol: TCP
           resources: {}
           volumeMounts:
             - mountPath: /var/lib/mysql
@@ -214,8 +210,6 @@ spec:
           name: wordpress
           ports:
             - containerPort: 80
-              hostPort: 8000
-              protocol: TCP
           resources: {}
           volumeMounts:
             - mountPath: /var/www/html
@@ -274,8 +268,6 @@ spec:
           name: wordpress
           ports:
             - containerPort: 80
-              hostPort: 8000
-              protocol: TCP
           resources: {}
           volumeMounts:
             - mountPath: /var/www/html
diff --git a/script/test/fixtures/v2/output-k8s.yaml b/script/test/fixtures/v2/output-k8s.yaml
index 3a9c0a3d..48c69531 100644
--- a/script/test/fixtures/v2/output-k8s.yaml
+++ b/script/test/fixtures/v2/output-k8s.yaml
@@ -150,81 +150,42 @@ spec:
       name: foo
       ports:
         - containerPort: 6379
-          protocol: TCP
         - containerPort: 6379
           protocol: UDP
         - containerPort: 3000
-          protocol: TCP
         - containerPort: 3001
-          protocol: TCP
         - containerPort: 3002
-          protocol: TCP
         - containerPort: 3003
-          protocol: TCP
         - containerPort: 3004
-          protocol: TCP
         - containerPort: 3005
-          protocol: TCP
         - containerPort: 8000
-          hostPort: 8000
-          protocol: TCP
         - containerPort: 8080
-          hostPort: 9090
-          protocol: TCP
         - containerPort: 8081
-          hostPort: 9091
-          protocol: TCP
         - containerPort: 22
-          hostPort: 49100
-          protocol: TCP
         - containerPort: 8001
           hostIP: 127.0.0.1
-          hostPort: 8001
-          protocol: TCP
         - containerPort: 5000
           hostIP: 127.0.0.1
-          hostPort: 5000
-          protocol: TCP
         - containerPort: 5001
           hostIP: 127.0.0.1
-          hostPort: 5001
-          protocol: TCP
         - containerPort: 5002
           hostIP: 127.0.0.1
-          hostPort: 5002
-          protocol: TCP
         - containerPort: 5003
           hostIP: 127.0.0.1
-          hostPort: 5003
-          protocol: TCP
         - containerPort: 5004
           hostIP: 127.0.0.1
-          hostPort: 5004
-          protocol: TCP
         - containerPort: 5005
           hostIP: 127.0.0.1
-          hostPort: 5005
-          protocol: TCP
         - containerPort: 5006
           hostIP: 127.0.0.1
-          hostPort: 5006
-          protocol: TCP
         - containerPort: 5007
           hostIP: 127.0.0.1
-          hostPort: 5007
-          protocol: TCP
         - containerPort: 5008
           hostIP: 127.0.0.1
-          hostPort: 5008
-          protocol: TCP
         - containerPort: 5009
           hostIP: 127.0.0.1
-          hostPort: 5009
-          protocol: TCP
         - containerPort: 5010
           hostIP: 127.0.0.1
-          hostPort: 5010
-          protocol: TCP
       resources:
         limits:
           memory: "10e3"
@@ -280,9 +241,7 @@ spec:
           name: redis
           ports:
             - containerPort: 6379
-              protocol: TCP
             - containerPort: 1235
-              hostPort: 1234
               protocol: UDP
           resources:
             limits:
diff --git a/script/test/fixtures/v2/output-os.yaml b/script/test/fixtures/v2/output-os.yaml
index a68ae3e7..4b33eb4d 100644
--- a/script/test/fixtures/v2/output-os.yaml
+++ b/script/test/fixtures/v2/output-os.yaml
@@ -150,81 +150,42 @@ spec:
       name: foo
       ports:
         - containerPort: 6379
-          protocol: TCP
         - containerPort: 6379
           protocol: UDP
         - containerPort: 3000
-          protocol: TCP
         - containerPort: 3001
-          protocol: TCP
         - containerPort: 3002
-          protocol: TCP
         - containerPort: 3003
-          protocol: TCP
         - containerPort: 3004
-          protocol: TCP
         - containerPort: 3005
-          protocol: TCP
         - containerPort: 8000
-          hostPort: 8000
-          protocol: TCP
         - containerPort: 8080
-          hostPort: 9090
-          protocol: TCP
         - containerPort: 8081
-          hostPort: 9091
-          protocol: TCP
         - containerPort: 22
-          hostPort: 49100
-          protocol: TCP
         - containerPort: 8001
           hostIP: 127.0.0.1
-          hostPort: 8001
-          protocol: TCP
         - containerPort: 5000
           hostIP: 127.0.0.1
-          hostPort: 5000
-          protocol: TCP
         - containerPort: 5001
           hostIP: 127.0.0.1
-          hostPort: 5001
-          protocol: TCP
         - containerPort: 5002
           hostIP: 127.0.0.1
-          hostPort: 5002
-          protocol: TCP
         - containerPort: 5003
           hostIP: 127.0.0.1
-          hostPort: 5003
-          protocol: TCP
         - containerPort: 5004
           hostIP: 127.0.0.1
-          hostPort: 5004
-          protocol: TCP
         - containerPort: 5005
           hostIP: 127.0.0.1
-          hostPort: 5005
-          protocol: TCP
         - containerPort: 5006
           hostIP: 127.0.0.1
-          hostPort: 5006
-          protocol: TCP
         - containerPort: 5007
           hostIP: 127.0.0.1
-          hostPort: 5007
-          protocol: TCP
         - containerPort: 5008
           hostIP: 127.0.0.1
-          hostPort: 5008
-          protocol: TCP
         - containerPort: 5009
           hostIP: 127.0.0.1
-          hostPort: 5009
-          protocol: TCP
         - containerPort: 5010
           hostIP: 127.0.0.1
-          hostPort: 5010
-          protocol: TCP
       resources:
         limits:
           memory: "10e3"
@@ -262,9 +223,7 @@ spec:
           name: redis
           ports:
             - containerPort: 6379
-              protocol: TCP
             - containerPort: 1235
-              hostPort: 1234
               protocol: UDP
           resources:
             limits:
diff --git a/script/test/fixtures/v3.0/docker-compose.yaml b/script/test/fixtures/v3.0/docker-compose.yaml
index 3c193cdc..991e785b 100644
--- a/script/test/fixtures/v3.0/docker-compose.yaml
+++ b/script/test/fixtures/v3.0/docker-compose.yaml
@@ -25,4 +25,4 @@ networks:
       name: app-network
   web:
     external:
-      name: web-network
\ No newline at end of file
+      name: web-network
diff --git a/script/test/fixtures/v3.0/output-k8s.yaml b/script/test/fixtures/v3.0/output-k8s.yaml
index f8b6dd80..0d04318a 100644
--- a/script/test/fixtures/v3.0/output-k8s.yaml
+++ b/script/test/fixtures/v3.0/output-k8s.yaml
@@ -53,6 +53,54 @@ spec:
       restartPolicy: Always
 status: {}
 
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  creationTimestamp: null
+  name: normalized-network
+spec:
+  ingress:
+    - from:
+        - podSelector:
+            matchLabels:
+              io.kompose.network/normalized-network: "true"
+  podSelector:
+    matchLabels:
+      io.kompose.network/normalized-network: "true"
+
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  creationTimestamp: null
+  name: v30-normalized-network
+spec:
+  ingress:
+    - from:
+        - podSelector:
+            matchLabels:
+              io.kompose.network/v30-normalized-network: "true"
+  podSelector:
+    matchLabels:
+      io.kompose.network/v30-normalized-network: "true"
+
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  creationTimestamp: null
+  name: app-network
+spec:
+  ingress:
+    - from:
+        - podSelector:
+            matchLabels:
+              io.kompose.network/normalized-network: "true"
+  podSelector:
+    matchLabels:
+      io.kompose.network/normalized-network: "true"
+
 ---
 apiVersion: networking.k8s.io/v1
 kind: NetworkPolicy
@@ -90,16 +138,16 @@ apiVersion: networking.k8s.io/v1
 kind: NetworkPolicy
 metadata:
   creationTimestamp: null
-  name: v30-normalized-network
+  name: normalized-network
 spec:
   ingress:
     - from:
         - podSelector:
             matchLabels:
-              io.kompose.network/v30-normalized-network: "true"
+              io.kompose.network/normalized-network: "true"
   podSelector:
     matchLabels:
-      io.kompose.network/v30-normalized-network: "true"
+      io.kompose.network/normalized-network: "true"
 
 ---
 apiVersion: apps/v1
@@ -155,4 +203,3 @@ spec:
   podSelector:
     matchLabels:
       io.kompose.network/v30-default: "true"
-
diff --git a/script/test/fixtures/v3.0/output-os.yaml b/script/test/fixtures/v3.0/output-os.yaml
index 7d4e0548..0000c3cf 100644
--- a/script/test/fixtures/v3.0/output-os.yaml
+++ b/script/test/fixtures/v3.0/output-os.yaml
@@ -171,4 +171,3 @@ spec:
         type: ""
 status:
   dockerImageRepository: ""
-
diff --git a/script/test/fixtures/volume-mounts/windows/output-k8s.yaml b/script/test/fixtures/volume-mounts/windows/output-k8s.yaml
index adb67865..42d84998 100644
--- a/script/test/fixtures/volume-mounts/windows/output-k8s.yaml
+++ b/script/test/fixtures/volume-mounts/windows/output-k8s.yaml
@@ -43,7 +43,6 @@ spec:
           name: db
           ports:
             - containerPort: 80
-              protocol: TCP
           resources: {}
           volumeMounts:
             - mountPath: D:\config
diff --git a/script/test/fixtures/volume-mounts/windows/output-os.yaml b/script/test/fixtures/volume-mounts/windows/output-os.yaml
index f0f0a762..c6e15b05 100644
--- a/script/test/fixtures/volume-mounts/windows/output-os.yaml
+++ b/script/test/fixtures/volume-mounts/windows/output-os.yaml
@@ -43,7 +43,6 @@ spec:
           name: db
           ports:
             - containerPort: 80
-              protocol: TCP
           resources: {}
           volumeMounts:
             - mountPath: D:\config