diff --git a/pkg/controller/ecs/service/conversions.go b/pkg/controller/ecs/service/conversions.go new file mode 100644 index 0000000000..77061826ab --- /dev/null +++ b/pkg/controller/ecs/service/conversions.go @@ -0,0 +1,46 @@ +package service + +import ( + svcsdk "github.com/aws/aws-sdk-go/service/ecs" + + svcapitypes "github.com/crossplane-contrib/provider-aws/apis/ecs/v1alpha1" +) + +// GenerateServiceCustom returns the current state in the form of *svcapitypes.Service with custom prarameters set +func GenerateServiceCustom(resp *svcsdk.DescribeServicesOutput) *svcapitypes.Service { + if len(resp.Services) != 1 { + return nil + } + + out := resp.Services[0] + service := GenerateService(resp) + params := &service.Spec.ForProvider + + if out.NetworkConfiguration != nil && out.NetworkConfiguration.AwsvpcConfiguration != nil { + params.CustomServiceParameters.NetworkConfiguration = &svcapitypes.CustomNetworkConfiguration{ + AWSvpcConfiguration: &svcapitypes.CustomAWSVPCConfiguration{ + AssignPublicIP: out.NetworkConfiguration.AwsvpcConfiguration.AssignPublicIp, + SecurityGroups: out.NetworkConfiguration.AwsvpcConfiguration.SecurityGroups, + Subnets: out.NetworkConfiguration.AwsvpcConfiguration.Subnets, + }, + } + } + + if out.LoadBalancers != nil { + params.CustomServiceParameters.LoadBalancers = []*svcapitypes.CustomLoadBalancer{} + for _, lb := range out.LoadBalancers { + params.CustomServiceParameters.LoadBalancers = append(params.CustomServiceParameters.LoadBalancers, &svcapitypes.CustomLoadBalancer{ + ContainerName: lb.ContainerName, + ContainerPort: lb.ContainerPort, + LoadBalancerName: lb.LoadBalancerName, + TargetGroupARN: lb.TargetGroupArn, + }) + } + } + + if out.TaskDefinition != nil { + params.CustomServiceParameters.TaskDefinition = out.TaskDefinition + } + + return service +} diff --git a/pkg/controller/ecs/service/conversions_test.go b/pkg/controller/ecs/service/conversions_test.go new file mode 100644 index 0000000000..45a3cf2a39 --- /dev/null +++ b/pkg/controller/ecs/service/conversions_test.go @@ -0,0 +1,249 @@ +package service + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws" + svcsdk "github.com/aws/aws-sdk-go/service/ecs" + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "k8s.io/utils/ptr" + + "github.com/crossplane-contrib/provider-aws/apis/ecs/v1alpha1" + svcapitypes "github.com/crossplane-contrib/provider-aws/apis/ecs/v1alpha1" +) + +func TestGenerateServiceCustom(t *testing.T) { + cases := map[string]struct { + reason string + out *svcsdk.DescribeServicesOutput + want *svcapitypes.Service + }{ + "TestAllParameters": { + "When passed a DescribeServicesOutput, generate a Service", + &svcsdk.DescribeServicesOutput{ + Services: []*svcsdk.Service{ + { + SchedulingStrategy: ptr.To("Test 12"), + CapacityProviderStrategy: []*svcsdk.CapacityProviderStrategyItem{ + { + Base: ptr.To(int64(1)), + Weight: ptr.To(int64(2)), + CapacityProvider: ptr.To("Test"), + }, + }, + DeploymentConfiguration: &svcsdk.DeploymentConfiguration{ + Alarms: &svcsdk.DeploymentAlarms{ + AlarmNames: []*string{ptr.To("Test 1"), ptr.To("Test 2")}, + Enable: ptr.To(true), + Rollback: ptr.To(false), + }, + DeploymentCircuitBreaker: &svcsdk.DeploymentCircuitBreaker{ + Enable: ptr.To(true), + Rollback: ptr.To(false), + }, + MaximumPercent: ptr.To(int64(3)), + MinimumHealthyPercent: ptr.To(int64(4)), + }, + DesiredCount: ptr.To(int64(5)), + EnableECSManagedTags: ptr.To(true), + EnableExecuteCommand: ptr.To(false), + HealthCheckGracePeriodSeconds: ptr.To(int64(6)), + PlacementConstraints: []*svcsdk.PlacementConstraint{ + { + Expression: ptr.To("Test 5"), + Type: ptr.To("Test 6"), + }, + }, + PlacementStrategy: []*svcsdk.PlacementStrategy{ + { + Field: ptr.To("Test 7"), + Type: ptr.To("Test 8"), + }, + }, + PlatformVersion: ptr.To("Test 9"), + PropagateTags: ptr.To("Test 10"), + ServiceRegistries: []*svcsdk.ServiceRegistry{ + { + ContainerName: ptr.To("Test 20"), + ContainerPort: ptr.To(int64(8082)), + Port: ptr.To(int64(8083)), + RegistryArn: ptr.To("Test 21"), + }, + }, + LoadBalancers: []*svcsdk.LoadBalancer{ + { + ContainerName: aws.String("test-container"), + ContainerPort: aws.Int64(443), + LoadBalancerName: aws.String("test-loadbalancer"), + TargetGroupArn: aws.String("arn:::test-listener"), + }, + }, + NetworkConfiguration: &svcsdk.NetworkConfiguration{ + AwsvpcConfiguration: &svcsdk.AwsVpcConfiguration{ + Subnets: []*string{ + aws.String("subnet-12345"), + aws.String("subnet-45678"), + }, + }, + }, + TaskDefinition: ptr.To("arn:aws:ecs:xx:xx:task-definition/xx:1"), + Tags: []*svcsdk.Tag{ + { + Key: ptr.To("Test 22"), + Value: ptr.To("Test 23"), + }, + }, + }, + }, + }, + &svcapitypes.Service{ + Spec: svcapitypes.ServiceSpec{ + ForProvider: svcapitypes.ServiceParameters{ + Region: "us-east-1", + CapacityProviderStrategy: []*svcapitypes.CapacityProviderStrategyItem{ + { + Base: ptr.To(int64(1)), + Weight: ptr.To(int64(2)), + CapacityProvider: ptr.To("Test"), + }, + }, + DeploymentConfiguration: &svcapitypes.DeploymentConfiguration{ + Alarms: &svcapitypes.DeploymentAlarms{ + AlarmNames: []*string{ptr.To("Test 1"), ptr.To("Test 2")}, + Enable: ptr.To(true), + Rollback: ptr.To(false), + }, + DeploymentCircuitBreaker: &svcapitypes.DeploymentCircuitBreaker{ + Enable: ptr.To(true), + Rollback: ptr.To(false), + }, + MaximumPercent: ptr.To(int64(3)), + MinimumHealthyPercent: ptr.To(int64(4)), + }, + DesiredCount: ptr.To(int64(5)), + EnableECSManagedTags: ptr.To(true), + EnableExecuteCommand: ptr.To(false), + HealthCheckGracePeriodSeconds: ptr.To(int64(6)), + PlacementConstraints: []*svcapitypes.PlacementConstraint{ + { + Expression: ptr.To("Test 5"), + Type: ptr.To("Test 6"), + }, + }, + PlacementStrategy: []*svcapitypes.PlacementStrategy{ + { + Field: ptr.To("Test 7"), + Type: ptr.To("Test 8"), + }, + }, + PlatformVersion: ptr.To("Test 9"), + PropagateTags: ptr.To("Test 10"), + SchedulingStrategy: ptr.To("Test 12"), + ServiceConnectConfiguration: &svcapitypes.ServiceConnectConfiguration{ + Enabled: ptr.To(true), + LogConfiguration: &svcapitypes.LogConfiguration{ + LogDriver: ptr.To("Test 13"), + Options: map[string]*string{"test": ptr.To("Test 14")}, + SecretOptions: []*svcapitypes.Secret{ + { + Name: ptr.To("Test 15"), + ValueFrom: ptr.To("Test 16"), + }, + }, + }, + Namespace: ptr.To("Test 17"), + Services: []*svcapitypes.ServiceConnectService{ + { + ClientAliases: []*svcapitypes.ServiceConnectClientAlias{ + { + DNSName: ptr.To("Test 17"), + Port: ptr.To(int64(8080)), + }, + }, + DiscoveryName: ptr.To("Test 18"), + IngressPortOverride: ptr.To(int64(8081)), + PortName: ptr.To("Test 19"), + }, + }, + }, + ServiceRegistries: []*svcapitypes.ServiceRegistry{ + { + ContainerName: ptr.To("Test 20"), + ContainerPort: ptr.To(int64(8082)), + Port: ptr.To(int64(8083)), + RegistryARN: ptr.To("Test 21"), + }, + }, + Tags: []*svcapitypes.Tag{ + { + Key: ptr.To("Test 22"), + Value: ptr.To("Test 23"), + }, + }, + CustomServiceParameters: svcapitypes.CustomServiceParameters{ + ForceDeletion: true, + LoadBalancers: []*svcapitypes.CustomLoadBalancer{ + { + ContainerName: aws.String("test-container"), + ContainerPort: aws.Int64(443), + LoadBalancerName: aws.String("test-loadbalancer"), + TargetGroupARN: aws.String("arn:::test-listener"), + TargetGroupARNRef: &xpv1.Reference{ + Name: "test-listener", + }, + TargetGroupARNSelector: &xpv1.Selector{ + MatchLabels: map[string]string{ + "crossplane.io/name": "test-loadbalancer", + }, + }, + }, + }, + NetworkConfiguration: &svcapitypes.CustomNetworkConfiguration{ + AWSvpcConfiguration: &svcapitypes.CustomAWSVPCConfiguration{ + Subnets: []*string{ + aws.String("subnet-12345"), + aws.String("subnet-45678"), + }, + SubnetRefs: []xpv1.Reference{ + { + Name: "subnet1234", + }, + { + Name: "subnet5678", + }, + }, + SubnetSelector: &xpv1.Selector{ + MatchLabels: map[string]string{ + "createdBy": "test", + }, + }, + }, + }, + TaskDefinition: ptr.To("arn:aws:ecs:xx:xx:task-definition/xx:1"), + }, + }, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got := GenerateServiceCustom(tc.out) + + if diff := cmp.Diff(tc.want, got, + cmpopts.EquateEmpty(), + cmpopts.IgnoreFields(v1alpha1.Service{}, "Status"), + // Not in DescribeServicesOutput + cmpopts.IgnoreFields(v1alpha1.ServiceParameters{}, "ForceDeletion"), + cmpopts.IgnoreFields(v1alpha1.ServiceParameters{}, "Region"), + cmpopts.IgnoreFields(v1alpha1.ServiceParameters{}, "ServiceConnectConfiguration"), + cmpopts.IgnoreTypes(&xpv1.Reference{}, &xpv1.Selector{}, []xpv1.Reference{}), + ); diff != "" { + t.Errorf("%s\nExample(...): -want, +got:\n%s", tc.reason, diff) + } + }) + } +} diff --git a/pkg/controller/ecs/service/setup.go b/pkg/controller/ecs/service/setup.go index 16a68b0b70..4d7e1cb015 100644 --- a/pkg/controller/ecs/service/setup.go +++ b/pkg/controller/ecs/service/setup.go @@ -4,6 +4,7 @@ import ( "context" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecs" svcsdk "github.com/aws/aws-sdk-go/service/ecs" xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/crossplane-runtime/pkg/connection" @@ -12,6 +13,8 @@ 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" + "github.com/google/go-cmp/cmp/cmpopts" ctrl "sigs.k8s.io/controller-runtime" svcapitypes "github.com/crossplane-contrib/provider-aws/apis/ecs/v1alpha1" @@ -28,7 +31,10 @@ func SetupService(mgr ctrl.Manager, o controller.Options) error { e.preObserve = preObserve e.postObserve = postObserve e.preCreate = preCreate + e.preUpdate = preUpdate e.preDelete = preDelete + e.isUpToDate = isUpToDate + e.lateInitialize = lateInitialize }, } cps := []managed.ConnectionPublisher{managed.NewAPISecretPublisher(mgr.GetClient(), mgr.GetScheme())} @@ -61,6 +67,47 @@ func SetupService(mgr ctrl.Manager, o controller.Options) error { Complete(r) } +func isUpToDate(context context.Context, service *svcapitypes.Service, output *svcsdk.DescribeServicesOutput) (bool, string, error) { + t := service.Spec.ForProvider.DeepCopy() + c := GenerateServiceCustom(output).Spec.ForProvider.DeepCopy() + + tags := func(a, b *ecs.Tag) bool { return aws.StringValue(a.Key) < aws.StringValue(b.Key) } + + diff := cmp.Diff(c, t, + cmpopts.EquateEmpty(), + cmpopts.SortSlices(tags), + // Not present in DescribeServicesOutput + cmpopts.IgnoreFields(svcapitypes.ServiceParameters{}, "Region"), + cmpopts.IgnoreFields(svcapitypes.CustomServiceParameters{}, "Cluster"), + cmpopts.IgnoreFields(svcapitypes.CustomServiceParameters{}, "ForceDeletion"), + cmpopts.IgnoreTypes(&xpv1.Reference{}, &xpv1.Selector{}, []xpv1.Reference{})) + + return diff == "", diff, nil +} + +func lateInitialize(in *svcapitypes.ServiceParameters, out *svcsdk.DescribeServicesOutput) error { + if len(out.Services) != 1 { + return nil + } + o := out.Services[0] + + if in.PlatformVersion == nil { + in.PlatformVersion = o.PlatformVersion + } + + if in.DeploymentController == nil && o.DeploymentController != nil { + in.DeploymentController = &svcapitypes.DeploymentController{ + Type: o.DeploymentController.Type, + } + } + + if in.PropagateTags == nil { + in.PropagateTags = o.PropagateTags + } + + return nil +} + func preObserve(_ context.Context, cr *svcapitypes.Service, obj *svcsdk.DescribeServicesInput) error { obj.Cluster = cr.Spec.ForProvider.Cluster obj.Services = []*string{aws.String(meta.GetExternalName(cr))} @@ -112,6 +159,18 @@ func preCreate(_ context.Context, cr *svcapitypes.Service, obj *svcsdk.CreateSer return nil } +func preUpdate(context context.Context, cr *svcapitypes.Service, obj *svcsdk.UpdateServiceInput) error { + obj.Cluster = cr.Spec.ForProvider.Cluster + obj.Service = aws.String(meta.GetExternalName(cr)) + + mapUpdateServiceInput(cr, obj) + + if err := obj.Validate(); err != nil { + return err + } + return nil +} + func preDelete(_ context.Context, cr *svcapitypes.Service, obj *svcsdk.DeleteServiceInput) (bool, error) { obj.SetForce(cr.Spec.ForProvider.ForceDeletion) @@ -124,6 +183,231 @@ func preDelete(_ context.Context, cr *svcapitypes.Service, obj *svcsdk.DeleteSer return false, nil } +// Helper func to map the values of the APIs Service to the +// SDKs UpdateServiceInput. +func mapUpdateServiceInput(cr *svcapitypes.Service, obj *svcsdk.UpdateServiceInput) { //nolint:gocyclo + // From: ecs/service/zz_conversions.go + // Removed DeploymentConfiguration, LaunchType, Role, SchedulingStrategy and Tags as they cannot be updated + if cr.Spec.ForProvider.CapacityProviderStrategy != nil { + f0 := []*svcsdk.CapacityProviderStrategyItem{} + for _, f0iter := range cr.Spec.ForProvider.CapacityProviderStrategy { + f0elem := &svcsdk.CapacityProviderStrategyItem{} + if f0iter.Base != nil { + f0elem.SetBase(*f0iter.Base) + } + if f0iter.CapacityProvider != nil { + f0elem.SetCapacityProvider(*f0iter.CapacityProvider) + } + if f0iter.Weight != nil { + f0elem.SetWeight(*f0iter.Weight) + } + f0 = append(f0, f0elem) + } + obj.SetCapacityProviderStrategy(f0) + } + if cr.Spec.ForProvider.DeploymentConfiguration != nil { + f1 := &svcsdk.DeploymentConfiguration{} + if cr.Spec.ForProvider.DeploymentConfiguration.Alarms != nil { + f1f0 := &svcsdk.DeploymentAlarms{} + if cr.Spec.ForProvider.DeploymentConfiguration.Alarms.AlarmNames != nil { + f1f0f0 := []*string{} + for _, f1f0f0iter := range cr.Spec.ForProvider.DeploymentConfiguration.Alarms.AlarmNames { + f1f0f0elem := *f1f0f0iter + f1f0f0 = append(f1f0f0, &f1f0f0elem) + } + f1f0.SetAlarmNames(f1f0f0) + } + if cr.Spec.ForProvider.DeploymentConfiguration.Alarms.Enable != nil { + f1f0.SetEnable(*cr.Spec.ForProvider.DeploymentConfiguration.Alarms.Enable) + } + if cr.Spec.ForProvider.DeploymentConfiguration.Alarms.Rollback != nil { + f1f0.SetRollback(*cr.Spec.ForProvider.DeploymentConfiguration.Alarms.Rollback) + } + f1.SetAlarms(f1f0) + } + if cr.Spec.ForProvider.DeploymentConfiguration.DeploymentCircuitBreaker != nil { + f1f1 := &svcsdk.DeploymentCircuitBreaker{} + if cr.Spec.ForProvider.DeploymentConfiguration.DeploymentCircuitBreaker.Enable != nil { + f1f1.SetEnable(*cr.Spec.ForProvider.DeploymentConfiguration.DeploymentCircuitBreaker.Enable) + } + if cr.Spec.ForProvider.DeploymentConfiguration.DeploymentCircuitBreaker.Rollback != nil { + f1f1.SetRollback(*cr.Spec.ForProvider.DeploymentConfiguration.DeploymentCircuitBreaker.Rollback) + } + f1.SetDeploymentCircuitBreaker(f1f1) + } + if cr.Spec.ForProvider.DeploymentConfiguration.MaximumPercent != nil { + f1.SetMaximumPercent(*cr.Spec.ForProvider.DeploymentConfiguration.MaximumPercent) + } + if cr.Spec.ForProvider.DeploymentConfiguration.MinimumHealthyPercent != nil { + f1.SetMinimumHealthyPercent(*cr.Spec.ForProvider.DeploymentConfiguration.MinimumHealthyPercent) + } + obj.SetDeploymentConfiguration(f1) + } + if cr.Spec.ForProvider.DesiredCount != nil { + obj.SetDesiredCount(*cr.Spec.ForProvider.DesiredCount) + } + if cr.Spec.ForProvider.EnableECSManagedTags != nil { + obj.SetEnableECSManagedTags(*cr.Spec.ForProvider.EnableECSManagedTags) + } + if cr.Spec.ForProvider.EnableExecuteCommand != nil { + obj.SetEnableExecuteCommand(*cr.Spec.ForProvider.EnableExecuteCommand) + } + if cr.Spec.ForProvider.HealthCheckGracePeriodSeconds != nil { + obj.SetHealthCheckGracePeriodSeconds(*cr.Spec.ForProvider.HealthCheckGracePeriodSeconds) + } + if cr.Spec.ForProvider.PlacementConstraints != nil { + f8 := []*svcsdk.PlacementConstraint{} + for _, f8iter := range cr.Spec.ForProvider.PlacementConstraints { + f8elem := &svcsdk.PlacementConstraint{} + if f8iter.Expression != nil { + f8elem.SetExpression(*f8iter.Expression) + } + if f8iter.Type != nil { + f8elem.SetType(*f8iter.Type) + } + f8 = append(f8, f8elem) + } + obj.SetPlacementConstraints(f8) + } + if cr.Spec.ForProvider.PlacementStrategy != nil { + f9 := []*svcsdk.PlacementStrategy{} + for _, f9iter := range cr.Spec.ForProvider.PlacementStrategy { + f9elem := &svcsdk.PlacementStrategy{} + if f9iter.Field != nil { + f9elem.SetField(*f9iter.Field) + } + if f9iter.Type != nil { + f9elem.SetType(*f9iter.Type) + } + f9 = append(f9, f9elem) + } + obj.SetPlacementStrategy(f9) + } + if cr.Spec.ForProvider.PlatformVersion != nil { + obj.SetPlatformVersion(*cr.Spec.ForProvider.PlatformVersion) + } + if cr.Spec.ForProvider.PropagateTags != nil { + obj.SetPropagateTags(*cr.Spec.ForProvider.PropagateTags) + } + if cr.Spec.ForProvider.ServiceConnectConfiguration != nil { + f14 := &svcsdk.ServiceConnectConfiguration{} + if cr.Spec.ForProvider.ServiceConnectConfiguration.Enabled != nil { + f14.SetEnabled(*cr.Spec.ForProvider.ServiceConnectConfiguration.Enabled) + } + if cr.Spec.ForProvider.ServiceConnectConfiguration.LogConfiguration != nil { + f14f1 := &svcsdk.LogConfiguration{} + if cr.Spec.ForProvider.ServiceConnectConfiguration.LogConfiguration.LogDriver != nil { + f14f1.SetLogDriver(*cr.Spec.ForProvider.ServiceConnectConfiguration.LogConfiguration.LogDriver) + } + if cr.Spec.ForProvider.ServiceConnectConfiguration.LogConfiguration.Options != nil { + f14f1f1 := map[string]*string{} + for f14f1f1key, f14f1f1valiter := range cr.Spec.ForProvider.ServiceConnectConfiguration.LogConfiguration.Options { + f14f1f1val := *f14f1f1valiter + f14f1f1[f14f1f1key] = &f14f1f1val + } + f14f1.SetOptions(f14f1f1) + } + if cr.Spec.ForProvider.ServiceConnectConfiguration.LogConfiguration.SecretOptions != nil { + f14f1f2 := []*svcsdk.Secret{} + for _, f14f1f2iter := range cr.Spec.ForProvider.ServiceConnectConfiguration.LogConfiguration.SecretOptions { + f14f1f2elem := &svcsdk.Secret{} + if f14f1f2iter.Name != nil { + f14f1f2elem.SetName(*f14f1f2iter.Name) + } + if f14f1f2iter.ValueFrom != nil { + f14f1f2elem.SetValueFrom(*f14f1f2iter.ValueFrom) + } + f14f1f2 = append(f14f1f2, f14f1f2elem) + } + f14f1.SetSecretOptions(f14f1f2) + } + f14.SetLogConfiguration(f14f1) + } + if cr.Spec.ForProvider.ServiceConnectConfiguration.Namespace != nil { + f14.SetNamespace(*cr.Spec.ForProvider.ServiceConnectConfiguration.Namespace) + } + if cr.Spec.ForProvider.ServiceConnectConfiguration.Services != nil { + f14f3 := []*svcsdk.ServiceConnectService{} + for _, f14f3iter := range cr.Spec.ForProvider.ServiceConnectConfiguration.Services { + f14f3elem := &svcsdk.ServiceConnectService{} + if f14f3iter.ClientAliases != nil { + f14f3elemf0 := []*svcsdk.ServiceConnectClientAlias{} + for _, f14f3elemf0iter := range f14f3iter.ClientAliases { + f14f3elemf0elem := &svcsdk.ServiceConnectClientAlias{} + if f14f3elemf0iter.DNSName != nil { + f14f3elemf0elem.SetDnsName(*f14f3elemf0iter.DNSName) + } + if f14f3elemf0iter.Port != nil { + f14f3elemf0elem.SetPort(*f14f3elemf0iter.Port) + } + f14f3elemf0 = append(f14f3elemf0, f14f3elemf0elem) + } + f14f3elem.SetClientAliases(f14f3elemf0) + } + if f14f3iter.DiscoveryName != nil { + f14f3elem.SetDiscoveryName(*f14f3iter.DiscoveryName) + } + if f14f3iter.IngressPortOverride != nil { + f14f3elem.SetIngressPortOverride(*f14f3iter.IngressPortOverride) + } + if f14f3iter.PortName != nil { + f14f3elem.SetPortName(*f14f3iter.PortName) + } + f14f3 = append(f14f3, f14f3elem) + } + f14.SetServices(f14f3) + } + obj.SetServiceConnectConfiguration(f14) + } + if cr.Spec.ForProvider.ServiceRegistries != nil { + f15 := []*svcsdk.ServiceRegistry{} + for _, f15iter := range cr.Spec.ForProvider.ServiceRegistries { + f15elem := &svcsdk.ServiceRegistry{} + if f15iter.ContainerName != nil { + f15elem.SetContainerName(*f15iter.ContainerName) + } + if f15iter.ContainerPort != nil { + f15elem.SetContainerPort(*f15iter.ContainerPort) + } + if f15iter.Port != nil { + f15elem.SetPort(*f15iter.Port) + } + if f15iter.RegistryARN != nil { + f15elem.SetRegistryArn(*f15iter.RegistryARN) + } + f15 = append(f15, f15elem) + } + obj.SetServiceRegistries(f15) + } + + // CustomServiceParameters + if cr.Spec.ForProvider.CustomServiceParameters.NetworkConfiguration != nil { + obj.NetworkConfiguration = &svcsdk.NetworkConfiguration{ + AwsvpcConfiguration: &svcsdk.AwsVpcConfiguration{ + AssignPublicIp: cr.Spec.ForProvider.CustomServiceParameters.NetworkConfiguration.AWSvpcConfiguration.AssignPublicIP, + SecurityGroups: cr.Spec.ForProvider.CustomServiceParameters.NetworkConfiguration.AWSvpcConfiguration.SecurityGroups, + Subnets: cr.Spec.ForProvider.CustomServiceParameters.NetworkConfiguration.AWSvpcConfiguration.Subnets, + }, + } + } + + if cr.Spec.ForProvider.CustomServiceParameters.LoadBalancers != nil { + obj.LoadBalancers = []*svcsdk.LoadBalancer{} + for _, lb := range cr.Spec.ForProvider.CustomServiceParameters.LoadBalancers { + obj.LoadBalancers = append(obj.LoadBalancers, &svcsdk.LoadBalancer{ + ContainerName: lb.ContainerName, + ContainerPort: lb.ContainerPort, + LoadBalancerName: lb.LoadBalancerName, + TargetGroupArn: lb.TargetGroupARN, + }) + } + } + + if cr.Spec.ForProvider.TaskDefinition != nil { + obj.TaskDefinition = cr.Spec.ForProvider.TaskDefinition + } +} + // Helper func to generate SDK LoadBalancer types from the crossplane // types included in the Service api. func generateLoadBalancers(cr *svcapitypes.Service) []*svcsdk.LoadBalancer { diff --git a/pkg/controller/ecs/service/setup_test.go b/pkg/controller/ecs/service/setup_test.go index bd3afe91b1..a783c7cf43 100644 --- a/pkg/controller/ecs/service/setup_test.go +++ b/pkg/controller/ecs/service/setup_test.go @@ -7,6 +7,7 @@ import ( svcsdk "github.com/aws/aws-sdk-go/service/ecs" xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/google/go-cmp/cmp" + "k8s.io/utils/ptr" svcapitypes "github.com/crossplane-contrib/provider-aws/apis/ecs/v1alpha1" ) @@ -254,3 +255,251 @@ func TestGenerateNetworkConfiguraiton(t *testing.T) { }) } } + +func TestMapUpdateServiceInput(t *testing.T) { + cases := map[string]struct { + reason string + cr *svcapitypes.Service + want *svcsdk.UpdateServiceInput + }{ + "TestAllParameters": { + "When passed all parameters, generate a complete UpdateServiceInput", + &svcapitypes.Service{ + Spec: svcapitypes.ServiceSpec{ + ForProvider: svcapitypes.ServiceParameters{ + Region: "us-east-1", + CapacityProviderStrategy: []*svcapitypes.CapacityProviderStrategyItem{ + { + Base: ptr.To(int64(1)), + Weight: ptr.To(int64(2)), + CapacityProvider: ptr.To("Test"), + }, + }, + DeploymentConfiguration: &svcapitypes.DeploymentConfiguration{ + Alarms: &svcapitypes.DeploymentAlarms{ + AlarmNames: []*string{ptr.To("Test 1"), ptr.To("Test 2")}, + Enable: ptr.To(true), + Rollback: ptr.To(false), + }, + DeploymentCircuitBreaker: &svcapitypes.DeploymentCircuitBreaker{ + Enable: ptr.To(true), + Rollback: ptr.To(false), + }, + MaximumPercent: ptr.To(int64(3)), + MinimumHealthyPercent: ptr.To(int64(4)), + }, + DeploymentController: &svcapitypes.DeploymentController{ + Type: ptr.To("Test 3"), + }, + DesiredCount: ptr.To(int64(5)), + EnableECSManagedTags: ptr.To(true), + EnableExecuteCommand: ptr.To(false), + HealthCheckGracePeriodSeconds: ptr.To(int64(6)), + LaunchType: ptr.To("Test 4"), + PlacementConstraints: []*svcapitypes.PlacementConstraint{ + { + Expression: ptr.To("Test 5"), + Type: ptr.To("Test 6"), + }, + }, + PlacementStrategy: []*svcapitypes.PlacementStrategy{ + { + Field: ptr.To("Test 7"), + Type: ptr.To("Test 8"), + }, + }, + PlatformVersion: ptr.To("Test 9"), + PropagateTags: ptr.To("Test 10"), + Role: ptr.To("Test 11"), + SchedulingStrategy: ptr.To("Test 12"), + ServiceConnectConfiguration: &svcapitypes.ServiceConnectConfiguration{ + Enabled: ptr.To(true), + LogConfiguration: &svcapitypes.LogConfiguration{ + LogDriver: ptr.To("Test 13"), + Options: map[string]*string{"test": ptr.To("Test 14")}, + SecretOptions: []*svcapitypes.Secret{ + { + Name: ptr.To("Test 15"), + ValueFrom: ptr.To("Test 16"), + }, + }, + }, + Namespace: ptr.To("Test 17"), + Services: []*svcapitypes.ServiceConnectService{ + { + ClientAliases: []*svcapitypes.ServiceConnectClientAlias{ + { + DNSName: ptr.To("Test 17"), + Port: ptr.To(int64(8080)), + }, + }, + DiscoveryName: ptr.To("Test 18"), + IngressPortOverride: ptr.To(int64(8081)), + PortName: ptr.To("Test 19"), + }, + }, + }, + ServiceRegistries: []*svcapitypes.ServiceRegistry{ + { + ContainerName: ptr.To("Test 20"), + ContainerPort: ptr.To(int64(8082)), + Port: ptr.To(int64(8083)), + RegistryARN: ptr.To("Test 21"), + }, + }, + Tags: []*svcapitypes.Tag{ + { + Key: ptr.To("Test 22"), + Value: ptr.To("Test 23"), + }, + }, + CustomServiceParameters: svcapitypes.CustomServiceParameters{ + ForceDeletion: true, + LoadBalancers: []*svcapitypes.CustomLoadBalancer{ + { + ContainerName: aws.String("test-container"), + ContainerPort: aws.Int64(443), + LoadBalancerName: aws.String("test-loadbalancer"), + TargetGroupARN: aws.String("arn:::test-listener"), + TargetGroupARNRef: &xpv1.Reference{ + Name: "test-listener", + }, + TargetGroupARNSelector: &xpv1.Selector{ + MatchLabels: map[string]string{ + "crossplane.io/name": "test-loadbalancer", + }, + }, + }, + }, + NetworkConfiguration: &svcapitypes.CustomNetworkConfiguration{ + AWSvpcConfiguration: &svcapitypes.CustomAWSVPCConfiguration{ + Subnets: []*string{ + aws.String("subnet-12345"), + aws.String("subnet-45678"), + }, + SubnetRefs: []xpv1.Reference{ + { + Name: "subnet1234", + }, + { + Name: "subnet5678", + }, + }, + SubnetSelector: &xpv1.Selector{ + MatchLabels: map[string]string{ + "createdBy": "test", + }, + }, + }, + }, + TaskDefinition: ptr.To("arn:aws:ecs:xx:xx:task-definition/xx:1"), + }, + }, + }, + }, + &svcsdk.UpdateServiceInput{ + CapacityProviderStrategy: []*svcsdk.CapacityProviderStrategyItem{ + { + Base: ptr.To(int64(1)), + Weight: ptr.To(int64(2)), + CapacityProvider: ptr.To("Test"), + }, + }, + DeploymentConfiguration: &svcsdk.DeploymentConfiguration{ + Alarms: &svcsdk.DeploymentAlarms{ + AlarmNames: []*string{ptr.To("Test 1"), ptr.To("Test 2")}, + Enable: ptr.To(true), + Rollback: ptr.To(false), + }, + DeploymentCircuitBreaker: &svcsdk.DeploymentCircuitBreaker{ + Enable: ptr.To(true), + Rollback: ptr.To(false), + }, + MaximumPercent: ptr.To(int64(3)), + MinimumHealthyPercent: ptr.To(int64(4)), + }, + DesiredCount: ptr.To(int64(5)), + EnableECSManagedTags: ptr.To(true), + EnableExecuteCommand: ptr.To(false), + HealthCheckGracePeriodSeconds: ptr.To(int64(6)), + PlacementConstraints: []*svcsdk.PlacementConstraint{ + { + Expression: ptr.To("Test 5"), + Type: ptr.To("Test 6"), + }, + }, + PlacementStrategy: []*svcsdk.PlacementStrategy{ + { + Field: ptr.To("Test 7"), + Type: ptr.To("Test 8"), + }, + }, + PlatformVersion: ptr.To("Test 9"), + PropagateTags: ptr.To("Test 10"), + ServiceConnectConfiguration: &svcsdk.ServiceConnectConfiguration{ + Enabled: ptr.To(true), + LogConfiguration: &svcsdk.LogConfiguration{ + LogDriver: ptr.To("Test 13"), + Options: map[string]*string{"test": ptr.To("Test 14")}, + SecretOptions: []*svcsdk.Secret{ + { + Name: ptr.To("Test 15"), + ValueFrom: ptr.To("Test 16"), + }, + }, + }, + Namespace: ptr.To("Test 17"), + Services: []*svcsdk.ServiceConnectService{ + { + ClientAliases: []*svcsdk.ServiceConnectClientAlias{ + { + DnsName: ptr.To("Test 17"), + Port: ptr.To(int64(8080)), + }, + }, + DiscoveryName: ptr.To("Test 18"), + IngressPortOverride: ptr.To(int64(8081)), + PortName: ptr.To("Test 19"), + }, + }, + }, + ServiceRegistries: []*svcsdk.ServiceRegistry{ + { + ContainerName: ptr.To("Test 20"), + ContainerPort: ptr.To(int64(8082)), + Port: ptr.To(int64(8083)), + RegistryArn: ptr.To("Test 21"), + }, + }, + LoadBalancers: []*svcsdk.LoadBalancer{ + { + ContainerName: aws.String("test-container"), + ContainerPort: aws.Int64(443), + LoadBalancerName: aws.String("test-loadbalancer"), + TargetGroupArn: aws.String("arn:::test-listener"), + }, + }, + NetworkConfiguration: &svcsdk.NetworkConfiguration{ + AwsvpcConfiguration: &svcsdk.AwsVpcConfiguration{ + Subnets: []*string{ + aws.String("subnet-12345"), + aws.String("subnet-45678"), + }, + }, + }, + TaskDefinition: ptr.To("arn:aws:ecs:xx:xx:task-definition/xx:1"), + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got := &svcsdk.UpdateServiceInput{} + mapUpdateServiceInput(tc.cr, got) + + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("%s\nExample(...): -want, +got:\n%s", tc.reason, diff) + } + }) + } +}