Repository: linkerd/linkerd2
Pull request #12534 (2024-05-01 22:02:16)

Update `values.go` to better align with Helm
Some fields in the Helm templates are not represented in `values.go`. This causes data loss when valid Helm values are unmarshalled into a `linkerd2.Values` struct.

Update `linkerd2.Values` to include additional fields already represented in the Helm templates.

<!--  Thanks for sending a pull request!

If you already have a well-structured git commit message, chances are GitHub
set the title and description of this PR to the git commit message subject and
body, respectively. If so, you may delete these instructions and submit your PR.

If this is your first time, please read our contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md

The title and description of your Pull Request should match the git commit
subject and body, respectively. Git commit messages are structured as follows:

```
Subject

Problem

Solution

Validation

Fixes #[GitHub issue ID]

DCO Sign off
```

Example git commit message:

```
Introduce Pull Request Template

GitHub's community guidelines recommend a pull request template, the repo was
lacking one.

Introduce a `PULL_REQUEST_TEMPLATE.md` file.

Once merged, the
[Community profile checklist](https://github.com/linkerd/linkerd2/community)
should indicate the repo now provides a pull request template.

Fixes #3321

Signed-off-by: Jane Smith <jane.smith@example.com>
```

Note the git commit message subject becomes the pull request title.

For more details around git commits, see the section on Committing in our
contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md#committing
-->



Patch:
diff --git a/controller/gen/apis/server/v1beta1/doc.go b/controller/gen/apis/server/v1beta1/doc.go
new file mode 100644
index 000000000..ffd023fe6
--- /dev/null
+++ b/controller/gen/apis/server/v1beta1/doc.go
@@ -0,0 +1,3 @@
+// +k8s:deepcopy-gen=package
+
+package v1beta1
diff --git a/controller/gen/apis/server/v1beta1/register.go b/controller/gen/apis/server/v1beta1/register.go
new file mode 100644
index 000000000..175a7ce81
--- /dev/null
+++ b/controller/gen/apis/server/v1beta1/register.go
@@ -0,0 +1,49 @@
+package v1beta1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+
+	"github.com/linkerd/linkerd2/controller/gen/apis/server"
+)
+
+var (
+	// SchemeGroupVersion is the identifier for the API which includes the name
+	// of the group and the version of the API.
+	SchemeGroupVersion = schema.GroupVersion{
+		Group:   server.GroupName,
+		Version: "v1beta1",
+	}
+
+	// SchemeBuilder collects functions that add things to a scheme. It's to
+	// allow code to compile without explicitly referencing generated types.
+	// You should declare one in each package that will have generated deep
+	// copy or conversion functions.
+	SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
+
+	// AddToScheme applies all the stored functions to the scheme. A non-nil error
+	// indicates that one function failed and the attempt was abandoned.
+	AddToScheme = SchemeBuilder.AddToScheme
+)
+
+// Kind takes an unqualified kind and returns back a Group qualified GroupKind
+func Kind(kind string) schema.GroupKind {
+	return SchemeGroupVersion.WithKind(kind).GroupKind()
+}
+
+// Resource takes an unqualified resource and returns a Group qualified
+// GroupResource
+func Resource(resource string) schema.GroupResource {
+	return SchemeGroupVersion.WithResource(resource).GroupResource()
+}
+
+// Adds the list of known types to Scheme.
+func addKnownTypes(scheme *runtime.Scheme) error {
+	scheme.AddKnownTypes(SchemeGroupVersion,
+		&Server{},
+		&ServerList{},
+	)
+	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
+	return nil
+}
diff --git a/controller/gen/apis/server/v1beta1/types.go b/controller/gen/apis/server/v1beta1/types.go
new file mode 100644
index 000000000..5b5211deb
--- /dev/null
+++ b/controller/gen/apis/server/v1beta1/types.go
@@ -0,0 +1,45 @@
+package v1beta1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+)
+
+// +genclient
+// +genclient:noStatus
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +groupName=policy.linkerd.io
+
+type Server struct {
+	// TypeMeta is the metadata for the resource, like kind and apiversion
+	metav1.TypeMeta `json:",inline"`
+
+	// ObjectMeta contains the metadata for the particular object, including
+	// things like...
+	//  - name
+	//  - namespace
+	//  - self link
+	//  - labels
+	//  - ... etc ...
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	// Spec is the custom resource spec
+	Spec ServerSpec `json:"spec"`
+}
+
+// ServerSpec specifies a Server resource.
+type ServerSpec struct {
+	PodSelector   *metav1.LabelSelector `json:"podSelector"`
+	Port          intstr.IntOrString    `json:"port,omitempty"`
+	ProxyProtocol string                `json:"proxyProtocol,omitempty"`
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// ServerList is a list of Server resources.
+type ServerList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata"`
+
+	Items []Server `json:"items"`
+}
diff --git a/controller/gen/apis/server/v1beta1/zz_generated.deepcopy.go b/controller/gen/apis/server/v1beta1/zz_generated.deepcopy.go
new file mode 100644
index 000000000..9a6fa4257
--- /dev/null
+++ b/controller/gen/apis/server/v1beta1/zz_generated.deepcopy.go
@@ -0,0 +1,109 @@
+//go:build !ignore_autogenerated
+// +build !ignore_autogenerated
+
+/*
+Copyright The Kubernetes 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.
+*/
+
+// Code generated by deepcopy-gen. DO NOT EDIT.
+
+package v1beta1
+
+import (
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Server) DeepCopyInto(out *Server) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Server.
+func (in *Server) DeepCopy() *Server {
+	if in == nil {
+		return nil
+	}
+	out := new(Server)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Server) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServerList) DeepCopyInto(out *ServerList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]Server, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerList.
+func (in *ServerList) DeepCopy() *ServerList {
+	if in == nil {
+		return nil
+	}
+	out := new(ServerList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ServerList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServerSpec) DeepCopyInto(out *ServerSpec) {
+	*out = *in
+	if in.PodSelector != nil {
+		in, out := &in.PodSelector, &out.PodSelector
+		*out = new(v1.LabelSelector)
+		(*in).DeepCopyInto(*out)
+	}
+	out.Port = in.Port
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerSpec.
+func (in *ServerSpec) DeepCopy() *ServerSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ServerSpec)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/controller/gen/apis/server/v1beta2/types.go b/controller/gen/apis/server/v1beta2/types.go
index 44768db32..f27004fd6 100644
--- a/controller/gen/apis/server/v1beta2/types.go
+++ b/controller/gen/apis/server/v1beta2/types.go
@@ -29,8 +29,8 @@ type Server struct {
 
 // ServerSpec specifies a Server resource.
 type ServerSpec struct {
-	PodSelector              *metav1.LabelSelector `json:"podSelector"`
-	ExternalWorkloadSelector *metav1.LabelSelector `json:"externalWorkloadSelector"`
+	PodSelector              *metav1.LabelSelector `json:"podSelector,omitempty"`
+	ExternalWorkloadSelector *metav1.LabelSelector `json:"externalWorkloadSelector,omitempty"`
 	Port                     intstr.IntOrString    `json:"port,omitempty"`
 	ProxyProtocol            string                `json:"proxyProtocol,omitempty"`
 }
diff --git a/controller/gen/client/clientset/versioned/clientset.go b/controller/gen/client/clientset/versioned/clientset.go
index b02da86c7..dad7724bf 100644
--- a/controller/gen/client/clientset/versioned/clientset.go
+++ b/controller/gen/client/clientset/versioned/clientset.go
@@ -26,6 +26,7 @@ import (
 	linkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha1"
 	policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1"
 	policyv1beta3 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1beta3"
+	serverv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta1"
 	serverv1beta2 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta2"
 	serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1"
 	linkerdv1alpha2 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2"
@@ -40,6 +41,7 @@ type Interface interface {
 	LinkV1alpha1() linkv1alpha1.LinkV1alpha1Interface
 	PolicyV1alpha1() policyv1alpha1.PolicyV1alpha1Interface
 	PolicyV1beta3() policyv1beta3.PolicyV1beta3Interface
+	ServerV1beta1() serverv1beta1.ServerV1beta1Interface
 	ServerV1beta2() serverv1beta2.ServerV1beta2Interface
 	ServerauthorizationV1beta1() serverauthorizationv1beta1.ServerauthorizationV1beta1Interface
 	LinkerdV1alpha2() linkerdv1alpha2.LinkerdV1alpha2Interface
@@ -52,6 +54,7 @@ type Clientset struct {
 	linkV1alpha1               *linkv1alpha1.LinkV1alpha1Client
 	policyV1alpha1             *policyv1alpha1.PolicyV1alpha1Client
 	policyV1beta3              *policyv1beta3.PolicyV1beta3Client
+	serverV1beta1              *serverv1beta1.ServerV1beta1Client
 	serverV1beta2              *serverv1beta2.ServerV1beta2Client
 	serverauthorizationV1beta1 *serverauthorizationv1beta1.ServerauthorizationV1beta1Client
 	linkerdV1alpha2            *linkerdv1alpha2.LinkerdV1alpha2Client
@@ -77,6 +80,11 @@ func (c *Clientset) PolicyV1beta3() policyv1beta3.PolicyV1beta3Interface {
 	return c.policyV1beta3
 }
 
+// ServerV1beta1 retrieves the ServerV1beta1Client
+func (c *Clientset) ServerV1beta1() serverv1beta1.ServerV1beta1Interface {
+	return c.serverV1beta1
+}
+
 // ServerV1beta2 retrieves the ServerV1beta2Client
 func (c *Clientset) ServerV1beta2() serverv1beta2.ServerV1beta2Interface {
 	return c.serverV1beta2
@@ -152,6 +160,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset,
 	if err != nil {
 		return nil, err
 	}
+	cs.serverV1beta1, err = serverv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)
+	if err != nil {
+		return nil, err
+	}
 	cs.serverV1beta2, err = serverv1beta2.NewForConfigAndClient(&configShallowCopy, httpClient)
 	if err != nil {
 		return nil, err
@@ -189,6 +201,7 @@ func New(c rest.Interface) *Clientset {
 	cs.linkV1alpha1 = linkv1alpha1.New(c)
 	cs.policyV1alpha1 = policyv1alpha1.New(c)
 	cs.policyV1beta3 = policyv1beta3.New(c)
+	cs.serverV1beta1 = serverv1beta1.New(c)
 	cs.serverV1beta2 = serverv1beta2.New(c)
 	cs.serverauthorizationV1beta1 = serverauthorizationv1beta1.New(c)
 	cs.linkerdV1alpha2 = linkerdv1alpha2.New(c)
diff --git a/controller/gen/client/clientset/versioned/fake/clientset_generated.go b/controller/gen/client/clientset/versioned/fake/clientset_generated.go
index 72a6c2637..07b93877a 100644
--- a/controller/gen/client/clientset/versioned/fake/clientset_generated.go
+++ b/controller/gen/client/clientset/versioned/fake/clientset_generated.go
@@ -28,6 +28,8 @@ import (
 	fakepolicyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1/fake"
 	policyv1beta3 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1beta3"
 	fakepolicyv1beta3 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1beta3/fake"
+	serverv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta1"
+	fakeserverv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta1/fake"
 	serverv1beta2 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta2"
 	fakeserverv1beta2 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta2/fake"
 	serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1"
@@ -111,6 +113,11 @@ func (c *Clientset) PolicyV1beta3() policyv1beta3.PolicyV1beta3Interface {
 	return &fakepolicyv1beta3.FakePolicyV1beta3{Fake: &c.Fake}
 }
 
+// ServerV1beta1 retrieves the ServerV1beta1Client
+func (c *Clientset) ServerV1beta1() serverv1beta1.ServerV1beta1Interface {
+	return &fakeserverv1beta1.FakeServerV1beta1{Fake: &c.Fake}
+}
+
 // ServerV1beta2 retrieves the ServerV1beta2Client
 func (c *Clientset) ServerV1beta2() serverv1beta2.ServerV1beta2Interface {
 	return &fakeserverv1beta2.FakeServerV1beta2{Fake: &c.Fake}
diff --git a/controller/gen/client/clientset/versioned/fake/register.go b/controller/gen/client/clientset/versioned/fake/register.go
index c4aa0e327..8ebba89fb 100644
--- a/controller/gen/client/clientset/versioned/fake/register.go
+++ b/controller/gen/client/clientset/versioned/fake/register.go
@@ -23,6 +23,7 @@ import (
 	linkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
 	policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
 	policyv1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
+	serverv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
 	serverv1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
 	serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
 	linkerdv1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
@@ -41,6 +42,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
 	linkv1alpha1.AddToScheme,
 	policyv1alpha1.AddToScheme,
 	policyv1beta3.AddToScheme,
+	serverv1beta1.AddToScheme,
 	serverv1beta2.AddToScheme,
 	serverauthorizationv1beta1.AddToScheme,
 	linkerdv1alpha2.AddToScheme,
diff --git a/controller/gen/client/clientset/versioned/scheme/register.go b/controller/gen/client/clientset/versioned/scheme/register.go
index 192576aa8..0da750f79 100644
--- a/controller/gen/client/clientset/versioned/scheme/register.go
+++ b/controller/gen/client/clientset/versioned/scheme/register.go
@@ -23,6 +23,7 @@ import (
 	linkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
 	policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
 	policyv1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
+	serverv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
 	serverv1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
 	serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
 	linkerdv1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
@@ -41,6 +42,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
 	linkv1alpha1.AddToScheme,
 	policyv1alpha1.AddToScheme,
 	policyv1beta3.AddToScheme,
+	serverv1beta1.AddToScheme,
 	serverv1beta2.AddToScheme,
 	serverauthorizationv1beta1.AddToScheme,
 	linkerdv1alpha2.AddToScheme,
diff --git a/controller/gen/client/clientset/versioned/typed/server/v1beta1/doc.go b/controller/gen/client/clientset/versioned/typed/server/v1beta1/doc.go
new file mode 100644
index 000000000..771101956
--- /dev/null
+++ b/controller/gen/client/clientset/versioned/typed/server/v1beta1/doc.go
@@ -0,0 +1,20 @@
+/*
+Copyright The Kubernetes 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+// This package has the automatically generated typed clients.
+package v1beta1
diff --git a/controller/gen/client/clientset/versioned/typed/server/v1beta1/fake/doc.go b/controller/gen/client/clientset/versioned/typed/server/v1beta1/fake/doc.go
new file mode 100644
index 000000000..16f443990
--- /dev/null
+++ b/controller/gen/client/clientset/versioned/typed/server/v1beta1/fake/doc.go
@@ -0,0 +1,20 @@
+/*
+Copyright The Kubernetes 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+// Package fake has the automatically generated clients.
+package fake
diff --git a/controller/gen/client/clientset/versioned/typed/server/v1beta1/fake/fake_server.go b/controller/gen/client/clientset/versioned/typed/server/v1beta1/fake/fake_server.go
new file mode 100644
index 000000000..c9199ab13
--- /dev/null
+++ b/controller/gen/client/clientset/versioned/typed/server/v1beta1/fake/fake_server.go
@@ -0,0 +1,129 @@
+/*
+Copyright The Kubernetes 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+	"context"
+
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	labels "k8s.io/apimachinery/pkg/labels"
+	types "k8s.io/apimachinery/pkg/types"
+	watch "k8s.io/apimachinery/pkg/watch"
+	testing "k8s.io/client-go/testing"
+)
+
+// FakeServers implements ServerInterface
+type FakeServers struct {
+	Fake *FakeServerV1beta1
+	ns   string
+}
+
+var serversResource = v1beta1.SchemeGroupVersion.WithResource("servers")
+
+var serversKind = v1beta1.SchemeGroupVersion.WithKind("Server")
+
+// Get takes name of the server, and returns the corresponding server object, and an error if there is any.
+func (c *FakeServers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Server, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewGetAction(serversResource, c.ns, name), &v1beta1.Server{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1beta1.Server), err
+}
+
+// List takes label and field selectors, and returns the list of Servers that match those selectors.
+func (c *FakeServers) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ServerList, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewListAction(serversResource, serversKind, c.ns, opts), &v1beta1.ServerList{})
+
+	if obj == nil {
+		return nil, err
+	}
+
+	label, _, _ := testing.ExtractFromListOptions(opts)
+	if label == nil {
+		label = labels.Everything()
+	}
+	list := &v1beta1.ServerList{ListMeta: obj.(*v1beta1.ServerList).ListMeta}
+	for _, item := range obj.(*v1beta1.ServerList).Items {
+		if label.Matches(labels.Set(item.Labels)) {
+			list.Items = append(list.Items, item)
+		}
+	}
+	return list, err
+}
+
+// Watch returns a watch.Interface that watches the requested servers.
+func (c *FakeServers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+	return c.Fake.
+		InvokesWatch(testing.NewWatchAction(serversResource, c.ns, opts))
+
+}
+
+// Create takes the representation of a server and creates it.  Returns the server's representation of the server, and an error, if there is any.
+func (c *FakeServers) Create(ctx context.Context, server *v1beta1.Server, opts v1.CreateOptions) (result *v1beta1.Server, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewCreateAction(serversResource, c.ns, server), &v1beta1.Server{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1beta1.Server), err
+}
+
+// Update takes the representation of a server and updates it. Returns the server's representation of the server, and an error, if there is any.
+func (c *FakeServers) Update(ctx context.Context, server *v1beta1.Server, opts v1.UpdateOptions) (result *v1beta1.Server, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewUpdateAction(serversResource, c.ns, server), &v1beta1.Server{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1beta1.Server), err
+}
+
+// Delete takes name of the server and deletes it. Returns an error if one occurs.
+func (c *FakeServers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+	_, err := c.Fake.
+		Invokes(testing.NewDeleteActionWithOptions(serversResource, c.ns, name, opts), &v1beta1.Server{})
+
+	return err
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *FakeServers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+	action := testing.NewDeleteCollectionAction(serversResource, c.ns, listOpts)
+
+	_, err := c.Fake.Invokes(action, &v1beta1.ServerList{})
+	return err
+}
+
+// Patch applies the patch and returns the patched server.
+func (c *FakeServers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Server, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewPatchSubresourceAction(serversResource, c.ns, name, pt, data, subresources...), &v1beta1.Server{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1beta1.Server), err
+}
diff --git a/controller/gen/client/clientset/versioned/typed/server/v1beta1/fake/fake_server_client.go b/controller/gen/client/clientset/versioned/typed/server/v1beta1/fake/fake_server_client.go
new file mode 100644
index 000000000..ccb82dc2b
--- /dev/null
+++ b/controller/gen/client/clientset/versioned/typed/server/v1beta1/fake/fake_server_client.go
@@ -0,0 +1,40 @@
+/*
+Copyright The Kubernetes 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta1"
+	rest "k8s.io/client-go/rest"
+	testing "k8s.io/client-go/testing"
+)
+
+type FakeServerV1beta1 struct {
+	*testing.Fake
+}
+
+func (c *FakeServerV1beta1) Servers(namespace string) v1beta1.ServerInterface {
+	return &FakeServers{c, namespace}
+}
+
+// RESTClient returns a RESTClient that is used to communicate
+// with API server by this client implementation.
+func (c *FakeServerV1beta1) RESTClient() rest.Interface {
+	var ret *rest.RESTClient
+	return ret
+}
diff --git a/controller/gen/client/clientset/versioned/typed/server/v1beta1/generated_expansion.go b/controller/gen/client/clientset/versioned/typed/server/v1beta1/generated_expansion.go
new file mode 100644
index 000000000..386f7bb3c
--- /dev/null
+++ b/controller/gen/client/clientset/versioned/typed/server/v1beta1/generated_expansion.go
@@ -0,0 +1,21 @@
+/*
+Copyright The Kubernetes 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1beta1
+
+type ServerExpansion interface{}
diff --git a/controller/gen/client/clientset/versioned/typed/server/v1beta1/server.go b/controller/gen/client/clientset/versioned/typed/server/v1beta1/server.go
new file mode 100644
index 000000000..0296afa77
--- /dev/null
+++ b/controller/gen/client/clientset/versioned/typed/server/v1beta1/server.go
@@ -0,0 +1,178 @@
+/*
+Copyright The Kubernetes 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1beta1
+
+import (
+	"context"
+	"time"
+
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
+	scheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	types "k8s.io/apimachinery/pkg/types"
+	watch "k8s.io/apimachinery/pkg/watch"
+	rest "k8s.io/client-go/rest"
+)
+
+// ServersGetter has a method to return a ServerInterface.
+// A group's client should implement this interface.
+type ServersGetter interface {
+	Servers(namespace string) ServerInterface
+}
+
+// ServerInterface has methods to work with Server resources.
+type ServerInterface interface {
+	Create(ctx context.Context, server *v1beta1.Server, opts v1.CreateOptions) (*v1beta1.Server, error)
+	Update(ctx context.Context, server *v1beta1.Server, opts v1.UpdateOptions) (*v1beta1.Server, error)
+	Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
+	DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
+	Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta1.Server, error)
+	List(ctx context.Context, opts v1.ListOptions) (*v1beta1.ServerList, error)
+	Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
+	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Server, err error)
+	ServerExpansion
+}
+
+// servers implements ServerInterface
+type servers struct {
+	client rest.Interface
+	ns     string
+}
+
+// newServers returns a Servers
+func newServers(c *ServerV1beta1Client, namespace string) *servers {
+	return &servers{
+		client: c.RESTClient(),
+		ns:     namespace,
+	}
+}
+
+// Get takes name of the server, and returns the corresponding server object, and an error if there is any.
+func (c *servers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Server, err error) {
+	result = &v1beta1.Server{}
+	err = c.client.Get().
+		Namespace(c.ns).
+		Resource("servers").
+		Name(name).
+		VersionedParams(&options, scheme.ParameterCodec).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// List takes label and field selectors, and returns the list of Servers that match those selectors.
+func (c *servers) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ServerList, err error) {
+	var timeout time.Duration
+	if opts.TimeoutSeconds != nil {
+		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+	}
+	result = &v1beta1.ServerList{}
+	err = c.client.Get().
+		Namespace(c.ns).
+		Resource("servers").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Timeout(timeout).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// Watch returns a watch.Interface that watches the requested servers.
+func (c *servers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+	var timeout time.Duration
+	if opts.TimeoutSeconds != nil {
+		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+	}
+	opts.Watch = true
+	return c.client.Get().
+		Namespace(c.ns).
+		Resource("servers").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Timeout(timeout).
+		Watch(ctx)
+}
+
+// Create takes the representation of a server and creates it.  Returns the server's representation of the server, and an error, if there is any.
+func (c *servers) Create(ctx context.Context, server *v1beta1.Server, opts v1.CreateOptions) (result *v1beta1.Server, err error) {
+	result = &v1beta1.Server{}
+	err = c.client.Post().
+		Namespace(c.ns).
+		Resource("servers").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Body(server).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// Update takes the representation of a server and updates it. Returns the server's representation of the server, and an error, if there is any.
+func (c *servers) Update(ctx context.Context, server *v1beta1.Server, opts v1.UpdateOptions) (result *v1beta1.Server, err error) {
+	result = &v1beta1.Server{}
+	err = c.client.Put().
+		Namespace(c.ns).
+		Resource("servers").
+		Name(server.Name).
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Body(server).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// Delete takes name of the server and deletes it. Returns an error if one occurs.
+func (c *servers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+	return c.client.Delete().
+		Namespace(c.ns).
+		Resource("servers").
+		Name(name).
+		Body(&opts).
+		Do(ctx).
+		Error()
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *servers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+	var timeout time.Duration
+	if listOpts.TimeoutSeconds != nil {
+		timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
+	}
+	return c.client.Delete().
+		Namespace(c.ns).
+		Resource("servers").
+		VersionedParams(&listOpts, scheme.ParameterCodec).
+		Timeout(timeout).
+		Body(&opts).
+		Do(ctx).
+		Error()
+}
+
+// Patch applies the patch and returns the patched server.
+func (c *servers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Server, err error) {
+	result = &v1beta1.Server{}
+	err = c.client.Patch(pt).
+		Namespace(c.ns).
+		Resource("servers").
+		Name(name).
+		SubResource(subresources...).
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Body(data).
+		Do(ctx).
+		Into(result)
+	return
+}
diff --git a/controller/gen/client/clientset/versioned/typed/server/v1beta1/server_client.go b/controller/gen/client/clientset/versioned/typed/server/v1beta1/server_client.go
new file mode 100644
index 000000000..b23b1848f
--- /dev/null
+++ b/controller/gen/client/clientset/versioned/typed/server/v1beta1/server_client.go
@@ -0,0 +1,107 @@
+/*
+Copyright The Kubernetes 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1beta1
+
+import (
+	"net/http"
+
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
+	"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
+	rest "k8s.io/client-go/rest"
+)
+
+type ServerV1beta1Interface interface {
+	RESTClient() rest.Interface
+	ServersGetter
+}
+
+// ServerV1beta1Client is used to interact with features provided by the server group.
+type ServerV1beta1Client struct {
+	restClient rest.Interface
+}
+
+func (c *ServerV1beta1Client) Servers(namespace string) ServerInterface {
+	return newServers(c, namespace)
+}
+
+// NewForConfig creates a new ServerV1beta1Client for the given config.
+// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
+// where httpClient was generated with rest.HTTPClientFor(c).
+func NewForConfig(c *rest.Config) (*ServerV1beta1Client, error) {
+	config := *c
+	if err := setConfigDefaults(&config); err != nil {
+		return nil, err
+	}
+	httpClient, err := rest.HTTPClientFor(&config)
+	if err != nil {
+		return nil, err
+	}
+	return NewForConfigAndClient(&config, httpClient)
+}
+
+// NewForConfigAndClient creates a new ServerV1beta1Client for the given config and http client.
+// Note the http client provided takes precedence over the configured transport values.
+func NewForConfigAndClient(c *rest.Config, h *http.Client) (*ServerV1beta1Client, error) {
+	config := *c
+	if err := setConfigDefaults(&config); err != nil {
+		return nil, err
+	}
+	client, err := rest.RESTClientForConfigAndClient(&config, h)
+	if err != nil {
+		return nil, err
+	}
+	return &ServerV1beta1Client{client}, nil
+}
+
+// NewForConfigOrDie creates a new ServerV1beta1Client for the given config and
+// panics if there is an error in the config.
+func NewForConfigOrDie(c *rest.Config) *ServerV1beta1Client {
+	client, err := NewForConfig(c)
+	if err != nil {
+		panic(err)
+	}
+	return client
+}
+
+// New creates a new ServerV1beta1Client for the given RESTClient.
+func New(c rest.Interface) *ServerV1beta1Client {
+	return &ServerV1beta1Client{c}
+}
+
+func setConfigDefaults(config *rest.Config) error {
+	gv := v1beta1.SchemeGroupVersion
+	config.GroupVersion = &gv
+	config.APIPath = "/apis"
+	config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
+
+	if config.UserAgent == "" {
+		config.UserAgent = rest.DefaultKubernetesUserAgent()
+	}
+
+	return nil
+}
+
+// RESTClient returns a RESTClient that is used to communicate
+// with API server by this client implementation.
+func (c *ServerV1beta1Client) RESTClient() rest.Interface {
+	if c == nil {
+		return nil
+	}
+	return c.restClient
+}
diff --git a/controller/gen/client/informers/externalversions/generic.go b/controller/gen/client/informers/externalversions/generic.go
index d49fbc0fe..0a5bff745 100644
--- a/controller/gen/client/informers/externalversions/generic.go
+++ b/controller/gen/client/informers/externalversions/generic.go
@@ -25,6 +25,7 @@ import (
 	v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
 	policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
 	v1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
+	serverv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
 	v1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
 	serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
 	v1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
@@ -84,6 +85,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
 	case v1beta3.SchemeGroupVersion.WithResource("httproutes"):
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Policy().V1beta3().HTTPRoutes().Informer()}, nil
 
+		// Group=server, Version=v1beta1
+	case serverv1beta1.SchemeGroupVersion.WithResource("servers"):
+		return &genericInformer{resource: resource.GroupResource(), informer: f.Server().V1beta1().Servers().Informer()}, nil
+
 		// Group=server, Version=v1beta2
 	case v1beta2.SchemeGroupVersion.WithResource("servers"):
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Server().V1beta2().Servers().Informer()}, nil
diff --git a/controller/gen/client/informers/externalversions/server/interface.go b/controller/gen/client/informers/externalversions/server/interface.go
index 1d0bc3547..9e78a70b0 100644
--- a/controller/gen/client/informers/externalversions/server/interface.go
+++ b/controller/gen/client/informers/externalversions/server/interface.go
@@ -20,11 +20,14 @@ package server
 
 import (
 	internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/server/v1beta1"
 	v1beta2 "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/server/v1beta2"
 )
 
 // Interface provides access to each of this group's versions.
 type Interface interface {
+	// V1beta1 provides access to shared informers for resources in V1beta1.
+	V1beta1() v1beta1.Interface
 	// V1beta2 provides access to shared informers for resources in V1beta2.
 	V1beta2() v1beta2.Interface
 }
@@ -40,6 +43,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
 	return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
 }
 
+// V1beta1 returns a new v1beta1.Interface.
+func (g *group) V1beta1() v1beta1.Interface {
+	return v1beta1.New(g.factory, g.namespace, g.tweakListOptions)
+}
+
 // V1beta2 returns a new v1beta2.Interface.
 func (g *group) V1beta2() v1beta2.Interface {
 	return v1beta2.New(g.factory, g.namespace, g.tweakListOptions)
diff --git a/controller/gen/client/informers/externalversions/server/v1beta1/interface.go b/controller/gen/client/informers/externalversions/server/v1beta1/interface.go
new file mode 100644
index 000000000..717142453
--- /dev/null
+++ b/controller/gen/client/informers/externalversions/server/v1beta1/interface.go
@@ -0,0 +1,45 @@
+/*
+Copyright The Kubernetes 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.
+*/
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package v1beta1
+
+import (
+	internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
+)
+
+// Interface provides access to all the informers in this group version.
+type Interface interface {
+	// Servers returns a ServerInformer.
+	Servers() ServerInformer
+}
+
+type version struct {
+	factory          internalinterfaces.SharedInformerFactory
+	namespace        string
+	tweakListOptions internalinterfaces.TweakListOptionsFunc
+}
+
+// New returns a new Interface.
+func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
+	return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
+}
+
+// Servers returns a ServerInformer.
+func (v *version) Servers() ServerInformer {
+	return &serverInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
+}
diff --git a/controller/gen/client/informers/externalversions/server/v1beta1/server.go b/controller/gen/client/informers/externalversions/server/v1beta1/server.go
new file mode 100644
index 000000000..4267426a4
--- /dev/null
+++ b/controller/gen/client/informers/externalversions/server/v1beta1/server.go
@@ -0,0 +1,90 @@
+/*
+Copyright The Kubernetes 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.
+*/
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package v1beta1
+
+import (
+	"context"
+	time "time"
+
+	serverv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
+	versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
+	internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/listers/server/v1beta1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+	watch "k8s.io/apimachinery/pkg/watch"
+	cache "k8s.io/client-go/tools/cache"
+)
+
+// ServerInformer provides access to a shared informer and lister for
+// Servers.
+type ServerInformer interface {
+	Informer() cache.SharedIndexInformer
+	Lister() v1beta1.ServerLister
+}
+
+type serverInformer struct {
+	factory          internalinterfaces.SharedInformerFactory
+	tweakListOptions internalinterfaces.TweakListOptionsFunc
+	namespace        string
+}
+
+// NewServerInformer constructs a new informer for Server type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewServerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+	return NewFilteredServerInformer(client, namespace, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredServerInformer constructs a new informer for Server type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewFilteredServerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
+	return cache.NewSharedIndexInformer(
+		&cache.ListWatch{
+			ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
+				if tweakListOptions != nil {
+					tweakListOptions(&options)
+				}
+				return client.ServerV1beta1().Servers(namespace).List(context.TODO(), options)
+			},
+			WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
+				if tweakListOptions != nil {
+					tweakListOptions(&options)
+				}
+				return client.ServerV1beta1().Servers(namespace).Watch(context.TODO(), options)
+			},
+		},
+		&serverv1beta1.Server{},
+		resyncPeriod,
+		indexers,
+	)
+}
+
+func (f *serverInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+	return NewFilteredServerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *serverInformer) Informer() cache.SharedIndexInformer {
+	return f.factory.InformerFor(&serverv1beta1.Server{}, f.defaultInformer)
+}
+
+func (f *serverInformer) Lister() v1beta1.ServerLister {
+	return v1beta1.NewServerLister(f.Informer().GetIndexer())
+}
diff --git a/controller/gen/client/listers/server/v1beta1/expansion_generated.go b/controller/gen/client/listers/server/v1beta1/expansion_generated.go
new file mode 100644
index 000000000..195815706
--- /dev/null
+++ b/controller/gen/client/listers/server/v1beta1/expansion_generated.go
@@ -0,0 +1,27 @@
+/*
+Copyright The Kubernetes 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.
+*/
+
+// Code generated by lister-gen. DO NOT EDIT.
+
+package v1beta1
+
+// ServerListerExpansion allows custom methods to be added to
+// ServerLister.
+type ServerListerExpansion interface{}
+
+// ServerNamespaceListerExpansion allows custom methods to be added to
+// ServerNamespaceLister.
+type ServerNamespaceListerExpansion interface{}
diff --git a/controller/gen/client/listers/server/v1beta1/server.go b/controller/gen/client/listers/server/v1beta1/server.go
new file mode 100644
index 000000000..c2486f96b
--- /dev/null
+++ b/controller/gen/client/listers/server/v1beta1/server.go
@@ -0,0 +1,99 @@
+/*
+Copyright The Kubernetes 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.
+*/
+
+// Code generated by lister-gen. DO NOT EDIT.
+
+package v1beta1
+
+import (
+	v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/client-go/tools/cache"
+)
+
+// ServerLister helps list Servers.
+// All objects returned here must be treated as read-only.
+type ServerLister interface {
+	// List lists all Servers in the indexer.
+	// Objects returned here must be treated as read-only.
+	List(selector labels.Selector) (ret []*v1beta1.Server, err error)
+	// Servers returns an object that can list and get Servers.
+	Servers(namespace string) ServerNamespaceLister
+	ServerListerExpansion
+}
+
+// serverLister implements the ServerLister interface.
+type serverLister struct {
+	indexer cache.Indexer
+}
+
+// NewServerLister returns a new ServerLister.
+func NewServerLister(indexer cache.Indexer) ServerLister {
+	return &serverLister{indexer: indexer}
+}
+
+// List lists all Servers in the indexer.
+func (s *serverLister) List(selector labels.Selector) (ret []*v1beta1.Server, err error) {
+	err = cache.ListAll(s.indexer, selector, func(m interface{}) {
+		ret = append(ret, m.(*v1beta1.Server))
+	})
+	return ret, err
+}
+
+// Servers returns an object that can list and get Servers.
+func (s *serverLister) Servers(namespace string) ServerNamespaceLister {
+	return serverNamespaceLister{indexer: s.indexer, namespace: namespace}
+}
+
+// ServerNamespaceLister helps list and get Servers.
+// All objects returned here must be treated as read-only.
+type ServerNamespaceLister interface {
+	// List lists all Servers in the indexer for a given namespace.
+	// Objects returned here must be treated as read-only.
+	List(selector labels.Selector) (ret []*v1beta1.Server, err error)
+	// Get retrieves the Server from the indexer for a given namespace and name.
+	// Objects returned here must be treated as read-only.
+	Get(name string) (*v1beta1.Server, error)
+	ServerNamespaceListerExpansion
+}
+
+// serverNamespaceLister implements the ServerNamespaceLister
+// interface.
+type serverNamespaceLister struct {
+	indexer   cache.Indexer
+	namespace string
+}
+
+// List lists all Servers in the indexer for a given namespace.
+func (s serverNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.Server, err error) {
+	err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
+		ret = append(ret, m.(*v1beta1.Server))
+	})
+	return ret, err
+}
+
+// Get retrieves the Server from the indexer for a given namespace and name.
+func (s serverNamespaceLister) Get(name string) (*v1beta1.Server, error) {
+	obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
+	if err != nil {
+		return nil, err
+	}
+	if !exists {
+		return nil, errors.NewNotFound(v1beta1.Resource("server"), name)
+	}
+	return obj.(*v1beta1.Server), nil
+}
diff --git a/multicluster/values/values.go b/multicluster/values/values.go
index db97532a0..52168a99a 100644
--- a/multicluster/values/values.go
+++ b/multicluster/values/values.go
@@ -8,6 +8,7 @@ import (
 	"github.com/linkerd/linkerd2/pkg/k8s"
 	"helm.sh/helm/v3/pkg/chart/loader"
 	"helm.sh/helm/v3/pkg/chartutil"
+	corev1 "k8s.io/api/core/v1"
 	"sigs.k8s.io/yaml"
 )
 
@@ -37,6 +38,9 @@ type Values struct {
 	TargetClusterName              string   `json:"targetClusterName"`
 	EnablePodAntiAffinity          bool     `json:"enablePodAntiAffinity"`
 	RevisionHistoryLimit           uint32   `json:"revisionHistoryLimit"`
+
+	ServiceMirrorAdditionalEnv   []corev1.EnvVar `json:"serviceMirrorAdditionalEnv"`
+	ServiceMirrorExperimentalEnv []corev1.EnvVar `json:"serviceMirrorExperimentalEnv"`
 }
 
 // Gateway contains all options related to the Gateway Service
diff --git a/pkg/charts/linkerd2/values.go b/pkg/charts/linkerd2/values.go
index fa9d4a5f0..a89f8f187 100644
--- a/pkg/charts/linkerd2/values.go
+++ b/pkg/charts/linkerd2/values.go
@@ -60,6 +60,8 @@ type (
 		RevisionHistoryLimit         uint                   `json:"revisionHistoryLimit"`
 
 		DestinationController map[string]interface{} `json:"destinationController"`
+		Heartbeat             map[string]interface{} `json:"heartbeat"`
+		SPValidator           map[string]interface{} `json:"spValidator"`
 
 		PodAnnotations    map[string]string `json:"podAnnotations"`
 		PodLabels         map[string]string `json:"podLabels"`
@@ -72,7 +74,7 @@ type (
 		NetworkValidator *NetworkValidator `json:"networkValidator"`
 		Identity         *Identity         `json:"identity"`
 		DebugContainer   *DebugContainer   `json:"debugContainer"`
-		ProxyInjector    *Webhook          `json:"proxyInjector"`
+		ProxyInjector    *ProxyInjector    `json:"proxyInjector"`
 		ProfileValidator *Webhook          `json:"profileValidator"`
 		PolicyValidator  *Webhook          `json:"policyValidator"`
 		NodeSelector     map[string]string `json:"nodeSelector"`
@@ -285,6 +287,9 @@ type (
 		ServiceAccountTokenProjection bool     `json:"serviceAccountTokenProjection"`
 		Issuer                        *Issuer  `json:"issuer"`
 		KubeAPI                       *KubeAPI `json:"kubeAPI"`
+
+		AdditionalEnv   []corev1.EnvVar `json:"additionalEnv"`
+		ExperimentalEnv []corev1.EnvVar `json:"experimentalEnv"`
 	}
 
 	// Issuer has the Helm variables of the identity issuer
@@ -301,6 +306,13 @@ type (
 		ClientBurst int     `json:"clientBurst"`
 	}
 
+	// ProxyInjector configures the proxy-injector webhook
+	ProxyInjector struct {
+		Webhook
+		AdditionalEnv   []corev1.EnvVar `json:"additionalEnv"`
+		ExperimentalEnv []corev1.EnvVar `json:"experimentalEnv"`
+	}
+
 	// Webhook Helm variables for a webhook
 	Webhook struct {
 		*TLS


Test patch:
diff --git a/cli/cmd/testdata/install_controlplane_tracing_output.golden b/cli/cmd/testdata/install_controlplane_tracing_output.golden
index 813292918..32ddc985f 100644
--- a/cli/cmd/testdata/install_controlplane_tracing_output.golden
+++ b/cli/cmd/testdata/install_controlplane_tracing_output.golden
@@ -530,10 +530,13 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: false
     enablePodDisruptionBudget: false
+    heartbeat: null
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -752,8 +755,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: proxy injector CA bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -771,6 +776,7 @@ data:
     proxyInjectorProxyResources: null
     proxyInjectorResources: null
     revisionHistoryLimit: 10
+    spValidator: null
     tolerations: null
     webhookFailurePolicy: Ignore
 ---
diff --git a/cli/cmd/testdata/install_custom_domain.golden b/cli/cmd/testdata/install_custom_domain.golden
index 1c004630c..382568930 100644
--- a/cli/cmd/testdata/install_custom_domain.golden
+++ b/cli/cmd/testdata/install_custom_domain.golden
@@ -530,10 +530,13 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: false
     enablePodDisruptionBudget: false
+    heartbeat: null
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -752,8 +755,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: proxy injector CA bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -771,6 +776,7 @@ data:
     proxyInjectorProxyResources: null
     proxyInjectorResources: null
     revisionHistoryLimit: 10
+    spValidator: null
     tolerations: null
     webhookFailurePolicy: Ignore
 ---
diff --git a/cli/cmd/testdata/install_custom_registry.golden b/cli/cmd/testdata/install_custom_registry.golden
index 0571ac9e6..1ac1a8ea4 100644
--- a/cli/cmd/testdata/install_custom_registry.golden
+++ b/cli/cmd/testdata/install_custom_registry.golden
@@ -530,10 +530,13 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: false
     enablePodDisruptionBudget: false
+    heartbeat: null
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -752,8 +755,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: proxy injector CA bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -771,6 +776,7 @@ data:
     proxyInjectorProxyResources: null
     proxyInjectorResources: null
     revisionHistoryLimit: 10
+    spValidator: null
     tolerations: null
     webhookFailurePolicy: Ignore
 ---
diff --git a/cli/cmd/testdata/install_default.golden b/cli/cmd/testdata/install_default.golden
index 1c004630c..382568930 100644
--- a/cli/cmd/testdata/install_default.golden
+++ b/cli/cmd/testdata/install_default.golden
@@ -530,10 +530,13 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: false
     enablePodDisruptionBudget: false
+    heartbeat: null
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -752,8 +755,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: proxy injector CA bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -771,6 +776,7 @@ data:
     proxyInjectorProxyResources: null
     proxyInjectorResources: null
     revisionHistoryLimit: 10
+    spValidator: null
     tolerations: null
     webhookFailurePolicy: Ignore
 ---
diff --git a/cli/cmd/testdata/install_default_override_dst_get_nets.golden b/cli/cmd/testdata/install_default_override_dst_get_nets.golden
index b8ff1014d..1f09b5730 100644
--- a/cli/cmd/testdata/install_default_override_dst_get_nets.golden
+++ b/cli/cmd/testdata/install_default_override_dst_get_nets.golden
@@ -530,10 +530,13 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: false
     enablePodDisruptionBudget: false
+    heartbeat: null
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -752,8 +755,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: proxy injector CA bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -771,6 +776,7 @@ data:
     proxyInjectorProxyResources: null
     proxyInjectorResources: null
     revisionHistoryLimit: 10
+    spValidator: null
     tolerations: null
     webhookFailurePolicy: Ignore
 ---
diff --git a/cli/cmd/testdata/install_default_token.golden b/cli/cmd/testdata/install_default_token.golden
index b6145ead8..8973f42cc 100644
--- a/cli/cmd/testdata/install_default_token.golden
+++ b/cli/cmd/testdata/install_default_token.golden
@@ -530,10 +530,13 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: false
     enablePodDisruptionBudget: false
+    heartbeat: null
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -752,8 +755,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: proxy injector CA bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -771,6 +776,7 @@ data:
     proxyInjectorProxyResources: null
     proxyInjectorResources: null
     revisionHistoryLimit: 10
+    spValidator: null
     tolerations: null
     webhookFailurePolicy: Ignore
 ---
diff --git a/cli/cmd/testdata/install_ha_output.golden b/cli/cmd/testdata/install_ha_output.golden
index f97f23a25..10e25c60e 100644
--- a/cli/cmd/testdata/install_ha_output.golden
+++ b/cli/cmd/testdata/install_ha_output.golden
@@ -539,6 +539,7 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: true
     enablePodDisruptionBudget: true
+    heartbeat: null
     heartbeatResources:
       cpu:
         limit: ""
@@ -552,6 +553,8 @@ data:
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: true
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -779,8 +782,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: proxy injector CA bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -807,6 +812,7 @@ data:
         limit: 250Mi
         request: 50Mi
     revisionHistoryLimit: 10
+    spValidator: null
     tolerations: null
     webhookFailurePolicy: Fail
 ---
diff --git a/cli/cmd/testdata/install_ha_with_overrides_output.golden b/cli/cmd/testdata/install_ha_with_overrides_output.golden
index ad907dbbf..86fad4682 100644
--- a/cli/cmd/testdata/install_ha_with_overrides_output.golden
+++ b/cli/cmd/testdata/install_ha_with_overrides_output.golden
@@ -539,6 +539,7 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: true
     enablePodDisruptionBudget: true
+    heartbeat: null
     heartbeatResources:
       cpu:
         limit: ""
@@ -552,6 +553,8 @@ data:
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: true
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -779,8 +782,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: proxy injector CA bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -807,6 +812,7 @@ data:
         limit: 250Mi
         request: 50Mi
     revisionHistoryLimit: 10
+    spValidator: null
     tolerations: null
     webhookFailurePolicy: Fail
 ---
diff --git a/cli/cmd/testdata/install_heartbeat_disabled_output.golden b/cli/cmd/testdata/install_heartbeat_disabled_output.golden
index d7923f0bb..c5cb43cfe 100644
--- a/cli/cmd/testdata/install_heartbeat_disabled_output.golden
+++ b/cli/cmd/testdata/install_heartbeat_disabled_output.golden
@@ -461,10 +461,13 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: false
     enablePodDisruptionBudget: false
+    heartbeat: null
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -683,8 +686,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: proxy injector CA bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -702,6 +707,7 @@ data:
     proxyInjectorProxyResources: null
     proxyInjectorResources: null
     revisionHistoryLimit: 10
+    spValidator: null
     tolerations: null
     webhookFailurePolicy: Ignore
 ---
diff --git a/cli/cmd/testdata/install_helm_control_plane_output.golden b/cli/cmd/testdata/install_helm_control_plane_output.golden
index 687d2090f..fc7e887f9 100644
--- a/cli/cmd/testdata/install_helm_control_plane_output.golden
+++ b/cli/cmd/testdata/install_helm_control_plane_output.golden
@@ -531,10 +531,13 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: false
     enablePodDisruptionBudget: false
+    heartbeat: null
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -729,8 +732,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: test-proxy-injector-ca-bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -748,6 +753,7 @@ data:
     proxyInjectorProxyResources: null
     proxyInjectorResources: null
     revisionHistoryLimit: 10
+    spValidator: null
     tap:
       caBundle: test-tap-ca-bundle
       externalSecret: true
diff --git a/cli/cmd/testdata/install_helm_control_plane_output_ha.golden b/cli/cmd/testdata/install_helm_control_plane_output_ha.golden
index bb722a1ab..fa5872db1 100644
--- a/cli/cmd/testdata/install_helm_control_plane_output_ha.golden
+++ b/cli/cmd/testdata/install_helm_control_plane_output_ha.golden
@@ -540,6 +540,7 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: true
     enablePodDisruptionBudget: true
+    heartbeat: null
     heartbeatResources:
       cpu:
         limit: ""
@@ -553,6 +554,8 @@ data:
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: true
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -756,8 +759,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: test-proxy-injector-ca-bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -784,6 +789,7 @@ data:
         limit: 250Mi
         request: 50Mi
     revisionHistoryLimit: 10
+    spValidator: null
     tap:
       caBundle: test-tap-ca-bundle
       externalSecret: true
diff --git a/cli/cmd/testdata/install_helm_output_ha_labels.golden b/cli/cmd/testdata/install_helm_output_ha_labels.golden
index 79aea9d55..57d46c4e5 100644
--- a/cli/cmd/testdata/install_helm_output_ha_labels.golden
+++ b/cli/cmd/testdata/install_helm_output_ha_labels.golden
@@ -540,6 +540,7 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: true
     enablePodDisruptionBudget: true
+    heartbeat: null
     heartbeatResources:
       cpu:
         limit: ""
@@ -553,6 +554,8 @@ data:
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: true
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -760,8 +763,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: test-proxy-injector-ca-bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -788,6 +793,7 @@ data:
         limit: 250Mi
         request: 50Mi
     revisionHistoryLimit: 10
+    spValidator: null
     tap:
       caBundle: test-tap-ca-bundle
       externalSecret: true
diff --git a/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden b/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
index 360f16fb2..89cf1031e 100644
--- a/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
+++ b/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
@@ -535,6 +535,7 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: true
     enablePodDisruptionBudget: true
+    heartbeat: null
     heartbeatResources:
       cpu:
         limit: ""
@@ -548,6 +549,8 @@ data:
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: true
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -751,8 +754,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: test-proxy-injector-ca-bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -774,6 +779,7 @@ data:
         limit: 250Mi
         request: 50Mi
     revisionHistoryLimit: 10
+    spValidator: null
     tap:
       caBundle: test-tap-ca-bundle
       externalSecret: true
diff --git a/cli/cmd/testdata/install_no_init_container.golden b/cli/cmd/testdata/install_no_init_container.golden
index ff0e6abe2..e7a67880c 100644
--- a/cli/cmd/testdata/install_no_init_container.golden
+++ b/cli/cmd/testdata/install_no_init_container.golden
@@ -530,10 +530,13 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: false
     enablePodDisruptionBudget: false
+    heartbeat: null
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -752,8 +755,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: proxy injector CA bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -771,6 +776,7 @@ data:
     proxyInjectorProxyResources: null
     proxyInjectorResources: null
     revisionHistoryLimit: 10
+    spValidator: null
     tolerations: null
     webhookFailurePolicy: Ignore
 ---
diff --git a/cli/cmd/testdata/install_output.golden b/cli/cmd/testdata/install_output.golden
index 454fdf917..9a3a6d76a 100644
--- a/cli/cmd/testdata/install_output.golden
+++ b/cli/cmd/testdata/install_output.golden
@@ -514,10 +514,13 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: false
     enablePodDisruptionBudget: false
+    heartbeat: null
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -705,8 +708,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: proxy injector CA bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -724,6 +729,7 @@ data:
     proxyInjectorProxyResources: null
     proxyInjectorResources: null
     revisionHistoryLimit: 10
+    spValidator: null
     tolerations: null
     webhookFailurePolicy: WebhookFailurePolicy
 ---
diff --git a/cli/cmd/testdata/install_proxy_ignores.golden b/cli/cmd/testdata/install_proxy_ignores.golden
index f88f3faa5..8363f983a 100644
--- a/cli/cmd/testdata/install_proxy_ignores.golden
+++ b/cli/cmd/testdata/install_proxy_ignores.golden
@@ -530,10 +530,13 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: false
     enablePodDisruptionBudget: false
+    heartbeat: null
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -752,8 +755,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: proxy injector CA bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -771,6 +776,7 @@ data:
     proxyInjectorProxyResources: null
     proxyInjectorResources: null
     revisionHistoryLimit: 10
+    spValidator: null
     tolerations: null
     webhookFailurePolicy: Ignore
 ---
diff --git a/cli/cmd/testdata/install_values_file.golden b/cli/cmd/testdata/install_values_file.golden
index 210b58d40..2f47a3760 100644
--- a/cli/cmd/testdata/install_values_file.golden
+++ b/cli/cmd/testdata/install_values_file.golden
@@ -530,10 +530,13 @@ data:
     enableH2Upgrade: true
     enablePodAntiAffinity: false
     enablePodDisruptionBudget: false
+    heartbeat: null
     heartbeatResources: null
     heartbeatSchedule: 1 2 3 4 5
     highAvailability: false
     identity:
+      additionalEnv: null
+      experimentalEnv: null
       externalCA: false
       issuer:
         clockSkewAllowance: 20s
@@ -752,8 +755,10 @@ data:
         name: linkerd-proxy-init-xtables-lock
         readOnly: false
     proxyInjector:
+      additionalEnv: null
       caBundle: proxy injector CA bundle
       crtPEM: ""
+      experimentalEnv: null
       externalSecret: true
       injectCaFrom: ""
       injectCaFromSecret: ""
@@ -771,6 +776,7 @@ data:
     proxyInjectorProxyResources: null
     proxyInjectorResources: null
     revisionHistoryLimit: 10
+    spValidator: null
     tolerations: null
     webhookFailurePolicy: Ignore
 ---
diff --git a/pkg/charts/linkerd2/values_test.go b/pkg/charts/linkerd2/values_test.go
index 274f72a9d..3779376af 100644
--- a/pkg/charts/linkerd2/values_test.go
+++ b/pkg/charts/linkerd2/values_test.go
@@ -246,7 +246,7 @@ func TestNewValues(t *testing.T) {
 			},
 		},
 
-		ProxyInjector:    &Webhook{TLS: &TLS{}, NamespaceSelector: namespaceSelectorInjector},
+		ProxyInjector:    &ProxyInjector{Webhook: Webhook{TLS: &TLS{}, NamespaceSelector: namespaceSelectorInjector}},
 		ProfileValidator: &Webhook{TLS: &TLS{}, NamespaceSelector: namespaceSelectorSimple},
 		PolicyValidator:  &Webhook{TLS: &TLS{}, NamespaceSelector: namespaceSelectorSimple},
 	}