package virtualgateway import ( "context" "strings" 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/aws/aws-app-mesh-controller-for-k8s/pkg/webhook" "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" ) // MembershipDesignator designates VirtualGateway membership for pods and namespaced AppMesh GatewayRoute CRs. type MembershipDesignator interface { // DesignateForGatewayRoute will choose a VirtualGateway for given namespaced GatewayRoute CR. DesignateForGatewayRoute(ctx context.Context, obj *appmesh.GatewayRoute) (*appmesh.VirtualGateway, error) // DesignateForPod will choose a VirtualGateway for given pod. DesignateForPod(ctx context.Context, pod *corev1.Pod) (*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) { // see https://github.com/kubernetes/kubernetes/issues/88282 and https://github.com/kubernetes/kubernetes/issues/76680 req := webhook.ContextGetAdmissionRequest(ctx) vgList := appmesh.VirtualGatewayList{} if err := d.k8sClient.List(ctx, &vgList, client.InNamespace(req.Namespace)); 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) { // see https://github.com/kubernetes/kubernetes/issues/88282 and https://github.com/kubernetes/kubernetes/issues/76680 req := webhook.ContextGetAdmissionRequest(ctx) objNS := corev1.Namespace{} if err := d.k8sClient.Get(ctx, types.NamespacedName{Name: req.Namespace}, &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 { if matches, err := matchesNamespaceSelector(objNS, &vgObj); err != nil { return nil, err } else if !matches { continue } if matches, err := matchesGatewayRouteSelector(obj, &vgObj); err != nil { return nil, err } else if !matches { continue } vgCandidates = append(vgCandidates, vgObj.DeepCopy()) } // No matching VirtualGateway if len(vgCandidates) == 0 { return nil, errors.Errorf("failed to find matching virtualGateway for gatewayRoute: %s, expecting 1 but found 0", obj.GetName()) } // Multiple matching VirtualGateway 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 gatewayRoute: %s, expecting 1 but found %d: %s", obj.GetName(), len(vgCandidates), strings.Join(vgCandidatesNames, ",")) } return vgCandidates[0], nil } // Checks if given VirtualGateway has namespace selector which matches with the given namespace labels func matchesNamespaceSelector(objNS corev1.Namespace, vgObj *appmesh.VirtualGateway) (bool, error) { selector, err := metav1.LabelSelectorAsSelector(vgObj.Spec.NamespaceSelector) if err != nil { return false, err } if !selector.Matches(labels.Set(objNS.Labels)) { return false, nil } return true, nil } // Checks if given VirtualGateway has GatewayRouteSelector which matches with the given GatewayRoute func matchesGatewayRouteSelector(obj *appmesh.GatewayRoute, vg *appmesh.VirtualGateway) (bool, error) { gatewayRouteSel := labels.Everything() var err error if vg.Spec.GatewayRouteSelector != nil { gatewayRouteSel, err = metav1.LabelSelectorAsSelector(vg.Spec.GatewayRouteSelector) if err != nil { return false, err } } if !gatewayRouteSel.Matches(labels.Set(obj.Labels)) { return false, nil } return true, nil }