Repository: kubernetes-sigs/ingress2gateway (go)

Issue:
Add support for running only some of the providers 
<!-- Please only use this template for submitting enhancement requests -->

**What would you like to be added**:
Add a new flag that enables users to choose which concrete providers should be used when executing the 'print' command.

1. By default, all providers should be used.
2. A user should be able to choose more than one provider.

We can start working on it after #40 is merged.

Pull request:
Support to specify providers to execute convert action
Add a new flag `--provider`, in order to users to choose which concrete providers should be used when executing the 'print' command.

**What type of PR is this?**
<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind test

Optionally add one or more of the following kinds if applicable:
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->
/kind feature

**What this PR does / why we need it**:

Now, we already refactor the layout in order to support multiple providers #40. We also need a mechanism to enables users to choose which concrete providers should be used when executing the 'print' command.

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #45

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, please enter a release note below:
-->
```release-note
Add a new flag `--provider`, that enables users to choose which concrete providers should be used when executing the 'print' command.
```


Patch:
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
deleted file mode 100644
index 7cfa22d..0000000
--- a/.github/workflows/build.yaml
+++ /dev/null
@@ -1,34 +0,0 @@
-name: build
-on:
-  pull_request:
-    branches:
-      - main
-  push:
-    branches:
-      - main
-permissions:
-  contents: read
-jobs:
-  goreleaser:
-    runs-on: ubuntu-latest
-    environment: release
-    steps:
-      - name: Set up Go
-        uses: actions/setup-go@v3
-        with:
-          go-version: '1.20'
-        id: go
-
-      - name: Check out code into the Go module directory
-        uses: actions/checkout@v3
-        with:
-          fetch-depth: 0
-
-      - name: GoReleaser - Release
-        uses: goreleaser/goreleaser-action@v4
-        with:
-          version: latest
-          args: build --snapshot --clean
-
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
deleted file mode 100644
index a51f8d6..0000000
--- a/.github/workflows/release.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
-name: release
-on:
-  push:
-    tags:
-      - "v*.*.*"
-permissions:
-  contents: write
-jobs:
-  goreleaser:
-    runs-on: ubuntu-latest
-    environment: release
-    steps:
-      - name: Set up Go
-        uses: actions/setup-go@v3
-        with:
-          go-version: '1.20'
-        id: go
-
-      - name: Check out code into the Go module directory
-        uses: actions/checkout@v3
-        with:
-          fetch-depth: 0
-
-      - name: GoReleaser - Release
-        uses: goreleaser/goreleaser-action@v4
-        with:
-          version: latest
-          args: release --clean
-
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
deleted file mode 100644
index 47fd4b2..0000000
--- a/.goreleaser.yaml
+++ /dev/null
@@ -1,56 +0,0 @@
-# This is an example .goreleaser.yml file with some sensible defaults.
-# Make sure to check the documentation at https://goreleaser.com
-before:
-  hooks:
-    # You may remove this if you don't use go modules.
-    - go mod tidy
-    # you may remove this if you don't need go generate
-    # - go generate ./...
-builds:
-  - env:
-      - CGO_ENABLED=0
-    goos:
-      - linux
-      - darwin
-      - windows
-
-archives:
-  - format: tar.gz
-    # this name template makes the OS and Arch compatible with the results of uname.
-    name_template: >-
-      {{ .ProjectName }}_
-      {{- title .Os }}_
-      {{- if eq .Arch "amd64" }}x86_64
-      {{- else if eq .Arch "386" }}i386
-      {{- else }}{{ .Arch }}{{ end }}
-      {{- if .Arm }}v{{ .Arm }}{{ end }}
-    # use zip for windows archives
-    format_overrides:
-    - goos: windows
-      format: zip
-checksum:
-  name_template: 'checksums.txt'
-snapshot:
-  name_template: "{{ incpatch .Version }}-next"
-release:
-  name_template: "Release-v{{.Version}}"
-  header: |
-    ## Installation
-
-    To install this release, refer to the instructions at https://github.com/kubernetes-sigs/ingress2gateway/blob/v{{.Version}}/README.md
-
-    (ingress2gateway v{{.Version}} was tagged on {{.Date}}.)
-
-changelog:
-  # leaving this github-native option in case we will want to use the GitHub release notes generation API in the future.
-  # use: github-native
-  sort: asc
-  filters:
-    exclude:
-      - '^docs:'
-      - '^test:'
-
-# The lines beneath this are called `modelines`. See `:help modeline`
-# Feel free to remove those if you don't want/use them.
-# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
-# vim: set ts=2 sw=2 tw=0 fo=cnqoj
diff --git a/cmd/print.go b/cmd/print.go
index fde8cf2..3da5740 100644
--- a/cmd/print.go
+++ b/cmd/print.go
@@ -54,6 +54,9 @@ type PrintRunner struct {
 
 	// Only resources that matches this filter will be processed.
 	namespaceFilter string
+
+	// providers indicates which providers are used to execute convert action.
+	providers []string
 }
 
 // PrintGatewaysAndHTTPRoutes performs necessary steps to digest and print
@@ -69,7 +72,7 @@ func (pr *PrintRunner) PrintGatewaysAndHTTPRoutes(cmd *cobra.Command, _ []string
 		return fmt.Errorf("failed to initialize namespace filter: %w", err)
 	}
 
-	httpRoutes, gateways, err := i2gw.ToGatewayAPIResources(cmd.Context(), pr.namespaceFilter, pr.inputFile)
+	httpRoutes, gateways, err := i2gw.ToGatewayAPIResources(cmd.Context(), pr.namespaceFilter, pr.inputFile, pr.providers)
 	if err != nil {
 		return err
 	}
@@ -176,6 +179,9 @@ func newPrintCommand() *cobra.Command {
 		`If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even
 if specified with --namespace.`)
 
+	cmd.Flags().StringSliceVar(&pr.providers, "providers", i2gw.GetSupportedProviders(),
+		fmt.Sprintf("If present, the tool will try to convert only resources related to the specified providers, supported values are %v", i2gw.GetSupportedProviders()))
+
 	cmd.MarkFlagsMutuallyExclusive("namespace", "all-namespaces")
 	return cmd
 }
diff --git a/go.mod b/go.mod
index 67efb16..511d1de 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/kubernetes-sigs/ingress2gateway
 
-go 1.20
+go 1.18
 
 require (
 	github.com/google/go-cmp v0.5.8
@@ -9,7 +9,6 @@ require (
 	k8s.io/apimachinery v0.25.2
 	k8s.io/cli-runtime v0.25.2
 	k8s.io/client-go v0.25.2
-	k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed
 	sigs.k8s.io/controller-runtime v0.13.0
 	sigs.k8s.io/gateway-api v0.5.0
 )
@@ -62,6 +61,7 @@ require (
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	k8s.io/klog/v2 v2.70.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
+	k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
 	sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
 	sigs.k8s.io/kustomize/api v0.12.1 // indirect
 	sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
diff --git a/pkg/i2gw/ingress2gateway.go b/pkg/i2gw/ingress2gateway.go
index 44b8921..2d55c6e 100644
--- a/pkg/i2gw/ingress2gateway.go
+++ b/pkg/i2gw/ingress2gateway.go
@@ -34,7 +34,7 @@ import (
 	gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
 )
 
-func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile string) ([]gatewayv1beta1.HTTPRoute, []gatewayv1beta1.Gateway, error) {
+func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile string, providers []string) ([]gatewayv1beta1.HTTPRoute, []gatewayv1beta1.Gateway, error) {
 	conf, err := config.GetConfig()
 	if err != nil {
 		return nil, nil, fmt.Errorf("failed to get client config: %w", err)
@@ -50,9 +50,12 @@ func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile stri
 	var gateways []gatewayv1beta1.Gateway
 	var httpRoutes []gatewayv1beta1.HTTPRoute
 
-	providerByName := constructProviders(&ProviderConf{
+	providerByName, err := constructProviders(&ProviderConf{
 		Client: cl,
-	})
+	}, providers)
+	if err != nil {
+		return nil, nil, err
+	}
 
 	resources := InputResources{}
 
@@ -120,16 +123,19 @@ func ConstructIngressesFromCluster(ctx context.Context, cl client.Client, ingres
 
 // constructProviders constructs a map of concrete Provider implementations
 // by their ProviderName.
-//
-// TODO (levikobi): Issue #45 - let users filter by provider name.
-func constructProviders(conf *ProviderConf) map[ProviderName]Provider {
+func constructProviders(conf *ProviderConf, providers []string) (map[ProviderName]Provider, error) {
 	providerByName := make(map[ProviderName]Provider, len(ProviderConstructorByName))
 
-	for name, newProviderFunc := range ProviderConstructorByName {
-		providerByName[name] = newProviderFunc(conf)
+	for _, requestedProvider := range providers {
+		requestedProviderName := ProviderName(requestedProvider)
+		newProviderFunc, ok := ProviderConstructorByName[requestedProviderName]
+		if !ok {
+			return nil, fmt.Errorf("%s is not a supported provider", requestedProvider)
+		}
+		providerByName[requestedProviderName] = newProviderFunc(conf)
 	}
 
-	return providerByName
+	return providerByName, nil
 }
 
 // extractObjectsFromReader extracts all objects from a reader,
@@ -216,3 +222,12 @@ func aggregatedErrs(errs field.ErrorList) error {
 	}
 	return errMsg
 }
+
+// GetSupportedProviders returns the names of all providers that are supported now
+func GetSupportedProviders() []string {
+	supportedProviders := make([]string, 0, len(ProviderConstructorByName))
+	for key := range ProviderConstructorByName {
+		supportedProviders = append(supportedProviders, string(key))
+	}
+	return supportedProviders
+}


Test patch:
diff --git a/pkg/i2gw/ingress2gateway_test.go b/pkg/i2gw/ingress2gateway_test.go
index 1b92f9b..e4ee299 100644
--- a/pkg/i2gw/ingress2gateway_test.go
+++ b/pkg/i2gw/ingress2gateway_test.go
@@ -150,3 +150,73 @@ func Test_constructIngressesFromCluster(t *testing.T) {
 		})
 	}
 }
+
+func Test_constructProviders(t *testing.T) {
+	supportProviders := []string{"ingress-nginx"}
+	for _, provider := range supportProviders {
+		ProviderConstructorByName[ProviderName(provider)] = func(conf *ProviderConf) Provider { return nil }
+	}
+	testCases := []struct {
+		name              string
+		providers         []string
+		expectedProviders []string
+		expectedError     error
+	}{{
+		name:              "Test construct providers with default providers",
+		providers:         GetSupportedProviders(),
+		expectedProviders: supportProviders,
+		expectedError:     nil,
+	}, {
+		name:              "Test construct providers with provider that not supported",
+		providers:         []string{"ingress-nginx", "fake-provider"},
+		expectedProviders: []string{},
+		expectedError:     fmt.Errorf("%s is not a supported provider", "fake-provider"),
+	}}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			cl := fake.NewClientBuilder().WithRuntimeObjects([]runtime.Object{}...).Build()
+			providerByName, err := constructProviders(&ProviderConf{
+				Client: cl,
+			}, tc.providers)
+			if tc.expectedError != nil {
+				if err == nil {
+					t.Errorf("Expected error but got none")
+				} else if tc.expectedError.Error() != err.Error() {
+					t.Errorf("The got error '%s' not equal to expected error '%s'", err.Error(), tc.expectedError.Error())
+				}
+			} else {
+				if err != nil {
+					t.Errorf("Expected no error but got %v", err)
+				}
+				if len(tc.expectedProviders) != len(providerByName) {
+					t.Errorf("Expected contructed providers num is %d but got %d", len(tc.providers), len(providerByName))
+				}
+				for _, provider := range tc.expectedProviders {
+					if _, ok := providerByName[ProviderName(provider)]; !ok {
+						t.Errorf("The expected provider %s was not constructed", provider)
+					}
+				}
+			}
+		})
+	}
+}
+
+func Test_GetSupportedProviders(t *testing.T) {
+	supportProviders := []string{"ingress-nginx"}
+	for _, provider := range supportProviders {
+		ProviderConstructorByName[ProviderName(provider)] = func(conf *ProviderConf) Provider { return nil }
+	}
+	t.Run("Test GetSupportedProviders", func(t *testing.T) {
+		allProviders := GetSupportedProviders()
+		if len(allProviders) != len(ProviderConstructorByName) {
+			t.Errorf("The acutal number of the providers we supported is %d but we got the number is: %d",
+				len(ProviderConstructorByName), len(allProviders))
+		}
+		for _, provider := range allProviders {
+			providerName := ProviderName(provider)
+			if _, ok := ProviderConstructorByName[providerName]; !ok {
+				t.Errorf("%s is not a supported provider", providerName)
+			}
+		}
+	})
+}