Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(transfer): implement isUpToDate for transfer server #2151

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions apis/transfer/generator-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,26 @@ ignore:
- Agreement
- Connector
- Profile
operations:
- UpdateServer
resources:
Server:
fields:
HostKeyFingerprint:
is_read_only: true
from:
operation: DescribeServer
path: Server.HostKeyFingerprint
Tags:
is_read_only: true
from:
operation: DescribeServer
path: Server.Tags
ARN:
is_read_only: true
from:
operation: DescribeServer
path: Server.Arn
exceptions:
errors:
404:
Expand Down
21 changes: 21 additions & 0 deletions apis/transfer/v1alpha1/zz_generated.deepcopy.go

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

9 changes: 9 additions & 0 deletions apis/transfer/v1alpha1/zz_server.go

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

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.18.0
go.uber.org/zap v1.26.0
golang.org/x/crypto v0.21.0
golang.org/x/sync v0.7.0
gopkg.in/alecthomas/kingpin.v2 v2.2.6
k8s.io/api v0.29.1
Expand Down Expand Up @@ -124,7 +125,6 @@ require (
github.com/src-d/gcfg v1.4.0 // indirect
github.com/xanzy/ssh-agent v0.2.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.23.0 // indirect
Expand Down
22 changes: 22 additions & 0 deletions package/crds/transfer.aws.crossplane.io_servers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1196,10 +1196,32 @@ spec:
atProvider:
description: ServerObservation defines the observed state of Server
properties:
arn:
description: Specifies the unique Amazon Resource Name (ARN) of
the server.
type: string
hostKeyFingerprint:
description: |-
Specifies the Base64-encoded SHA256 fingerprint of the server's host key.
This value is equivalent to the output of the ssh-keygen -l -f my-new-server-key
command.
type: string
serverID:
description: The service-assigned identifier of the server that
is created.
type: string
tags:
description: |-
Specifies the key-value pairs that you can use to search for and group servers
that were assigned to the server that was described.
items:
properties:
key:
type: string
value:
type: string
type: object
type: array
type: object
conditions:
description: Conditions of the resource.
Expand Down
3 changes: 3 additions & 0 deletions pkg/controller/transfer/server/custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,8 @@ func (c *customConnector) Connect(ctx context.Context, mg cpresource.Managed) (m
external.preObserve = preObserve
external.preDelete = preDelete
external.preCreate = preCreate
external.isUpToDate = isUpToDate
external.lateInitialize = lateInitialize
external.update = external.UpdateServer
return external, nil
}
224 changes: 224 additions & 0 deletions pkg/controller/transfer/server/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -27,11 +29,15 @@ 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"
"golang.org/x/crypto/ssh"
"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"
Expand Down Expand Up @@ -130,6 +136,9 @@ func (c *custom) postObserve(_ context.Context, cr *svcapitypes.Server, obj *svc
}
}

cr.Status.AtProvider.ARN = obj.Server.Arn
cr.Status.AtProvider.Tags = utils.ConvertTagsFromDescribeServer(obj.Server.Tags)

return obs, nil
}

Expand Down Expand Up @@ -186,3 +195,218 @@ func (c *custom) DescribeVpcEndpoint(obj *svcsdk.DescribeServerOutput) (dnsEntri
}
return []*ec2.VpcEndpoint{}, nil
}

func lateInitialize(spec *svcapitypes.ServerParameters, obj *svcsdk.DescribeServerOutput) error {
if spec.ProtocolDetails == nil {
spec.ProtocolDetails = &svcapitypes.ProtocolDetails{}
}
if obj.Server.ProtocolDetails != nil {
if spec.ProtocolDetails.As2Transports == nil {
spec.ProtocolDetails.As2Transports = obj.Server.ProtocolDetails.As2Transports
}
if spec.ProtocolDetails.PassiveIP == nil {
spec.ProtocolDetails.PassiveIP = obj.Server.ProtocolDetails.PassiveIp
}
if spec.ProtocolDetails.SetStatOption == nil {
spec.ProtocolDetails.SetStatOption = obj.Server.ProtocolDetails.SetStatOption
}
if spec.ProtocolDetails.TLSSessionResumptionMode == nil {
spec.ProtocolDetails.TLSSessionResumptionMode = obj.Server.ProtocolDetails.TlsSessionResumptionMode
}
}
if spec.CustomEndpointDetails != nil && spec.CustomEndpointDetails.VPCEndpointID == nil && obj.Server.EndpointDetails.VpcEndpointId != nil {
spec.CustomEndpointDetails.VPCEndpointID = obj.Server.EndpointDetails.VpcEndpointId
}
return 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 { //nolint:gocyclo
kkendzia marked this conversation as resolved.
Show resolved Hide resolved
if !cmp.Equal(in.Certificate, out.Certificate) {
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
}

if !isHostKeyUpToDate(in.HostKey, out.HostKeyFingerprint) {
return true
}

return false
}

func isIdentityProviderDetailsUpToDate(in *svcapitypes.IdentityProviderDetails, out *svcsdk.IdentityProviderDetails) bool {
if in == nil && out == nil {
return true
}
if in == nil || out == nil {
return false
}
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 in == nil && out == nil {
return true
}
if in == nil || out == nil {
return false
}
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 in == nil && out == nil {
return true
}
if in == nil || out == nil {
return false
}
if !cmp.Equal(in.AddressAllocationIDs, out.AddressAllocationIds) {
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 { //nolint:gocyclo
if in == nil && out == nil {
return true
}
if in == nil || out == nil {
return false
}
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)
}

func isHostKeyUpToDate(in *string, out *string) bool {
// if there is no HostKey set, AWS generates a HostKey by itself. if the HostKey gets deleted from the spec, don't update.
if in == nil {
return true
}
if out == nil {
return false
}
key, err := ssh.ParsePrivateKey([]byte(ptr.Deref(in, "")))
if err != nil {
panic(err)
}
fingerprint := ssh.FingerprintSHA256(key.PublicKey())
currentFingerprint := strings.TrimSuffix(ptr.Deref(out, ""), "=")
return fingerprint == currentFingerprint
}
Loading
Loading