From 63926df2e3fe4fe520402d7f32d96176d26fa536 Mon Sep 17 00:00:00 2001 From: Kevin Kendzia Date: Thu, 23 Jan 2025 15:52:43 +0100 Subject: [PATCH] feat: implement isUpToDate for transfer server Signed-off-by: Kevin Kendzia --- pkg/controller/transfer/server/custom.go | 1 + pkg/controller/transfer/server/setup.go | 158 ++++++++++++++++ pkg/controller/transfer/server/setup_test.go | 178 +++++++++++++++++++ 3 files changed, 337 insertions(+) create mode 100644 pkg/controller/transfer/server/setup_test.go diff --git a/pkg/controller/transfer/server/custom.go b/pkg/controller/transfer/server/custom.go index b93bcd3882..65750aa82b 100644 --- a/pkg/controller/transfer/server/custom.go +++ b/pkg/controller/transfer/server/custom.go @@ -68,5 +68,6 @@ func (c *customConnector) Connect(ctx context.Context, mg cpresource.Managed) (m external.preObserve = preObserve external.preDelete = preDelete external.preCreate = preCreate + external.isUpToDate = isUpToDate return external, nil } diff --git a/pkg/controller/transfer/server/setup.go b/pkg/controller/transfer/server/setup.go index eb8d963a8a..0b1aa36f3a 100644 --- a/pkg/controller/transfer/server/setup.go +++ b/pkg/controller/transfer/server/setup.go @@ -16,6 +16,8 @@ package server import ( "context" "fmt" + "slices" + "strings" "github.com/aws/aws-sdk-go/service/ec2" svcsdk "github.com/aws/aws-sdk-go/service/transfer" @@ -27,11 +29,14 @@ import ( "github.com/crossplane/crossplane-runtime/pkg/meta" "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/google/go-cmp/cmp" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" svcapitypes "github.com/crossplane-contrib/provider-aws/apis/transfer/v1alpha1" "github.com/crossplane-contrib/provider-aws/apis/v1alpha1" + "github.com/crossplane-contrib/provider-aws/pkg/controller/transfer/utils" "github.com/crossplane-contrib/provider-aws/pkg/features" "github.com/crossplane-contrib/provider-aws/pkg/utils/pointer" custommanaged "github.com/crossplane-contrib/provider-aws/pkg/utils/reconciler/managed" @@ -186,3 +191,156 @@ func (c *custom) DescribeVpcEndpoint(obj *svcsdk.DescribeServerOutput) (dnsEntri } return []*ec2.VpcEndpoint{}, nil } + +func isUpToDate(_ context.Context, cr *svcapitypes.Server, cur *svcsdk.DescribeServerOutput) (bool, string, error) { + in := cr.Spec.ForProvider + out := cur.Server + + if isNotUpToDate(in, out) { + return false, "", nil + } + + return true, "", nil + +} + +func isNotUpToDate(in svcapitypes.ServerParameters, out *svcsdk.DescribedServer) bool { + if !cmp.Equal(in.Certificate, out.Certificate) { + return true + } + + if !cmp.Equal(in.Domain, out.Domain) { + return true + } + + if !cmp.Equal(in.EndpointType, out.EndpointType) { + return true + } + + if !cmp.Equal(in.IdentityProviderType, out.IdentityProviderType) { + return true + } + + if !cmp.Equal(in.LoggingRole, out.LoggingRole) { + return true + } + + if !cmp.Equal(in.PostAuthenticationLoginBanner, out.PostAuthenticationLoginBanner) { + return true + } + + if !cmp.Equal(in.PreAuthenticationLoginBanner, out.PreAuthenticationLoginBanner) { + return true + } + + if !cmp.Equal(in.SecurityPolicyName, out.SecurityPolicyName) { + return true + } + + if !cmp.Equal(in.StructuredLogDestinations, out.StructuredLogDestinations) { + return true + } + + if !isIdentityProviderDetailsUpToDate(in.IdentityProviderDetails, out.IdentityProviderDetails) { + return true + } + + if !isProtocolDetailsUpToDate(in.ProtocolDetails, out.ProtocolDetails) { + return true + } + + if !isCustomEndpointUpToDate(in.CustomEndpointDetails, out.EndpointDetails) { + return true + } + + if !isWorkflowDetailsUpToDate(in.WorkflowDetails, out.WorkflowDetails) { + return true + } + + if upToDate, _, _ := utils.DiffTags(in.Tags, out.Tags); !upToDate { + return true + } + + return false +} + +func isIdentityProviderDetailsUpToDate(in *svcapitypes.IdentityProviderDetails, out *svcsdk.IdentityProviderDetails) bool { + if !cmp.Equal(in.DirectoryID, out.DirectoryId) { + return false + } + if !cmp.Equal(in.Function, out.Function) { + return false + } + if !cmp.Equal(in.InvocationRole, out.InvocationRole) { + return false + } + if !cmp.Equal(in.SftpAuthenticationMethods, out.SftpAuthenticationMethods) { + return false + } + if !cmp.Equal(in.URL, out.Url) { + return false + } + return true +} + +func isProtocolDetailsUpToDate(in *svcapitypes.ProtocolDetails, out *svcsdk.ProtocolDetails) bool { + if !cmp.Equal(in.As2Transports, out.As2Transports) { + return false + } + if !cmp.Equal(in.PassiveIP, out.PassiveIp) { + return false + } + if !cmp.Equal(in.SetStatOption, out.SetStatOption) { + return false + } + if !cmp.Equal(in.TLSSessionResumptionMode, out.TlsSessionResumptionMode) { + return false + } + return true +} + +func isCustomEndpointUpToDate(in *svcapitypes.CustomEndpointDetails, out *svcsdk.EndpointDetails) bool { + if !cmp.Equal(in.AddressAllocationIDs, out.AddressAllocationIds) { + return false + } + if !cmp.Equal(in.SecurityGroupIDs, out.SecurityGroupIds) { + return false + } + if !cmp.Equal(in.SubnetIDs, out.SubnetIds) { + return false + } + if !cmp.Equal(in.VPCEndpointID, out.VpcEndpointId) { + return false + } + if !cmp.Equal(in.VPCID, out.VpcId) { + return false + } + return true +} + +func isWorkflowDetailsUpToDate(in *svcapitypes.WorkflowDetails, out *svcsdk.WorkflowDetails) bool { + if len(in.OnPartialUpload) != len(out.OnPartialUpload) || len(in.OnUpload) != len(out.OnUpload) { + return false + } + + if len(in.OnPartialUpload) == 0 && len(in.OnUpload) == 0 { + return true + } + + apiTypesSort := func(a *svcapitypes.WorkflowDetail, b *svcapitypes.WorkflowDetail) int { + return strings.Compare(*a.WorkflowID, *b.WorkflowID) + } + sdkSort := func(a *svcsdk.WorkflowDetail, b *svcsdk.WorkflowDetail) int { + return strings.Compare(*a.WorkflowId, *b.WorkflowId) + } + compareApiSdk := func(a *svcapitypes.WorkflowDetail, b *svcsdk.WorkflowDetail) bool { + return ptr.Deref(a.ExecutionRole, "") == ptr.Deref(b.ExecutionRole, "") && ptr.Deref(a.WorkflowID, "") == ptr.Deref(b.WorkflowId, "") + } + + slices.SortFunc(in.OnPartialUpload, apiTypesSort) + slices.SortFunc(in.OnUpload, apiTypesSort) + slices.SortFunc(out.OnPartialUpload, sdkSort) + slices.SortFunc(out.OnUpload, sdkSort) + + return slices.EqualFunc(in.OnPartialUpload, out.OnPartialUpload, compareApiSdk) && slices.EqualFunc(in.OnUpload, out.OnUpload, compareApiSdk) +} diff --git a/pkg/controller/transfer/server/setup_test.go b/pkg/controller/transfer/server/setup_test.go new file mode 100644 index 0000000000..564c31a921 --- /dev/null +++ b/pkg/controller/transfer/server/setup_test.go @@ -0,0 +1,178 @@ +package server + +import ( + "context" + "testing" + + svcsdk "github.com/aws/aws-sdk-go/service/transfer" + svcapitypes "github.com/crossplane-contrib/provider-aws/apis/transfer/v1alpha1" + "k8s.io/utils/ptr" +) + +var serverParameters = &svcapitypes.Server{ + Spec: svcapitypes.ServerSpec{ + ForProvider: svcapitypes.ServerParameters{ + CustomServerParameters: svcapitypes.CustomServerParameters{ + Certificate: ptr.To("samecertificate"), + CustomEndpointDetails: &svcapitypes.CustomEndpointDetails{ + AddressAllocationIDs: []*string{ + ptr.To("id"), + }, + SecurityGroupIDs: []*string{ + ptr.To("id"), + }, + SubnetIDs: []*string{ + ptr.To("id"), + }, + VPCEndpointID: ptr.To("id"), + VPCID: ptr.To("id"), + }, + LoggingRole: ptr.To("role"), + }, + Domain: ptr.To("S3"), + EndpointType: ptr.To("VPC_ENDPOINT"), + IdentityProviderDetails: &svcapitypes.IdentityProviderDetails{ + DirectoryID: ptr.To("id"), + Function: ptr.To("function"), + InvocationRole: ptr.To("role"), + SftpAuthenticationMethods: ptr.To("method"), + URL: ptr.To("url"), + }, + IdentityProviderType: ptr.To("SERVICE_MANAGED"), + PostAuthenticationLoginBanner: ptr.To("postbanner"), + PreAuthenticationLoginBanner: ptr.To("prebanner"), + ProtocolDetails: &svcapitypes.ProtocolDetails{ + As2Transports: []*string{ + ptr.To("HTTP"), + }, + PassiveIP: ptr.To("127.0.0.1"), + SetStatOption: ptr.To("SETSTAT"), + TLSSessionResumptionMode: ptr.To("ENFORCED"), + }, + Protocols: []*string{ptr.To("SFTP")}, + SecurityPolicyName: ptr.To("TransferSecurityPolicy-2024-01"), + StructuredLogDestinations: []*string{ + ptr.To("arn:log"), + }, + Tags: []*svcapitypes.Tag{ + {Key: ptr.To("key"), Value: ptr.To("value")}, + }, + WorkflowDetails: &svcapitypes.WorkflowDetails{ + OnPartialUpload: []*svcapitypes.WorkflowDetail{ + { + WorkflowID: ptr.To("1"), + ExecutionRole: ptr.To("role"), + }, + { + WorkflowID: ptr.To("2"), + ExecutionRole: ptr.To("role2"), + }, + }, + }, + }, + }, +} + +var describedServer = &svcsdk.DescribeServerOutput{ + Server: &svcsdk.DescribedServer{ + Certificate: ptr.To("samecertificate"), + Domain: ptr.To("S3"), + EndpointDetails: &svcsdk.EndpointDetails{ + AddressAllocationIds: []*string{ + ptr.To("id"), + }, + SecurityGroupIds: []*string{ + ptr.To("id"), + }, + SubnetIds: []*string{ + ptr.To("id"), + }, + VpcEndpointId: ptr.To("id"), + VpcId: ptr.To("id"), + }, + EndpointType: ptr.To("VPC_ENDPOINT"), + IdentityProviderDetails: &svcsdk.IdentityProviderDetails{ + DirectoryId: ptr.To("id"), + Function: ptr.To("function"), + InvocationRole: ptr.To("role"), + SftpAuthenticationMethods: ptr.To("method"), + Url: ptr.To("url"), + }, + IdentityProviderType: ptr.To("SERVICE_MANAGED"), + LoggingRole: ptr.To("role"), + PostAuthenticationLoginBanner: ptr.To("postbanner"), + PreAuthenticationLoginBanner: ptr.To("prebanner"), + ProtocolDetails: &svcsdk.ProtocolDetails{ + As2Transports: []*string{ + ptr.To("HTTP"), + }, + PassiveIp: ptr.To("127.0.0.1"), + SetStatOption: ptr.To("SETSTAT"), + TlsSessionResumptionMode: ptr.To("ENFORCED"), + }, + Protocols: []*string{ptr.To("SFTP")}, + SecurityPolicyName: ptr.To("TransferSecurityPolicy-2024-01"), + ServerId: ptr.To("s-1234567890"), + State: ptr.To("STARTING"), + StructuredLogDestinations: []*string{ + ptr.To("arn:log"), + }, + Tags: []*svcsdk.Tag{ + {Key: ptr.To("key"), Value: ptr.To("value")}, + }, + UserCount: ptr.To(int64(0)), + WorkflowDetails: &svcsdk.WorkflowDetails{ + OnPartialUpload: []*svcsdk.WorkflowDetail{ + { + WorkflowId: ptr.To("1"), + ExecutionRole: ptr.To("role"), + }, + { + WorkflowId: ptr.To("2"), + ExecutionRole: ptr.To("role2"), + }, + }, + }, + }, +} + +func TestUpToDateEqual(t *testing.T) { + in := serverParameters.DeepCopy() + if upToDate, _, _ := isUpToDate(context.TODO(), in, describedServer); !upToDate { + t.Error("isUpToDate should be true.") + } +} + +func TestUpToDateWorkflowOrder(t *testing.T) { + in := serverParameters.DeepCopy() + in.Spec.ForProvider.WorkflowDetails = &svcapitypes.WorkflowDetails{ + OnPartialUpload: []*svcapitypes.WorkflowDetail{ + { + WorkflowID: ptr.To("2"), + ExecutionRole: ptr.To("role2"), + }, + { + WorkflowID: ptr.To("1"), + ExecutionRole: ptr.To("role"), + }, + }, + } + if upToDate, _, _ := isUpToDate(context.TODO(), in, describedServer); !upToDate { + t.Error("isUpToDate should be true.") + } +} + +func TestUpToDateWorkflow(t *testing.T) { + in := serverParameters.DeepCopy() + in.Spec.ForProvider.WorkflowDetails = &svcapitypes.WorkflowDetails{ + OnPartialUpload: []*svcapitypes.WorkflowDetail{ + { + WorkflowID: ptr.To("2"), + ExecutionRole: ptr.To("role2"), + }, + }, + } + if upToDate, _, _ := isUpToDate(context.TODO(), in, describedServer); upToDate { + t.Error("isUpToDate should be false. Different WorkflowDetails.") + } +}