Repository: knative/pkg (go)

Issue:
Disable controllers using a flag
<!--
Pro-tip: You can leave this block commented, and it still works!

Select the appropriate areas for your issue:

/area API

Classify what kind of issue this is:

/kind feature
-->

I would like to disable controllers using a command-line flag

## Proposal

- Add a `disable-controllers` flag that is a comma-separated list of controller names.
- Add [1] to main.go

[1]:

```golang
// MainNamed runs the generic main flow for controllers and webhooks.
func MainNamed(ctx context.Context, component string, ctors ...injection.NamedControllerConstructor) {

	disabledControllers := flag.String("disable-controllers", "", "Comma-separated list of disabled controllers.")

	// HACK: This parses flags, so the above should be set once this runs.
	cfg := injection.ParseAndGetRESTConfigOrDie()

	enabledCtors := enabledControllers(strings.Split(*disabledControllers, ","), ctors)

	MainWithConfig(ctx, component, cfg, enabledCtors...)
}
```

where `NamedControllerConstructor` is

```golang
// NamedControllerConstructor is a ControllerConstructor with an associated name.
type NamedControllerConstructor struct {
	// Name is the name of associated with the controller returned by ControllerConstructor.
	Name string
	// ControllerConstructor is a constructor for a controller.
	ControllerConstructor ControllerConstructor
}
```

Pull request description:
Disable controllers using a command-line flag
This patch allows disabling controllers when using a new `Main` function
called `MainNamed` which takes a list of `NamedControllerConstructor`.

<!-- Thanks for sending a pull request! -->

# Changes

<!-- 
Describe your changes here- ideally you can get that description straight from
your descriptive commit message(s)! 

- :gift: Add new feature
- :bug: Fix bug
- :broom: Update or clean up current behavior
- :wastebasket: Remove feature or internal logic
-->

- Disable controllers using a command-line flag

<!--
In addition, categorize the changes you're making using the "/kind" Prow command, example:

/kind feature

Supported kinds are: api-change, bug, cleanup, deprecation, removal, documentation, enhancement, performance

-->
/kind feature

<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #2338

<!-- Please include the 'why' behind your changes if no issue exists -->

**Release Note**

<!--
:page_facing_up: If this change has user-visible impact, write a release note in the block
below. Include the string "action required" if additional action is required of
users switching to the new release, for example in case of a breaking change.

Write as if you are speaking to users, not other Knative contributors. If this
change has no user-visible impact, no release-note is needed.
-->
```release-note
Add MainNamedthat allows disabling controllers using a command-line flag (`disable-controllers`)
```

**Docs**

<!--
:book: If this change has user-visible impact, link to an issue or PR in
https://github.com/knative/docs.

See here for guidance on getting permanent links to files: https://help.github.com/en/articles/getting-permanent-links-to-files

Please use the following format for linking documentation:
- [knative/docs]: <issue or pr link>
- [Feature Track]: <link>
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs
None
```


Patch:
diff --git a/.github/workflows/knative-boilerplate.yaml b/.github/workflows/knative-boilerplate.yaml
index b29db540a..2a0b01ba4 100644
--- a/.github/workflows/knative-boilerplate.yaml
+++ b/.github/workflows/knative-boilerplate.yaml
@@ -61,7 +61,7 @@ jobs:
           echo '::endgroup::'
 
           echo '::group:: Installing boilerplate-check ... https://github.com/mattmoor/boilerplate-check'
-          go get github.com/mattmoor/boilerplate-check/cmd/boilerplate-check
+          go install github.com/mattmoor/boilerplate-check/cmd/boilerplate-check@latest
           echo '::endgroup::'
 
           echo "${TEMP_PATH}" >> $GITHUB_PATH
diff --git a/.github/workflows/knative-releasability.yaml b/.github/workflows/knative-releasability.yaml
index 00e38a127..1d9111951 100644
--- a/.github/workflows/knative-releasability.yaml
+++ b/.github/workflows/knative-releasability.yaml
@@ -66,7 +66,7 @@ jobs:
           go-version: 1.17.x
 
       - name: Install Dependencies
-        run: GO111MODULE=on go get knative.dev/test-infra/buoy@main
+        run: go install knative.dev/test-infra/buoy@main
 
       - name: Check out code
         uses: actions/checkout@v2
diff --git a/.github/workflows/knative-release-notes.yaml b/.github/workflows/knative-release-notes.yaml
index dd8bf7afb..999231ca7 100644
--- a/.github/workflows/knative-release-notes.yaml
+++ b/.github/workflows/knative-release-notes.yaml
@@ -44,7 +44,7 @@ jobs:
           go-version: 1.17.x
 
       - name: Install Dependencies
-        run: GO111MODULE=on go get k8s.io/release/cmd/release-notes
+        run: go install k8s.io/release/cmd/release-notes@latest
 
       - name: Check out code
         uses: actions/checkout@v2
diff --git a/.github/workflows/knative-style.yaml b/.github/workflows/knative-style.yaml
index d13ef91b9..bbd306783 100644
--- a/.github/workflows/knative-style.yaml
+++ b/.github/workflows/knative-style.yaml
@@ -37,7 +37,7 @@ jobs:
         - tool: gofmt
           options: -s
         - tool: goimports
-          importpath: golang.org/x/tools/cmd/goimports
+          package: golang.org/x/tools/cmd/goimports@latest
 
     steps:
       - name: Set up Go
@@ -50,10 +50,10 @@ jobs:
         uses: actions/checkout@v2
 
       - name: Install Dependencies
-        if: ${{ matrix.importpath != '' }}
+        if: ${{ matrix.package != '' }}
         run: |
           cd $(mktemp -d)
-          GO111MODULE=on go get ${{ matrix.importpath }}
+          go install ${{ matrix.package }}
 
       - name: ${{ matrix.tool }} ${{ matrix.options }}
         shell: bash
@@ -106,7 +106,7 @@ jobs:
         if: steps.golangci_configuration.outputs.files_exists == 'true'
         uses: golangci/golangci-lint-action@v2
         with:
-          version: v1.42
+          version: v1.43
 
       - name: Install Tools
         env:
diff --git a/.github/workflows/knative-verify.yaml b/.github/workflows/knative-verify.yaml
index 541ca5c25..8106b2674 100644
--- a/.github/workflows/knative-verify.yaml
+++ b/.github/workflows/knative-verify.yaml
@@ -32,11 +32,7 @@ jobs:
 
     runs-on: ${{ matrix.platform }}
 
-    env:
-      GOPATH: ${{ github.workspace }}
-
     steps:
-
     - name: Set up Go ${{ matrix.go-version }}
       uses: actions/setup-go@v2
       with:
@@ -45,22 +41,16 @@ jobs:
 
     - name: Install Dependencies
       run: |
-        curl -L https://github.com/google/ko/releases/download/v0.6.0/ko_0.6.0_Linux_x86_64.tar.gz | tar xzf - ko
-        chmod +x ./ko
-        sudo mv ko /usr/local/bin
-        go get github.com/google/go-licenses
+        go install github.com/google/ko@latest
+        go install github.com/google/go-licenses@latest
 
-    - name: Check out code onto GOPATH
+    - name: Check out code
       uses: actions/checkout@v2
-      with:
-        path: ./src/knative.dev/${{ github.event.repository.name }}
 
     - name: Update Codegen
       shell: bash
       run: |
-        pushd ./src/knative.dev/${{ github.event.repository.name }}
         [[ ! -f hack/update-codegen.sh ]] || ./hack/update-codegen.sh
-        popd
 
     - name: Verify
       shell: bash
@@ -72,7 +62,6 @@ jobs:
           sed ':begin;$!N;s/\n/%0A/;tbegin'
         }
 
-        pushd ./src/knative.dev/${{ github.event.repository.name }}
         if [[ -z "$(git status --porcelain)" ]]; then
             echo "${{ github.repository }} up to date."
         else
@@ -85,4 +74,3 @@ jobs:
             echo "${{ github.repository }} is out of date. Please run hack/update-codegen.sh"
             exit 1
         fi
-        popd
diff --git a/go.mod b/go.mod
index 31dd1316e..5ca4d88a9 100644
--- a/go.mod
+++ b/go.mod
@@ -56,6 +56,6 @@ require (
 	k8s.io/code-generator v0.21.4
 	k8s.io/gengo v0.0.0-20210915205010-39e73c8a59cd
 	k8s.io/klog v1.0.0
-	knative.dev/hack v0.0.0-20211105231158-29f86c2653b5
+	knative.dev/hack v0.0.0-20211112192837-128cf0150a69
 	sigs.k8s.io/yaml v1.3.0
 )
diff --git a/go.sum b/go.sum
index 571b50fc9..3e5a3a05d 100644
--- a/go.sum
+++ b/go.sum
@@ -1136,8 +1136,8 @@ k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7Br
 k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
 k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw=
 k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-knative.dev/hack v0.0.0-20211105231158-29f86c2653b5 h1:9scsqLnVcUh6wnMY0vYo8MGBy1NAkjpjoukyhhkChU4=
-knative.dev/hack v0.0.0-20211105231158-29f86c2653b5/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI=
+knative.dev/hack v0.0.0-20211112192837-128cf0150a69 h1:/3NW6B9VeqAwpW0ZAS+8xLfwgVMIqH+CPmesDATovhk=
+knative.dev/hack v0.0.0-20211112192837-128cf0150a69/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI=
 pgregory.net/rapid v0.3.3 h1:jCjBsY4ln4Atz78QoBWxUEvAHaFyNDQg9+WU62aCn1U=
 pgregory.net/rapid v0.3.3/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/injection/interface.go b/injection/interface.go
index 433728298..158864015 100644
--- a/injection/interface.go
+++ b/injection/interface.go
@@ -103,6 +103,14 @@ type DynamicInterface interface {
 
 type ControllerConstructor func(context.Context, configmap.Watcher) *controller.Impl
 
+// NamedControllerConstructor is a ControllerConstructor with an associated name.
+type NamedControllerConstructor struct {
+	// Name is the name associated with the controller returned by ControllerConstructor.
+	Name string
+	// ControllerConstructor is a constructor for a controller.
+	ControllerConstructor ControllerConstructor
+}
+
 var (
 	// Check that impl implements Interface
 	_ Interface = (*impl)(nil)
diff --git a/injection/sharedmain/main.go b/injection/sharedmain/main.go
index 8cc6fb195..b2a1d5117 100644
--- a/injection/sharedmain/main.go
+++ b/injection/sharedmain/main.go
@@ -26,6 +26,8 @@ import (
 	"os"
 	"time"
 
+	"github.com/spf13/pflag"
+
 	"go.uber.org/automaxprocs/maxprocs" // automatically set GOMAXPROCS based on cgroups
 	"go.uber.org/zap"
 	"golang.org/x/sync/errgroup"
@@ -34,6 +36,7 @@ import (
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/rest"
 
@@ -111,6 +114,43 @@ var (
 	WebhookMainWithConfig  = MainWithConfig
 )
 
+// MainNamed runs the generic main flow for controllers and webhooks.
+//
+// In addition to the MainWithConfig flow, it defines a `disabled-controllers` flag that allows disabling controllers
+// by name.
+func MainNamed(ctx context.Context, component string, ctors ...injection.NamedControllerConstructor) {
+
+	disabledControllers := pflag.StringSlice("disable-controllers", []string{}, "Comma-separated list of disabled controllers.")
+
+	// HACK: This parses flags, so the above should be set once this runs.
+	cfg := injection.ParseAndGetRESTConfigOrDie()
+
+	enabledCtors := enabledControllers(*disabledControllers, ctors)
+
+	MainWithConfig(ctx, component, cfg, toControllerConstructors(enabledCtors)...)
+}
+
+func enabledControllers(disabledControllers []string, ctors []injection.NamedControllerConstructor) []injection.NamedControllerConstructor {
+	disabledControllersSet := sets.NewString(disabledControllers...)
+	activeCtors := make([]injection.NamedControllerConstructor, 0, len(ctors))
+	for _, ctor := range ctors {
+		if disabledControllersSet.Has(ctor.Name) {
+			log.Printf("Disabling controller %s", ctor.Name)
+			continue
+		}
+		activeCtors = append(activeCtors, ctor)
+	}
+	return activeCtors
+}
+
+func toControllerConstructors(namedCtors []injection.NamedControllerConstructor) []injection.ControllerConstructor {
+	ctors := make([]injection.ControllerConstructor, 0, len(namedCtors))
+	for _, ctor := range namedCtors {
+		ctors = append(ctors, ctor.ControllerConstructor)
+	}
+	return ctors
+}
+
 // MainWithContext runs the generic main flow for controllers and
 // webhooks. Use MainWithContext if you do not need to serve webhooks.
 func MainWithContext(ctx context.Context, component string, ctors ...injection.ControllerConstructor) {
diff --git a/vendor/knative.dev/hack/library.sh b/vendor/knative.dev/hack/library.sh
index 4af73bec5..17ce662ed 100644
--- a/vendor/knative.dev/hack/library.sh
+++ b/vendor/knative.dev/hack/library.sh
@@ -186,6 +186,32 @@ function wait_until_object_does_not_exist() {
   return 1
 }
 
+# Waits until the given object exists.
+# Parameters: $1 - the kind of the object.
+#             $2 - object's name.
+#             $3 - namespace (optional).
+function wait_until_object_exists() {
+  local KUBECTL_ARGS="get $1 $2"
+  local DESCRIPTION="$1 $2"
+
+  if [[ -n $3 ]]; then
+    KUBECTL_ARGS="get -n $3 $1 $2"
+    DESCRIPTION="$1 $3/$2"
+  fi
+  echo -n "Waiting until ${DESCRIPTION} exists"
+  for i in {1..150}; do  # timeout after 5 minutes
+    if kubectl ${KUBECTL_ARGS} > /dev/null 2>&1; then
+      echo -e "\n${DESCRIPTION} exists"
+      return 0
+    fi
+    echo -n "."
+    sleep 2
+  done
+  echo -e "\n\nERROR: timeout waiting for ${DESCRIPTION} to exist"
+  kubectl ${KUBECTL_ARGS}
+  return 1
+}
+
 # Waits until all pods are running in the given namespace.
 # This function handles some edge cases that `kubectl wait` does not support,
 # and it provides nice debug info on the state of the pod if it failed,
diff --git a/vendor/knative.dev/hack/release.sh b/vendor/knative.dev/hack/release.sh
index 25284371c..6f21ce60c 100644
--- a/vendor/knative.dev/hack/release.sh
+++ b/vendor/knative.dev/hack/release.sh
@@ -82,10 +82,10 @@ function publish_to_gcs() {
 # These are global environment variables.
 SKIP_TESTS=0
 PRESUBMIT_TEST_FAIL_FAST=1
-TAG_RELEASE=0
+export TAG_RELEASE=0
 PUBLISH_RELEASE=0
 PUBLISH_TO_GITHUB=0
-TAG=""
+export TAG=""
 BUILD_COMMIT_HASH=""
 BUILD_YYYYMMDD=""
 BUILD_TIMESTAMP=""
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 8a9d4be70..5cb8abc45 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1019,7 +1019,7 @@ k8s.io/utils/buffer
 k8s.io/utils/integer
 k8s.io/utils/pointer
 k8s.io/utils/trace
-# knative.dev/hack v0.0.0-20211105231158-29f86c2653b5
+# knative.dev/hack v0.0.0-20211112192837-128cf0150a69
 ## explicit
 knative.dev/hack
 # sigs.k8s.io/structured-merge-diff/v4 v4.1.2


Test patch:
diff --git a/injection/sharedmain/main_test.go b/injection/sharedmain/main_test.go
new file mode 100644
index 000000000..673bda005
--- /dev/null
+++ b/injection/sharedmain/main_test.go
@@ -0,0 +1,72 @@
+/*
+Copyright 2021 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package sharedmain
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+
+	"knative.dev/pkg/injection"
+)
+
+func TestEnabledControllers(t *testing.T) {
+	tests := []struct {
+		name                string
+		disabledControllers []string
+		ctors               []injection.NamedControllerConstructor
+		wantNames           []string
+	}{
+		{
+			name:                "zero",
+			disabledControllers: []string{"foo"},
+			ctors:               []injection.NamedControllerConstructor{{Name: "bar"}},
+			wantNames:           []string{"bar"},
+		},
+		{
+			name:                "one",
+			disabledControllers: []string{"foo"},
+			ctors:               []injection.NamedControllerConstructor{{Name: "foo"}},
+			wantNames:           []string{},
+		},
+		{
+			name:                "two",
+			disabledControllers: []string{"foo"},
+			ctors: []injection.NamedControllerConstructor{
+				{Name: "foo"},
+				{Name: "bar"},
+			},
+			wantNames: []string{"bar"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := enabledControllers(tt.disabledControllers, tt.ctors)
+			if diff := cmp.Diff(tt.wantNames, namesOf(got)); diff != "" {
+				t.Error("(-want, +got)", diff)
+			}
+		})
+	}
+}
+
+func namesOf(ctors []injection.NamedControllerConstructor) []string {
+	names := make([]string, 0, len(ctors))
+	for _, x := range ctors {
+		names = append(names, x.Name)
+	}
+	return names
+}
diff --git a/test/request.go b/test/request.go
index fd495c121..a22542e29 100644
--- a/test/request.go
+++ b/test/request.go
@@ -105,7 +105,7 @@ func WaitForEndpointStateWithTimeout(
 	timeout time.Duration,
 	opts ...interface{}) (*spoof.Response, error) {
 
-	client, rOpts, err := makeSpoofClient(ctx, kubeClient, logf, url, resolvable, timeout /* true, */, opts...)
+	client, rOpts, err := makeSpoofClient(ctx, kubeClient, logf, url, resolvable, timeout, opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -152,7 +152,7 @@ func CheckEndpointState(
 	resolvable bool,
 	opts ...interface{},
 ) (*spoof.Response, error) {
-	client, rOpts, err := makeSpoofClient(ctx, kubeClient, logf, url, resolvable, Flags.SpoofRequestTimeout /* false, */, opts...)
+	client, rOpts, err := makeSpoofClient(ctx, kubeClient, logf, url, resolvable, Flags.SpoofRequestTimeout, opts...)
 	if err != nil {
 		return nil, err
 	}