diff --git a/pkg/controller/transfer/server/custom.go b/pkg/controller/transfer/server/custom.go new file mode 100644 index 0000000000..b93bcd3882 --- /dev/null +++ b/pkg/controller/transfer/server/custom.go @@ -0,0 +1,72 @@ +// /* +// Copyright 2021 The Crossplane Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ + +package server + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" + svcsdk "github.com/aws/aws-sdk-go/service/transfer" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + cpresource "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/pkg/errors" + + svcapitypes "github.com/crossplane-contrib/provider-aws/apis/transfer/v1alpha1" + connectaws "github.com/crossplane-contrib/provider-aws/pkg/utils/connect/aws" +) + +type vpcEndpointClient interface { + DescribeVpcEndpoints(*ec2.DescribeVpcEndpointsInput) (*ec2.DescribeVpcEndpointsOutput, error) +} + +// newVPCClient generates an ec2 client for describing the vpc endpoint +func newVPCClient(sess *session.Session) vpcEndpointClient { + return ec2.New(sess) +} + +type customConnector struct { + *connector + newClientFn func(config *session.Session) vpcEndpointClient +} + +func (c *customConnector) Connect(ctx context.Context, mg cpresource.Managed) (managed.ExternalClient, error) { + cr, ok := mg.(*svcapitypes.Server) + if !ok { + return nil, errors.New(errUnexpectedObject) + } + sess, err := connectaws.GetConfigV1(ctx, c.kube, mg, cr.Spec.ForProvider.Region) + if err != nil { + return nil, errors.Wrap(err, errCreateSession) + } + + external := newExternal(c.kube, svcsdk.New(sess), c.opts) + vpcClient := c.newClientFn(sess) + if vpcClient == nil { + return nil, errors.New("failed to initialize VPC client") + } + custom := &custom{ + client: external.client, + kube: external.kube, + external: external, + vpcEndpointClient: vpcClient, + } + + external.postObserve = custom.postObserve + external.postCreate = postCreate + external.preObserve = preObserve + external.preDelete = preDelete + external.preCreate = preCreate + return external, nil +} diff --git a/pkg/controller/transfer/server/setup.go b/pkg/controller/transfer/server/setup.go index dcdf5d2808..eb8d963a8a 100644 --- a/pkg/controller/transfer/server/setup.go +++ b/pkg/controller/transfer/server/setup.go @@ -15,8 +15,11 @@ package server import ( "context" + "fmt" + "github.com/aws/aws-sdk-go/service/ec2" svcsdk "github.com/aws/aws-sdk-go/service/transfer" + svcsdkapitransfer "github.com/aws/aws-sdk-go/service/transfer/transferiface" xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/crossplane-runtime/pkg/connection" "github.com/crossplane/crossplane-runtime/pkg/controller" @@ -25,6 +28,7 @@ import ( "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" "github.com/crossplane/crossplane-runtime/pkg/resource" 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" @@ -33,20 +37,17 @@ import ( custommanaged "github.com/crossplane-contrib/provider-aws/pkg/utils/reconciler/managed" ) +type custom struct { + kube client.Client + client svcsdkapitransfer.TransferAPI + external *external + vpcEndpointClient vpcEndpointClient +} + // SetupServer adds a controller that reconciles Server. func SetupServer(mgr ctrl.Manager, o controller.Options) error { name := managed.ControllerName(svcapitypes.ServerGroupKind) - opts := []option{ - func(e *external) { - e.postObserve = postObserve - e.postCreate = postCreate - e.preObserve = preObserve - e.preDelete = preDelete - e.preCreate = preCreate - }, - } - cps := []managed.ConnectionPublisher{managed.NewAPISecretPublisher(mgr.GetClient(), mgr.GetScheme())} if o.Features.Enabled(features.EnableAlphaExternalSecretStores) { cps = append(cps, connection.NewDetailsManager(mgr.GetClient(), v1alpha1.StoreConfigGroupVersionKind)) @@ -55,7 +56,7 @@ func SetupServer(mgr ctrl.Manager, o controller.Options) error { reconcilerOpts := []managed.ReconcilerOption{ managed.WithInitializers(), managed.WithCriticalAnnotationUpdater(custommanaged.NewRetryingCriticalAnnotationUpdater(mgr.GetClient())), - managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), opts: opts}), + managed.WithExternalConnecter(&customConnector{connector: &connector{kube: mgr.GetClient()}, newClientFn: newVPCClient}), managed.WithPollInterval(o.PollInterval), managed.WithLogger(o.Logger.WithValues("controller", name)), managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), @@ -90,7 +91,7 @@ func preDelete(_ context.Context, cr *svcapitypes.Server, obj *svcsdk.DeleteServ return false, nil } -func postObserve(_ context.Context, cr *svcapitypes.Server, obj *svcsdk.DescribeServerOutput, obs managed.ExternalObservation, err error) (managed.ExternalObservation, error) { +func (c *custom) postObserve(_ context.Context, cr *svcapitypes.Server, obj *svcsdk.DescribeServerOutput, obs managed.ExternalObservation, err error) (managed.ExternalObservation, error) { //nolint:gocyclo if err != nil { return managed.ExternalObservation{}, err } @@ -109,10 +110,25 @@ func postObserve(_ context.Context, cr *svcapitypes.Server, obj *svcsdk.Describe case string(svcapitypes.State_STOP_FAILED): cr.SetConditions(xpv1.ReconcileError(err)) } - obs.ConnectionDetails = managed.ConnectionDetails{ "HostKeyFingerprint": []byte(pointer.StringValue(obj.Server.HostKeyFingerprint)), } + // fetch endpoint details for EndpointType VPC only + // for EndpointType 'PUBLIC' neither endpoint id nor endpoint name is present in the describe output/aws cli v2 output + // endpoint name can only be found in the console + if pointer.StringValue(cr.Spec.ForProvider.EndpointType) == "VPC" { + vpcEndpointsResults, err := c.DescribeVpcEndpoint(obj) + if err != nil { + return managed.ExternalObservation{}, err + } + + for i, vep := range vpcEndpointsResults { + for j, dnsEntry := range vep.DnsEntries { + key := fmt.Sprintf("endpoint.%d.dns.%d", i, j) + obs.ConnectionDetails[key] = []byte(pointer.StringValue(dnsEntry.DnsName)) + } + } + } return obs, nil } @@ -155,3 +171,18 @@ func preCreate(_ context.Context, cr *svcapitypes.Server, obj *svcsdk.CreateServ return nil } + +func (c *custom) DescribeVpcEndpoint(obj *svcsdk.DescribeServerOutput) (dnsEntries []*ec2.VpcEndpoint, err error) { + if obj.Server != nil && obj.Server.EndpointDetails != nil && obj.Server.EndpointDetails.VpcEndpointId != nil { + describeEndpointOutput, err := c.vpcEndpointClient.DescribeVpcEndpoints(&ec2.DescribeVpcEndpointsInput{ + VpcEndpointIds: []*string{ + obj.Server.EndpointDetails.VpcEndpointId, + }, + }) + if err != nil { + return []*ec2.VpcEndpoint{}, err + } + return describeEndpointOutput.VpcEndpoints, nil + } + return []*ec2.VpcEndpoint{}, nil +}