diff --git a/README.md b/README.md index daaf9d6..7eeea87 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,8 @@ The defaults are set for the most common use cases, but can be changed by settin | AUTOSCALING\_UP\_COOLDOWN | 5 | Cooldown period after scaling up | | AUTOSCALING\_UP\_INTERVAL | 60 | Seconds between intervals to check resource usage before scaling, after a scaling up operation is detected | | AUTOSCALING\_UP\_PERIOD | 5 | Periods to check before scaling | +| SERVICE\_DISCOVERY\_TTL | 60 | TTL for service discovery records | +| SERVICE_DISCOVERY_FAILURETHRESHOLD | 3 | Failure threshold for service discovery records | | AWS\_RESOURCE\_CREATION\_ENABLED | yes | Let ecs-deploy create AWS IAM resources for you | ### Autoscaling Strategies diff --git a/provider/ecs/ecs.go b/provider/ecs/ecs.go index ffa3cd0..662586a 100644 --- a/provider/ecs/ecs.go +++ b/provider/ecs/ecs.go @@ -696,18 +696,28 @@ func (e *ECS) CreateService(d service.Deploy) error { // set ServiceRegistry if d.ServiceRegistry != "" && strings.ToLower(d.ServiceProtocol) != "none" { sd := ServiceDiscovery{} - serviceDiscoveryRegistryArn, err := sd.GetNamespaceArn(d.ServiceRegistry) + _, serviceDiscoveryNamespaceID, err := sd.getNamespaceArnAndId(d.ServiceRegistry) if err != nil { ecsLogger.Warningf("Could not apply ServiceRegistry Config: %s", err.Error()) } else { - ecsLogger.Debugf("Applying ServiceRegistry for %s with Arn %s", e.ServiceName, serviceDiscoveryRegistryArn) - input.SetServiceRegistries([]*ecs.ServiceRegistry{ - { - ContainerName: aws.String(e.ServiceName), - ContainerPort: aws.Int64(d.ServicePort), - RegistryArn: aws.String(serviceDiscoveryRegistryArn), - }, - }) + serviceDiscoveryServiceArn, err := sd.getServiceArn(d.ServiceName, serviceDiscoveryNamespaceID) + if err != nil && strings.HasPrefix(err.Error(), "Service not found") { + // Service not found, create service in service registry + serviceDiscoveryServiceArn, err = sd.createService(d.ServiceName, serviceDiscoveryNamespaceID) + } + // check for error, else set service registry + if err != nil && !strings.HasPrefix(err.Error(), "Service not found") { + ecsLogger.Warningf("Could not get service from ServiceRegistry: %s", err.Error()) + } else { + ecsLogger.Debugf("Applying ServiceRegistry for %s with Arn %s", e.ServiceName, serviceDiscoveryServiceArn) + input.SetServiceRegistries([]*ecs.ServiceRegistry{ + { + ContainerName: aws.String(e.ServiceName), + ContainerPort: aws.Int64(d.ServicePort), + RegistryArn: aws.String(serviceDiscoveryServiceArn), + }, + }) + } } } diff --git a/provider/ecs/servicediscovery.go b/provider/ecs/servicediscovery.go index b3ec292..d3b986b 100644 --- a/provider/ecs/servicediscovery.go +++ b/provider/ecs/servicediscovery.go @@ -2,11 +2,13 @@ package ecs import ( "errors" + "strconv" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/servicediscovery" + "github.com/in4it/ecs-deploy/util" "github.com/juju/loggo" ) @@ -17,8 +19,9 @@ var serviceDiscoveryLogger = loggo.GetLogger("servicediscovery") type ServiceDiscovery struct { } -func (s *ServiceDiscovery) GetNamespaceArn(name string) (string, error) { +func (s *ServiceDiscovery) getNamespaceArnAndId(name string) (string, string, error) { var result string + var id string svc := servicediscovery.New(session.New()) input := &servicediscovery.ListNamespacesInput{} pageNum := 0 @@ -28,6 +31,7 @@ func (s *ServiceDiscovery) GetNamespaceArn(name string) (string, error) { for _, v := range page.Namespaces { if aws.StringValue(v.Name) == name { result = aws.StringValue(v.Arn) + id = aws.StringValue(v.Id) } } return pageNum <= 100 @@ -41,7 +45,90 @@ func (s *ServiceDiscovery) GetNamespaceArn(name string) (string, error) { } } if result == "" { - return result, errors.New("Namespace not found namespace=" + name) + return result, id, errors.New("Namespace not found namespace=" + name) + } + return result, id, nil +} +func (s *ServiceDiscovery) getServiceArn(serviceName, namespaceID string) (string, error) { + var result string + svc := servicediscovery.New(session.New()) + input := &servicediscovery.ListServicesInput{ + Filters: []*servicediscovery.ServiceFilter{ + { + Name: aws.String("NAMESPACE_ID"), + Condition: aws.String("EQ"), + Values: aws.StringSlice([]string{namespaceID}), + }, + }, + } + pageNum := 0 + err := svc.ListServicesPages(input, + func(page *servicediscovery.ListServicesOutput, lastPage bool) bool { + pageNum++ + for _, v := range page.Services { + if aws.StringValue(v.Name) == serviceName { + result = aws.StringValue(v.Arn) + } + } + return pageNum <= 100 + }) + + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + ecsLogger.Errorf(aerr.Error()) + } else { + ecsLogger.Errorf(err.Error()) + } + } + if result == "" { + return result, errors.New("Service not found service=" + serviceName) } return result, nil } +func (s *ServiceDiscovery) createService(serviceName, namespaceID string) (string, error) { + var ( + ttl int64 + failureThreshold int64 + err error + output string + ) + ttl, err = strconv.ParseInt(util.GetEnv("SERVICE_DISCOVERY_TTL", "60"), 10, 64) + if err != nil { + ttl = 60 + } + failureThreshold, err = strconv.ParseInt(util.GetEnv("SERVICE_DISCOVERY_FAILURETHRESHOLD", "3"), 10, 64) + if err != nil { + failureThreshold = 3 + } + svc := servicediscovery.New(session.New()) + input := &servicediscovery.CreateServiceInput{ + CreatorRequestId: aws.String(serviceName + "-" + util.RandStringBytesMaskImprSrc(8)), + Description: aws.String(serviceName), + Name: aws.String(serviceName), + NamespaceId: aws.String(namespaceID), + DnsConfig: &servicediscovery.DnsConfig{ + DnsRecords: []*servicediscovery.DnsRecord{ + { + TTL: aws.Int64(ttl), + Type: aws.String("SRV"), + }, + }, + }, + HealthCheckCustomConfig: &servicediscovery.HealthCheckCustomConfig{ + FailureThreshold: aws.Int64(failureThreshold), + }, + } + result, err := svc.CreateService(input) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + ecsLogger.Errorf("%v", aerr.Error()) + } else { + ecsLogger.Errorf("%v", err.Error()) + } + return output, err + } + + output = aws.StringValue(result.Service.Arn) + + return output, nil +}