Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mutator and Validator Webhooks for GatewayRoute #192

Merged
merged 2 commits into from May 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions config/crd/bases/appmesh.k8s.aws_meshes.yaml
Expand Up @@ -57,9 +57,10 @@ spec:
if the account ID is not your own.
type: string
namespaceSelector:
description: NamespaceSelector selects Namespaces using labels to designate
mesh membership. This field follows standard label selector semantics;
if present but empty, it selects all namespaces.
description: "NamespaceSelector selects Namespaces using labels to designate
mesh membership. This field follows standard label selector semantics:
\tif present but empty, it selects all namespaces. \tif absent, it
selects no namespace."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements.
Expand Down
6 changes: 4 additions & 2 deletions config/crd/bases/appmesh.k8s.aws_virtualnodes.yaml
Expand Up @@ -393,8 +393,10 @@ spec:
- uid
type: object
podSelector:
description: PodSelector selects Pods using labels to designate VirtualNode
membership. if unspecified or empty, it selects no pods.
description: "PodSelector selects Pods using labels to designate VirtualNode
membership. This field follows standard label selector semantics:
\tif present but empty, it selects all pods within namespace. \tif
absent, it selects no pod."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements.
Expand Down
8 changes: 8 additions & 0 deletions config/rbac/role.yaml
Expand Up @@ -34,6 +34,14 @@ rules:
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods/status
verbs:
- get
- patch
- update
- apiGroups:
- appmesh.k8s.aws
resources:
Expand Down
36 changes: 36 additions & 0 deletions config/webhook/manifests.yaml
Expand Up @@ -6,6 +6,24 @@ metadata:
creationTimestamp: null
name: mutating-webhook-configuration
webhooks:
- clientConfig:
caBundle: Cg==
service:
name: webhook-service
namespace: system
path: /mutate-appmesh-k8s-aws-v1beta2-gatewayroute
failurePolicy: Fail
name: mgatewayroute.appmesh.k8s.aws
rules:
- apiGroups:
- appmesh.k8s.aws
apiVersions:
- v1beta2
operations:
- CREATE
- UPDATE
resources:
- gatewayroutes
- clientConfig:
caBundle: Cg==
service:
Expand Down Expand Up @@ -121,6 +139,24 @@ metadata:
creationTimestamp: null
name: validating-webhook-configuration
webhooks:
- clientConfig:
caBundle: Cg==
service:
name: webhook-service
namespace: system
path: /validate-appmesh-k8s-aws-v1beta2-gatewayroute
failurePolicy: Fail
name: vgatewayroute.appmesh.k8s.aws
rules:
- apiGroups:
- appmesh.k8s.aws
apiVersions:
- v1beta2
operations:
- CREATE
- UPDATE
resources:
- gatewayroutes
- clientConfig:
caBundle: Cg==
service:
Expand Down
1 change: 1 addition & 0 deletions hack/gen_mocks.sh 100644 → 100755
Expand Up @@ -2,6 +2,7 @@
mockgen -destination=./mocks/aws-app-mesh-controller-for-k8s/pkg/webhook/mock_mutator.go github.com/aws/aws-app-mesh-controller-for-k8s/pkg/webhook Mutator
mockgen -destination=./mocks/aws-app-mesh-controller-for-k8s/pkg/webhook/mock_validator.go github.com/aws/aws-app-mesh-controller-for-k8s/pkg/webhook Validator
mockgen -destination=./mocks/aws-app-mesh-controller-for-k8s/pkg/mesh/mock_membership_designator.go github.com/aws/aws-app-mesh-controller-for-k8s/pkg/mesh MembershipDesignator
mockgen -destination=./mocks/aws-app-mesh-controller-for-k8s/pkg/virtualgateway/mock_membership_designator.go github.com/aws/aws-app-mesh-controller-for-k8s/pkg/virtualgateway MembershipDesignator
mockgen -destination=./mocks/aws-app-mesh-controller-for-k8s/pkg/virtualnode/mock_membership_designator.go github.com/aws/aws-app-mesh-controller-for-k8s/pkg/virtualnode MembershipDesignator

# apimachinery
Expand Down
6 changes: 6 additions & 0 deletions main.go
Expand Up @@ -38,6 +38,7 @@ import (

"github.com/aws/aws-app-mesh-controller-for-k8s/pkg/inject"
"github.com/aws/aws-app-mesh-controller-for-k8s/pkg/mesh"
"github.com/aws/aws-app-mesh-controller-for-k8s/pkg/virtualgateway"
"github.com/aws/aws-app-mesh-controller-for-k8s/pkg/virtualnode"

appmeshv1beta2 "github.com/aws/aws-app-mesh-controller-for-k8s/apis/appmesh/v1beta2"
Expand Down Expand Up @@ -137,10 +138,15 @@ func main() {
}

meshMembershipDesignator := mesh.NewMembershipDesignator(mgr.GetClient())
vgMembershipDesignator := virtualgateway.NewMembershipDesignator(mgr.GetClient())
vnMembershipDesignator := virtualnode.NewMembershipDesignator(mgr.GetClient())
sidecarInjector := inject.NewSidecarInjector(injectConfig, cloud.Region(), mgr.GetClient(), referencesResolver, vnMembershipDesignator)
appmeshwebhook.NewMeshMutator().SetupWithManager(mgr)
appmeshwebhook.NewMeshValidator().SetupWithManager(mgr)
appmeshwebhook.NewVirtualGatewayMutator(meshMembershipDesignator).SetupWithManager(mgr)
appmeshwebhook.NewVirtualGatewayValidator().SetupWithManager(mgr)
appmeshwebhook.NewGatewayRouteMutator(meshMembershipDesignator, vgMembershipDesignator).SetupWithManager(mgr)
appmeshwebhook.NewGatewayRouteValidator().SetupWithManager(mgr)
appmeshwebhook.NewVirtualNodeMutator(meshMembershipDesignator).SetupWithManager(mgr)
appmeshwebhook.NewVirtualNodeValidator().SetupWithManager(mgr)
appmeshwebhook.NewVirtualServiceMutator(meshMembershipDesignator).SetupWithManager(mgr)
Expand Down
14 changes: 0 additions & 14 deletions mocks/apimachinery/pkg/conversion/mock_scope.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 9 additions & 18 deletions pkg/cloudmap/instances_reconciler_test.go
Expand Up @@ -383,8 +383,7 @@ func Test_defaultInstancesReconciler_matchDesiredInstancesAgainstExistingInstanc
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &defaultInstancesReconciler{
}
r := &defaultInstancesReconciler{}
gotInstancesToCreateOrUpdate, gotInstancesToDelete, gotInstancesToUpdateHealthy, gotInstancesToUpdateUnhealthy := r.matchDesiredInstancesAgainstExistingInstances(tt.args.desiredReadyInstancesAttrsByID, tt.args.desiredNotReadyInstancesAttrsByID, tt.args.existingInstancesAttrsByID)
assert.Equal(t, tt.wantInstancesToCreateOrUpdate, gotInstancesToCreateOrUpdate)
assert.Equal(t, tt.wantInstancesToDelete, gotInstancesToDelete)
Expand All @@ -396,12 +395,10 @@ func Test_defaultInstancesReconciler_matchDesiredInstancesAgainstExistingInstanc

func Test_defaultInstancesReconciler_buildInstanceAttributesByID(t *testing.T) {
vn := &appmesh.VirtualNode{
ObjectMeta: metav1.ObjectMeta{
},
ObjectMeta: metav1.ObjectMeta{},
Spec: appmesh.VirtualNodeSpec{
ServiceDiscovery: &appmesh.ServiceDiscovery{
AWSCloudMap: &appmesh.AWSCloudMapServiceDiscovery{
},
AWSCloudMap: &appmesh.AWSCloudMapServiceDiscovery{},
},
},
}
Expand Down Expand Up @@ -471,8 +468,7 @@ func Test_defaultInstancesReconciler_buildInstanceAttributesByID(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &defaultInstancesReconciler{
}
r := &defaultInstancesReconciler{}
got := r.buildInstanceAttributesByID(tt.args.vn, tt.args.pods)
assert.Equal(t, tt.want, got)
})
Expand Down Expand Up @@ -566,12 +562,10 @@ func Test_defaultInstancesReconciler_buildInstanceAttributes(t *testing.T) {
name: "attributes should have pod labels",
args: args{
vn: &appmesh.VirtualNode{
ObjectMeta: metav1.ObjectMeta{
},
ObjectMeta: metav1.ObjectMeta{},
Spec: appmesh.VirtualNodeSpec{
ServiceDiscovery: &appmesh.ServiceDiscovery{
AWSCloudMap: &appmesh.AWSCloudMapServiceDiscovery{
},
AWSCloudMap: &appmesh.AWSCloudMapServiceDiscovery{},
},
},
},
Expand Down Expand Up @@ -602,8 +596,7 @@ func Test_defaultInstancesReconciler_buildInstanceAttributes(t *testing.T) {
name: "attributes should have VirtualNode attributes",
args: args{
vn: &appmesh.VirtualNode{
ObjectMeta: metav1.ObjectMeta{
},
ObjectMeta: metav1.ObjectMeta{},
Spec: appmesh.VirtualNodeSpec{
ServiceDiscovery: &appmesh.ServiceDiscovery{
AWSCloudMap: &appmesh.AWSCloudMapServiceDiscovery{
Expand Down Expand Up @@ -645,8 +638,7 @@ func Test_defaultInstancesReconciler_buildInstanceAttributes(t *testing.T) {
name: "attributes should have both pod labels and VirtualNode attributes",
args: args{
vn: &appmesh.VirtualNode{
ObjectMeta: metav1.ObjectMeta{
},
ObjectMeta: metav1.ObjectMeta{},
Spec: appmesh.VirtualNodeSpec{
ServiceDiscovery: &appmesh.ServiceDiscovery{
AWSCloudMap: &appmesh.AWSCloudMapServiceDiscovery{
Expand Down Expand Up @@ -693,8 +685,7 @@ func Test_defaultInstancesReconciler_buildInstanceAttributes(t *testing.T) {
name: "when pod labels or virtualNode attributes contains core attributes, it should be overwritten",
args: args{
vn: &appmesh.VirtualNode{
ObjectMeta: metav1.ObjectMeta{
},
ObjectMeta: metav1.ObjectMeta{},
Spec: appmesh.VirtualNodeSpec{
ServiceDiscovery: &appmesh.ServiceDiscovery{
AWSCloudMap: &appmesh.AWSCloudMapServiceDiscovery{
Expand Down
103 changes: 103 additions & 0 deletions pkg/virtualgateway/membership_designator.go
@@ -0,0 +1,103 @@
package virtualgateway

import (
"context"
appmesh "github.com/aws/aws-app-mesh-controller-for-k8s/apis/appmesh/v1beta2"
"github.com/aws/aws-app-mesh-controller-for-k8s/pkg/k8s"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"strings"
)

// MembershipDesignator designates VirtualGateway membership for pods and namespaced AppMesh GatewayRoute CRs.
type MembershipDesignator interface {
// DesignateForPod will choose a VirtualGateway for given pod.
DesignateForPod(ctx context.Context, pod *corev1.Pod) (*appmesh.VirtualGateway, error)
// DesignateForGatewayRoute will choose a VirtualGateway for given namespaced GatewayRoute CR.
DesignateForGatewayRoute(ctx context.Context, obj *appmesh.GatewayRoute) (*appmesh.VirtualGateway, error)
}

// NewMembershipDesignator creates new MembershipDesignator.
func NewMembershipDesignator(k8sClient client.Client) MembershipDesignator {
return &membershipDesignator{k8sClient: k8sClient}
}

var _ MembershipDesignator = &membershipDesignator{}

// virtualGatewaySelectorDesignator designates VirtualGateway membership based on selectors on VirtualGateway.
type membershipDesignator struct {
k8sClient client.Client
}

// +kubebuilder:rbac:groups=appmesh.k8s.aws,resources=virtualgateways,verbs=get;list;watch

func (d *membershipDesignator) DesignateForPod(ctx context.Context, pod *corev1.Pod) (*appmesh.VirtualGateway, error) {
vgList := appmesh.VirtualGatewayList{}
if err := d.k8sClient.List(ctx, &vgList); err != nil {
return nil, errors.Wrap(err, "failed to list VirtualGateways in cluster")
}

var vgCandidates []*appmesh.VirtualGateway
for _, vgObj := range vgList.Items {
selector, err := metav1.LabelSelectorAsSelector(vgObj.Spec.PodSelector)
if err != nil {
return nil, err
}
if selector.Matches(labels.Set(pod.Labels)) {
vgCandidates = append(vgCandidates, vgObj.DeepCopy())
}
}
if len(vgCandidates) == 0 {
return nil, nil
}
if len(vgCandidates) > 1 {
var vgCandidatesNames []string
for _, vg := range vgCandidates {
vgCandidatesNames = append(vgCandidatesNames, k8s.NamespacedName(vg).String())
}
return nil, errors.Errorf("found multiple matching VirtualGateways for pod %s: %s",
k8s.NamespacedName(pod).String(), strings.Join(vgCandidatesNames, ","))
}
return vgCandidates[0], nil
}

// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch

func (d *membershipDesignator) DesignateForGatewayRoute(ctx context.Context, obj *appmesh.GatewayRoute) (*appmesh.VirtualGateway, error) {
objNS := corev1.Namespace{}
if err := d.k8sClient.Get(ctx, types.NamespacedName{Name: obj.GetNamespace()}, &objNS); err != nil {
return nil, errors.Wrapf(err, "failed to get namespace: %s", obj.GetNamespace())
}
vgList := appmesh.VirtualGatewayList{}
if err := d.k8sClient.List(ctx, &vgList); err != nil {
return nil, errors.Wrap(err, "failed to list virtualGateways in cluster")
}

var vgCandidates []*appmesh.VirtualGateway
for _, vgObj := range vgList.Items {
selector, err := metav1.LabelSelectorAsSelector(vgObj.Spec.NamespaceSelector)
if err != nil {
return nil, err
}
if selector.Matches(labels.Set(objNS.Labels)) {
vgCandidates = append(vgCandidates, vgObj.DeepCopy())
}
}
if len(vgCandidates) == 0 {
return nil, errors.Errorf("failed to find matching virtualGateway for namespace: %s, expecting 1 but found %d",
obj.GetNamespace(), 0)
}
if len(vgCandidates) > 1 {
var vgCandidatesNames []string
for _, vg := range vgCandidates {
vgCandidatesNames = append(vgCandidatesNames, vg.Name)
}
return nil, errors.Errorf("found multiple matching virtualGateways for namespace: %s, expecting 1 but found %d: %s",
obj.GetNamespace(), len(vgCandidates), strings.Join(vgCandidatesNames, ","))
}
return vgCandidates[0], nil
}