diff --git a/Makefile b/Makefile index 1356d7a4..d16a1782 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ help: ## Display this help. .PHONY: run run: ## Run in development mode - DEV_MODE=1 go run cmd/aws-application-networking-k8s/main.go + DEV_MODE=1 LOG_LEVEL=debug go run cmd/aws-application-networking-k8s/main.go .PHONY: presubmit @@ -102,6 +102,7 @@ e2e-test-namespace := "e2e-test" .PHONY: e2e-test e2e-test: ## Run e2e tests against cluster pointed to by ~/.kube/config @kubectl create namespace $(e2e-test-namespace) > /dev/null 2>&1 || true # ignore already exists error + LOG_LEVEL=debug cd test && go test \ -p 1 \ -count 1 \ diff --git a/cmd/aws-application-networking-k8s/main.go b/cmd/aws-application-networking-k8s/main.go index cac8000d..6ee85ee3 100644 --- a/cmd/aws-application-networking-k8s/main.go +++ b/cmd/aws-application-networking-k8s/main.go @@ -39,8 +39,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/external-dns/endpoint" - gateway_api_v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gateway_api_v1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/aws/aws-application-networking-k8s/pkg/controllers" @@ -60,8 +60,8 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme - utilruntime.Must(gateway_api_v1alpha2.AddToScheme(scheme)) - utilruntime.Must(gateway_api_v1beta1.AddToScheme(scheme)) + utilruntime.Must(gwv1alpha2.AddToScheme(scheme)) + utilruntime.Must(gwv1beta1.AddToScheme(scheme)) utilruntime.Must(anv1alpha1.AddToScheme(scheme)) addOptionalCRDs(scheme) } @@ -115,6 +115,7 @@ func main() { "AccountId", config.AccountID, "DefaultServiceNetwork", config.DefaultServiceNetwork, "ClusterName", config.ClusterName, + "LogLevel", logLevel, ) cloud, err := aws.NewCloud(log.Named("cloud"), aws.CloudConfig{ diff --git a/go.mod b/go.mod index 04d6080e..65bc8017 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.4 go.uber.org/zap v1.26.0 - golang.org/x/exp v0.0.0-20231006140011-7918f672742d + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa k8s.io/api v0.28.3 k8s.io/apimachinery v0.28.3 k8s.io/client-go v0.28.3 @@ -61,7 +61,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/sys v0.14.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect diff --git a/go.sum b/go.sum index 18b1a86a..a97af59f 100644 --- a/go.sum +++ b/go.sum @@ -157,8 +157,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -202,8 +202,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -227,7 +227,7 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/apis/applicationnetworking/v1alpha1/authpolicy_types.go b/pkg/apis/applicationnetworking/v1alpha1/authpolicy_types.go index 4d870e44..9f75e7ed 100644 --- a/pkg/apis/applicationnetworking/v1alpha1/authpolicy_types.go +++ b/pkg/apis/applicationnetworking/v1alpha1/authpolicy_types.go @@ -1,12 +1,8 @@ package v1alpha1 import ( - apimachineryv1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/gateway-api/apis/v1alpha2" - - "github.com/aws/aws-application-networking-k8s/pkg/k8s" - "github.com/aws/aws-application-networking-k8s/pkg/model/core" ) const ( @@ -21,8 +17,8 @@ const ( // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` // +kubebuilder:subresource:status type IAMAuthPolicy struct { - apimachineryv1.TypeMeta `json:",inline"` - apimachineryv1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` Spec IAMAuthPolicySpec `json:"spec"` @@ -35,9 +31,9 @@ type IAMAuthPolicy struct { // +kubebuilder:object:root=true // IAMAuthPolicyList contains a list of IAMAuthPolicies. type IAMAuthPolicyList struct { - apimachineryv1.TypeMeta `json:",inline"` - apimachineryv1.ListMeta `json:"metadata,omitempty"` - Items []IAMAuthPolicy `json:"items"` + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []IAMAuthPolicy `json:"items"` } // IAMAuthPolicySpec defines the desired state of IAMAuthPolicy. @@ -73,29 +69,17 @@ type IAMAuthPolicyStatus struct { // +listMapKey=type // +kubebuilder:validation:MaxItems=8 // +kubebuilder:default={{type: "Accepted", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"},{type: "Programmed", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}} - Conditions []apimachineryv1.Condition `json:"conditions,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` } func (p *IAMAuthPolicy) GetTargetRef() *v1alpha2.PolicyTargetReference { return p.Spec.TargetRef } -func (p *IAMAuthPolicy) GetStatusConditions() []apimachineryv1.Condition { - return p.Status.Conditions -} - -func (p *IAMAuthPolicy) SetStatusConditions(conditions []apimachineryv1.Condition) { - p.Status.Conditions = conditions -} - -func (p *IAMAuthPolicy) GetNamespacedName() types.NamespacedName { - return k8s.NamespacedName(p) +func (p *IAMAuthPolicy) GetStatusConditions() *[]metav1.Condition { + return &p.Status.Conditions } -func (pl *IAMAuthPolicyList) GetItems() []core.Policy { - items := make([]core.Policy, len(pl.Items)) - for i, item := range pl.Items { - items[i] = &item - } - return items +func (pl *IAMAuthPolicyList) GetItems() []*IAMAuthPolicy { + return toPtrSlice(pl.Items) } diff --git a/pkg/apis/applicationnetworking/v1alpha1/common.go b/pkg/apis/applicationnetworking/v1alpha1/common.go new file mode 100644 index 00000000..4c69f21d --- /dev/null +++ b/pkg/apis/applicationnetworking/v1alpha1/common.go @@ -0,0 +1,10 @@ +package v1alpha1 + +func toPtrSlice[T any](s []T) []*T { + ps := make([]*T, len(s)) + for i, t := range s { + ct := t + ps[i] = &ct + } + return ps +} diff --git a/pkg/apis/applicationnetworking/v1alpha1/common_test.go b/pkg/apis/applicationnetworking/v1alpha1/common_test.go new file mode 100644 index 00000000..dc2bf065 --- /dev/null +++ b/pkg/apis/applicationnetworking/v1alpha1/common_test.go @@ -0,0 +1,32 @@ +package v1alpha1 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestToPtrSlice(t *testing.T) { + + type A struct { + x int + } + + type test struct { + name string + in []A + want []*A + } + + tests := []test{ + {"empty", []A{}, []*A{}}, + {"single item", []A{{1}}, []*A{{1}}}, + {"multiple items", []A{{1}, {2}, {3}}, []*A{{1}, {2}, {3}}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, toPtrSlice(tt.in), tt.want) + }) + } +} diff --git a/pkg/apis/applicationnetworking/v1alpha1/targetgrouppolicy_types.go b/pkg/apis/applicationnetworking/v1alpha1/targetgrouppolicy_types.go index f6b8ef2b..40540ef3 100644 --- a/pkg/apis/applicationnetworking/v1alpha1/targetgrouppolicy_types.go +++ b/pkg/apis/applicationnetworking/v1alpha1/targetgrouppolicy_types.go @@ -2,12 +2,7 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/gateway-api/apis/v1alpha2" - - "github.com/aws/aws-application-networking-k8s/pkg/k8s" - "github.com/aws/aws-application-networking-k8s/pkg/model/core" - "github.com/aws/aws-application-networking-k8s/pkg/utils" ) const ( @@ -164,20 +159,10 @@ func (p *TargetGroupPolicy) GetTargetRef() *v1alpha2.PolicyTargetReference { return p.Spec.TargetRef } -func (p *TargetGroupPolicy) GetNamespacedName() types.NamespacedName { - return k8s.NamespacedName(p) -} - -func (p *TargetGroupPolicy) GetStatusConditions() []metav1.Condition { - return p.Status.Conditions -} - -func (p *TargetGroupPolicy) SetStatusConditions(conditions []metav1.Condition) { - p.Status.Conditions = conditions +func (p *TargetGroupPolicy) GetStatusConditions() *[]metav1.Condition { + return &p.Status.Conditions } -func (pl *TargetGroupPolicyList) GetItems() []core.Policy { - return utils.SliceMap(pl.Items, func(p TargetGroupPolicy) core.Policy { - return &p - }) +func (pl *TargetGroupPolicyList) GetItems() []*TargetGroupPolicy { + return toPtrSlice(pl.Items) } diff --git a/pkg/apis/applicationnetworking/v1alpha1/vpcassociationpolicy_types.go b/pkg/apis/applicationnetworking/v1alpha1/vpcassociationpolicy_types.go index ecb346e6..6646dbe3 100644 --- a/pkg/apis/applicationnetworking/v1alpha1/vpcassociationpolicy_types.go +++ b/pkg/apis/applicationnetworking/v1alpha1/vpcassociationpolicy_types.go @@ -2,12 +2,7 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/gateway-api/apis/v1alpha2" - - "github.com/aws/aws-application-networking-k8s/pkg/k8s" - "github.com/aws/aws-application-networking-k8s/pkg/model/core" - "github.com/aws/aws-application-networking-k8s/pkg/utils" ) const ( @@ -92,20 +87,10 @@ func (p *VpcAssociationPolicy) GetTargetRef() *v1alpha2.PolicyTargetReference { return p.Spec.TargetRef } -func (p *VpcAssociationPolicy) GetStatusConditions() []metav1.Condition { - return p.Status.Conditions -} - -func (p *VpcAssociationPolicy) SetStatusConditions(conditions []metav1.Condition) { - p.Status.Conditions = conditions -} - -func (p *VpcAssociationPolicy) GetNamespacedName() types.NamespacedName { - return k8s.NamespacedName(p) +func (p *VpcAssociationPolicy) GetStatusConditions() *[]metav1.Condition { + return &p.Status.Conditions } -func (pl *VpcAssociationPolicyList) GetItems() []core.Policy { - return utils.SliceMap(pl.Items, func(p VpcAssociationPolicy) core.Policy { - return &p - }) +func (pl *VpcAssociationPolicyList) GetItems() []*VpcAssociationPolicy { + return toPtrSlice(pl.Items) } diff --git a/pkg/controllers/eventhandlers/mapper.go b/pkg/controllers/eventhandlers/mapper.go index 19868f22..3b854536 100644 --- a/pkg/controllers/eventhandlers/mapper.go +++ b/pkg/controllers/eventhandlers/mapper.go @@ -12,7 +12,8 @@ import ( gateway_api "sigs.k8s.io/gateway-api/apis/v1beta1" anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1" - "github.com/aws/aws-application-networking-k8s/pkg/k8s" + k8sutils "github.com/aws/aws-application-networking-k8s/pkg/k8s" + "github.com/aws/aws-application-networking-k8s/pkg/k8s/policyhelper" "github.com/aws/aws-application-networking-k8s/pkg/model/core" "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" ) @@ -47,7 +48,7 @@ func (r *resourceMapper) ServiceToServiceExport(ctx context.Context, svc *corev1 return nil } svcExport := &anv1alpha1.ServiceExport{} - if err := r.client.Get(ctx, k8s.NamespacedName(svc), svcExport); err != nil { + if err := r.client.Get(ctx, k8sutils.NamespacedName(svc), svcExport); err != nil { return nil } return svcExport @@ -58,7 +59,7 @@ func (r *resourceMapper) EndpointsToService(ctx context.Context, ep *corev1.Endp return nil } svc := &corev1.Service{} - if err := r.client.Get(ctx, k8s.NamespacedName(ep), svc); err != nil { + if err := r.client.Get(ctx, k8sutils.NamespacedName(ep), svc); err != nil { return nil } return svc @@ -72,12 +73,12 @@ func (r *resourceMapper) VpcAssociationPolicyToGateway(ctx context.Context, vap return policyToTargetRefObj(r, ctx, vap, &gateway_api.Gateway{}) } -func policyToTargetRefObj[T client.Object](r *resourceMapper, ctx context.Context, policy core.Policy, retObj T) T { +func policyToTargetRefObj[T client.Object](r *resourceMapper, ctx context.Context, policy policyhelper.Policy, retObj T) T { null := *new(T) if policy == nil { return null } - policyNamespacedName := policy.GetNamespacedName() + policyNamespacedName := k8sutils.NamespacedName(policy) targetRef := policy.GetTargetRef() if targetRef == nil { @@ -173,7 +174,7 @@ func (r *resourceMapper) backendRefToRoutes(ctx context.Context, obj client.Obje return filteredRoutes } -func (r *resourceMapper) isBackendRefUsedByRoute(route core.Route, obj k8s.NamespacedAndNamed, group, kind string) bool { +func (r *resourceMapper) isBackendRefUsedByRoute(route core.Route, obj client.Object, group, kind string) bool { for _, rule := range route.Spec().Rules() { for _, backendRef := range rule.BackendRefs() { isGroupEqual := backendRef.Group() != nil && string(*backendRef.Group()) == group diff --git a/pkg/controllers/eventhandlers/policy.go b/pkg/controllers/eventhandlers/policy.go deleted file mode 100644 index 95ac2ab0..00000000 --- a/pkg/controllers/eventhandlers/policy.go +++ /dev/null @@ -1,42 +0,0 @@ -package eventhandlers - -import ( - "context" - "github.com/aws/aws-application-networking-k8s/pkg/k8s" - "github.com/aws/aws-application-networking-k8s/pkg/k8s/policyhelper" - "github.com/aws/aws-application-networking-k8s/pkg/model/core" - "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/reconcile" -) - -type policyEventHandler[T core.Policy] struct { - log gwlog.Logger - client client.Client - policy T -} - -func NewPolicyEventHandler[T core.Policy](log gwlog.Logger, client client.Client, policy T) *policyEventHandler[T] { - return &policyEventHandler[T]{log: log, client: client, policy: policy} -} - -func (h *policyEventHandler[T]) MapObjectToPolicy() handler.EventHandler { - return handler.EnqueueRequestsFromMapFunc(h.mapObjectToPolicy) -} - -func (h *policyEventHandler[T]) mapObjectToPolicy(ctx context.Context, eventObj client.Object) []reconcile.Request { - var requests []reconcile.Request - - policies, err := policyhelper.GetAttachedPolicies(ctx, h.client, k8s.NamespacedName(eventObj), *new(T)) - if err != nil { - h.log.Errorf("Failed calling k8s operation: %s", err.Error()) - return requests - } - for _, p := range policies { - requests = append(requests, reconcile.Request{ - NamespacedName: p.GetNamespacedName(), - }) - } - return requests -} diff --git a/pkg/controllers/eventhandlers/policy_test.go b/pkg/controllers/eventhandlers/policy_test.go deleted file mode 100644 index 6e8a3908..00000000 --- a/pkg/controllers/eventhandlers/policy_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package eventhandlers - -import ( - "context" - mock_client "github.com/aws/aws-application-networking-k8s/mocks/controller-runtime/client" - anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1" - "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - "testing" -) - -func TestMapObjectToPolicy(t *testing.T) { - c := gomock.NewController(t) - defer c.Finish() - - mockClient := mock_client.NewMockClient(c) - h := NewPolicyEventHandler(gwlog.FallbackLogger, mockClient, &anv1alpha1.VpcAssociationPolicy{}) - - policy := anv1alpha1.VpcAssociationPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw-1-policy", - Namespace: "default", - }, - Spec: anv1alpha1.VpcAssociationPolicySpec{ - TargetRef: &gwv1alpha2.PolicyTargetReference{ - Group: gwv1alpha2.GroupName, - Name: "gw-1", - Kind: "Gateway", - }, - }, - } - - mockClient.EXPECT().List(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, list *anv1alpha1.VpcAssociationPolicyList, arg3 ...interface{}) error { - list.Items = append(list.Items, policy) - return nil - }) - - reqs := h.mapObjectToPolicy(context.Background(), &gwv1alpha2.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw-1", - Namespace: "default", - }, - }) - assert.Len(t, reqs, 1) - assert.Equal(t, "gw-1-policy", reqs[0].Name) -} diff --git a/pkg/controllers/eventhandlers/service_test.go b/pkg/controllers/eventhandlers/service_test.go index 41e44d5b..a04d12aa 100644 --- a/pkg/controllers/eventhandlers/service_test.go +++ b/pkg/controllers/eventhandlers/service_test.go @@ -2,10 +2,8 @@ package eventhandlers import ( "context" - mock_client "github.com/aws/aws-application-networking-k8s/mocks/controller-runtime/client" - anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1" - "github.com/aws/aws-application-networking-k8s/pkg/model/core" - "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" + "testing" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" @@ -15,7 +13,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "testing" + + mock_client "github.com/aws/aws-application-networking-k8s/mocks/controller-runtime/client" + anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1" + "github.com/aws/aws-application-networking-k8s/pkg/model/core" + "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" ) func TestServiceEventHandler_MapToRoute(t *testing.T) { diff --git a/pkg/controllers/iamauthpolicy_controller.go b/pkg/controllers/iamauthpolicy_controller.go index 5cfffe7f..e34f4984 100644 --- a/pkg/controllers/iamauthpolicy_controller.go +++ b/pkg/controllers/iamauthpolicy_controller.go @@ -2,26 +2,20 @@ package controllers import ( "context" - "errors" - "fmt" anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1" pkg_aws "github.com/aws/aws-application-networking-k8s/pkg/aws" "github.com/aws/aws-application-networking-k8s/pkg/aws/services" deploy "github.com/aws/aws-application-networking-k8s/pkg/deploy/lattice" "github.com/aws/aws-application-networking-k8s/pkg/k8s" + policy "github.com/aws/aws-application-networking-k8s/pkg/k8s/policyhelper" model "github.com/aws/aws-application-networking-k8s/pkg/model/lattice" "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" - "golang.org/x/exp/slices" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -35,30 +29,34 @@ const ( IAMAuthPolicyFinalizer = k8s.AnnotationPrefix + IAMAuthPolicyAnnotation ) +type ( + IAP = anv1alpha1.IAMAuthPolicy +) + type IAMAuthPolicyController struct { - log gwlog.Logger - client client.Client - policyMgr *deploy.IAMAuthPolicyManager - cloud pkg_aws.Cloud + log gwlog.Logger + client client.Client + pm *deploy.IAMAuthPolicyManager + ph *policy.PolicyHandler[*IAP] + cloud pkg_aws.Cloud } func RegisterIAMAuthPolicyController(log gwlog.Logger, mgr ctrl.Manager, cloud pkg_aws.Cloud) error { + ph := policy.NewIAMAuthPolicyHandler(log, mgr.GetClient()) + controller := &IAMAuthPolicyController{ - log: log, - client: mgr.GetClient(), - policyMgr: deploy.NewIAMAuthPolicyManager(cloud), - cloud: cloud, - } - mapfn := iamAuthPolicyMapFunc(mgr.GetClient(), log) - err := ctrl.NewControllerManagedBy(mgr). - For(&anv1alpha1.IAMAuthPolicy{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). - Watches(&gwv1beta1.Gateway{}, - handler.EnqueueRequestsFromMapFunc(mapfn)). - Watches(&gwv1beta1.HTTPRoute{}, - handler.EnqueueRequestsFromMapFunc(mapfn)). - Watches(&gwv1alpha2.GRPCRoute{}, - handler.EnqueueRequestsFromMapFunc(mapfn)). - Complete(controller) + log: log, + client: mgr.GetClient(), + pm: deploy.NewIAMAuthPolicyManager(cloud), + ph: ph, + cloud: cloud, + } + + b := ctrl. + NewControllerManagedBy(mgr). + For(&anv1alpha1.IAMAuthPolicy{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})) + ph.AddWatchers(b, &gwv1beta1.Gateway{}, &gwv1beta1.HTTPRoute{}, &gwv1alpha2.GRPCRoute{}) + err := b.Complete(controller) return err } @@ -85,8 +83,10 @@ func (c *IAMAuthPolicyController) Reconcile(ctx context.Context, req ctrl.Reques if err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } + c.log.Infow("reconcile IAM policy", "req", req, "targetRef", k8sPolicy.Spec.TargetRef) isDelete := !k8sPolicy.DeletionTimestamp.IsZero() + var res ctrl.Result if isDelete { res, err = c.reconcileDelete(ctx, k8sPolicy) @@ -96,10 +96,12 @@ func (c *IAMAuthPolicyController) Reconcile(ctx context.Context, req ctrl.Reques if err != nil { return ctrl.Result{}, err } + err = c.client.Update(ctx, k8sPolicy) if err != nil { return reconcile.Result{}, err } + c.log.Infow("reconciled IAM policy", "req", req, "targetRef", k8sPolicy.Spec.TargetRef, @@ -109,10 +111,10 @@ func (c *IAMAuthPolicyController) Reconcile(ctx context.Context, req ctrl.Reques } func (c *IAMAuthPolicyController) reconcileDelete(ctx context.Context, k8sPolicy *anv1alpha1.IAMAuthPolicy) (ctrl.Result, error) { - err := c.validateSpec(ctx, k8sPolicy) + err := c.ph.ValidateTargetRef(ctx, k8sPolicy) if err == nil { modelPolicy := model.NewIAMAuthPolicy(k8sPolicy) - _, err := c.policyMgr.Delete(ctx, modelPolicy) + _, err := c.pm.Delete(ctx, modelPolicy) if err != nil { return ctrl.Result{}, services.IgnoreNotFound(err) } @@ -126,102 +128,29 @@ func (c *IAMAuthPolicyController) reconcileDelete(ctx context.Context, k8sPolicy } func (c *IAMAuthPolicyController) reconcileUpsert(ctx context.Context, k8sPolicy *anv1alpha1.IAMAuthPolicy) (ctrl.Result, error) { - validationErr := c.validateSpec(ctx, k8sPolicy) - err := c.updateStatus(ctx, k8sPolicy, validationErr) + reason, err := c.ph.ValidateAndUpdateCondition(ctx, k8sPolicy) if err != nil { return ctrl.Result{}, err } - var statusPolicy model.IAMAuthPolicyStatus - if validationErr == nil { - modelPolicy := model.NewIAMAuthPolicy(k8sPolicy) - c.addFinalizer(k8sPolicy) - err = c.client.Update(ctx, k8sPolicy) - if err != nil { - return reconcile.Result{}, err - } - statusPolicy, err = c.policyMgr.Put(ctx, modelPolicy) - if err != nil { - return reconcile.Result{}, services.IgnoreNotFound(err) - } - c.updateLatticeAnnotaion(k8sPolicy, statusPolicy.ResourceId, modelPolicy.Type) + if reason != policy.ReasonAccepted { + return ctrl.Result{}, nil } - err = c.handleLatticeResourceChange(ctx, k8sPolicy, statusPolicy) + modelPolicy := model.NewIAMAuthPolicy(k8sPolicy) + c.addFinalizer(k8sPolicy) + err = c.client.Update(ctx, k8sPolicy) if err != nil { return reconcile.Result{}, err } - return ctrl.Result{}, nil -} - -func (c *IAMAuthPolicyController) validateSpec(ctx context.Context, k8sPolicy *anv1alpha1.IAMAuthPolicy) error { - tr := k8sPolicy.Spec.TargetRef - if tr.Group != gwv1beta1.GroupName { - return fmt.Errorf("%w: %s", GroupNameError, tr.Group) - } - if !slices.Contains([]string{"Gateway", "HTTPRoute", "GRPCRoute"}, string(tr.Kind)) { - return fmt.Errorf("%w: %s", KindError, tr.Kind) - } - refExists, err := c.targetRefExists(ctx, k8sPolicy) + statusPolicy, err := c.pm.Put(ctx, modelPolicy) if err != nil { - return err - } - if !refExists { - return fmt.Errorf("%w: %s", TargetRefNotFound, tr.Name) + return reconcile.Result{}, services.IgnoreNotFound(err) } - conflictingPolicies, err := c.findConflictingPolicies(ctx, k8sPolicy) + c.updateLatticeAnnotaion(k8sPolicy, statusPolicy.ResourceId, modelPolicy.Type) + err = c.handleLatticeResourceChange(ctx, k8sPolicy, statusPolicy) if err != nil { - return err - } - if len(conflictingPolicies) > 0 { - return fmt.Errorf("%w, policies: %v", TargetRefConflict, conflictingPolicies) - } - return nil -} - -func (c *IAMAuthPolicyController) updateStatus(ctx context.Context, k8sPolicy *anv1alpha1.IAMAuthPolicy, validationErr error) error { - reason := validationErrToStatusReason(validationErr) - msg := "" - if validationErr != nil { - msg = validationErr.Error() - } - c.updatePolicyCondition(k8sPolicy, reason, msg) - err := c.client.Status().Update(ctx, k8sPolicy) - return err -} - -func validationErrToStatusReason(validationErr error) gwv1alpha2.PolicyConditionReason { - var reason gwv1alpha2.PolicyConditionReason - switch { - case validationErr == nil: - reason = gwv1alpha2.PolicyReasonAccepted - case errors.Is(validationErr, GroupNameError) || errors.Is(validationErr, KindError): - reason = gwv1alpha2.PolicyReasonInvalid - case errors.Is(validationErr, TargetRefNotFound): - reason = gwv1alpha2.PolicyReasonTargetNotFound - case errors.Is(validationErr, TargetRefConflict): - reason = gwv1alpha2.PolicyReasonConflicted - default: - panic("unexpected validation error: " + validationErr.Error()) - } - return reason -} - -func (c *IAMAuthPolicyController) targetRefExists(ctx context.Context, k8sPolicy *anv1alpha1.IAMAuthPolicy) (bool, error) { - tr := k8sPolicy.Spec.TargetRef - var obj client.Object - switch tr.Kind { - case "Gateway": - obj = &gwv1beta1.Gateway{} - case "HTTPRoute": - obj = &gwv1beta1.HTTPRoute{} - case "GRPCRoute": - obj = &gwv1alpha2.GRPCRoute{} - default: - panic("unexpected targetRef Kind=" + tr.Kind) + return reconcile.Result{}, err } - return k8s.ObjExists(ctx, c.client, types.NamespacedName{ - Namespace: k8sPolicy.Namespace, - Name: string(tr.Name), - }, obj) + return ctrl.Result{}, nil } func (c *IAMAuthPolicyController) removeFinalizer(k8sPolicy *anv1alpha1.IAMAuthPolicy) { @@ -236,26 +165,6 @@ func (c *IAMAuthPolicyController) addFinalizer(k8sPolicy *anv1alpha1.IAMAuthPoli } } -func (c *IAMAuthPolicyController) findConflictingPolicies(ctx context.Context, k8sPolicy *anv1alpha1.IAMAuthPolicy) ([]string, error) { - var out []string - policies := &anv1alpha1.IAMAuthPolicyList{} - err := c.client.List(ctx, policies, &client.ListOptions{ - Namespace: k8sPolicy.Namespace, - }) - if err != nil { - return out, err - } - for _, p := range policies.Items { - if k8sPolicy.Name == p.Name { - continue - } - if *k8sPolicy.Spec.TargetRef == *p.Spec.TargetRef { - out = append(out, p.Name) - } - } - return out, nil -} - // cleanup lattice resources after targetRef changes func (c *IAMAuthPolicyController) handleLatticeResourceChange(ctx context.Context, k8sPolicy *anv1alpha1.IAMAuthPolicy, statusPolicy model.IAMAuthPolicyStatus) error { prevModel, ok := c.getLatticeAnnotation(k8sPolicy) @@ -263,7 +172,7 @@ func (c *IAMAuthPolicyController) handleLatticeResourceChange(ctx context.Contex return nil } if prevModel.ResourceId != statusPolicy.ResourceId { - _, err := c.policyMgr.Delete(ctx, prevModel) + _, err := c.pm.Delete(ctx, prevModel) if err != nil { return services.IgnoreNotFound(err) } @@ -271,38 +180,6 @@ func (c *IAMAuthPolicyController) handleLatticeResourceChange(ctx context.Contex return nil } -func (c *IAMAuthPolicyController) updatePolicyCondition(k8sPolicy *anv1alpha1.IAMAuthPolicy, reason gwv1alpha2.PolicyConditionReason, msg string) { - status := metav1.ConditionTrue - if reason != gwv1alpha2.PolicyReasonAccepted { - status = metav1.ConditionFalse - } - cnd := metav1.Condition{ - Type: string(gwv1alpha2.PolicyConditionAccepted), - Status: status, - Reason: string(reason), - Message: msg, - } - meta.SetStatusCondition(&k8sPolicy.Status.Conditions, cnd) -} - -func iamAuthPolicyMapFunc(c client.Client, log gwlog.Logger) handler.MapFunc { - return func(ctx context.Context, obj client.Object) []ctrl.Request { - requests := []ctrl.Request{} - policies := &anv1alpha1.IAMAuthPolicyList{} - err := c.List(ctx, policies, &client.ListOptions{Namespace: obj.GetNamespace()}) - if err != nil { - log.Error(err) - return requests - } - for _, policy := range policies.Items { - if obj.GetName() == string(policy.Spec.TargetRef.Name) { - requests = append(requests, ctrl.Request{NamespacedName: policy.GetNamespacedName()}) - } - } - return requests - } -} - func (c *IAMAuthPolicyController) updateLatticeAnnotaion(k8sPolicy *anv1alpha1.IAMAuthPolicy, resId, resType string) { if k8sPolicy.Annotations == nil { k8sPolicy.Annotations = make(map[string]string) diff --git a/pkg/controllers/iamauthpolicy_controller_test.go b/pkg/controllers/iamauthpolicy_controller_test.go index 7572731d..2d329367 100644 --- a/pkg/controllers/iamauthpolicy_controller_test.go +++ b/pkg/controllers/iamauthpolicy_controller_test.go @@ -1,130 +1 @@ package controllers - -import ( - "context" - "fmt" - "testing" - - mock_client "github.com/aws/aws-application-networking-k8s/mocks/controller-runtime/client" - anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1" - - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/assert" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func TestIAMAuthControllerValidate(t *testing.T) { - c := gomock.NewController(t) - defer c.Finish() - - ctx := context.Background() - mockClient := mock_client.NewMockClient(c) - iamCtrl := &IAMAuthPolicyController{ - client: mockClient, - } - k8sPolicy := &anv1alpha1.IAMAuthPolicy{} - k8sPolicy.Name = "iam-policy" - - t.Run("wrong group name", func(t *testing.T) { - k8sPolicy.Spec.TargetRef = &gwv1alpha2.PolicyTargetReference{ - Group: "Wrong", - } - - err := iamCtrl.validateSpec(ctx, k8sPolicy) - assert.ErrorIs(t, err, GroupNameError) - }) - - t.Run("wrong kind", func(t *testing.T) { - k8sPolicy.Spec.TargetRef = &gwv1alpha2.PolicyTargetReference{ - Group: gwv1alpha2.GroupName, - Kind: "Wrong", - } - - err := iamCtrl.validateSpec(ctx, k8sPolicy) - assert.ErrorIs(t, err, KindError) - }) - - t.Run("targetRef not found", func(t *testing.T) { - k8sPolicy.Spec.TargetRef = &gwv1alpha2.PolicyTargetReference{ - Group: gwv1beta1.GroupName, - Kind: "Gateway", - Name: "GwName", - } - - // mock for not found - notFoundErr := apierrors.NewNotFound(schema.GroupResource{}, "") - mockClient.EXPECT().Get(ctx, types.NamespacedName{ - Name: "GwName", - }, gomock.Any()).Return(notFoundErr) - - err := iamCtrl.validateSpec(ctx, k8sPolicy) - assert.ErrorIs(t, err, TargetRefNotFound) - }) - - t.Run("targetRef conflict", func(t *testing.T) { - k8sPolicy.Spec.TargetRef = &gwv1alpha2.PolicyTargetReference{ - Group: gwv1beta1.GroupName, - Kind: "Gateway", - Name: "GwName", - } - - // mock for not found - mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.Any()).Return(nil) - // mock for conflicts - conflictPolicy := anv1alpha1.IAMAuthPolicy{} - conflictPolicy.Name = "another-policy" - conflictPolicy.Spec.TargetRef = k8sPolicy.Spec.TargetRef - mockClient.EXPECT().List(ctx, gomock.Any(), gomock.Any()). - DoAndReturn(func(_ context.Context, l *anv1alpha1.IAMAuthPolicyList, _ ...client.ListOption) error { - l.Items = append(l.Items, conflictPolicy) - return nil - }) - - err := iamCtrl.validateSpec(ctx, k8sPolicy) - assert.ErrorIs(t, err, TargetRefConflict) - }) - - t.Run("valid policy", func(t *testing.T) { - k8sPolicy.Spec.TargetRef = &gwv1alpha2.PolicyTargetReference{ - Group: gwv1beta1.GroupName, - Kind: "Gateway", - Name: "GwName", - } - - // mock for not found - mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.Any()).Return(nil) - // mock for conflicts - mockClient.EXPECT().List(ctx, gomock.Any(), gomock.Any()).Return(nil) - - err := iamCtrl.validateSpec(ctx, k8sPolicy) - assert.Nil(t, err) - }) -} - -func TestIAMAuthPolicyValidationErrToStatus(t *testing.T) { - - type test struct { - validationErr error - wantReason gwv1alpha2.PolicyConditionReason - } - - tests := []test{ - {fmt.Errorf("%w", GroupNameError), gwv1alpha2.PolicyReasonInvalid}, - {fmt.Errorf("%w", KindError), gwv1alpha2.PolicyReasonInvalid}, - {fmt.Errorf("%w", TargetRefNotFound), gwv1alpha2.PolicyReasonTargetNotFound}, - {fmt.Errorf("%w", TargetRefConflict), gwv1alpha2.PolicyReasonConflicted}, - {nil, gwv1alpha2.PolicyReasonAccepted}, - } - - for _, tt := range tests { - t.Run(fmt.Sprintf("%T", tt.validationErr), func(t *testing.T) { - reason := validationErrToStatusReason(tt.validationErr) - assert.Equal(t, tt.wantReason, reason) - }) - } -} diff --git a/pkg/controllers/targetgrouppolicy_controller.go b/pkg/controllers/targetgrouppolicy_controller.go index 053e5353..9a686864 100644 --- a/pkg/controllers/targetgrouppolicy_controller.go +++ b/pkg/controllers/targetgrouppolicy_controller.go @@ -2,58 +2,51 @@ package controllers import ( "context" - "fmt" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1" - "github.com/aws/aws-application-networking-k8s/pkg/k8s" - "github.com/aws/aws-application-networking-k8s/pkg/k8s/policyhelper" + policy "github.com/aws/aws-application-networking-k8s/pkg/k8s/policyhelper" "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" ) +type ( + TGP = anv1alpha1.TargetGroupPolicy +) + type TargetGroupPolicyController struct { log gwlog.Logger client client.Client + ph *policy.PolicyHandler[*TGP] } func RegisterTargetGroupPolicyController(log gwlog.Logger, mgr ctrl.Manager) error { + ph := policy.NewTargetGroupPolicyHandler(log, mgr.GetClient()) controller := &TargetGroupPolicyController{ log: log, client: mgr.GetClient(), + ph: ph, } - mapfn := targetGroupPolicyMapFunc(mgr.GetClient(), log) - return ctrl.NewControllerManagedBy(mgr). - For(&anv1alpha1.TargetGroupPolicy{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). - Watches(&corev1.Service{}, handler.EnqueueRequestsFromMapFunc(mapfn)). - Complete(controller) + + b := ctrl.NewControllerManagedBy(mgr). + For(&TGP{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})) + ph.AddWatchers(b, &corev1.Service{}) + return b.Complete(controller) } func (c *TargetGroupPolicyController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - tgPolicy := &anv1alpha1.TargetGroupPolicy{} + tgPolicy := &TGP{} err := c.client.Get(ctx, req.NamespacedName, tgPolicy) if err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } c.log.Infow("reconcile target group policy", "req", req, "targetRef", tgPolicy.Spec.TargetRef) - validationErr := c.validateSpec(ctx, tgPolicy) - reason := validationErrToStatusReason(validationErr) - msg := "" - if validationErr != nil { - msg = validationErr.Error() - } - c.updatePolicyCondition(tgPolicy, reason, msg) - err = c.client.Status().Update(ctx, tgPolicy) + _, err = c.ph.ValidateAndUpdateCondition(ctx, tgPolicy) if err != nil { return ctrl.Result{}, err } @@ -64,79 +57,3 @@ func (c *TargetGroupPolicyController) Reconcile(ctx context.Context, req ctrl.Re ) return ctrl.Result{}, nil } - -func (c *TargetGroupPolicyController) validateSpec(ctx context.Context, tgPolicy *anv1alpha1.TargetGroupPolicy) error { - tr := tgPolicy.Spec.TargetRef - if tr.Group != corev1.GroupName { - return fmt.Errorf("%w: %s", GroupNameError, tr.Group) - } - if string(tr.Kind) != "Service" { - return fmt.Errorf("%w: %s", KindError, tr.Kind) - } - tgref := types.NamespacedName{ - Namespace: tgPolicy.Namespace, - Name: string(tgPolicy.Spec.TargetRef.Name), - } - valid, err := policyhelper.GetValidPolicy(ctx, c.client, tgref, tgPolicy) - if err != nil { - return nil - } - if valid != nil && valid.GetNamespacedName() != tgPolicy.GetNamespacedName() { - return fmt.Errorf("%w, with policy %s", TargetRefConflict, valid.GetName()) - } - refExists, err := c.targetRefExists(ctx, tgPolicy) - if err != nil { - return err - } - if !refExists { - return fmt.Errorf("%w: %s", TargetRefNotFound, tr.Name) - } - return nil -} - -func (c *TargetGroupPolicyController) targetRefExists(ctx context.Context, tgPolicy *anv1alpha1.TargetGroupPolicy) (bool, error) { - tr := tgPolicy.Spec.TargetRef - var obj client.Object - switch tr.Kind { - case "Service": - obj = &corev1.Service{} - default: - panic("unexpected targetRef Kind=" + tr.Kind) - } - return k8s.ObjExists(ctx, c.client, types.NamespacedName{ - Namespace: tgPolicy.Namespace, - Name: string(tr.Name), - }, obj) -} - -func (c *TargetGroupPolicyController) updatePolicyCondition(tgPolicy *anv1alpha1.TargetGroupPolicy, reason gwv1alpha2.PolicyConditionReason, msg string) { - status := metav1.ConditionTrue - if reason != gwv1alpha2.PolicyReasonAccepted { - status = metav1.ConditionFalse - } - cnd := metav1.Condition{ - Type: string(gwv1alpha2.PolicyConditionAccepted), - Status: status, - Reason: string(reason), - Message: msg, - } - meta.SetStatusCondition(&tgPolicy.Status.Conditions, cnd) -} - -func targetGroupPolicyMapFunc(c client.Client, log gwlog.Logger) handler.MapFunc { - return func(ctx context.Context, obj client.Object) []ctrl.Request { - requests := []ctrl.Request{} - policies := &anv1alpha1.TargetGroupPolicyList{} - err := c.List(ctx, policies, &client.ListOptions{Namespace: obj.GetNamespace()}) - if err != nil { - log.Error(err) - return requests - } - for _, policy := range policies.Items { - if obj.GetName() == string(policy.Spec.TargetRef.Name) { - requests = append(requests, ctrl.Request{NamespacedName: policy.GetNamespacedName()}) - } - } - return requests - } -} diff --git a/pkg/controllers/vpcassociationpolicy_controller.go b/pkg/controllers/vpcassociationpolicy_controller.go index 7bf47607..9a61f9c3 100644 --- a/pkg/controllers/vpcassociationpolicy_controller.go +++ b/pkg/controllers/vpcassociationpolicy_controller.go @@ -4,23 +4,24 @@ import ( "context" "time" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/predicate" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1" pkg_aws "github.com/aws/aws-application-networking-k8s/pkg/aws" "github.com/aws/aws-application-networking-k8s/pkg/aws/services" deploy "github.com/aws/aws-application-networking-k8s/pkg/deploy/lattice" - "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" - - "github.com/aws/aws-application-networking-k8s/pkg/controllers/eventhandlers" "github.com/aws/aws-application-networking-k8s/pkg/k8s" + policy "github.com/aws/aws-application-networking-k8s/pkg/k8s/policyhelper" "github.com/aws/aws-application-networking-k8s/pkg/utils" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/predicate" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" +) + +type ( + VAP = anv1alpha1.VpcAssociationPolicy ) const ( @@ -33,23 +34,24 @@ type vpcAssociationPolicyReconciler struct { cloud pkg_aws.Cloud finalizerManager k8s.FinalizerManager manager deploy.ServiceNetworkManager + ph *policy.PolicyHandler[*VAP] } func RegisterVpcAssociationPolicyController(log gwlog.Logger, cloud pkg_aws.Cloud, finalizerManager k8s.FinalizerManager, mgr ctrl.Manager) error { + ph := policy.NewVpcAssociationPolicyHandler(log, mgr.GetClient()) controller := &vpcAssociationPolicyReconciler{ log: log, client: mgr.GetClient(), cloud: cloud, finalizerManager: finalizerManager, manager: deploy.NewDefaultServiceNetworkManager(log, cloud), + ph: ph, } - eh := eventhandlers.NewPolicyEventHandler(log, mgr.GetClient(), &anv1alpha1.VpcAssociationPolicy{}) - err := ctrl.NewControllerManagedBy(mgr). - For(&anv1alpha1.VpcAssociationPolicy{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). - Watches(&gwv1beta1.Gateway{}, eh.MapObjectToPolicy()). - Complete(controller) - return err + b := ctrl.NewControllerManagedBy(mgr). + For(&anv1alpha1.VpcAssociationPolicy{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})) + ph.AddWatchers(b, &gwv1beta1.Gateway{}) + return b.Complete(controller) } func (c *vpcAssociationPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -63,21 +65,11 @@ func (c *vpcAssociationPolicyReconciler) Reconcile(ctx context.Context, req ctrl isDelete := !k8sPolicy.DeletionTimestamp.IsZero() isAssociation := k8sPolicy.Spec.AssociateWithVpc == nil || *k8sPolicy.Spec.AssociateWithVpc - kind := k8sPolicy.Spec.TargetRef.Kind - switch kind { - case "Gateway": - if isDelete || !isAssociation { - err = c.delete(ctx, k8sPolicy) - } else { - err = c.upsert(ctx, k8sPolicy) - } - default: - err = c.updatePolicyCondition(ctx, k8sPolicy, gwv1alpha2.PolicyReasonInvalid) - if err != nil { - return ctrl.Result{}, err - } + if isDelete || !isAssociation { + err = c.delete(ctx, k8sPolicy) + } else { + err = c.upsert(ctx, k8sPolicy) } - if err != nil { c.log.Infof("reconcile error, retry in 30 sec: %s", err) return ctrl.Result{RequeueAfter: time.Second * 30}, nil @@ -92,7 +84,15 @@ func (c *vpcAssociationPolicyReconciler) Reconcile(ctx context.Context, req ctrl } func (c *vpcAssociationPolicyReconciler) upsert(ctx context.Context, k8sPolicy *anv1alpha1.VpcAssociationPolicy) error { - err := c.finalizerManager.AddFinalizers(ctx, k8sPolicy, finalizer) + reason, err := c.ph.ValidateAndUpdateCondition(ctx, k8sPolicy) + if err != nil { + return err + } + if reason != policy.ReasonAccepted { + return nil + } + + err = c.finalizerManager.AddFinalizers(ctx, k8sPolicy, finalizer) if err != nil { return err } @@ -102,16 +102,11 @@ func (c *vpcAssociationPolicyReconciler) upsert(ctx context.Context, k8sPolicy * return &str }) snva, err := c.manager.UpsertVpcAssociation(ctx, snName, sgIds) - if err != nil { - return c.handleUpsertError(ctx, k8sPolicy, err) - } - err = c.updateLatticeAnnotation(ctx, k8sPolicy, snva) if err != nil { return err } - err = c.updatePolicyCondition(ctx, k8sPolicy, gwv1alpha2.PolicyReasonAccepted) + err = c.updateLatticeAnnotation(ctx, k8sPolicy, snva) if err != nil { - c.log.Debugf("seems like an error: %s, %s", k8sPolicy.GetNamespacedName(), k8sPolicy.GroupVersionKind().String()) return err } return nil @@ -130,31 +125,6 @@ func (c *vpcAssociationPolicyReconciler) delete(ctx context.Context, k8sPolicy * return nil } -func (c *vpcAssociationPolicyReconciler) updatePolicyCondition(ctx context.Context, k8sPolicy *anv1alpha1.VpcAssociationPolicy, reason gwv1alpha2.PolicyConditionReason) error { - status := metav1.ConditionTrue - if reason != gwv1alpha2.PolicyReasonAccepted { - status = metav1.ConditionFalse - } - cnd := metav1.Condition{ - Type: string(gwv1alpha2.PolicyConditionAccepted), - Status: status, - Reason: string(reason), - } - meta.SetStatusCondition(&k8sPolicy.Status.Conditions, cnd) - err := c.client.Status().Update(ctx, k8sPolicy) - return err -} - -func (c *vpcAssociationPolicyReconciler) handleUpsertError(ctx context.Context, k8sPolicy *anv1alpha1.VpcAssociationPolicy, err error) error { - switch { - case services.IsNotFoundError(err): - err = c.updatePolicyCondition(ctx, k8sPolicy, gwv1alpha2.PolicyReasonTargetNotFound) - case services.IsConflictError(err): - err = c.updatePolicyCondition(ctx, k8sPolicy, gwv1alpha2.PolicyReasonConflicted) - } - return err -} - func (c *vpcAssociationPolicyReconciler) handleDeleteError(err error) error { switch { case services.IsNotFoundError(err): diff --git a/pkg/deploy/lattice/service_network_manager.go b/pkg/deploy/lattice/service_network_manager.go index 914a3f4c..ff74093a 100644 --- a/pkg/deploy/lattice/service_network_manager.go +++ b/pkg/deploy/lattice/service_network_manager.go @@ -139,7 +139,8 @@ func (m *defaultServiceNetworkManager) getActiveVpcAssociation(ctx context.Conte if aws.StringValue(snva.Status) == vpclattice.ServiceNetworkVpcAssociationStatusActive { return snva, nil } - m.log.Debugf("snva %s status: %s", snva.Arn, snva.Status) + m.log.Debugf("snva %s status: %s", + aws.StringValue(snva.Arn), aws.StringValue(snva.Status)) switch aws.StringValue(snva.Status) { case vpclattice.ServiceNetworkVpcAssociationStatusActive, vpclattice.ServiceNetworkVpcAssociationStatusDeleteFailed, diff --git a/pkg/gateway/model_build_targetgroup.go b/pkg/gateway/model_build_targetgroup.go index 443e6c00..6e01ebf5 100644 --- a/pkg/gateway/model_build_targetgroup.go +++ b/pkg/gateway/model_build_targetgroup.go @@ -14,12 +14,16 @@ import ( anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1" "github.com/aws/aws-application-networking-k8s/pkg/config" "github.com/aws/aws-application-networking-k8s/pkg/k8s" - "github.com/aws/aws-application-networking-k8s/pkg/k8s/policyhelper" + policy "github.com/aws/aws-application-networking-k8s/pkg/k8s/policyhelper" "github.com/aws/aws-application-networking-k8s/pkg/model/core" model "github.com/aws/aws-application-networking-k8s/pkg/model/lattice" "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" ) +type ( + TGP = anv1alpha1.TargetGroupPolicy +) + type InvalidBackendRefError struct { BackendRef core.BackendRef Reason string @@ -57,6 +61,7 @@ func NewSvcExportTargetGroupBuilder( type svcExportTargetGroupModelBuildTask struct { log gwlog.Logger client client.Client + tgp *policy.PolicyHandler[*TGP] serviceExport *anv1alpha1.ServiceExport stack core.Stack } @@ -72,6 +77,7 @@ func (b *SvcExportTargetGroupBuilder) Build( serviceExport: svcExport, stack: stack, client: b.client, + tgp: policy.NewTargetGroupPolicyHandler(b.log, b.client), } if err := task.run(ctx); err != nil { @@ -89,6 +95,7 @@ func (b *SvcExportTargetGroupBuilder) BuildTargetGroup(ctx context.Context, svcE serviceExport: svcExport, stack: stack, client: b.client, + tgp: policy.NewTargetGroupPolicyHandler(b.log, b.client), } return task.buildTargetGroup(ctx) @@ -130,7 +137,8 @@ func (t *svcExportTargetGroupModelBuildTask) buildTargetGroup(ctx context.Contex // if we're deleting, it's OK if the service isn't there noSvcFoundAndDeleting = true } else { // either it's some other error or we aren't deleting - return nil, fmt.Errorf("Failed to find corresponding k8sService %s, error :%w ", k8s.NamespacedName(t.serviceExport), err) + return nil, fmt.Errorf("failed to find corresponding k8sService %s, error :%w ", + k8s.NamespacedName(t.serviceExport), err) } } @@ -145,8 +153,7 @@ func (t *svcExportTargetGroupModelBuildTask) buildTargetGroup(ctx context.Contex } } - tgp, err := policyhelper.GetValidPolicy(ctx, t.client, - k8s.NamespacedName(t.serviceExport), &anv1alpha1.TargetGroupPolicy{}) + tgp, err := t.tgp.ObjResolvedPolicy(ctx, t.serviceExport) if err != nil { return nil, err } @@ -210,6 +217,7 @@ type backendRefTargetGroupModelBuildTask struct { stack core.Stack route core.Route backendRef core.BackendRef + tgp *policy.PolicyHandler[*TGP] } func (b *BackendRefTargetGroupBuilder) Build( @@ -229,6 +237,7 @@ func (b *BackendRefTargetGroupBuilder) Build( stack: stack, route: route, backendRef: backendRef, + tgp: policy.NewTargetGroupPolicyHandler(b.log, b.client), } stackTg, err := task.buildTargetGroup(ctx) @@ -313,7 +322,7 @@ func (t *backendRefTargetGroupModelBuildTask) buildTargetGroupSpec(ctx context.C } } - tgp, err := policyhelper.GetValidPolicy(ctx, t.client, backendRefNsName, &anv1alpha1.TargetGroupPolicy{}) + tgp, err := t.tgp.ObjResolvedPolicy(ctx, svc) if err != nil { return model.TargetGroupSpec{}, err } diff --git a/pkg/k8s/policyhelper/kind.go b/pkg/k8s/policyhelper/kind.go new file mode 100644 index 00000000..03343bfa --- /dev/null +++ b/pkg/k8s/policyhelper/kind.go @@ -0,0 +1,50 @@ +package policyhelper + +import ( + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +type GroupKind struct { + Group string + Kind string +} + +func ObjToGroupKind(obj client.Object) GroupKind { + switch obj.(type) { + case *gwv1beta1.Gateway: + return GroupKind{gwv1beta1.GroupName, "Gateway"} + case *gwv1beta1.HTTPRoute: + return GroupKind{gwv1beta1.GroupName, "HTTPRoute"} + case *gwv1alpha2.GRPCRoute: + return GroupKind{gwv1alpha2.GroupName, "GRPCRoute"} + case *corev1.Service: + return GroupKind{corev1.GroupName, "Service"} + default: + return GroupKind{} + } +} + +func TargetRefGroupKind(tr *TargetRef) GroupKind { + return GroupKind{ + Group: string(tr.Group), + Kind: string(tr.Kind), + } +} + +func GroupKindToObj(gk GroupKind) (client.Object, bool) { + switch gk { + case GroupKind{gwv1beta1.GroupName, "Gateway"}: + return &gwv1beta1.Gateway{}, true + case GroupKind{gwv1beta1.GroupName, "HTTPRoute"}: + return &gwv1beta1.HTTPRoute{}, true + case GroupKind{gwv1alpha2.GroupName, "GRPCRoute"}: + return &gwv1alpha2.GRPCRoute{}, true + case GroupKind{corev1.GroupName, "Service"}: + return &corev1.Service{}, true + default: + return nil, false + } +} diff --git a/pkg/k8s/policyhelper/kind_test.go b/pkg/k8s/policyhelper/kind_test.go new file mode 100644 index 00000000..25355953 --- /dev/null +++ b/pkg/k8s/policyhelper/kind_test.go @@ -0,0 +1,38 @@ +package policyhelper + +import ( + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func TestGroupKind(t *testing.T) { + type Test struct { + obj client.Object + kind GroupKind + } + + tests := []Test{ + {&gwv1beta1.Gateway{}, GroupKind{Group: gwv1beta1.GroupName, Kind: "Gateway"}}, + {&gwv1beta1.HTTPRoute{}, GroupKind{Group: gwv1beta1.GroupName, Kind: "HTTPRoute"}}, + {&gwv1alpha2.GRPCRoute{}, GroupKind{Group: gwv1alpha2.GroupName, Kind: "GRPCRoute"}}, + {&corev1.Service{}, GroupKind{Group: corev1.GroupName, Kind: "Service"}}, + } + + t.Run("obj to kind", func(t *testing.T) { + for _, tt := range tests { + assert.Equal(t, ObjToGroupKind(tt.obj), tt.kind) + } + }) + + t.Run("kind to obj", func(t *testing.T) { + for _, tt := range tests { + kind, _ := GroupKindToObj(tt.kind) + assert.Equal(t, kind, tt.obj) + } + }) +} diff --git a/pkg/k8s/policyhelper/policy.go b/pkg/k8s/policyhelper/policy.go index d52bd12f..125acbd2 100644 --- a/pkg/k8s/policyhelper/policy.go +++ b/pkg/k8s/policyhelper/policy.go @@ -2,101 +2,378 @@ package policyhelper import ( "context" + "errors" "fmt" "strings" "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/builder" + k8sclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1" - "github.com/aws/aws-application-networking-k8s/pkg/model/core" + "github.com/aws/aws-application-networking-k8s/pkg/utils" + "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" ) -type policyInfo struct { - policyList core.PolicyList - group gwv1beta1.Group - kind gwv1beta1.Kind +var ( + ErrGroupKind = errors.New("group/kind error") + ErrTargetRefNotFound = errors.New("targetRef not found") + ErrTargetRefConflict = errors.New("targetRef has conflict") +) + +type ( + TargetRef = gwv1alpha2.PolicyTargetReference + ConditionType = gwv1alpha2.PolicyConditionType + ConditionReason = gwv1alpha2.PolicyConditionReason +) + +const ( + // GEP + + ConditionTypeAccepted = gwv1alpha2.PolicyConditionAccepted + + ReasonAccepted = gwv1alpha2.PolicyReasonAccepted + ReasonInvalid = gwv1alpha2.PolicyReasonInvalid + ReasonTargetNotFound = gwv1alpha2.PolicyReasonTargetNotFound + ReasonConflicted = gwv1alpha2.PolicyReasonConflicted + + // Non-GEP + + ReasonUnknown = ConditionReason("Unknown") +) + +type ( + TGP = anv1alpha1.TargetGroupPolicy + TGPL = anv1alpha1.TargetGroupPolicyList + IAP = anv1alpha1.IAMAuthPolicy + IAPL = anv1alpha1.IAMAuthPolicyList + VAP = anv1alpha1.VpcAssociationPolicy + VAPL = anv1alpha1.VpcAssociationPolicyList +) + +func NewVpcAssociationPolicyHandler(log gwlog.Logger, c k8sclient.Client) *PolicyHandler[*VAP] { + phcfg := PolicyHandlerConfig{ + Log: log, + Client: c, + TargetRefKinds: NewGroupKindSet(&gwv1beta1.Gateway{}), + } + return NewPolicyHandler[VAP, VAPL](phcfg) +} + +func NewTargetGroupPolicyHandler(log gwlog.Logger, c k8sclient.Client) *PolicyHandler[*TGP] { + phcfg := PolicyHandlerConfig{ + Log: log, + Client: c, + TargetRefKinds: NewGroupKindSet(&corev1.Service{}), + } + return NewPolicyHandler[TGP, TGPL](phcfg) +} + +func NewIAMAuthPolicyHandler(log gwlog.Logger, c k8sclient.Client) *PolicyHandler[*IAP] { + phcfg := PolicyHandlerConfig{ + Log: log, + Client: c, + TargetRefKinds: NewGroupKindSet(&gwv1beta1.Gateway{}, &gwv1beta1.HTTPRoute{}, &gwv1alpha2.GRPCRoute{}), + } + return NewPolicyHandler[IAP, IAPL](phcfg) +} + +// Policy with PolicyTargetReference +type Policy interface { + k8sclient.Object + GetTargetRef() *TargetRef + GetStatusConditions() *[]metav1.Condition +} + +type PolicyList[P Policy] interface { + k8sclient.ObjectList + GetItems() []P +} + +type GroupKindSet = utils.Set[GroupKind] + +func NewGroupKindSet(objs ...k8sclient.Object) *GroupKindSet { + gks := utils.SliceMap(objs, func(o k8sclient.Object) GroupKind { + return ObjToGroupKind(o) + }) + set := utils.NewSet(gks...) + return &set +} + +// A generic handler for common operations on particular policy type +type PolicyHandler[P Policy] struct { + log gwlog.Logger + kinds *GroupKindSet + client PolicyClient[P] +} + +type PolicyHandlerConfig struct { + Log gwlog.Logger + Client k8sclient.Client + TargetRefKinds *GroupKindSet +} + +// Creates policy handler for specific policy. T and TL are type and list-type for Policy (struct type, not reference). +// P and PL are reference types and should derive from T and TL. P and PL do not require explicit declaration. For example: +// +// ph := NewPolicyHandler[IAMAuthPolicy, IAMAuthPolicyList](cfg) +func NewPolicyHandler[T, TL any, P policyPtr[T], PL policyListPtr[TL, P]](cfg PolicyHandlerConfig) *PolicyHandler[P] { + ph := &PolicyHandler[P]{ + log: cfg.Log, + client: newK8sPolicyClient[T, TL, P, PL](cfg.Client), + kinds: cfg.TargetRefKinds, + } + return ph +} + +// Strong-typed interface to work with k8s client +type PolicyClient[P Policy] interface { + List(ctx context.Context, namespace string) ([]P, error) + Get(ctx context.Context, nsname types.NamespacedName) (P, error) + TargetRefObj(ctx context.Context, policy P) (k8sclient.Object, error) + UpdateStatus(ctx context.Context, policy P) error +} + +type policyPtr[T any] interface { + Policy + *T +} + +type policyListPtr[T any, P Policy] interface { + PolicyList[P] + *T +} + +// k8s client based implementation of PolicyClient +type k8sPolicyClient[T, U any, P policyPtr[T], PL policyListPtr[U, P]] struct { + client k8sclient.Client +} + +func newK8sPolicyClient[T, U any, P policyPtr[T], PL policyListPtr[U, P]](c k8sclient.Client) *k8sPolicyClient[T, U, P, PL] { + return &k8sPolicyClient[T, U, P, PL]{client: c} +} + +func (pc *k8sPolicyClient[T, U, P, PL]) newList() PL { + var u U + return &u +} + +func (pc *k8sPolicyClient[T, U, P, PL]) newPolicy() P { + var t T + return &t +} + +func (pc *k8sPolicyClient[T, U, P, PL]) List(ctx context.Context, namespace string) ([]P, error) { + l := pc.newList() + err := pc.client.List(ctx, l, &k8sclient.ListOptions{Namespace: namespace}) + if err != nil { + return nil, err + } + return l.GetItems(), nil +} + +func (pc *k8sPolicyClient[T, U, P, PL]) Get(ctx context.Context, nsname types.NamespacedName) (P, error) { + p := pc.newPolicy() + err := pc.client.Get(ctx, nsname, p) + return p, err +} + +func (pc *k8sPolicyClient[T, U, P, PL]) TargetRefObj(ctx context.Context, p P) (k8sclient.Object, error) { + tr := p.GetTargetRef() + obj, ok := GroupKindToObj(TargetRefGroupKind(tr)) + if !ok { + return nil, fmt.Errorf("not supported GroupKind of targetRef, group/kind=%s/%s", + tr.Group, tr.Kind) + } + key := types.NamespacedName{ + Namespace: p.GetNamespace(), + Name: string(tr.Name), + } + err := pc.client.Get(ctx, key, obj) + if err != nil { + return nil, err + } + return obj, nil +} + +func (pc *k8sPolicyClient[T, U, P, PL]) UpdateStatus(ctx context.Context, policy P) error { + return pc.client.Status().Update(ctx, policy) } -func GetValidPolicy[T core.Policy](ctx context.Context, k8sClient client.Client, searchTargetRef types.NamespacedName, policy T) (T, error) { - var empty T - policies, err := GetAttachedPolicies(ctx, k8sClient, searchTargetRef, policy) - conflictResolutionSort(policies) +// Get all policies for given object, filtered by targetRef match and sorted by conflict resolution +// rules. First policy in the list is not-conflicting policy, but it might be in Accepted or Invalid +// state. Conflict resolution order uses CreationTimestamp and Name. +func (h *PolicyHandler[P]) ObjPolicies(ctx context.Context, obj k8sclient.Object) ([]P, error) { + allPolicies, err := h.client.List(ctx, obj.GetNamespace()) + if err != nil { + return nil, err + } + out := []P{} + for _, policy := range allPolicies { + tr := policy.GetTargetRef() + if h.targetRefMatch(obj, tr) { + out = append(out, policy) + } + } + h.conflictResolutionSort(out) + return out, nil +} + +// Get Accepted policy for given object. Returns policy with conflict resolution and status +// Accepted. Will return at most single policy. +func (h *PolicyHandler[P]) ObjResolvedPolicy(ctx context.Context, obj k8sclient.Object) (P, error) { + var empty P + objPolicies, err := h.ObjPolicies(ctx, obj) if err != nil { return empty, err } - if len(policies) == 0 { + if len(objPolicies) == 0 { return empty, nil } - return policies[0], nil + policy := objPolicies[0] + cnd := meta.FindStatusCondition(*policy.GetStatusConditions(), string(ConditionTypeAccepted)) + if cnd != nil && cnd.Reason != string(ReasonAccepted) { + return empty, nil + } + return objPolicies[0], nil } -func GetAttachedPolicies[T core.Policy](ctx context.Context, k8sClient client.Client, searchTargetRef types.NamespacedName, policy T) ([]T, error) { - var policies []T - info, err := getPolicyInfo(policy) - if err != nil { - return policies, err +// Add Watchers for configured Kinds to controller builder +func (h *PolicyHandler[P]) AddWatchers(b *builder.Builder, objs ...k8sclient.Object) { + h.log.Debugf("add watchers for types: %v", NewGroupKindSet(objs...).Items()) + for _, watchObj := range objs { + b.Watches(watchObj, handler.EnqueueRequestsFromMapFunc(h.watchMapFn)) } +} - pl := info.policyList - err = k8sClient.List(ctx, pl.(client.ObjectList), &client.ListOptions{ - Namespace: searchTargetRef.Namespace, - }) +func (h *PolicyHandler[P]) watchMapFn(ctx context.Context, obj k8sclient.Object) []reconcile.Request { + out := []reconcile.Request{} + policies, err := h.client.List(ctx, obj.GetNamespace()) if err != nil { - if meta.IsNoMatchError(err) { - // CRD does not exist - return policies, nil - } - return policies, err + h.log.Errorf("watch mapfn error: for obj=%s/%s: %w", + obj.GetName(), obj.GetNamespace(), err) + return nil } - for _, p := range pl.GetItems() { - targetRef := p.GetTargetRef() - if targetRef == nil { - continue + for _, policy := range policies { + if h.targetRefMatch(obj, policy.GetTargetRef()) { + out = append(out, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: policy.GetName(), + Namespace: policy.GetNamespace(), + }, + }) } - groupKindMatch := targetRef.Group == info.group && targetRef.Kind == info.kind - nameMatch := string(targetRef.Name) == searchTargetRef.Name + } + return out +} + +// Checks if objects matches targetReference, returns true if they match +// targetRef might not have namespace set, it should be inferred from policy itself. +// In this case we assume namespace already checked +func (h *PolicyHandler[P]) targetRefMatch(obj k8sclient.Object, tr *gwv1alpha2.PolicyTargetReference) bool { + objGk := ObjToGroupKind(obj) + trGk := TargetRefGroupKind(tr) + return objGk == trGk && obj.GetName() == string(tr.Name) +} + +// Validate Policy and update Accepted status condition. +func (h *PolicyHandler[P]) ValidateAndUpdateCondition(ctx context.Context, policy P) (ConditionReason, error) { + validationErr := h.ValidateTargetRef(ctx, policy) + reason := errToReason(validationErr) + msg := "" + if validationErr != nil { + msg = validationErr.Error() + } + err := h.UpdateAcceptedCondition(ctx, policy, reason, msg) + if err != nil { + return ReasonUnknown, err + } + return reason, nil +} + +func (h *PolicyHandler[P]) ValidateTargetRef(ctx context.Context, policy P) error { + tr := policy.GetTargetRef() + + // invalid + trGk := TargetRefGroupKind(tr) + if !h.kinds.Contains(trGk) { + return fmt.Errorf("%w: not supported GroupKind=%s/%s", + ErrGroupKind, tr.Group, tr.Kind) + } - retrievedNamespace := p.GetNamespacedName().Namespace - if targetRef.Namespace != nil { - retrievedNamespace = string(*targetRef.Namespace) + // not found + targetRefObj, err := h.client.TargetRefObj(ctx, policy) + if err != nil { + if apierrors.IsNotFound(err) { + return fmt.Errorf("%w, target=%s/%s", + ErrTargetRefNotFound, policy.GetNamespace(), tr.Name) } - namespaceMatch := retrievedNamespace == searchTargetRef.Namespace - if groupKindMatch && nameMatch && namespaceMatch { - policies = append(policies, p.(T)) + return err + } + + // conflicted + objPolicies, err := h.ObjPolicies(ctx, targetRefObj) + if err != nil { + return err + } + if len(objPolicies) > 0 { + resolvedPolicy := objPolicies[0] + if resolvedPolicy.GetName() != policy.GetName() { + return fmt.Errorf("%w, policy=%s", + ErrTargetRefConflict, resolvedPolicy.GetName()) } } - return policies, nil -} - -func getPolicyInfo(policyType core.Policy) (policyInfo, error) { - switch policyType.(type) { - case *anv1alpha1.VpcAssociationPolicy: - return policyInfo{ - policyList: &anv1alpha1.VpcAssociationPolicyList{}, - group: gwv1beta1.GroupName, - kind: "Gateway", - }, nil - case *anv1alpha1.TargetGroupPolicy: - return policyInfo{ - policyList: &anv1alpha1.TargetGroupPolicyList{}, - group: corev1.GroupName, - kind: "Service", - }, nil + + // valid + return nil +} + +func errToReason(err error) ConditionReason { + switch { + case err == nil: + return ReasonAccepted + case errors.Is(err, ErrGroupKind): + return ReasonInvalid + case errors.Is(err, ErrTargetRefNotFound): + return ReasonTargetNotFound + case errors.Is(err, ErrTargetRefConflict): + return ReasonConflicted default: - return policyInfo{}, fmt.Errorf("unsupported policy type %T", policyType) + return ReasonUnknown + } +} + +func (h *PolicyHandler[P]) UpdateAcceptedCondition(ctx context.Context, policy P, reason ConditionReason, msg string) error { + status := metav1.ConditionTrue + if reason != ReasonAccepted { + status = metav1.ConditionFalse + } + cnd := metav1.Condition{ + Type: string(ConditionTypeAccepted), + Status: status, + ObservedGeneration: policy.GetGeneration(), + Reason: string(reason), + Message: msg, } + meta.SetStatusCondition(policy.GetStatusConditions(), cnd) + err := h.client.UpdateStatus(ctx, policy) + return err } // sort in-place for policy conflict resolution // 1. older policy (CreationTimeStamp) has precedence // 2. alphabetical order namespace, then name -func conflictResolutionSort[T core.Policy](policies []T) { - slices.SortFunc(policies, func(a, b T) int { +func (h *PolicyHandler[P]) conflictResolutionSort(policies []P) { + slices.SortFunc(policies, func(a, b P) int { tsA := a.GetCreationTimestamp().Time tsB := b.GetCreationTimestamp().Time switch { @@ -105,16 +382,8 @@ func conflictResolutionSort[T core.Policy](policies []T) { case tsA.After(tsB): return 1 default: - nsnA := a.GetNamespacedName() - nsnB := b.GetNamespacedName() - nsA := nsnA.Namespace - nsB := nsnB.Namespace - nsCmp := strings.Compare(nsA, nsB) - if nsCmp != 0 { - return nsCmp - } - nA := nsnA.Name - nB := nsnB.Name + nA := a.GetName() + nB := b.GetName() return strings.Compare(nA, nB) } }) diff --git a/pkg/k8s/policyhelper/policy_test.go b/pkg/k8s/policyhelper/policy_test.go index 8b942361..ace28eca 100644 --- a/pkg/k8s/policyhelper/policy_test.go +++ b/pkg/k8s/policyhelper/policy_test.go @@ -1,243 +1,39 @@ package policyhelper import ( - "context" "testing" - "time" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - mock_client "github.com/aws/aws-application-networking-k8s/mocks/controller-runtime/client" anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1" - "github.com/aws/aws-application-networking-k8s/pkg/model/core" ) -func Test_GetAttachedPolicies(t *testing.T) { - type args struct { - refObjNamespacedName types.NamespacedName - policy core.Policy - } - type testCase struct { - name string - args args - k8sReturned []core.Policy - want []core.Policy - } - ns := "test-ns" - typedNs := gwv1alpha2.Namespace(ns) - tgpHappyPath := &anv1alpha1.TargetGroupPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-target-group-policy", - Namespace: ns, - }, - Spec: anv1alpha1.TargetGroupPolicySpec{ - TargetRef: &gwv1alpha2.PolicyTargetReference{ - Group: corev1.GroupName, - Name: "svc-1", - Kind: "Service", - Namespace: &typedNs, - }, - }, - } +func TestPolicyClient(t *testing.T) { + type iap = anv1alpha1.IAMAuthPolicy + type iapl = anv1alpha1.IAMAuthPolicyList - tgpTargetRefSectionNil := tgpHappyPath.DeepCopy() - tgpTargetRefSectionNil.Spec.TargetRef = nil - - tgpTargetRefKindWrong := tgpHappyPath.DeepCopy() - tgpTargetRefKindWrong.Spec.TargetRef.Kind = "ServiceImport" - - tgpUnrelated := tgpHappyPath.DeepCopy() - tgpUnrelated.Spec.TargetRef.Name = "svc-unrelated" - - tgpDuplicated := tgpHappyPath.DeepCopy() - tgpDuplicated.ObjectMeta.Name = "test-policy-2" - - vapHappyPath := &anv1alpha1.VpcAssociationPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-vpc-association-policy", - Namespace: ns, - }, - Spec: anv1alpha1.VpcAssociationPolicySpec{ - TargetRef: &gwv1alpha2.PolicyTargetReference{ - Group: gwv1alpha2.GroupName, - Name: "gw-1", - Kind: "Gateway", - }, - SecurityGroupIds: []anv1alpha1.SecurityGroupId{"sg-1", "sg-2"}, - }, - } - - vapUnrelated := vapHappyPath.DeepCopy() - vapUnrelated.Spec.TargetRef.Name = "gw-unrelated" - - var tests = []testCase{ - { - name: "Get k8sService attached TargetGroupPolicy, happy path", - args: args{ - refObjNamespacedName: types.NamespacedName{ - Namespace: ns, - Name: "svc-1", - }, - policy: &anv1alpha1.TargetGroupPolicy{}, - }, - k8sReturned: []core.Policy{tgpHappyPath, tgpUnrelated}, - want: []core.Policy{tgpHappyPath}, - }, - { - name: "Get multiple k8sService attached TargetGroupPolicies, happy path", - args: args{ - refObjNamespacedName: types.NamespacedName{ - Namespace: ns, - Name: "svc-1", - }, - policy: &anv1alpha1.TargetGroupPolicy{}, - }, - k8sReturned: []core.Policy{tgpHappyPath, tgpDuplicated, tgpUnrelated}, - want: []core.Policy{tgpHappyPath, tgpDuplicated}, - }, - { - name: "Get k8sService attached TargetGroupPolicy, policy not found due to input refObj name mismatch", - args: args{ - refObjNamespacedName: types.NamespacedName{ - Namespace: ns, - Name: "another-svc", - }, - policy: &anv1alpha1.TargetGroupPolicy{}, - }, - k8sReturned: []core.Policy{tgpHappyPath, tgpUnrelated}, - want: nil, - }, - { - name: "Get k8sService attached TargetGroupPolicy, policy not found due to cluster don't have matched policy", - args: args{ - refObjNamespacedName: types.NamespacedName{ - Namespace: ns, - Name: "svc-1", - }, - policy: &anv1alpha1.TargetGroupPolicy{}, - }, - k8sReturned: []core.Policy{tgpUnrelated}, - want: nil, - }, - { - name: "Get k8sService attached TargetGroupPolicy, policy not found due to policy targetRef section is nil", - args: args{ - refObjNamespacedName: types.NamespacedName{ - Namespace: ns, - Name: "svc-1", - }, - policy: &anv1alpha1.TargetGroupPolicy{}, - }, - k8sReturned: []core.Policy{tgpTargetRefSectionNil, tgpUnrelated}, - want: nil, - }, - { - name: "Get k8sService attached TargetGroupPolicy, policy not found due to policy targetRef Kind mismatch", - args: args{ - refObjNamespacedName: types.NamespacedName{ - Namespace: ns, - Name: "svc-1", - }, - policy: &anv1alpha1.TargetGroupPolicy{}, - }, - k8sReturned: []core.Policy{tgpTargetRefKindWrong, tgpUnrelated}, - want: nil, - }, - { - name: "Get k8sGateway attached VpcAssociationPolicy, happy path", - args: args{ - refObjNamespacedName: types.NamespacedName{ - Namespace: ns, - Name: "gw-1", - }, - policy: &anv1alpha1.VpcAssociationPolicy{}, - }, - k8sReturned: []core.Policy{vapHappyPath, vapUnrelated}, - want: []core.Policy{vapHappyPath}, - }, - { - name: "Get k8sGateway attached VpcAssociationPolicy, Not found", - args: args{ - refObjNamespacedName: types.NamespacedName{ - Namespace: ns, - Name: "gw-1", - }, - policy: &anv1alpha1.VpcAssociationPolicy{}, - }, - k8sReturned: []core.Policy{vapUnrelated}, - want: nil, - }, - } - c := gomock.NewController(t) - defer c.Finish() - ctx := context.TODO() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockK8sClient := mock_client.NewMockClient(c) - - if _, ok := tt.args.policy.(*anv1alpha1.TargetGroupPolicy); ok { - mockK8sClient.EXPECT().List(ctx, gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, list *anv1alpha1.TargetGroupPolicyList, arg3 ...interface{}) error { - for _, item := range tt.k8sReturned { - list.Items = append(list.Items, (*item.(*anv1alpha1.TargetGroupPolicy))) - } - return nil - }) - } else if _, ok := tt.args.policy.(*anv1alpha1.VpcAssociationPolicy); ok { - mockK8sClient.EXPECT().List(ctx, gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, list *anv1alpha1.VpcAssociationPolicyList, arg3 ...interface{}) error { - for _, item := range tt.k8sReturned { - list.Items = append(list.Items, (*item.(*anv1alpha1.VpcAssociationPolicy))) - } - return nil - }) - } - - got, err := GetAttachedPolicies(ctx, mockK8sClient, tt.args.refObjNamespacedName, tt.args.policy) - assert.Nil(t, err) - if len(tt.want) == 0 { - assert.Empty(t, got) - return - } - assert.ElementsMatch(t, got, tt.want) - }) - } + t.Run("new list and policy", func(t *testing.T) { + c := newK8sPolicyClient[iap, iapl](nil) + assert.NotNil(t, c.newPolicy()) + assert.NotNil(t, c.newList()) + }) } -func TestConflictResolutionSort(t *testing.T) { +func TestPolicyHandler(t *testing.T) { + type iap = anv1alpha1.IAMAuthPolicy + type iapl = anv1alpha1.IAMAuthPolicyList - t.Run("CreationTimestamp", func(t *testing.T) { - tnow := metav1.Now() - policies := []*anv1alpha1.TargetGroupPolicy{ - {ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: tnow.Add(time.Hour)}}}, - {ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: tnow.Add(time.Second)}}}, - {ObjectMeta: metav1.ObjectMeta{CreationTimestamp: tnow}}, - } - conflictResolutionSort(policies) - if policies[0].CreationTimestamp != tnow { - t.Errorf("wrong order, expect ts=%s, got ts=%s", tnow, policies[0].CreationTimestamp) - } - }) + phcfg := PolicyHandlerConfig{} + _ = NewPolicyHandler[iap, iapl](phcfg) +} - t.Run("Namespace and Name", func(t *testing.T) { - policies := []*anv1alpha1.TargetGroupPolicy{ - {ObjectMeta: metav1.ObjectMeta{Namespace: "a", Name: "b"}}, - {ObjectMeta: metav1.ObjectMeta{Namespace: "a", Name: "c"}}, - {ObjectMeta: metav1.ObjectMeta{Namespace: "a", Name: "a"}}, - {ObjectMeta: metav1.ObjectMeta{Namespace: "b", Name: "z"}}, - {ObjectMeta: metav1.ObjectMeta{Namespace: "b", Name: "y"}}, - } - conflictResolutionSort(policies) - if policies[0].Name != "a" { - t.Errorf("expect 'a' being first, got %s", policies[0].Name) - } - if policies[4].Name != "z" { - t.Errorf("expect 'z' being last, got %s", policies[4].Name) - } - }) +func TestGroupKindSet(t *testing.T) { + objs := []client.Object{&gwv1beta1.Gateway{}, &gwv1beta1.HTTPRoute{}, &gwv1alpha2.GRPCRoute{}} + gks := NewGroupKindSet(objs...) + assert.True(t, gks.Contains(GroupKind{gwv1beta1.GroupName, "Gateway"})) + assert.True(t, gks.Contains(GroupKind{gwv1beta1.GroupName, "HTTPRoute"})) + assert.True(t, gks.Contains(GroupKind{gwv1alpha2.GroupName, "GRPCRoute"})) } diff --git a/pkg/k8s/utils.go b/pkg/k8s/utils.go index 0a1a39cf..ab4c47a0 100644 --- a/pkg/k8s/utils.go +++ b/pkg/k8s/utils.go @@ -14,13 +14,8 @@ import ( const AnnotationPrefix = "application-networking.k8s.aws/" -type NamespacedAndNamed interface { - GetNamespace() string - GetName() string -} - // NamespacedName returns the namespaced name for k8s objects -func NamespacedName(obj NamespacedAndNamed) types.NamespacedName { +func NamespacedName(obj client.Object) types.NamespacedName { return types.NamespacedName{ Namespace: obj.GetNamespace(), Name: obj.GetName(), diff --git a/pkg/utils/common.go b/pkg/utils/common.go index 72e87000..341c9ca2 100644 --- a/pkg/utils/common.go +++ b/pkg/utils/common.go @@ -45,6 +45,10 @@ func SliceMap[T any, U any](in []T, f MapFunc[T, U]) []U { return out } +func SliceMapToPtr[T any](in []T) []*T { + return SliceMap(in, func(t T) *T { return &t }) +} + func SliceFilter[T any](in []T, f FilterFunc[T]) []T { out := []T{} for _, t := range in { @@ -80,3 +84,48 @@ func TargetRefToLatticeResourceName( return "", fmt.Errorf("unsupported targetRef Kind: %s", targetRef.Kind) } + +type none struct{} + +type Set[T comparable] struct { + set map[T]none +} + +func NewSet[T comparable](objs ...T) Set[T] { + s := Set[T]{ + set: make(map[T]none, len(objs)), + } + for _, t := range objs { + s.set[t] = none{} + } + return s +} + +func (s *Set[T]) Put(t T) { + if s.set == nil { + s.set = map[T]none{} + } + s.set[t] = none{} +} + +func (s *Set[T]) Delete(t T) { + delete(s.set, t) +} + +func (s *Set[T]) Contains(t T) bool { + if s.set == nil { + s.set = map[T]none{} + } + _, ok := s.set[t] + return ok +} + +func (s *Set[T]) Items() []T { + out := make([]T, len(s.set)) + i := 0 + for t := range s.set { + out[i] = t + i++ + } + return out +} diff --git a/pkg/utils/common_test.go b/pkg/utils/common_test.go index c6fdceea..9e9b0907 100644 --- a/pkg/utils/common_test.go +++ b/pkg/utils/common_test.go @@ -3,6 +3,8 @@ package utils import ( "fmt" "testing" + + "github.com/stretchr/testify/assert" ) func TestChunks(t *testing.T) { @@ -49,3 +51,13 @@ func makeIntSlice(s int) []int { } return out } + +func TestSet(t *testing.T) { + + t.Run("empty set", func(t *testing.T) { + s := Set[int]{} + s.Put(1) + assert.True(t, s.Contains(1)) + }) + +} diff --git a/test/suites/integration/byoc_test.go b/test/suites/integration/byoc_test.go index 0a4d0fcd..d8ccf7a8 100644 --- a/test/suites/integration/byoc_test.go +++ b/test/suites/integration/byoc_test.go @@ -39,9 +39,9 @@ var _ = Describe("Bring your own certificate (BYOC)", Ordered, func() { ) var ( - log = testFramework.Log.Named("byoc") - sess = session.New() - awsCfg = aws.NewConfig().WithRegion(config.Region) + log = testFramework.Log.Named("byoc") + awsCfg = aws.NewConfig().WithRegion(config.Region) + sess, _ = session.NewSession(awsCfg) acmClient = acm.New(sess, awsCfg) r53Client = route53.New(sess) diff --git a/test/suites/integration/iamauthpolicy_test.go b/test/suites/integration/iamauthpolicy_test.go index 8ccb6f7e..b2dc8948 100644 --- a/test/suites/integration/iamauthpolicy_test.go +++ b/test/suites/integration/iamauthpolicy_test.go @@ -201,11 +201,11 @@ var _ = Describe("IAM Auth Policy", Ordered, func() { }) type StatusConditionsReader interface { - GetStatusConditions() []apimachineryv1.Condition + GetStatusConditions() *[]apimachineryv1.Condition } func GetPolicyStatusReason(obj StatusConditionsReader) gwv1alpha2.PolicyConditionReason { - cnd := meta.FindStatusCondition(obj.GetStatusConditions(), string(gwv1alpha2.PolicyConditionAccepted)) + cnd := meta.FindStatusCondition(*obj.GetStatusConditions(), string(gwv1alpha2.PolicyConditionAccepted)) if cnd != nil { return gwv1alpha2.PolicyConditionReason(cnd.Reason) }