diff --git a/api/controllers/environment.go b/api/controllers/environment.go index 707c13b5df..20fb30247a 100644 --- a/api/controllers/environment.go +++ b/api/controllers/environment.go @@ -39,12 +39,12 @@ func EnvironmentSet(rw http.ResponseWriter, r *http.Request) *httperr.Error { return httperr.Server(err) } - releaseId, err := models.PutEnvironment(app, models.LoadEnvironment(body)) + releaseID, err := models.PutEnvironment(app, models.LoadEnvironment(body)) if err != nil { return httperr.Server(err) } - rw.Header().Set("Release-Id", releaseId) + rw.Header().Set("Release-Id", releaseID) env, err := models.GetEnvironment(app) if err != nil { @@ -71,13 +71,13 @@ func EnvironmentDelete(rw http.ResponseWriter, r *http.Request) *httperr.Error { delete(env, name) - releaseId, err := models.PutEnvironment(app, env) + releaseID, err := models.PutEnvironment(app, env) if err != nil { return httperr.Server(err) } - rw.Header().Set("Release-Id", releaseId) + rw.Header().Set("Release-Id", releaseID) env, err = models.GetEnvironment(app) diff --git a/api/controllers/processes.go b/api/controllers/processes.go index ca82c472c3..2f3fc9b860 100644 --- a/api/controllers/processes.go +++ b/api/controllers/processes.go @@ -4,6 +4,7 @@ import ( "net/http" "sort" "strconv" + "strings" "sync" "github.com/convox/rack/api/httperr" @@ -126,6 +127,10 @@ func ProcessRunDetached(rw http.ResponseWriter, r *http.Request) *httperr.Error err = a.RunDetached(process, command, release) if err != nil { + if strings.HasPrefix(err.Error(), "no such release") { + return httperr.Errorf(404, err.Error()) + } + return httperr.Server(err) } diff --git a/api/controllers/services.go b/api/controllers/services.go index dba1bd26a9..73874774c0 100644 --- a/api/controllers/services.go +++ b/api/controllers/services.go @@ -33,7 +33,8 @@ func ServiceShow(rw http.ResponseWriter, r *http.Request) *httperr.Error { } // new services should use the provider interfaces - if s.Type == "syslog" { + switch s.Type { + case "fluentd", "papertrail", "syslog": s, err := provider.ServiceGet(service) if err != nil { return httperr.Server(err) @@ -64,7 +65,8 @@ func ServiceCreate(rw http.ResponseWriter, r *http.Request) *httperr.Error { delete(params, "type") // new services should use the provider interfaces - if kind == "syslog" || kind == "papertrail" { + switch kind { + case "fluentd", "papertrail", "syslog": s, err := provider.ServiceCreate(name, kind, params) if err != nil { return httperr.Server(err) @@ -111,9 +113,6 @@ func ServiceDelete(rw http.ResponseWriter, r *http.Request) *httperr.Error { service := mux.Vars(r)["service"] s, err := provider.ServiceGet(service) - if awsError(err) == "ValidationError" { - return httperr.Errorf(404, "no such service: %s", service) - } if err != nil { return httperr.Server(err) } diff --git a/api/dist/kernel.json b/api/dist/kernel.json index 88f6d1cf7e..87deceb184 100644 --- a/api/dist/kernel.json +++ b/api/dist/kernel.json @@ -5,11 +5,13 @@ "BlankAmi": { "Fn::Equals": [ { "Ref": "Ami" }, "" ] }, "BlankCertificate": { "Fn::Equals": [ { "Ref": "Certificate" }, "" ] }, "BlankDockerImageApi": { "Fn::Equals": [ { "Ref": "DockerImageApi" }, "" ] }, + "BlankExistingVpc": { "Fn::Equals": [ { "Ref": "ExistingVpc" }, "" ] }, "BlankInstanceBootCommand": { "Fn::Equals": [ { "Ref": "InstanceBootCommand" }, "" ] }, "BlankInstanceRunCommand": { "Fn::Equals": [ { "Ref": "InstanceRunCommand" }, "" ] }, "BlankKey": { "Fn::Equals": [ { "Ref": "Key" }, "" ] }, "BlankRegistryHost": { "Fn::Equals": [ { "Ref": "RegistryHost" }, "" ] }, "Development": { "Fn::Equals": [ { "Ref": "Development" }, "Yes" ] }, + "ExistingVpc": { "Fn::Not": [ { "Fn::Equals": [ { "Ref": "ExistingVpc" }, "" ] } ] }, "Private": { "Fn::Equals": [ { "Ref": "Private" }, "Yes" ] }, "PrivateApi": { "Fn::Equals": [ { "Ref": "PrivateApi" }, "Yes" ] }, "RegionHasEFS": { @@ -123,7 +125,18 @@ }, "Subnets": { "Condition": "Development", - "Value": { "Fn::Join": [ ",", [ { "Ref": "Subnet0" }, { "Ref": "Subnet1" }, { "Ref": "Subnet2" } ] ] } + "Value": { "Fn::Join": [ ",", { "Fn::If": [ "BlankExistingVpc", + [ + { "Ref": "Subnet0" }, + { "Ref": "Subnet1" }, + { "Ref": "Subnet2" } + ], + [ + { "Fn::Select": [ 0, { "Ref": "ExistingSubnets" } ] }, + { "Fn::Select": [ 1, { "Ref": "ExistingSubnets" } ] }, + { "Fn::Select": [ 2, { "Ref": "ExistingSubnets" } ] } + ] + ] } ] } }, "SubnetsPrivate": { "Condition": "Development", @@ -138,7 +151,10 @@ }, "Vpc": { "Condition": "Development", - "Value": { "Ref": "Vpc" } + "Value": { "Fn::If": [ "BlankExistingVpc", + { "Ref": "Vpc" }, + { "Ref": "ExistingVpc" } + ] } } }, "Parameters": { @@ -200,6 +216,16 @@ "Default": "Yes", "AllowedValues": [ "Yes", "No" ] }, + "ExistingSubnets": { + "Default": "", + "Description": "Existing VPC Subnets", + "Type": "List" + }, + "ExistingVpc": { + "Default": "", + "Description": "Existing VPC ID", + "Type": "String" + }, "InstanceBootCommand": { "Type": "String", "Description": "A single line of shell script to run as CloudInit command early during instance boot.", @@ -496,11 +522,14 @@ } }, "AvailabilityZones": { - "DependsOn": [ "CustomTopic", "Vpc" ], + "DependsOn": [ "CustomTopic" ], "Type": "Custom::EC2AvailabilityZones", "Properties": { "ServiceToken": { "Fn::GetAtt": [ "CustomTopic", "Arn" ] }, - "Vpc": { "Ref": "Vpc" } + "Vpc": { "Fn::If": [ "BlankExistingVpc", + { "Ref": "Vpc" }, + { "Ref": "ExistingVpc" } + ] } } }, "KernelUser": { @@ -531,6 +560,7 @@ }, "Vpc": { "Type": "AWS::EC2::VPC", + "Condition": "BlankExistingVpc", "Properties": { "CidrBlock": { "Ref": "VPCCIDR" }, "EnableDnsSupport": "true", @@ -543,12 +573,14 @@ }, "Gateway": { "Type": "AWS::EC2::InternetGateway", + "Condition": "BlankExistingVpc", "Properties": { } }, "GatewayAttachment": { "DependsOn": [ "Gateway", "Vpc" ], "Type": "AWS::EC2::VPCGatewayAttachment", + "Condition": "BlankExistingVpc", "Properties": { "InternetGatewayId": { "Ref": "Gateway" }, "VpcId": { "Ref": "Vpc" } @@ -605,6 +637,7 @@ "Subnet0": { "DependsOn": [ "AvailabilityZones", "Vpc" ], "Type": "AWS::EC2::Subnet", + "Condition": "BlankExistingVpc", "Properties": { "Tags": [ {"Key": "Name", "Value": { "Fn::Join": [ " ", [ {"Ref": "AWS::StackName"}, "public", "0" ] ] }} @@ -617,6 +650,7 @@ "Subnet1": { "DependsOn": [ "AvailabilityZones", "Vpc" ], "Type": "AWS::EC2::Subnet", + "Condition": "BlankExistingVpc", "Properties": { "Tags": [ {"Key": "Name", "Value": { "Fn::Join": [ " ", [ {"Ref": "AWS::StackName"}, "public", "1" ] ] }} @@ -629,6 +663,7 @@ "Subnet2": { "DependsOn": [ "AvailabilityZones", "Vpc" ], "Type": "AWS::EC2::Subnet", + "Condition": "BlankExistingVpc", "Properties": { "Tags": [ {"Key": "Name", "Value": { "Fn::Join": [ " ", [ {"Ref": "AWS::StackName"}, "public", "2" ] ] }} @@ -680,6 +715,7 @@ "Routes": { "DependsOn": [ "Gateway", "GatewayAttachment", "Vpc" ], "Type": "AWS::EC2::RouteTable", + "Condition": "BlankExistingVpc", "Properties": { "VpcId": { "Ref": "Vpc" } } @@ -687,6 +723,7 @@ "RouteDefault": { "DependsOn": [ "Gateway", "GatewayAttachment", "Routes" ], "Type": "AWS::EC2::Route", + "Condition": "BlankExistingVpc", "Properties": { "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "Gateway" }, @@ -753,6 +790,7 @@ "Subnet0Routes": { "DependsOn": [ "Subnet0", "Routes" ], "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Condition": "BlankExistingVpc", "Properties": { "SubnetId": { "Ref": "Subnet0" }, "RouteTableId": { "Ref": "Routes" } @@ -761,6 +799,7 @@ "Subnet1Routes": { "DependsOn": [ "Subnet1", "Routes" ], "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Condition": "BlankExistingVpc", "Properties": { "SubnetId": { "Ref": "Subnet1" }, "RouteTableId": { "Ref": "Routes" } @@ -769,6 +808,7 @@ "Subnet2Routes": { "DependsOn": [ "Subnet2", "Routes" ], "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Condition": "BlankExistingVpc", "Properties": { "SubnetId": { "Ref": "Subnet2" }, "RouteTableId": { "Ref": "Routes" } @@ -802,7 +842,6 @@ } }, "SecurityGroup": { - "DependsOn": "Vpc", "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "Instances", @@ -811,7 +850,10 @@ { "IpProtocol": "tcp", "FromPort": "0", "ToPort": "65535", "CidrIp": { "Ref": "VPCCIDR" } }, { "IpProtocol": "udp", "FromPort": "0", "ToPort": "65535", "CidrIp": { "Ref": "VPCCIDR" } } ], - "VpcId": { "Ref": "Vpc" } + "VpcId": { "Fn::If": [ "BlankExistingVpc", + { "Ref": "Vpc" }, + { "Ref": "ExistingVpc" } + ] } } }, "IamRole": { @@ -969,7 +1011,7 @@ } }, "Instances": { - "DependsOn": [ "AvailabilityZones", "GatewayAttachment", "Subnet0", "Subnet1", "Subnet2" ], + "DependsOn": [ "AvailabilityZones" ], "Type": "AWS::AutoScaling::AutoScalingGroup", "Properties" : { "LaunchConfigurationName" : { "Ref": "LaunchConfiguration" }, @@ -978,18 +1020,23 @@ { "Fn::GetAtt": [ "AvailabilityZones", "AvailabilityZone1" ] }, { "Fn::GetAtt": [ "AvailabilityZones", "AvailabilityZone2" ] } ], - "VPCZoneIdentifier": { "Fn::If": [ "Private", - [ - { "Ref": "SubnetPrivate0" }, - { "Ref": "SubnetPrivate1" }, - { "Ref": "SubnetPrivate2" } - ], - [ - { "Ref": "Subnet0" }, - { "Ref": "Subnet1" }, - { "Ref": "Subnet2" } - ] - ] }, + "VPCZoneIdentifier": { + "Fn::If": [ "ExistingVpc", [ + { "Fn::Select": [ 0, { "Ref": "ExistingSubnets" } ] }, + { "Fn::Select": [ 1, { "Ref": "ExistingSubnets" } ] }, + { "Fn::Select": [ 2, { "Ref": "ExistingSubnets" } ] } + ], { + "Fn::If": [ "Private", [ + { "Ref": "SubnetPrivate0" }, + { "Ref": "SubnetPrivate1" }, + { "Ref": "SubnetPrivate2" } + ], [ + { "Ref": "Subnet0" }, + { "Ref": "Subnet1" }, + { "Ref": "Subnet2" } + ] ] + } ] + }, "Cooldown": 5, "DesiredCapacity": { "Ref": "InstanceCount" }, "HealthCheckType": "EC2", @@ -1208,7 +1255,7 @@ } }, "Balancer": { - "DependsOn": [ "BalancerSecurityGroup", "GatewayAttachment" ], + "DependsOn": [ "BalancerSecurityGroup" ], "Properties": { "ConnectionDrainingPolicy": { "Enabled": true, @@ -1277,23 +1324,27 @@ "Ref": "BalancerSecurityGroup" } ], - "Subnets": { "Fn::If": [ "PrivateApi", - [ - { "Ref": "SubnetPrivate0" }, - { "Ref": "SubnetPrivate1" }, - { "Ref": "SubnetPrivate2" } - ], - [ - { "Ref": "Subnet0" }, - { "Ref": "Subnet1" }, - { "Ref": "Subnet2" } - ] - ] } + "Subnets": { + "Fn::If": [ "ExistingVpc", [ + { "Fn::Select": [ 0, { "Ref": "ExistingSubnets" } ] }, + { "Fn::Select": [ 1, { "Ref": "ExistingSubnets" } ] }, + { "Fn::Select": [ 2, { "Ref": "ExistingSubnets" } ] } + ], { + "Fn::If": [ "Private", [ + { "Ref": "SubnetPrivate0" }, + { "Ref": "SubnetPrivate1" }, + { "Ref": "SubnetPrivate2" } + ], [ + { "Ref": "Subnet0" }, + { "Ref": "Subnet1" }, + { "Ref": "Subnet2" } + ] ] + } ] + } }, "Type": "AWS::ElasticLoadBalancing::LoadBalancer" }, "BalancerSecurityGroup": { - "DependsOn": "Vpc", "Properties": { "GroupDescription": { "Fn::Join": [ @@ -1344,9 +1395,10 @@ "ToPort": "5000" } ], - "VpcId": { - "Ref": "Vpc" - } + "VpcId": { "Fn::If": [ "BlankExistingVpc", + { "Ref": "Vpc" }, + { "Ref": "ExistingVpc" } + ] } }, "Type": "AWS::EC2::SecurityGroup" }, @@ -1535,7 +1587,7 @@ "Type": "AWS::S3::Bucket" }, "ApiWebTasks": { - "DependsOn": [ "Balancer", "Cluster", "CustomTopic", "DynamoBuilds", "DynamoReleases", "KernelAccess", "LogGroup", "RegistryAccess", "RegistryBucket", "Subnet0", "Subnet1", "Subnet2", "Vpc" ], + "DependsOn": [ "Balancer", "Cluster", "CustomTopic", "DynamoBuilds", "DynamoReleases", "KernelAccess", "LogGroup", "RegistryAccess", "RegistryBucket" ], "Properties": { "Name": { "Fn::Join": [ "-", [ { "Ref": "AWS::StackName" }, "web" ] ] }, "ServiceToken": { "Fn::GetAtt": [ "CustomTopic", "Arn" ] }, @@ -1574,12 +1626,22 @@ "SEGMENT_WRITE_KEY": "KLvwCXo6qcTmQHLpF69DEwGf9zh7lt9i", "SETTINGS_BUCKET": { "Ref": "Settings" }, "STACK_ID": { "Ref": "AWS::StackId" }, - "SUBNETS": { "Fn::Join": [ ",", [ { "Ref": "Subnet0" }, { "Ref": "Subnet1" }, { "Ref": "Subnet2" } ] ] }, + "SUBNETS": { "Fn::If": [ "BlankExistingVpc", + { "Fn::Join": [ ",", [ { "Ref": "Subnet0" }, { "Ref": "Subnet1" }, { "Ref": "Subnet2" } ] ] }, + { "Fn::Join": [ ",", [ + { "Fn::Select": [ 0, { "Ref": "ExistingSubnets" } ] }, + { "Fn::Select": [ 1, { "Ref": "ExistingSubnets" } ] }, + { "Fn::Select": [ 2, { "Ref": "ExistingSubnets" } ] } + ] ] } + ] }, "SUBNETS_PRIVATE": { "Fn::If": [ "Private", { "Fn::Join": [ ",", [ { "Ref": "SubnetPrivate0" }, { "Ref": "SubnetPrivate1" }, { "Ref": "SubnetPrivate2" } ] ] }, "" ] }, - "VPC": { "Ref": "Vpc" }, + "VPC": { "Fn::If": [ "BlankExistingVpc", + { "Ref": "Vpc" }, + { "Ref": "ExistingVpc" } + ] }, "VPCCIDR": { "Ref": "VPCCIDR" } }, "Image": { "Fn::If": [ "BlankDockerImageApi", @@ -1626,7 +1688,7 @@ "Version": "1.0" }, "ApiMonitorTasks": { - "DependsOn": [ "Balancer", "Cluster", "CustomTopic", "DynamoBuilds", "DynamoReleases", "KernelAccess", "LogGroup", "Subnet0", "Subnet1", "Subnet2", "Vpc" ], + "DependsOn": [ "Balancer", "Cluster", "CustomTopic", "DynamoBuilds", "DynamoReleases", "KernelAccess", "LogGroup" ], "Properties": { "Name": { "Fn::Join": [ "-", [ { "Ref": "AWS::StackName" }, "monitor" ] ] }, "ServiceToken": { "Fn::GetAtt": [ "CustomTopic", "Arn" ] }, @@ -1665,12 +1727,22 @@ "ROLLBAR_TOKEN": "f67f25b8a9024d5690f997bd86bf14b0", "SEGMENT_WRITE_KEY": "KLvwCXo6qcTmQHLpF69DEwGf9zh7lt9i", "STACK_ID": { "Ref": "AWS::StackId" }, - "SUBNETS": { "Fn::Join": [ ",", [ { "Ref": "Subnet0" }, { "Ref": "Subnet1" }, { "Ref": "Subnet2" } ] ] }, + "SUBNETS": { "Fn::If": [ "BlankExistingVpc", + { "Fn::Join": [ ",", [ { "Ref": "Subnet0" }, { "Ref": "Subnet1" }, { "Ref": "Subnet2" } ] ] }, + { "Fn::Join": [ ",", [ + { "Fn::Select": [ 0, { "Ref": "ExistingSubnets" } ] }, + { "Fn::Select": [ 1, { "Ref": "ExistingSubnets" } ] }, + { "Fn::Select": [ 2, { "Ref": "ExistingSubnets" } ] } + ] ] } + ] }, "SUBNETS_PRIVATE": { "Fn::If": [ "Private", { "Fn::Join": [ ",", [ { "Ref": "SubnetPrivate0" }, { "Ref": "SubnetPrivate1" }, { "Ref": "SubnetPrivate2" } ] ] }, "" ] }, - "VPC": { "Ref": "Vpc" } + "VPC": { "Fn::If": [ "BlankExistingVpc", + { "Ref": "Vpc" }, + { "Ref": "ExistingVpc" } + ] } }, "Image": { "Fn::If": [ "BlankDockerImageApi", { "Fn::Join": [ ":", [ "convox/api", { "Ref": "Version" } ] ] }, diff --git a/api/models/app.go b/api/models/app.go index 59f4564fee..81700859db 100644 --- a/api/models/app.go +++ b/api/models/app.go @@ -459,13 +459,26 @@ func (a *App) ExecAttached(pid, command string, height, width int, rw io.ReadWri return nil } -func (a *App) RunAttached(process, command, releaseId string, height, width int, rw io.ReadWriter) error { +// RunAttached runs a command in the foreground (e.g blocking) and writing the output from said command to rw. +func (a *App) RunAttached(process, command, releaseID string, height, width int, rw io.ReadWriter) error { + //TODO: A lot of logic in here should be moved to the provider interface. + resources, err := a.Resources() if err != nil { return err } + if releaseID == "" { + releaseID = a.Release + } + + release, err := GetRelease(a.Name, releaseID) + if err != nil { + return err + } + var container *ecs.ContainerDefinition + unpromotedRelease := false task, err := ECS().DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{ TaskDefinition: aws.String(resources[UpperName(process)+"ECSTaskDefinition"].Id), @@ -474,76 +487,55 @@ func (a *App) RunAttached(process, command, releaseId string, height, width int, return err } - // If no releaseId is provided, use the last promoted release to run this process - // otherwise iterate over the previous revisions (starting with the latest) looking for the releaseId specified. - if releaseId == "" { - releaseId = a.Release - - for _, container = range task.TaskDefinition.ContainerDefinitions { - if *container.Name == process { - break - } + for _, container = range task.TaskDefinition.ContainerDefinitions { + if *container.Name == process { + break } + } - } else { + // This would force an app to be promoted once before being able to run a process. + if container == nil { + return fmt.Errorf("unable to find container for %s", process) + } - ts, err := ECS().ListTaskDefinitions(&ecs.ListTaskDefinitionsInput{ - FamilyPrefix: task.TaskDefinition.Family, - }) + // If the release ID provided does not equal the active one, some logic is needed to determine the next steps. + // - For a previous release, we iterate over the previous TaskDefinition revisions (starting with the latest) looking for the releaseID specified. + // - If the release has yet to be promoted, we use the most recent TaskDefinition with the provided release's environment. + if releaseID != a.Release { + + _, releaseContainer, err := findAppDefinitions(process, releaseID, *task.TaskDefinition.Family, 20) if err != nil { - return err + return nil } - for i := len(ts.TaskDefinitionArns) - 1; i >= 0; i-- { - t, err := ECS().DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{ - TaskDefinition: ts.TaskDefinitionArns[i], - }) - if err != nil { - continue - } - - if t == nil { - return fmt.Errorf("unable to retrieve task definition") - } - - ContainerCheck: - for _, container = range t.TaskDefinition.ContainerDefinitions { - releaseMatch := false - - ReleaseCheck: - for _, kv := range container.Environment { - if *kv.Name == "RELEASE" { - if *kv.Value == releaseId { - releaseMatch = true - break ReleaseCheck - } - } - } - - if *container.Name == process && releaseMatch { - break ContainerCheck - } - container = nil - } + // If container is nil, the release most likely hasn't been promoted and thus no TaskDefinition for it. + if releaseContainer != nil { + container = releaseContainer - if container != nil { - break - } + } else { + fmt.Printf("Unable to find container for %s. Basing container off of most recent release: %s.\n", process, a.Release) + unpromotedRelease = true } } - if container == nil { - return fmt.Errorf("unable to find container for %s", process) - } - - ea := make([]string, 0) + var rawEnvs []string for _, env := range container.Environment { - ea = append(ea, fmt.Sprintf("%s=%s", *env.Name, *env.Value)) + rawEnvs = append(rawEnvs, fmt.Sprintf("%s=%s", *env.Name, *env.Value)) } + containerEnvs := structs.Environment{} + containerEnvs.LoadRaw(strings.Join(rawEnvs, "\n")) - release, err := GetRelease(a.Name, releaseId) - if err != nil { - return err + // Update any environment variables that might be part of the unpromoted release. + if unpromotedRelease { + + releaseEnv := structs.Environment{} + releaseEnv.LoadRaw(release.Env) + + for key, value := range releaseEnv { + containerEnvs[key] = value + } + + containerEnvs["RELEASE"] = release.Id } manifest, err := LoadManifest(release.Manifest, a) @@ -635,7 +627,7 @@ func (a *App) RunAttached(process, command, releaseId string, height, width int, AttachStdin: true, AttachStdout: true, AttachStderr: true, - Env: ea, + Env: containerEnvs.List(), OpenStdin: true, Tty: true, Cmd: []string{"sh", "-c", command}, @@ -701,17 +693,98 @@ func (a *App) RunAttached(process, command, releaseId string, height, width int, return nil } -func (a *App) RunDetached(process, command, releaseId string) error { +// RunDetached runs a command in the background (e.g. non-blocking). +func (a *App) RunDetached(process, command, releaseID string) error { resources, err := a.Resources() if err != nil { return err } + taskDefinitionArn := resources[UpperName(process)+"ECSTaskDefinition"].Id + + if releaseID == "" { + releaseID = a.Release + } + + release, err := GetRelease(a.Name, releaseID) + if err != nil { + return err + } + + // If the releaseID specified isn't the app's current release: + // - We have to find the right task definition OR + // - create a new/temp task definition to run a release that hasn't been promoted. + if releaseID != a.Release { + task, err := ECS().DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{ + TaskDefinition: aws.String(taskDefinitionArn), + }) + if err != nil { + return err + } + + td, _, err := findAppDefinitions(process, releaseID, *task.TaskDefinition.Family, 20) + if err != nil { + return err + + } else if td != nil { + taskDefinitionArn = *td.TaskDefinitionArn + + } else { + // If reached, the release exist but doesn't have a task definition (isn't promoted). + // Create a task definition to run that release. + + var cd *ecs.ContainerDefinition + for _, cd = range task.TaskDefinition.ContainerDefinitions { + if *cd.Name == process { + break + } + cd = nil + } + if cd == nil { + return fmt.Errorf("unable to find container for process %s and release %s", process, releaseID) + } + + env := structs.Environment{} + env.LoadRaw(release.Env) + + for _, containerKV := range cd.Environment { + for key, value := range env { + + if *containerKV.Name == "RELEASE" { + *containerKV.Value = releaseID + break + + } + + if *containerKV.Name == key { + *containerKV.Value = value + break + } + } + } + + taskInput := &ecs.RegisterTaskDefinitionInput{ + ContainerDefinitions: []*ecs.ContainerDefinition{ + cd, + }, + Family: task.TaskDefinition.Family, + Volumes: []*ecs.Volume{}, + } + + resp, err := ECS().RegisterTaskDefinition(taskInput) + if err != nil { + return err + } + + taskDefinitionArn = *resp.TaskDefinition.TaskDefinitionArn + } + } + req := &ecs.RunTaskInput{ Cluster: aws.String(os.Getenv("CLUSTER")), Count: aws.Int64(1), StartedBy: aws.String("convox"), - TaskDefinition: aws.String(resources[UpperName(process)+"ECSTaskDefinition"].Id), + TaskDefinition: aws.String(taskDefinitionArn), } if command != "" { @@ -839,3 +912,59 @@ func (s Apps) Less(i, j int) bool { func (s Apps) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// findAppDefinitions looks for a specific ECS task revision and container definition that matches an app's process name and release ID. +// Given the taskDefinitionFamily prefix, this function will iterate the task's revisions starting with the most recent up to count revisions. +func findAppDefinitions(process, releaseID, taskDefinitionFamily string, count int) (*ecs.TaskDefinition, *ecs.ContainerDefinition, error) { + //TODO: Move this function over to the aws apps provider implemntation once the Run methods have been ported over. + + var containerDefinition *ecs.ContainerDefinition + + ts, err := ECS().ListTaskDefinitions(&ecs.ListTaskDefinitionsInput{ + FamilyPrefix: aws.String(taskDefinitionFamily), + }) + if err != nil { + return nil, nil, err + } + + startRevision := len(ts.TaskDefinitionArns) - 1 + maxRevision := 0 + // Only check the last 20 task definition revisions to run a release from. + // Avoid any API rate limits and iterating over hudreds of task definitions. + if startRevision > count { + maxRevision = startRevision - count + } + + for i := startRevision; i >= maxRevision; i-- { + taskDefinition, err := ECS().DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{ + TaskDefinition: ts.TaskDefinitionArns[i], + }) + if err != nil { + continue + } + + if taskDefinition == nil { + return nil, nil, fmt.Errorf("unable to retrieve task definition for %s", taskDefinitionFamily) + } + + /// Loop logic used to find a previous release. + ContainerSearch: + for _, containerDefinition = range taskDefinition.TaskDefinition.ContainerDefinitions { + + if *containerDefinition.Name != process { + continue ContainerSearch + } + + for _, kv := range containerDefinition.Environment { + if *kv.Name == "RELEASE" { + if *kv.Value == releaseID { + return taskDefinition.TaskDefinition, containerDefinition, nil + } + } + } + } + //////////////////////////////////////////////////// + } + + return nil, nil, nil +} diff --git a/api/models/templates.go b/api/models/templates.go index fb98adc2bb..85f9ec31a4 100644 --- a/api/models/templates.go +++ b/api/models/templates.go @@ -7,6 +7,7 @@ // models/templates/service/s3.tmpl // models/templates/service/sns.tmpl // models/templates/service/sqs.tmpl +// models/templates/service/syslog.tmpl // models/templates/service/webhook.tmpl // DO NOT EDIT! @@ -90,7 +91,7 @@ func templatesAppTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/app.tmpl", size: 25991, mode: os.FileMode(420), modTime: time.Unix(1468573220, 0)} + info := bindataFileInfo{name: "templates/app.tmpl", size: 25991, mode: os.FileMode(420), modTime: time.Unix(1468964874, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -110,7 +111,7 @@ func templatesServiceMysqlTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/service/mysql.tmpl", size: 3447, mode: os.FileMode(420), modTime: time.Unix(1465343086, 0)} + info := bindataFileInfo{name: "templates/service/mysql.tmpl", size: 3447, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -130,7 +131,7 @@ func templatesServicePostgresTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/service/postgres.tmpl", size: 4173, mode: os.FileMode(420), modTime: time.Unix(1465343086, 0)} + info := bindataFileInfo{name: "templates/service/postgres.tmpl", size: 4173, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -150,7 +151,7 @@ func templatesServiceRedisTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/service/redis.tmpl", size: 3162, mode: os.FileMode(420), modTime: time.Unix(1465343086, 0)} + info := bindataFileInfo{name: "templates/service/redis.tmpl", size: 3162, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -170,7 +171,7 @@ func templatesServiceS3Tmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/service/s3.tmpl", size: 4427, mode: os.FileMode(420), modTime: time.Unix(1464072726, 0)} + info := bindataFileInfo{name: "templates/service/s3.tmpl", size: 4427, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -190,7 +191,7 @@ func templatesServiceSnsTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/service/sns.tmpl", size: 2882, mode: os.FileMode(420), modTime: time.Unix(1464072726, 0)} + info := bindataFileInfo{name: "templates/service/sns.tmpl", size: 2882, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -210,7 +211,27 @@ func templatesServiceSqsTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/service/sqs.tmpl", size: 1343, mode: os.FileMode(420), modTime: time.Unix(1464072726, 0)} + info := bindataFileInfo{name: "templates/service/sqs.tmpl", size: 1343, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templatesServiceSyslogTmpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\x58\xdd\x6e\xdb\x36\x14\xbe\xf7\x53\x10\x44\x81\x02\x9d\x23\xc7\x09\x8a\x61\x04\x76\xe1\x25\x75\x97\x2d\x5d\x0d\x3b\x6d\x2f\x86\x5c\x30\x34\xed\x68\x96\x44\x41\xa4\x92\x26\x81\xdf\x7d\x87\xd4\x8f\x45\x8a\x52\x9c\x20\x56\x0b\x03\x32\x3f\x9e\xff\xf3\x9d\xe3\x3c\x3d\xa1\x25\x5f\x85\x09\x47\x58\xf2\xec\x2e\x64\x1c\xa3\xed\x76\xf0\x34\x40\x08\x4f\x7e\x2c\xae\x78\x9c\x46\x54\xf1\xa9\xc8\x62\xaa\xbe\xf3\x4c\x86\x22\xc1\x04\xe1\x93\xe3\xf1\xf1\xd1\xf1\x6f\xf0\x1f\x0f\x35\xf6\x6b\xae\xd2\x5c\x49\x38\xd2\x57\x11\x02\xb9\x19\x4d\xd6\x1c\xbd\xdb\x0c\xd1\x3b\x9a\xa6\x88\xfc\x8e\x82\x49\x9a\x4a\x2d\x1e\x99\x07\x03\x28\x4f\x53\x9e\x19\x40\xf0\x0f\x8d\x39\x1c\x5e\x86\xc9\xa6\x16\x63\x60\xdf\x69\x94\x73\xad\x14\xf0\x06\x59\x2a\x0b\x2e\xc5\xfa\x73\x26\xf2\x14\x6e\xe1\x12\xbe\x1d\x56\xea\x79\xb2\xac\x54\xe1\x6f\x59\xd4\x10\x59\x0b\x6c\xe8\x98\xf3\x95\xd6\xa0\x81\x95\xa4\x41\xf5\x69\x64\xe2\x19\xcd\xc0\x40\x05\x21\xa8\x6f\xba\x72\xcf\xb9\x64\x59\x98\xaa\x32\x46\x8b\x07\x19\x89\x35\xfa\x36\xbf\x1c\x22\x1e\xac\x03\xf4\x5e\xb1\xf4\x17\x15\x49\x32\x1a\xc1\x81\x1c\x07\x29\x05\xef\x55\x46\xc3\x48\xbb\xc5\x44\x4c\xc6\xe3\x93\xd3\x8f\xef\xf1\xb0\x12\x79\xf5\x90\x1a\xd7\x17\x2a\x0b\x93\x35\x76\x6c\x9a\x73\x29\xf2\x8c\xf1\xb7\x88\xfb\x8c\x67\x71\x28\xcb\xfc\x36\x22\x33\xcb\x84\x36\x32\x6c\x28\x29\x4f\x26\xac\xf2\x34\xa2\xf1\xcd\x92\x92\x8b\xe4\x4e\x6c\xf8\x34\x4f\x8a\x83\x61\x13\x5c\x7d\xab\xb5\x39\x82\x76\xe1\xaf\xaf\x36\x4e\xb7\x96\x98\x19\x84\x81\x85\x29\x8d\xda\x32\xa6\x09\x21\x7f\x89\x50\x1b\xf4\xaf\x75\x02\x67\x81\x65\x8c\x7e\x5c\x08\x80\x74\x4e\x5a\x38\xe4\xa8\xb1\x0d\x86\x16\x21\x64\xce\xd7\x8e\xcd\x1e\xcb\xcb\x7b\x34\xa6\x8f\x22\xa1\xf7\x52\x67\xdb\xbd\x72\x3d\xe8\x7a\xb3\x83\xb0\x30\x49\x9f\x30\x26\xf2\x44\x75\x06\xd3\xd8\x56\x82\x2e\x96\xdd\x21\x2d\xa5\x65\xc9\xcb\x42\xba\x4f\x44\x69\x96\x10\xf0\x95\xe8\xc8\x92\x43\x87\xf6\x55\x0a\xbc\xf1\xe9\xd1\x01\x9e\x1c\xad\x35\xe7\x90\x1e\x32\x22\x1f\x5e\x90\xd9\x81\x47\x5f\xdd\xf6\xc6\xc6\xcb\xa2\xbb\x48\xa3\x43\x07\xce\x15\x7f\x4b\x2f\xf2\x9b\x9a\x90\xa6\x61\x04\xdc\x65\xb7\xf6\x39\x4f\x81\x25\xe5\x57\x37\xbf\xcf\x12\x44\x8d\xbd\x1e\xee\x43\x14\xc0\x8b\x2a\x4c\xa8\x36\xa3\xb3\xcc\x3e\x73\x35\x51\xca\x57\x68\x5e\x3e\x31\x27\x5a\xd8\x9e\x2d\x53\xb8\x3f\xa3\x0a\x3e\x0d\x65\xd9\xec\x54\x25\xaf\x64\xa7\x7d\x26\x4d\x5f\xc2\x74\xb9\x13\x4f\xf4\x7b\x87\x54\xed\x67\x63\xa2\x74\xc4\x14\x9f\x89\xa5\xcb\xa2\x78\x71\xfa\x47\xce\x36\xdc\x43\x08\x7d\x6d\x7c\xb4\x4f\x1f\x33\x01\xcc\xfe\xf3\x4d\x1b\xf8\xb5\xd4\x77\xfa\x37\x7f\xd8\x8d\x9c\x91\x34\x33\x36\x78\x0c\x53\x7f\x5e\xec\x91\x6c\xc5\xab\x3d\xf2\xed\xab\x3d\x13\xcb\x72\x71\xa1\x28\xdb\x18\x90\x57\xcc\x9f\x34\x59\x46\xa6\xf3\x70\x98\x2c\xf9\xcf\xe0\xb6\xfc\xa2\x81\x99\x8b\xa8\xa5\xa2\xa7\x29\x0a\xbc\x9d\x0d\xb7\x19\xae\xbd\xc6\xcc\x81\xeb\xc2\xa2\xc4\x13\x28\xa1\xff\x9a\xf3\x0e\x5f\xc1\x89\xc8\x95\x59\xef\x3e\xb6\x29\xc6\xcb\x49\xf6\xc4\x2e\xc1\xae\x3f\x9d\x65\x3c\x91\x32\x8f\xb9\x46\xcf\x44\x14\xb2\x87\x73\xc1\xe0\xbd\x35\xd3\x60\xfb\x81\x1d\xb4\x3c\xb0\x43\xe1\x16\xe0\x6e\x29\xf1\x94\xb1\x54\x92\xec\x54\xb6\x38\xba\xc5\x2f\x9f\x56\x2b\xce\x4c\x3c\x26\x51\x24\xee\xdb\x04\xd4\xbd\x8d\x14\x66\x97\x1b\xb5\xcf\x18\x54\x95\x70\xd0\xbb\x12\xb8\x9d\xe1\x76\x4e\xf3\xcd\x72\x00\xdb\xcb\xfa\xc9\x11\xec\xeb\xe3\x5f\xfd\x25\x0a\xb4\x78\xab\x71\xa3\x66\x35\x98\x8c\x14\x19\x6b\x5a\xef\x50\x4b\x6f\xde\x8a\x20\x74\xe6\xae\x2d\xae\xbc\xd2\x93\x43\x73\xce\x22\x91\x2f\xef\xa9\x62\xb7\x64\x96\xab\x2f\x1c\x76\x63\x76\x4e\x15\xf5\x10\x94\xc1\xfb\xd7\x53\x0f\xb6\x55\x02\xe6\xfa\x73\x65\x60\x40\xd5\x2a\xae\x61\xad\xf1\xef\x5d\x26\x5e\xe5\xba\xd9\xa4\xce\x32\x0e\x31\xad\x66\x52\xa7\xd7\x16\x14\x7e\x40\x70\x1a\xf7\x62\x21\x96\x00\xfc\x74\x07\xb9\x92\x07\x8c\x8e\xb5\x14\x7e\xd0\xff\x0e\x17\x2e\x53\x29\x2b\xf3\xe3\x15\x70\xa4\x98\x06\x37\xdc\x50\xf6\x01\x7d\xf4\xd9\xda\x3f\x87\x6b\x4c\x47\x86\x7c\x63\xb9\xbe\x53\x45\xd4\xf1\xb6\x53\x94\x3f\x98\x0d\x47\x9e\x9f\xdf\xd5\xe3\x49\x54\x2d\xe7\x4d\x0c\xe8\x59\xd0\xf7\xb2\x41\xea\x54\x8f\xde\xc2\x12\xcf\x9c\x7f\x89\x25\x23\x4f\x99\x17\x8f\xcb\xf0\xdd\xdf\xba\x5b\x93\x67\x8f\x6a\xcd\xa8\x67\xe6\x80\xc7\xec\x92\xd4\xab\x5d\xb8\x18\xf5\x67\xba\xb6\xa6\x55\x6d\x99\xb7\x1f\x9a\x81\xb1\xf7\xb7\xcc\x75\xff\xf6\x70\x31\xf9\x02\xa5\x55\xcf\x60\xf3\x07\x8d\xc1\x76\xb0\x5b\x88\xff\x0f\x00\x00\xff\xff\x63\x18\xc5\x88\x97\x12\x00\x00") + +func templatesServiceSyslogTmplBytes() ([]byte, error) { + return bindataRead( + _templatesServiceSyslogTmpl, + "templates/service/syslog.tmpl", + ) +} + +func templatesServiceSyslogTmpl() (*asset, error) { + bytes, err := templatesServiceSyslogTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/service/syslog.tmpl", size: 4759, mode: os.FileMode(420), modTime: time.Unix(1469042489, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -230,7 +251,7 @@ func templatesServiceWebhookTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/service/webhook.tmpl", size: 781, mode: os.FileMode(420), modTime: time.Unix(1464072726, 0)} + info := bindataFileInfo{name: "templates/service/webhook.tmpl", size: 781, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -294,6 +315,7 @@ var _bindata = map[string]func() (*asset, error){ "templates/service/s3.tmpl": templatesServiceS3Tmpl, "templates/service/sns.tmpl": templatesServiceSnsTmpl, "templates/service/sqs.tmpl": templatesServiceSqsTmpl, + "templates/service/syslog.tmpl": templatesServiceSyslogTmpl, "templates/service/webhook.tmpl": templatesServiceWebhookTmpl, } @@ -346,6 +368,7 @@ var _bintree = &bintree{nil, map[string]*bintree{ "s3.tmpl": &bintree{templatesServiceS3Tmpl, map[string]*bintree{}}, "sns.tmpl": &bintree{templatesServiceSnsTmpl, map[string]*bintree{}}, "sqs.tmpl": &bintree{templatesServiceSqsTmpl, map[string]*bintree{}}, + "syslog.tmpl": &bintree{templatesServiceSyslogTmpl, map[string]*bintree{}}, "webhook.tmpl": &bintree{templatesServiceWebhookTmpl, map[string]*bintree{}}, }}, }}, diff --git a/api/provider/aws/service.go b/api/provider/aws/service.go index 9ce535d597..667c53f189 100644 --- a/api/provider/aws/service.go +++ b/api/provider/aws/service.go @@ -30,6 +30,8 @@ func (p *AWSProvider) ServiceCreate(name, kind string, params map[string]string) err = fmt.Errorf("papertrail is no longer supported. Create a `syslog` service instead") case "syslog": req, err = createSyslog(s) + case "fluentd": + req, err = createFluentD(s) default: err = fmt.Errorf("Invalid service type: %s", s.Type) } @@ -189,6 +191,8 @@ func (p *AWSProvider) ServiceLink(name, app, process string) (*structs.Service, switch s.Type { case "syslog": err = p.ServiceLinkSubscribe(a, s) // Update service to know about App + case "fluentd": + err = p.ServiceLinkSubscribe(a, s) // Update service to know about App case "s3", "sns", "sqs": err = p.ServiceLinkSet(a, s) // Updates app with S3_URL case "postgres": @@ -267,6 +271,8 @@ func (p *AWSProvider) ServiceUnlink(name, app, process string) (*structs.Service switch s.Type { case "syslog": err = p.ServiceUnlinkSubscribe(a, s) // Update service to forget about App + case "fluentd": + err = p.ServiceUnlinkSubscribe(a, s) // Update service to forget about App case "s3", "sns", "sqs": err = p.ServiceUnlinkSet(a, s) // Updates app without S3_URL case "postgres": @@ -346,6 +352,33 @@ func createSyslog(s *structs.Service) (*cloudformation.CreateStackInput, error) return req, nil } +func createFluentD(s *structs.Service) (*cloudformation.CreateStackInput, error) { + u, err := url.Parse(s.Parameters["Url"]) + if err != nil { + return nil, err + } + + switch u.Scheme { + case "tcp": + // proceed + default: + return nil, fmt.Errorf("Invalid url scheme `%s`. The only allowed scheme for this service is `tcp`", u.Scheme) + } + + formation, err := serviceFormation(s.Type, nil) + if err != nil { + return nil, err + } + + req := &cloudformation.CreateStackInput{ + Capabilities: []*string{aws.String("CAPABILITY_IAM")}, + StackName: aws.String(serviceStackName(s)), + TemplateBody: aws.String(formation), + } + + return req, nil +} + func serviceFormation(kind string, data interface{}) (string, error) { d, err := buildTemplate(fmt.Sprintf("service/%s", kind), "service", data) if err != nil { @@ -370,6 +403,8 @@ func serviceFromStack(stack *cloudformation.Stack) structs.Service { switch tags["Service"] { case "syslog": exports["URL"] = params["Url"] + case "fluentd": + exports["URL"] = params["Url"] } } diff --git a/api/provider/aws/templates.go b/api/provider/aws/templates.go index 433545ef5b..2ffa0d6707 100644 --- a/api/provider/aws/templates.go +++ b/api/provider/aws/templates.go @@ -1,5 +1,6 @@ // Code generated by go-bindata. // sources: +// provider/aws/templates/service/fluentd.tmpl // provider/aws/templates/service/syslog.tmpl // DO NOT EDIT! @@ -68,6 +69,26 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } +var _templatesServiceFluentdTmpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\x58\x5b\x6f\xdb\x36\x14\x7e\xf7\xaf\x20\x88\x02\x05\x0a\x47\x4e\xdd\x0e\xc3\x08\xec\xc1\x8b\xeb\x2e\x5b\xba\x1a\x76\xda\x3e\x0c\x79\x60\x28\xda\xd1\x2c\x91\x82\x44\xe5\xd2\xc0\xff\x7d\x87\xd4\x25\x22\x45\x29\x4e\x90\xa8\x85\x01\x99\x1f\xcf\xfd\x7c\xe7\x38\xf7\xf7\x28\xe4\x9b\x48\x70\x84\x73\x9e\x5d\x47\x8c\x63\xb4\xdf\x8f\xee\x47\x08\xe1\xd9\x8f\xf5\x39\x4f\xd2\x98\x2a\xbe\x90\x59\x42\xd5\x77\x9e\xe5\x91\x14\x98\x20\x3c\x3d\x7e\x7f\x7c\x74\xfc\x1b\xfc\xc7\x63\x8d\xfd\x5a\xa8\xb4\x50\x39\x1c\xe9\xab\x08\x81\xdc\x8c\x8a\x2d\x47\x6f\x76\x63\xf4\x86\xa6\x29\x22\xbf\xa3\x60\x96\xa6\xb9\x16\x8f\xcc\x83\x01\x54\xa4\x29\xcf\x0c\x20\xf8\x87\x26\x1c\x0e\xcf\x22\xb1\x6b\xc4\x18\xd8\x77\x1a\x17\x5c\x2b\x05\xbc\x41\x56\xca\x82\x33\xb9\xfd\x9c\xc9\x22\x85\x5b\xb8\x82\xef\xc7\xb5\x7a\x2e\xc2\x5a\x15\xfe\x96\xc5\x2d\x91\x8d\xc0\x96\x8e\x15\xdf\x68\x0d\x1a\x58\x4b\x1a\xd5\x9f\x46\x26\x5e\xd2\x0c\x0c\x54\x10\x82\xe6\xa6\x2b\x77\xce\x73\x96\x45\xa9\xaa\x62\xb4\x00\x2d\x42\xcd\xd1\xb7\xd5\xd9\x18\xf1\x60\x1b\xa0\xb7\x8a\xa5\x64\x32\xd9\x98\x83\xf0\x88\xc9\x38\xe6\x4c\xc9\x2c\xe0\xb7\x14\x02\xcd\x03\x26\x13\x32\xfd\x38\x9d\x7e\x7c\x8b\xc7\xb5\xd4\xf3\xbb\xd4\x78\xbf\x56\x59\x24\xb6\xd8\x31\x6b\xc5\x73\x59\x64\x8c\xbf\x44\xe8\x97\x3c\x4b\xa2\xbc\x4a\x71\x2b\x38\xcb\x4c\x02\x54\x45\x2d\x25\xd5\xc9\x8c\xd5\xce\xc6\x34\xb9\x0c\x29\x39\x15\xd7\x72\xc7\x17\x85\x28\x0f\xc6\x6d\x70\xfd\xad\xd6\xe6\x08\x7a\xc8\x40\x73\xb5\x75\xba\xb7\xc4\x2c\x21\x0c\x2c\x4a\x69\xdc\x95\xb1\x10\x84\xfc\x25\x23\x6d\xd0\xbf\xd6\x09\x9c\x05\x96\x31\xfa\x71\x21\x00\x8a\xe5\x36\xef\xe0\x90\xa3\xc6\x36\x18\xba\x84\x90\x15\xdf\x3a\x36\x7b\x2c\xaf\xee\xd1\x84\xfe\x94\x82\xde\xe4\x3a\xdb\xee\x95\x8b\x51\xdf\x9b\x1d\x84\xb5\x49\xfa\x8c\x31\x59\x08\xd5\x1b\x4c\x63\x5b\x05\x3a\x0d\xfb\x43\x5a\x49\xcb\xc4\xd3\x42\x7a\x48\x44\x69\x26\x08\xf8\x4a\x74\x64\xc9\x6b\x87\xf6\x59\x0a\xbc\xf1\x19\xd0\x01\x9e\x1c\x6d\x35\xed\x90\x01\x3e\x22\xef\x9e\x90\xd9\x91\x47\x5f\xd3\xf6\xc6\xc6\xb3\xb2\xbb\x48\xab\x43\x47\xce\x15\x7f\x4b\xaf\x8b\xcb\x86\x93\x16\x51\x0c\xf4\x65\xb7\xf6\x9c\xa7\x40\x94\xf9\x57\x37\xbf\x8f\x12\x44\x83\xbd\x18\x1f\x42\x14\x40\x8d\x2a\x12\x54\x9b\xd1\x5b\x66\x9f\xb9\x9a\x29\xe5\x2b\x34\x2f\x9f\x98\x13\x2d\xec\xc0\x96\x29\xdd\x5f\x52\x05\x9f\x86\xb2\x6c\x76\xaa\x93\x57\xb1\xd3\x21\xc3\x66\x28\x61\xba\xdc\x89\x27\xfa\x83\x73\xaa\xf1\xb3\x35\x54\x7a\x62\x8a\x4f\x64\xe8\xb2\x28\x5e\x7f\xf8\xa3\x60\x3b\xee\x21\x84\xa1\x36\x3e\x3a\xa4\x8f\x99\x04\x66\xbf\x7d\xd1\x06\x7e\x2e\xf5\x7d\xf8\x9b\xdf\x69\xe9\xd5\x18\x0d\x7e\x46\xa9\x3f\x23\xf6\x3c\xb6\x22\xd5\x9d\xf7\xf6\xd5\x81\x59\x65\x39\xb7\x56\x94\xed\x0c\xc8\x2b\xe6\x4f\x2a\xc2\xd8\xf4\x1c\x8e\x44\xc8\x6f\x83\xab\xea\x8b\x16\x66\x25\xe3\x8e\x8a\x81\x76\x28\xf1\x76\x1e\xdc\x36\xb8\xf0\x1a\xb3\x02\x96\x8b\xca\xe2\x16\x50\x3c\xff\xb5\x27\x1d\x3e\x87\x13\x59\x28\xb3\xdb\xfd\xd2\x25\x17\x2f\x1b\xd9\xb3\xba\x02\xbb\xfe\xf4\x16\xf0\x2c\xcf\x8b\x84\x6b\xf4\x52\xc6\x11\xbb\x9b\x4b\x06\xef\x9d\x69\x06\x7b\x0f\x2c\xa0\xd5\x81\x1d\x0a\xb7\xf4\x1e\xd6\x11\x4f\x01\xe7\x2a\x27\x0f\x2a\x3b\xec\xdc\x61\x96\x4f\x9b\x0d\xec\x66\xc6\xe7\x38\x96\x37\x5d\xea\xe9\xdf\x43\x4a\xb3\xab\x75\xda\x67\x0c\xaa\xf7\xa5\x60\x70\x19\x70\x7b\xc2\xed\x99\xf6\x9b\xe5\x00\xb6\x37\xf5\xe9\x11\x2c\xeb\xef\x7f\xf5\x97\x28\x10\xe2\x95\xc6\x4d\xda\xd5\x60\x32\x52\x66\xac\x6d\xbd\x43\x2a\x83\x79\x2b\x83\xd0\x9b\xbb\xae\xb8\xea\xca\x40\x0e\xcd\x39\x8b\x65\x11\xde\x50\xc5\xae\xc8\xb2\x50\x5f\x38\x6c\xc5\x6c\x4e\x15\xf5\x50\x93\xc1\xfb\x17\x53\x0f\xb6\x53\x02\xe6\xfa\x63\x65\x60\x40\xf5\x12\xae\x61\x9d\xc1\xef\x5d\x23\x9e\xe5\xba\xd9\xa1\x4e\x32\x0e\x31\xad\xa7\x51\xaf\xd7\x16\x14\x7e\x3a\x70\x9a\x0c\x62\x21\x96\x00\xfc\x74\x0d\xb9\xca\x5f\x31\x3a\xd6\x3a\xf8\x4e\xff\x7b\xbd\x70\x99\x4a\xd9\x98\x5f\xae\x80\x23\xe5\x34\xb8\xe4\x86\xb2\x5f\xd1\x47\x9f\xad\xc3\x13\xb8\xc1\xf4\x64\xc8\x37\x90\x9b\x3b\x75\x44\x1d\x6f\x7b\x45\xf9\x83\xd9\x72\xe4\xf1\xc9\x5d\x3f\x9e\x44\x35\x72\x5e\xc4\x80\x81\xd5\xfc\x20\x1b\x72\x9d\xea\xc9\x4b\x58\xe2\x99\xf3\x4f\xb1\x64\xe2\x29\xf3\xf2\x71\x19\xbe\xff\x5b\x77\x5f\xf2\x6c\x50\x9d\x19\xf5\xc8\x1c\xf0\x98\x5d\x91\x7a\xbd\x05\x97\xa3\xfe\x44\xd7\xd6\xa2\xae\x2d\xf3\xf6\x43\x33\x30\xf6\xfe\x8a\xb9\x18\xde\x1e\x4e\x67\x5f\xa0\xb4\x9a\x19\x6c\xfe\x94\x31\xda\x8f\x1e\x56\xe1\xff\x03\x00\x00\xff\xff\xdb\x9c\x77\x05\x94\x12\x00\x00") + +func templatesServiceFluentdTmplBytes() ([]byte, error) { + return bindataRead( + _templatesServiceFluentdTmpl, + "templates/service/fluentd.tmpl", + ) +} + +func templatesServiceFluentdTmpl() (*asset, error) { + bytes, err := templatesServiceFluentdTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/service/fluentd.tmpl", size: 4756, mode: os.FileMode(420), modTime: time.Unix(1468603685, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + var _templatesServiceSyslogTmpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\x58\xdd\x6e\xdb\x36\x14\xbe\xf7\x53\x10\x44\x81\x02\x9d\x23\xc7\x09\x8a\x61\x04\x76\xe1\x25\x75\x97\x2d\x5d\x0d\x3b\x6d\x2f\x86\x5c\x30\x34\xed\x68\x96\x45\x81\xa4\x92\x26\x81\xdf\x7d\x87\xd4\x4f\x44\x8a\x52\x9c\x20\x51\x0b\x03\x32\x3f\x9e\xff\xf3\x9d\xe3\x3c\x3c\xa0\x25\x5f\xc5\x29\x47\x58\x71\x79\x13\x33\x8e\xd1\x6e\x37\x78\x18\x20\x84\x27\x3f\x16\x17\x7c\x9b\x25\x54\xf3\xa9\x90\x5b\xaa\xbf\x73\xa9\x62\x91\x62\x82\xf0\xd1\xe1\xf8\xf0\xe0\xf0\x37\xf8\x8f\x87\x06\xfb\x35\xd7\x59\xae\x15\x1c\x99\xab\x08\x81\x5c\x49\xd3\x35\x47\xef\x36\x43\xf4\x8e\x66\x19\x22\xbf\xa3\x68\x92\x65\xca\x88\x47\xf6\xc1\x00\xca\xb3\x8c\x4b\x0b\x88\xfe\xa1\x5b\x0e\x87\xe7\x71\xba\xa9\xc5\x58\xd8\x77\x9a\xe4\xdc\x28\x05\xbc\x45\x96\xca\xa2\x73\xb1\xfe\x2c\x45\x9e\xc1\x2d\x5c\xc2\x77\xc3\x4a\x3d\x4f\x97\x95\x2a\xfc\x4d\x26\x0d\x91\xb5\xc0\x86\x8e\x39\x5f\x19\x0d\x06\x58\x49\x1a\x54\x9f\x56\x26\x9e\x51\x09\x06\x6a\x08\x41\x7d\xd3\x97\x7b\xca\x15\x93\x71\xa6\xcb\x18\x2d\xee\x54\x22\xd6\xe8\xdb\xfc\x7c\x88\x78\xb4\x8e\xd0\x7b\xcd\xb2\x5f\x74\xa2\xc8\x68\x04\x07\x6a\x1c\x65\x14\xbc\xd7\x92\xc6\x89\x71\x8b\x89\x2d\x19\x8f\x8f\x8e\x3f\xbe\xc7\xc3\x4a\xe4\xc5\x5d\x66\x5d\x5f\x68\x19\xa7\x6b\xec\xd9\x34\xe7\x4a\xe4\x92\xf1\xd7\x88\xfb\x8c\xcb\x6d\xac\xca\xfc\x36\x22\x33\x93\xc2\x18\x19\x37\x94\x94\x27\x13\x56\x79\x9a\xd0\xed\xd5\x92\x92\xb3\xf4\x46\x6c\xf8\x34\x4f\x8b\x83\x61\x13\x5c\x7d\x6b\xb4\x79\x82\x1e\xc3\x5f\x5f\x6d\x9c\xee\x1c\x31\x33\x08\x03\x8b\x33\x9a\xb4\x65\x4c\x53\x42\xfe\x12\xb1\x31\xe8\x5f\xe7\x04\xce\x22\xc7\x18\xf3\xf8\x10\x00\x99\x9c\xb4\x70\xc8\x53\xe3\x1a\x0c\x2d\x42\xc8\x9c\xaf\x3d\x9b\x03\x96\x97\xf7\xe8\x96\xde\x8b\x94\xde\x2a\x93\x6d\xff\xca\xe5\xa0\xeb\xcd\x0d\xc2\xc2\x26\x7d\xc2\x98\xc8\x53\xdd\x19\x4c\x6b\x5b\x09\x3a\x5b\x76\x87\xb4\x94\x26\xd3\xe7\x85\x74\x9f\x88\x52\x99\x12\xf0\x95\x98\xc8\x92\xb7\x0e\xed\x8b\x14\x04\xe3\xd3\xa3\x03\x3c\x39\x58\x1b\xce\x21\x3d\x64\x44\x3e\x3c\x23\xb3\x83\x80\xbe\xba\xed\xad\x8d\xe7\x45\x77\x91\x46\x87\x0e\xbc\x2b\xe1\x96\x5e\xe4\x57\x35\x21\x4d\xe3\x04\xb8\xcb\x6d\xed\x53\x9e\x01\x4b\xaa\xaf\x7e\x7e\x9f\x24\x88\x1a\x7b\x39\xdc\x87\x28\x80\x17\x75\x9c\x52\x63\x46\x67\x99\x7d\xe6\x7a\xa2\x75\xa8\xd0\x82\x7c\x62\x4f\x8c\xb0\x3d\x5b\xa6\x70\x7f\x46\x35\x7c\x5a\xca\x72\xd9\xa9\x4a\x5e\xc9\x4e\xfb\x4c\x9a\xbe\x84\x99\x72\x27\x81\xe8\xf7\x0e\xa9\xda\xcf\xc6\x44\xe9\x88\x29\x3e\x11\x4b\x9f\x45\xf1\xe2\xf8\x8f\x9c\x6d\x78\x80\x10\xfa\xda\xf8\x60\x9f\x3e\x66\x02\x98\xfd\xe7\xab\x36\xf0\x4b\xa9\xef\xf8\x6f\x7e\xf7\x38\x72\x46\xca\xce\xd8\xe8\x3e\xce\xc2\x79\x71\x47\xb2\x13\xaf\xf6\xc8\x77\xaf\xf6\x4c\x2c\xc7\xc5\x85\xa6\x6c\x63\x41\x41\x31\x7f\xd2\x74\x99\xd8\xce\xc3\x71\xba\xe4\x3f\xa3\xeb\xf2\x8b\x06\x66\x2e\x92\x96\x8a\x9e\xa6\x28\xf0\x6e\x36\xfc\x66\xb8\x0c\x1a\x33\x07\xae\x8b\x8b\x12\x4f\xa1\x84\xfe\x6b\xce\x3b\x7c\x01\x27\x22\xd7\x76\xbd\xfb\xd8\xa6\x98\x20\x27\xb9\x13\xbb\x04\xfb\xfe\x74\x96\xf1\x44\xa9\x7c\xcb\x0d\x7a\x26\x92\x98\xdd\x9d\x0a\x06\xef\xad\x99\x06\xdb\x0f\xec\xa0\xe5\x81\x1b\x0a\xbf\x00\x1f\x97\x92\x40\x19\x2b\xad\xc8\xa3\xca\x16\x47\xb7\xf8\xe5\xd3\x6a\xc5\x99\x8d\xc7\x24\x49\xc4\x6d\x9b\x80\xba\xb7\x91\xc2\xec\x72\xa3\x0e\x19\x83\xaa\x12\x8e\x7a\x57\x02\xbf\x33\xfc\xce\x69\xbe\x39\x0e\x60\x77\x59\x3f\x3a\x80\x7d\x7d\xfc\x6b\xb8\x44\x81\x16\xaf\x0d\x6e\xd4\xac\x06\x9b\x91\x22\x63\x4d\xeb\x3d\x6a\xe9\xcd\x5b\x11\x84\xce\xdc\xb5\xc5\x95\x57\x7a\x72\x68\xcf\x59\x22\xf2\xe5\x2d\xd5\xec\x9a\xcc\x72\xfd\x85\xc3\x6e\xcc\x4e\xa9\xa6\x01\x82\xb2\xf8\xf0\x7a\x1a\xc0\xb6\x4a\xc0\x5e\x7f\xaa\x0c\x2c\xa8\x5a\xc5\x0d\xac\x35\xfe\x83\xcb\xc4\x8b\x5c\xb7\x9b\xd4\x89\xe4\x10\xd3\x6a\x26\x75\x7a\xed\x40\xe1\x07\x04\xa7\xdb\x5e\x2c\xc4\x12\x80\x9f\x6e\x20\x57\xea\x0d\xa3\xe3\x2c\x85\x1f\xcc\xbf\xb7\x0b\x97\xad\x94\x95\xfd\xf1\x0a\x38\x52\x4c\x83\x2b\x6e\x29\xfb\x0d\x7d\x0c\xd9\xda\x3f\x87\x6b\x4c\x47\x86\x42\x63\xb9\xbe\x53\x45\xd4\xf3\xb6\x53\x54\x38\x98\x0d\x47\x9e\x9e\xdf\xd5\x13\x48\x54\x2d\xe7\x55\x0c\xe8\x59\xd0\xf7\xb2\x41\x99\x54\x8f\x5e\xc3\x92\xc0\x9c\x7f\x8e\x25\xa3\x40\x99\x17\x8f\xcf\xf0\xdd\xdf\xfa\x5b\x53\x60\x8f\x6a\xcd\xa8\x27\xe6\x40\xc0\xec\x92\xd4\xab\x5d\xb8\x18\xf5\x27\xa6\xb6\xa6\x55\x6d\xd9\xb7\x1f\x86\x81\x71\xf0\xb7\xcc\x65\xff\xf6\x70\x36\xf9\x02\xa5\x55\xcf\x60\xfb\x07\x8d\xc1\x6e\x50\x2f\xc4\xff\x07\x00\x00\xff\xff\xf4\xf5\x28\xc2\x96\x12\x00\x00") func templatesServiceSyslogTmplBytes() ([]byte, error) { @@ -83,7 +104,7 @@ func templatesServiceSyslogTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/service/syslog.tmpl", size: 4758, mode: os.FileMode(420), modTime: time.Unix(1464072726, 0)} + info := bindataFileInfo{name: "templates/service/syslog.tmpl", size: 4758, mode: os.FileMode(420), modTime: time.Unix(1468608994, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -140,6 +161,7 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ + "templates/service/fluentd.tmpl": templatesServiceFluentdTmpl, "templates/service/syslog.tmpl": templatesServiceSyslogTmpl, } @@ -185,6 +207,7 @@ type bintree struct { var _bintree = &bintree{nil, map[string]*bintree{ "templates": &bintree{nil, map[string]*bintree{ "service": &bintree{nil, map[string]*bintree{ + "fluentd.tmpl": &bintree{templatesServiceFluentdTmpl, map[string]*bintree{}}, "syslog.tmpl": &bintree{templatesServiceSyslogTmpl, map[string]*bintree{}}, }}, }}, diff --git a/api/provider/aws/templates/service/fluentd.tmpl b/api/provider/aws/templates/service/fluentd.tmpl new file mode 100644 index 0000000000..af93c77e3e --- /dev/null +++ b/api/provider/aws/templates/service/fluentd.tmpl @@ -0,0 +1,192 @@ +{{ define "service" }} +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Outputs": { + {{ range $k, $app := .Apps }} + "{{ upper $app.Name }}Link": { + "Value": "{{ $app.Outputs.LogGroup }}" + }, + {{ end }} + "Url": { + "Value": { + "Ref": "Url" + } + } + }, + "Parameters": { + "Url": { + "Description": "FluentD URL, e.g. 'tcp://fluentd-collector.example.com:24224'", + "Type": "String" + } + }, + "Resources": { + {{ range $k, $app := .Apps }} + "{{ upper $app.Name }}Permission": { + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "Function" + }, + "Principal": { + "Fn::Join": [ + ".", + [ + "logs", + { + "Ref": "AWS::Region" + }, + "amazonaws.com" + ] + ] + }, + "SourceAccount": { + "Ref": "AWS::AccountId" + }, + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:{{ $app.Outputs.LogGroup }}:*" + ] + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "{{ upper $app.Name }}SubscriptionFilter": { + "DependsOn": [ + "{{ upper $app.Name }}Permission" + ], + "Properties": { + "DestinationArn": { + "Fn::GetAtt": [ + "Function", + "Arn" + ] + }, + "FilterPattern": "", + "LogGroupName": "{{ $app.Outputs.LogGroup }}" + }, + "Type": "AWS::Logs::SubscriptionFilter" + }, + {{ end }} + "Function": { + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Join": [ + "-", + [ + "convox", + { + "Ref": "AWS::Region" + } + ] + ] + }, + "S3Key": "fluentd.zip" + }, + "Description": { + "Ref": "Url" + }, + "FunctionName": { + "Ref": "AWS::StackName" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "Role", + "Arn" + ] + }, + "Runtime": "nodejs", + "Timeout": "25" + }, + "Type": "AWS::Lambda::Function" + }, + "Role": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "Path": "/", + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cloudwatch:PutMetricData", + "lambda:InvokeFunction" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "arn:aws:logs:*:*:*" + }, + { + "Action": [ + "cloudformation:DescribeStacks" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:cloudformation:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":stack/", + { + "Ref": "AWS::StackName" + }, + "/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaCloudFormationCloudWatch" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} +{{ end }} diff --git a/api/structs/environment.go b/api/structs/environment.go index 9110acd792..20d200d518 100644 --- a/api/structs/environment.go +++ b/api/structs/environment.go @@ -61,3 +61,15 @@ func (e Environment) LoadRaw(raw string) { e[keyValue[0]] = keyValue[1] } } + +// List retuns a string slic of environment variables. e.g ["KEY=VALUE"] +func (e Environment) List() []string { + + list := []string{} + + for key, value := range e { + list = append(list, fmt.Sprintf("%s=%s", key, value)) + } + + return list +} diff --git a/cmd/convox/builds.go b/cmd/convox/builds.go index 4d12417c61..402a751abf 100644 --- a/cmd/convox/builds.go +++ b/cmd/convox/builds.go @@ -246,23 +246,23 @@ func cmdBuildsCopy(c *cli.Context) error { fmt.Println("OK") - releaseId, err := finishBuild(c, destApp, b) + releaseID, err := finishBuild(c, destApp, b) if err != nil { return stdcli.ExitError(err) } - if releaseId != "" { + if releaseID != "" { if c.Bool("promote") { - fmt.Printf("Promoting %s %s... ", destApp, releaseId) + fmt.Printf("Promoting %s %s... ", destApp, releaseID) - _, err = rackClient(c).PromoteRelease(destApp, releaseId) + _, err = rackClient(c).PromoteRelease(destApp, releaseID) if err != nil { return stdcli.ExitError(err) } fmt.Println("OK") } else { - fmt.Printf("To deploy this copy run `convox releases promote %s --app %s`\n", releaseId, destApp) + fmt.Printf("To deploy this copy run `convox releases promote %s --app %s`\n", releaseID, destApp) } } @@ -523,7 +523,8 @@ func executeBuildDir(c *cli.Context, dir, app, manifest, description string) (st cache := !c.Bool("no-cache") build, err := rackClient(c).CreateBuildSourceProgress(app, tar, cache, manifest, description, func(s string) { - fmt.Printf("\rUploading... %s", strings.TrimSpace(s)) + // Pad string with spaces at the end to clear any text left over from a longer string. + fmt.Printf("\rUploading... %s ", strings.TrimSpace(s)) }) if err != nil { return "", err diff --git a/cmd/convox/env.go b/cmd/convox/env.go index e13d6744ec..d93fd82d4c 100644 --- a/cmd/convox/env.go +++ b/cmd/convox/env.go @@ -156,25 +156,25 @@ func cmdEnvSet(c *cli.Context) error { fmt.Print("Updating environment... ") - _, releaseId, err := rackClient(c).SetEnvironment(app, strings.NewReader(data)) + _, releaseID, err := rackClient(c).SetEnvironment(app, strings.NewReader(data)) if err != nil { return stdcli.ExitError(err) } fmt.Println("OK") - if releaseId != "" { + if releaseID != "" { if c.Bool("promote") { - fmt.Printf("Promoting %s... ", releaseId) + fmt.Printf("Promoting %s... ", releaseID) - _, err = rackClient(c).PromoteRelease(app, releaseId) + _, err = rackClient(c).PromoteRelease(app, releaseID) if err != nil { return stdcli.ExitError(err) } fmt.Println("OK") } else { - fmt.Printf("To deploy these changes run `convox releases promote %s`\n", releaseId) + fmt.Printf("To deploy these changes run `convox releases promote %s`\n", releaseID) } } @@ -199,25 +199,25 @@ func cmdEnvUnset(c *cli.Context) error { fmt.Print("Updating environment... ") - _, releaseId, err := rackClient(c).DeleteEnvironment(app, key) + _, releaseID, err := rackClient(c).DeleteEnvironment(app, key) if err != nil { return stdcli.ExitError(err) } fmt.Println("OK") - if releaseId != "" { + if releaseID != "" { if c.Bool("promote") { - fmt.Printf("Promoting %s... ", releaseId) + fmt.Printf("Promoting %s... ", releaseID) - _, err = rackClient(c).PromoteRelease(app, releaseId) + _, err = rackClient(c).PromoteRelease(app, releaseID) if err != nil { return stdcli.ExitError(err) } fmt.Println("OK") } else { - fmt.Printf("To deploy these changes run `convox releases promote %s`\n", releaseId) + fmt.Printf("To deploy these changes run `convox releases promote %s`\n", releaseID) } } diff --git a/cmd/convox/install.go b/cmd/convox/install.go index c6ab529584..aca2b2cf11 100644 --- a/cmd/convox/install.go +++ b/cmd/convox/install.go @@ -73,105 +73,82 @@ func init() { Usage: "[credentials.csv]", Action: cmdInstall, Flags: []cli.Flag{ + cli.StringFlag{ + Name: "email", + EnvVar: "CONVOX_EMAIL", + Usage: "email address to receive project updates", + }, + cli.StringFlag{ + Name: "password", + EnvVar: "PASSWORD", + Value: "", + Usage: "custom rack password", + }, cli.StringFlag{ Name: "ami", Value: "", - Usage: "ID of the Amazon Machine Image to install", + Usage: "custom AMI for rack instances", }, cli.BoolFlag{ Name: "dedicated", Usage: "create EC2 instances on dedicated hardware", }, + cli.StringFlag{ + Name: "existing-vpc", + Value: "", + Usage: "existing vpc to use for rack instances", + }, + cli.StringFlag{ + Name: "existing-subnets", + Value: "", + Usage: "subnets for existing VPC (specify 3)", + }, cli.IntFlag{ Name: "instance-count", Value: 3, - Usage: "number of EC2 instances (must be greater than 1)", + Usage: "number of instances in the rack", }, cli.StringFlag{ Name: "instance-type", Value: "t2.small", - Usage: "type of EC2 instances", + Usage: "type of instances in the rack", + }, + cli.BoolFlag{ + Name: "private", + Usage: "use private subnets and NAT gateways to shield instances", + }, + cli.StringFlag{ + Name: "private-cidrs", + Value: "10.0.4.0/24,10.0.5.0/24,10.0.6.0/24", + Usage: "private subnet CIDRs", }, cli.StringFlag{ Name: "region", Value: "us-east-1", - Usage: "aws region to install in", + Usage: "aws region", EnvVar: "AWS_REGION", }, cli.StringFlag{ Name: "stack-name", EnvVar: "STACK_NAME", Value: "convox", - Usage: "name of the CloudFormation stack", - }, - cli.BoolFlag{ - Name: "development", - EnvVar: "DEVELOPMENT", - Usage: "create additional CloudFormation outputs to copy development .env file", - }, - cli.StringFlag{ - Name: "key", - Usage: "name of an SSH keypair to install on EC2 instances", - }, - cli.StringFlag{ - Name: "email", - EnvVar: "CONVOX_EMAIL", - Usage: "email address to receive project updates", - }, - cli.StringFlag{ - Name: "password", - EnvVar: "PASSWORD", - Value: "", - Usage: "custom API password. If not set a secure password will be randomly generated.", + Usage: "custom rack name", }, cli.StringFlag{ Name: "version", EnvVar: "VERSION", Value: "latest", - Usage: "release version in the format of '20150810161818', or 'latest' by default", + Usage: "install a specific version", }, cli.StringFlag{ Name: "vpc-cidr", Value: "10.0.0.0/16", - Usage: "The VPC CIDR block", - }, - cli.StringFlag{ - Name: "subnet0-cidr", - Value: "10.0.1.0/24", - Usage: "Subnet 0 CIDR block", - }, - cli.StringFlag{ - Name: "subnet1-cidr", - Value: "10.0.2.0/24", - Usage: "Subnet 1 CIDR block", - }, - cli.StringFlag{ - Name: "subnet2-cidr", - Value: "10.0.3.0/24", - Usage: "Subnet 2 CIDR block", - }, - cli.StringFlag{ - Name: "subnet-private0-cidr", - Value: "10.0.4.0/24", - Usage: "Private Subnet 0 CIDR block", + Usage: "custom VPC CIDR", }, cli.StringFlag{ - Name: "subnet-private1-cidr", - Value: "10.0.5.0/24", - Usage: "Private Subnet 1 CIDR block", - }, - cli.StringFlag{ - Name: "subnet-private2-cidr", - Value: "10.0.6.0/24", - Usage: "Private Subnet 2 CIDR block", - }, - cli.BoolFlag{ - Name: "private", - Usage: "Create private network resources", - }, - cli.BoolFlag{ - Name: "private-api", - Usage: "Put Rack API Load Balancer in private network. Implies --private", + Name: "subnet-cidrs", + Value: "10.0.1.0/24,10.0.2.0/24,10.0.3.0/24", + Usage: "subnet CIDRs", }, }, }) @@ -220,48 +197,43 @@ func cmdInstall(c *cli.Context) error { stdcli.Error(fmt.Errorf("instance-count must be greater than 1")) } - fmt.Println(Banner) + var subnet0CIDR, subnet1CIDR, subnet2CIDR string - distinctId, err := currentId() - if err != nil { - return stdcli.QOSEventSend("cli-install", distinctId, stdcli.QOSEventProperties{Error: err}) - } + if cidrs := c.String("subnet-cidrs"); cidrs != "" { + parts := strings.SplitN(cidrs, ",", 3) + if len(parts) < 3 { + return stdcli.ExitError(fmt.Errorf("subnet-cidrs must have 3 values")) + } - reader := bufio.NewReader(os.Stdin) + subnet0CIDR = parts[0] + subnet1CIDR = parts[1] + subnet2CIDR = parts[2] + } - if email := c.String("email"); email != "" { - distinctId = email - updateId(distinctId) - } else if distinctId == "" && terminal.IsTerminal(int(os.Stdin.Fd())) { - fmt.Print("Email Address (optional, to receive project updates): ") + var subnetPrivate0CIDR, subnetPrivate1CIDR, subnetPrivate2CIDR string - email, err := reader.ReadString('\n') - if err != nil { - return stdcli.QOSEventSend("cli-install", distinctId, stdcli.QOSEventProperties{Error: err}) + if cidrs := c.String("private-cidrs"); cidrs != "" { + parts := strings.SplitN(cidrs, ",", 3) + if len(parts) < 3 { + return stdcli.ExitError(fmt.Errorf("private-cidrs must have 3 values")) } - if strings.TrimSpace(email) != "" { - distinctId = email - updateId(email) - } + subnetPrivate0CIDR = parts[0] + subnetPrivate1CIDR = parts[1] + subnetPrivate2CIDR = parts[2] } - credentialsFile := "" - if len(c.Args()) >= 1 { - credentialsFile = c.Args()[0] - } + var existingVPC, existingSubnets string - creds, err := readCredentials(credentialsFile) - if err != nil { - return stdcli.QOSEventSend("cli-install", distinctId, stdcli.QOSEventProperties{Error: err}) - } - if creds == nil { - return stdcli.QOSEventSend("cli-install", distinctId, stdcli.QOSEventProperties{Error: fmt.Errorf("error reading credentials")}) - } + if vpc := c.String("existing-vpc"); vpc != "" { + existingVPC = vpc - err = validateUserAccess(region, creds) - if err != nil { - stdcli.Error(err) + parts := strings.SplitN(c.String("existing-subnets"), ",", 3) + if len(parts) < 3 { + return stdcli.ExitError(fmt.Errorf("existing-subnets must have 3 values")) + } + + existingSubnets = c.String("existing-subnets") } development := "No" @@ -287,27 +259,26 @@ func cmdInstall(c *cli.Context) error { vpcCIDR := c.String("vpc-cidr") - subnet0CIDR := c.String("subnet0-cidr") - subnet1CIDR := c.String("subnet1-cidr") - subnet2CIDR := c.String("subnet2-cidr") - - subnetPrivate0CIDR := c.String("subnet-private0-cidr") - subnetPrivate1CIDR := c.String("subnet-private1-cidr") - subnetPrivate2CIDR := c.String("subnet-private2-cidr") - versions, err := version.All() if err != nil { - return stdcli.QOSEventSend("cli-install", distinctId, stdcli.QOSEventProperties{Error: fmt.Errorf("error getting versions")}) + return stdcli.QOSEventSend("cli-install", "", stdcli.QOSEventProperties{Error: fmt.Errorf("error getting versions")}) } version, err := versions.Resolve(c.String("version")) if err != nil { - return stdcli.QOSEventSend("cli-install", distinctId, stdcli.QOSEventProperties{Error: fmt.Errorf("error resolving version")}) + return stdcli.QOSEventSend("cli-install", "", stdcli.QOSEventProperties{Error: fmt.Errorf("error resolving version")}) } versionName := version.Version furl := fmt.Sprintf(formationURL, versionName) + fmt.Println(Banner) + + distinctID, err := currentId() + if err != nil { + return stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) + } + fmt.Printf("Installing Convox (%s)...\n", versionName) if isDevelopment { @@ -318,6 +289,43 @@ func cmdInstall(c *cli.Context) error { fmt.Println("(Private Network Edition)") } + reader := bufio.NewReader(os.Stdin) + + if email := c.String("email"); email != "" { + distinctID = email + updateId(distinctID) + } else if terminal.IsTerminal(int(os.Stdin.Fd())) { + fmt.Print("Email Address (optional, to receive project updates): ") + + email, err := reader.ReadString('\n') + if err != nil { + return stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) + } + + if strings.TrimSpace(email) != "" { + distinctID = email + updateId(email) + } + } + + credentialsFile := "" + if len(c.Args()) >= 1 { + credentialsFile = c.Args()[0] + } + + creds, err := readCredentials(credentialsFile) + if err != nil { + return stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) + } + if creds == nil { + return stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: fmt.Errorf("error reading credentials")}) + } + + err = validateUserAccess(region, creds) + if err != nil { + stdcli.Error(err) + } + password := c.String("password") if password == "" { password = randomString(30) @@ -329,8 +337,10 @@ func cmdInstall(c *cli.Context) error { Capabilities: []*string{aws.String("CAPABILITY_IAM")}, Parameters: []*cloudformation.Parameter{ &cloudformation.Parameter{ParameterKey: aws.String("Ami"), ParameterValue: aws.String(ami)}, - &cloudformation.Parameter{ParameterKey: aws.String("ClientId"), ParameterValue: aws.String(distinctId)}, + &cloudformation.Parameter{ParameterKey: aws.String("ClientId"), ParameterValue: aws.String(distinctID)}, &cloudformation.Parameter{ParameterKey: aws.String("Development"), ParameterValue: aws.String(development)}, + &cloudformation.Parameter{ParameterKey: aws.String("ExistingSubnets"), ParameterValue: aws.String(existingSubnets)}, + &cloudformation.Parameter{ParameterKey: aws.String("ExistingVpc"), ParameterValue: aws.String(existingVPC)}, &cloudformation.Parameter{ParameterKey: aws.String("InstanceCount"), ParameterValue: aws.String(instanceCount)}, &cloudformation.Parameter{ParameterKey: aws.String("InstanceType"), ParameterValue: aws.String(instanceType)}, &cloudformation.Parameter{ParameterKey: aws.String("Key"), ParameterValue: aws.String(key)}, @@ -354,12 +364,12 @@ func cmdInstall(c *cli.Context) error { if tf := os.Getenv("TEMPLATE_FILE"); tf != "" { dat, err := ioutil.ReadFile(tf) if err != nil { - return stdcli.QOSEventSend("cli-install", distinctId, stdcli.QOSEventProperties{Error: fmt.Errorf("error reading template file")}) + return stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: fmt.Errorf("error reading template file")}) } t := new(bytes.Buffer) if err := json.Compact(t, dat); err != nil { - return stdcli.QOSEventSend("cli-install", distinctId, stdcli.QOSEventProperties{Error: err}) + return stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) } req.TemplateURL = nil @@ -374,7 +384,7 @@ func cmdInstall(c *cli.Context) error { } } - return stdcli.QOSEventSend("cli-install", distinctId, stdcli.QOSEventProperties{Error: err}) + return stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) } // NOTE: we start making lots of network requests here @@ -386,7 +396,7 @@ func cmdInstall(c *cli.Context) error { host, err := waitForCompletion(*res.StackId, CloudFormation, false) if err != nil { - return stdcli.QOSEventSend("cli-install", distinctId, stdcli.QOSEventProperties{Error: err}) + return stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) } if privateApi == "Yes" { @@ -400,18 +410,18 @@ func cmdInstall(c *cli.Context) error { err := addLogin(host, password) if err != nil { - return stdcli.QOSEventSend("cli-install", distinctId, stdcli.QOSEventProperties{Error: err}) + return stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) } err = switchHost(host) if err != nil { - return stdcli.QOSEventSend("cli-install", distinctId, stdcli.QOSEventProperties{Error: err}) + return stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) } fmt.Println("Success, try `convox apps`") } - return stdcli.QOSEventSend("cli-install", distinctId, ep) + return stdcli.QOSEventSend("cli-install", distinctID, ep) } /// validateUserAccess checks for the "AdministratorAccess" policy needed to create a rack. diff --git a/cmd/convox/run.go b/cmd/convox/run.go index 5abe711a2e..53a6d4755c 100644 --- a/cmd/convox/run.go +++ b/cmd/convox/run.go @@ -97,7 +97,7 @@ func cmdRunDetached(c *cli.Context) error { err = rackClient(c).RunProcessDetached(app, ps, command, release) if err != nil { - return err + return stdcli.ExitError(err) } fmt.Println("OK") diff --git a/cmd/convox/services.go b/cmd/convox/services.go index 314d10efb7..129ce827fd 100644 --- a/cmd/convox/services.go +++ b/cmd/convox/services.go @@ -46,6 +46,10 @@ func init() { "syslog", "--url=tcp+tls://logs1.papertrailapp.com:11235", }, + ServiceType{ + "fluentd", + "--url=tcp://fluentd-collector.example.com:24224", + }, ServiceType{ "webhook", "--url=https://console.convox.com/webhooks/1234", diff --git a/cmd/convox/templates/templates.go b/cmd/convox/templates/templates.go index 544b39d8e8..b5b309c407 100644 --- a/cmd/convox/templates/templates.go +++ b/cmd/convox/templates/templates.go @@ -158,7 +158,7 @@ func initRailsDockerignore() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/rails/.dockerignore", size: 78, mode: os.FileMode(420), modTime: time.Unix(1464724173, 0)} + info := bindataFileInfo{name: "init/rails/.dockerignore", size: 78, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -178,7 +178,7 @@ func initRailsDockerfile() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/rails/Dockerfile", size: 496, mode: os.FileMode(420), modTime: time.Unix(1464724173, 0)} + info := bindataFileInfo{name: "init/rails/Dockerfile", size: 496, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -198,7 +198,7 @@ func initRailsDockerComposeYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/rails/docker-compose.yml", size: 132, mode: os.FileMode(420), modTime: time.Unix(1465401430, 0)} + info := bindataFileInfo{name: "init/rails/docker-compose.yml", size: 132, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -218,7 +218,7 @@ func initRubyDockerignore() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ruby/.dockerignore", size: 30, mode: os.FileMode(420), modTime: time.Unix(1464724173, 0)} + info := bindataFileInfo{name: "init/ruby/.dockerignore", size: 30, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -238,7 +238,7 @@ func initRubyDockerfile() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ruby/Dockerfile", size: 298, mode: os.FileMode(420), modTime: time.Unix(1464724173, 0)} + info := bindataFileInfo{name: "init/ruby/Dockerfile", size: 298, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -258,7 +258,7 @@ func initRubyDockerComposeYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/ruby/docker-compose.yml", size: 200, mode: os.FileMode(420), modTime: time.Unix(1464724173, 0)} + info := bindataFileInfo{name: "init/ruby/docker-compose.yml", size: 200, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -278,7 +278,7 @@ func initSinatraDockerignore() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/sinatra/.dockerignore", size: 30, mode: os.FileMode(420), modTime: time.Unix(1464724173, 0)} + info := bindataFileInfo{name: "init/sinatra/.dockerignore", size: 30, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -298,7 +298,7 @@ func initSinatraDockerfile() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/sinatra/Dockerfile", size: 301, mode: os.FileMode(420), modTime: time.Unix(1464724173, 0)} + info := bindataFileInfo{name: "init/sinatra/Dockerfile", size: 301, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -318,7 +318,7 @@ func initSinatraDockerComposeYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/sinatra/docker-compose.yml", size: 132, mode: os.FileMode(420), modTime: time.Unix(1464724173, 0)} + info := bindataFileInfo{name: "init/sinatra/docker-compose.yml", size: 132, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -338,7 +338,7 @@ func initUnknownDockerignore() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/unknown/.dockerignore", size: 10, mode: os.FileMode(420), modTime: time.Unix(1464724173, 0)} + info := bindataFileInfo{name: "init/unknown/.dockerignore", size: 10, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -358,7 +358,7 @@ func initUnknownDockerfile() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/unknown/Dockerfile", size: 31, mode: os.FileMode(420), modTime: time.Unix(1464724173, 0)} + info := bindataFileInfo{name: "init/unknown/Dockerfile", size: 31, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -378,12 +378,12 @@ func initUnknownDockerComposeYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/unknown/docker-compose.yml", size: 17, mode: os.FileMode(420), modTime: time.Unix(1464724173, 0)} + info := bindataFileInfo{name: "init/unknown/docker-compose.yml", size: 17, mode: os.FileMode(420), modTime: time.Unix(1466784924, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _templatesGo = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x9b\x5b\x6f\x1b\x47\xb2\xc7\x9f\xc5\x4f\x31\x11\x90\x40\x3a\x50\xa4\xb9\x73\x46\x40\x5e\x62\xe7\x00\x79\x38\x09\x90\xcb\xc3\xc1\x7a\x11\xcc\xa5\x47\xcb\x8d\x44\x7a\x49\x2a\xb1\x62\xe4\xbb\x6f\xff\xaa\x6a\x44\x4a\x1c\x52\x32\x25\xc1\xde\x8b\x81\xb1\xc8\x9e\xee\xea\xaa\xee\xaa\x7f\x5d\xba\x79\x76\x16\xbc\x9a\xb5\x2e\xb8\x70\x53\x37\xaf\x96\xae\x0d\xea\x9b\xe0\x62\xf6\x65\x3d\x99\xb6\xd5\xb2\x3a\x1d\xf9\x0e\x8b\xd9\xf5\xbc\x71\x8b\x73\x3e\x2f\xdd\xd5\xdb\x4b\xdf\x6f\x71\x36\x99\x4e\x96\x67\xed\xdf\xab\xe9\xc5\xec\xec\xb4\x9d\x35\xbf\xba\xf9\xe4\x62\x3a\x9b\xbb\xed\xdd\x5e\x4b\xaf\x6e\x72\xb9\xa3\x8f\x52\xfa\xb2\x99\x5d\xbd\x9d\x2d\xdc\xe9\xcd\xd5\xe5\x40\xdf\x79\x35\xb9\x5c\x3c\x38\xab\xf6\xda\x39\xa9\x76\x79\xdc\x9c\xd7\xf5\xcd\xc3\x53\xd2\x69\xf7\x8c\xf4\x78\xd4\x84\x8b\xc9\xb4\x5a\xce\xab\x07\xe7\xec\xfb\xed\x9c\xb6\xef\xf4\xa8\x99\xaf\xa7\xbf\x4e\x67\xbf\x4f\x1f\x9c\xb9\xef\xb7\x73\xe6\xbe\xd3\x43\x33\xdf\x7e\x3a\xbd\x98\xf1\xe6\xf5\xf7\xc1\x77\xdf\xff\x14\x7c\xf3\xfa\xdb\x9f\x3e\x1b\x8d\xde\x56\xcd\xaf\xd5\x85\x5b\xf5\x1f\x8d\x26\x9e\xd0\x7c\x19\x1c\x8d\x0e\x0e\xeb\x1b\xdf\x72\xe8\x3f\x40\x7d\xee\x16\x8b\xb3\x8b\x3f\x26\x6f\x69\xe8\xae\x96\xfc\x99\xcc\xf4\xff\xb3\xc9\xec\x7a\x39\xb9\xe4\xcb\x4c\x06\xbc\xad\x96\x7f\x3b\x83\x73\x3e\xd0\xb0\x58\xce\x27\xd3\x0b\x79\xb7\x9c\x5c\xb9\xc3\xd1\xf1\x68\xd4\x5d\x4f\x9b\xc0\x2c\xe2\x07\x57\xb5\x47\x7c\x08\xfe\xf2\x57\xa6\x3d\x09\xa6\xd5\x95\x0b\x74\xd8\x71\x70\xd4\xb7\xba\xf9\x7c\x36\x3f\x0e\xde\x8f\x0e\x2e\xfe\x90\x6f\xc1\xf9\x57\x01\x5c\x9d\x7e\xe7\x7e\x87\x88\x9b\x1f\x09\xdb\x7c\xff\xfa\xba\xeb\xfc\x77\xc8\x1e\x1f\x8f\x0e\x26\x9d\x0c\xf8\xec\xab\x60\x3a\xb9\x84\xc4\xc1\xdc\x2d\xaf\xe7\x53\xbe\x9e\x04\x5e\xa4\xd3\x6f\xa0\xde\x1d\x1d\x42\x28\xf8\xfc\x1f\xe7\xc1\xe7\xbf\x1d\x2a\x27\x32\x97\xa7\xf1\xe7\x68\x74\xf0\x5b\x35\x0f\xea\xeb\x2e\xd0\x79\x74\x92\xd1\xc1\x2f\xca\xce\x57\xc1\x64\x76\xfa\x6a\xf6\xf6\xe6\xe8\x0b\xdf\xe7\xc4\xf3\xe6\x47\x35\x97\xdf\xf4\x9c\x9e\xbe\xba\xf4\xfb\x74\xe4\xc5\x7f\x26\x7e\x20\xa3\xf4\xb7\x10\xf2\x1d\x95\x6f\x6b\xf4\x6c\x9d\x7e\x0d\xeb\x47\xc7\x27\xf4\x18\xf9\x77\xcb\x9b\xb7\x2e\xa8\x16\x0b\xb7\x64\xc9\xaf\x9b\x25\x54\x44\x3e\xdb\x0f\x3f\xcd\xb4\x9b\x05\xc1\x6c\x71\xfa\xbf\x7e\x5b\xbf\xf5\x5f\x6e\xc7\xd9\x16\xf6\xed\x6b\x14\x64\x0f\xfd\x3f\xdd\xc6\xd1\xc1\x62\xf2\x87\x7c\x9f\x4c\x97\x79\x3a\x3a\xb8\x02\x22\x83\x5b\xa2\xff\xe7\xbf\x4a\xe3\x4f\x5e\x43\x02\xd4\xe4\x94\x4f\xcc\x23\xaa\x72\xd4\x4d\xee\xcf\x75\x1c\x7c\xe7\xa7\x38\x3a\xb6\x19\x98\xd3\xa4\xec\x26\xa7\xcc\xee\x07\x6f\x1f\xfb\xa3\x67\xc7\x8f\x15\x6e\xee\x0e\x85\xd1\x9d\x43\xe1\xd5\x0f\x5d\xe3\xfc\x2e\x01\x44\x7b\x88\x00\xc2\x79\x1a\xb7\x82\x6e\x50\x30\xe9\xb7\x13\xf9\x76\xf1\x7a\x32\xf7\x24\xea\xd9\xec\x72\x7d\x74\x75\xb9\x78\x40\xf2\x9b\x85\x0a\xee\xf1\xa5\x6a\xdc\xfb\x3f\xd7\x46\x9b\x4a\xa0\xe5\xbf\x00\x35\xaf\xc5\x83\xbc\x5e\xc3\x2c\xaf\xe4\xaa\x15\x47\x87\x6f\xde\x45\xdd\x9b\x77\x45\xfd\xe6\x5d\x58\xf8\x27\xb4\xa7\x7c\xf3\x2e\x77\xbe\xdd\xda\x3a\xdf\xa7\x8d\xdf\xbc\x4b\x7d\xbf\xc6\xb7\x37\xfe\x7b\xcc\x67\xff\x54\xfe\xb3\x0b\xd7\xde\xb7\xfa\xce\x25\x6b\x6d\xf4\x6f\x3c\xad\xc8\xcf\xe7\xdb\x4b\x4f\xdf\xf9\x67\xec\x9f\xce\x3f\x69\xe6\xe9\xf8\xbf\x99\xef\x53\x84\x6b\x7c\xd8\xdc\x3c\xd9\xf8\xcd\xbb\xc4\x8f\xcf\x3a\xe5\x21\x6a\xd7\xfb\x1d\xf6\x78\x34\x2c\xb1\xd9\xcb\x10\x0e\xf5\x56\xb5\x86\x63\xde\x00\xb7\xac\xdc\x89\x7f\x75\xb8\xd5\xc5\x1f\xfa\xd7\xc7\xb7\xea\x3e\x4c\x01\x26\xfe\x47\x2c\x75\x9d\x09\x31\xd5\x5b\x3c\xdc\x29\xc3\x43\xb8\x73\x0b\x17\x62\xf0\x9e\xda\x3d\xe5\x79\x8f\x59\x9d\x07\xbb\xa4\x08\x30\x9f\xf3\x20\x2e\x4f\x02\xec\xe0\x7c\xdd\x4c\x8e\xd2\x38\x3c\x96\x76\xb4\xfb\x5c\xb5\xff\xe7\xe9\xe4\xdd\x51\x94\xe6\x45\x91\x25\x45\x94\x9c\x04\xe1\xb1\x07\xb6\x8a\xd9\xbf\x10\x59\xdf\x8b\x80\xe7\x81\xc9\x09\x6b\xe7\xf2\xff\x9f\xb7\x1b\x50\x9d\xec\xd4\x5c\x9c\xd1\x5e\x7a\x5b\x78\x9d\x2a\x23\xd5\xcb\xca\xbf\x6b\xbd\xfe\x25\xfe\x5d\xe4\x9f\xc2\xeb\x5d\x37\x56\x3d\x2c\x2a\xed\x97\xa3\xcb\x9e\x6e\x9e\xfb\xbf\xfe\x7b\xec\xdf\xa7\xbe\x2d\xce\x54\x87\xf9\x5c\xa7\x5e\x0f\x79\xe7\xe7\x49\xfd\x93\xa1\xf3\x91\xea\x7c\xea\xfb\x64\x5e\xef\x23\x3f\xae\xf1\x4f\xee\xdb\x3a\x74\xdf\xff\x2d\x7c\xbf\x0c\xfa\x9e\xaf\xd2\x7f\xae\x23\xe5\xa7\xf5\x6d\x8e\xf9\x3c\x7f\xb5\x9f\xbb\x2e\xf4\x6f\xe3\xc7\x75\x91\xfe\xc5\x66\x72\x3f\x2e\xf5\x7d\x12\x1e\xcf\x43\xd7\xdb\x96\x1f\xdf\x94\x3a\x4f\x95\xab\xcd\xb5\xfe\x7b\xe9\x54\x46\x6c\x0d\xfb\x82\x5f\xe6\xc0\xc6\x52\x3f\x6f\x55\xeb\xfb\xd4\xd3\x6a\x42\x5d\xcf\xc8\xf7\xa9\x90\xd3\xd3\xc9\x91\xb1\xd5\x35\x86\x4f\xec\xae\xf2\xfd\xc7\x3c\xa9\xf6\x89\x4a\x9d\x9f\xf5\x0c\x7d\x5b\x15\x29\x6f\x49\xa9\xe3\x58\x3f\xda\x93\x4c\xf7\x25\xf2\x34\x4a\xf6\x20\xd7\x75\x82\xce\x18\xf9\x6b\x9d\x0f\x3c\xa9\x2b\xe5\x7f\xdc\x29\x2f\xb5\xef\x1b\x8e\x75\xed\x18\x5f\x20\x7b\xae\x74\xd9\x23\xd6\x38\xf4\xe3\x93\x4e\x79\x72\xc8\x90\xe8\x1e\x95\x7e\x8e\xd2\xb0\x27\x67\xbf\x63\xdb\x8f\x58\x9f\xd6\xf8\xa1\xad\x28\x55\x47\x32\xff\x3d\xaa\x74\x3d\xf2\x4a\x75\x24\x6c\xb5\x6f\x0b\x8d\x4c\xf7\x93\xbd\x2e\x73\xd3\x95\x4e\x75\x24\x63\x0d\x6c\xff\xc3\x5c\x65\xab\x43\x95\x0d\xbe\xe3\x4e\x69\x20\x13\x7b\x12\x3a\x1d\x0b\xef\x19\x7b\x81\xce\xf4\xfc\x27\xba\x9f\x05\x3a\x18\xd9\xde\xe4\x8a\x93\xe8\x28\xfa\xda\x1a\x6f\xb4\xa1\x97\xac\x4f\xe7\x74\xaf\xab\x56\xf1\x15\x9d\xc6\x5e\xd8\x37\xf4\x95\x77\xb9\x6f\x6f\x0b\xdd\xa7\x71\xac\x36\x80\xbe\x16\x89\xce\x05\x1f\xbc\x63\x7f\x53\xff\x24\x8d\xea\x15\xeb\x5b\x74\xaa\x8f\xbc\x47\x3f\x19\x8b\x4d\xb1\xbf\xe8\x0b\xf2\xb4\xec\x6b\xa4\x7a\x91\xc1\x73\xa9\x7b\x4e\x7f\xe8\xe7\x66\x37\x79\xa3\xeb\xcb\x9a\x22\x0f\x36\xc2\xbe\xe3\x13\x5c\xa6\xeb\x87\xcd\x45\xb6\x47\x49\xa5\xb2\xb2\x77\x65\xaa\xb6\x81\x4f\xc0\x26\x58\x3f\xf6\x0c\x5b\x42\x9f\x62\xa7\x76\x0f\x26\x38\xd3\xe7\xcc\xd6\x85\x3d\x72\xad\xda\x21\xbc\xe0\x5b\xb0\x21\xf6\x07\x59\xb1\xbf\x7c\x6c\x3a\x8f\x1e\x86\xaa\x27\x95\xe9\xb2\xbc\x63\xbd\x73\x95\x87\xb1\xe8\x8f\xeb\x94\x2e\x3c\x15\x4e\xf5\x34\xab\xd4\x6e\xf1\x87\xe8\x6c\xed\xc7\x96\x66\xf3\xa2\x6f\xd8\x6b\xa5\x7b\x59\x97\xaa\xa7\xb4\x57\x63\xc3\xa7\x5a\xed\xa0\x33\x7f\xc9\xfa\xb0\xf6\x45\xa4\x7b\xe1\x22\xb5\x61\xf4\xb0\xc6\x4e\x0b\xd5\x01\x79\xcf\x7e\x76\xca\x33\xbc\x83\x87\xac\xb1\xe8\x34\xf6\x1e\xab\xbc\x60\x25\xeb\x0f\x6e\xb2\x77\xac\x3d\xb2\x74\xa9\xfa\xf9\x2e\x51\x3c\x41\x87\x90\x89\x75\x62\x8e\x30\xdb\xf4\xd5\x71\xac\x63\x64\xcd\xd1\xf5\xcc\xec\x6d\xb7\xaf\x06\xe3\x9f\xee\xa9\xa1\xb2\xe1\xa7\x57\xaf\x76\x3b\x69\x7a\xec\xe3\xa2\xd7\x58\x7f\x09\x07\xbd\xce\xbe\x79\xe7\xbc\x4c\x3e\x21\xf7\xfc\x4a\xf3\xd7\xff\xbf\xba\xdc\xcb\x49\xe3\x04\x50\xca\x06\x07\xe0\x0d\xa1\x89\x57\x4e\x3a\x35\x27\xdd\xb5\xea\xa4\x01\x01\x9c\x15\x0a\x06\x6d\x40\xa5\xe8\x0d\xab\x52\xc0\x17\x47\xdf\x28\xc0\x46\xb5\x06\x8f\xb4\x03\x90\x38\x3e\x78\x00\x48\x01\x31\xda\x01\xf2\xbc\xd6\x39\x30\x36\xc0\x26\x37\x27\x0c\x0f\xd0\x02\x48\x6a\x33\x9c\xb1\x19\x2f\xca\x2f\x4e\x30\xb3\x40\xa3\x54\x87\x04\x1f\x80\x1e\xa0\x82\xd1\x60\xfc\x9d\x01\x08\x60\x8d\x83\x62\x1e\x69\x4b\x2d\x58\xc8\xd5\xa0\x00\x65\x0c\x46\x00\x8d\xbe\x95\x82\x3d\xc1\x85\x00\x7f\xa7\x8e\x01\xa3\x47\x1e\x09\xaa\xc7\x0a\x1e\xc8\x0b\x18\x25\x06\x0a\x80\x23\x8e\x33\x6c\x14\xac\x2a\x0b\x62\x00\x11\xe4\x2a\xcd\x39\x31\x46\xd6\x28\xd2\x35\xad\x0d\x0c\xe8\x07\x0f\x99\x39\x1f\x82\x9c\xd6\xc0\x08\x10\x62\x1f\xeb\x58\x65\xed\x9d\x3a\x00\xcd\xda\x24\x16\x6c\x01\x6e\xf4\x8d\x58\x7b\xff\x2e\xac\x94\x0e\x8e\x53\x64\x6f\x14\xcc\x9c\xd3\xfd\x65\x2d\x09\x6a\xca\x42\x81\x14\xc0\x41\x06\x71\xc4\xa5\x3a\x0a\xe4\xc3\xa9\x01\x68\x80\x3c\x0e\x02\xbd\x00\x8c\x49\x0c\xf2\x54\x81\x34\x36\xc7\x10\x46\x03\x20\x95\x29\x1f\xe8\x19\xeb\x0e\xc0\x3d\x22\xa1\x58\x69\xfa\xd3\xa1\x6a\x45\x6b\x03\xb0\x36\xeb\x42\xbb\x81\x6b\x45\x6a\x1f\xf8\xda\x10\xea\x25\x40\x6c\x48\xa4\x3e\xd5\x48\x3f\x36\x98\xfd\x40\xc9\xf3\x59\x92\x64\xec\x90\x40\x29\xae\x14\x33\x24\x01\xb6\x20\x0a\xa7\xba\xde\x67\x28\x99\x86\x56\x12\x6b\x70\x8c\x4e\x36\x95\x06\xec\xe8\xf4\xb8\x52\x27\xcf\xdc\x25\x38\xe4\xd4\xa6\x04\xdb\x9c\xda\x54\xe6\x34\xf0\x22\xd8\x01\x77\xe8\xcf\xdc\xe0\x2a\x78\x04\x5f\x12\x10\x8c\x75\x5e\x6c\x1d\x1c\x21\x28\x14\x7b\x89\x2c\xe0\xb4\x80\x9a\x00\x5e\x82\xd3\x3e\xa8\xa9\xf5\x1d\xe3\x62\x0b\x66\x24\x69\x37\xac\xbc\x6f\x67\x95\x25\x06\x65\xa6\x38\x01\x4f\x5b\xec\x6c\x63\x13\xf6\x33\xb1\x0d\x32\x2b\xeb\x1a\x28\x91\x6f\xda\xd5\xc6\xf8\xc7\x9a\xd4\x36\xfe\x9f\xd5\x9a\x06\x45\x30\x3b\x1a\x17\x1f\x6a\x46\xe9\x38\x4e\xa3\xf1\x4b\x98\xd1\xde\x19\x3b\x19\x01\x99\x1e\xce\x17\x65\xc1\x71\xf5\xc1\x80\x6b\xd4\x49\xe3\x24\x50\xde\xd6\x2a\x42\x18\x06\x51\x36\x0a\x59\x15\x5a\x91\x82\x06\xdf\x71\x38\x38\x64\x8c\x49\x1c\x4d\xad\xce\xc3\xc5\xea\x6c\x31\x9a\xc8\x14\x5d\xb2\xc3\xb1\xf2\xd0\x9a\xa1\xe1\x24\x88\xc4\x71\x36\x18\x23\xd1\xb2\x33\x67\x2f\xd9\x4f\xa2\xce\x7c\x6c\xd1\xb0\x18\x79\xab\xc6\x8f\x3c\x89\x55\x0b\xc8\x28\xcb\x5a\xb3\xc0\x71\x61\x59\xb3\xa7\x13\xe7\x1a\x08\x34\x16\x28\x88\x53\xee\x54\x5e\x0c\x93\x2a\x01\xce\x90\xa0\x07\x5e\xc6\xb9\xf2\x4d\x74\x4f\x24\x1e\x45\x1a\xf0\x34\x96\x99\xe3\x04\x09\xa4\xa8\x22\x94\xe6\xf4\x09\x28\x9c\x65\x9f\x12\x8c\x94\x0a\x12\x64\x0d\x00\x49\x9f\x6d\x13\x1c\x01\x4e\x9d\x65\x87\x92\x45\x5a\x26\x4f\xe6\xe4\x2c\xbb\xa0\x0d\x80\x61\xbd\x90\x59\x32\xad\x5a\xe7\x45\x2e\x32\x5c\xfe\xb2\x2e\x75\xa3\x81\x08\x00\x56\x58\x86\x4a\xa6\xc9\xde\x10\x1c\x21\x37\x99\x46\xd9\xe9\x3a\x10\xcc\x31\x8f\x64\xed\x99\x65\x7d\x99\x82\x62\x61\x59\x4e\x6e\xeb\xc0\xfc\x80\x2b\xfb\x4f\x1f\x67\xf3\xd0\x07\x3d\x20\x03\x47\x8f\xe8\x4b\x15\x44\xf4\xa7\x52\xd0\x45\xe7\x58\x3f\xe6\x22\xd0\x00\x40\x65\xbf\xd0\x15\x0b\xfe\xd8\xf3\x71\xa3\x7b\x9e\x59\xf6\x5d\x58\x75\x23\x21\x20\xcd\x95\x0e\xfb\x94\x58\x86\x8a\x6e\x12\xac\xa0\x07\x52\x69\xb1\xca\x06\x99\x3e\x3a\xca\xba\x67\x56\x95\x41\x2f\x6a\xfb\x0c\x90\xc6\xa6\xdb\x99\x7d\x96\xfd\xab\x2d\xe3\xac\x34\xa0\x02\xc0\x25\xc0\xad\x34\xe8\xcc\x4d\x9f\x59\x73\x02\x30\xd6\x9a\xb9\xe3\x44\xab\x3b\xe8\x18\xc1\x97\x64\xa1\x63\xab\x2a\xb5\xfa\x1e\x67\x84\x9e\xe1\x78\xd8\xf3\xd2\xf4\x03\x1d\x80\x2e\x8e\x02\xf9\xd1\x5b\xd6\x24\x6c\x37\x01\x1e\xdd\x80\x1f\xf6\xb3\x0f\x74\x57\x01\xd7\x36\x80\xdf\x3f\xd9\xbb\x47\xe4\x3e\xb8\xef\x4a\xf5\xee\x0d\xdd\x03\xd7\x5f\x2a\xd1\xdb\xe4\xdd\x20\x3d\x2d\xf3\x4f\x07\xd3\x9f\x98\xe6\xf5\xa1\x47\xda\x6a\xda\xe0\x2c\xcc\x01\xa9\xa5\x2e\x65\x21\x13\x1a\x4a\xc8\x02\xf2\xf1\x9e\xfa\x28\xf5\x36\x10\x00\xb4\xa5\x66\x41\xba\x91\xd9\x77\xd2\x06\x52\x18\x49\x03\xab\x15\xe2\xf3\xb7\xe9\x6b\x26\x56\x57\x91\xd4\x27\x53\xc4\x08\xb3\xd5\xf9\x43\x6c\x6d\xa0\x31\x0f\xe9\x58\xdf\x27\xb5\x7e\xb1\xfd\xed\x69\x82\x02\xd4\xf1\x68\xe7\x33\x08\x2c\xa8\x6b\x75\x40\x1e\xac\x54\x3c\x8c\x85\x40\x20\x08\xb5\x90\xa8\x47\x87\x54\x53\xb5\xc2\x6a\x64\x91\x21\x6d\x69\x9f\xfb\x87\x14\x07\x19\xa4\xfe\x6c\xe8\x2a\x56\x67\xe9\x5f\x3c\x10\x7a\xe1\x31\xa0\x4d\x3d\x06\x84\x05\xb9\x1e\x0e\xbd\x9e\x9a\xe1\x0c\x92\xba\x6f\xa5\x8f\xc9\x6f\x06\x09\xed\x61\xb3\x2f\x9b\xdd\x6c\x97\xc7\x2c\x38\x4a\xe2\x0f\xb5\xe0\x2c\x0d\xa3\x34\x09\x9f\xcd\x82\xaf\xeb\x9b\x7f\xa9\xdc\x66\xab\x42\x97\x5a\x97\x21\xdf\x19\xdb\x61\xc0\x36\x85\xbe\x27\xf3\x9e\xba\x7c\x8f\xca\x9a\x1a\x6f\x5c\x7c\x19\x50\xe0\x7b\xa3\x1f\xad\xbb\xc3\xbc\x3f\xaf\xda\x0e\xf0\x6f\x0a\x8b\xe2\x7d\x5c\x8f\x73\x2b\xff\xde\x49\x84\x1c\x25\x77\xa6\xa5\xce\x82\xe7\xfe\xd8\x2f\x55\x38\x24\xa8\x03\xb2\x63\x3b\x7a\x43\x4b\x09\x80\xa5\xf2\x54\xa8\x56\x13\x04\x95\x95\x06\xec\x9d\x1d\x65\xe0\x56\x24\xa8\x6b\xcd\xd5\x58\x50\x5f\xda\xf1\x13\xb4\x32\xab\x66\x11\xb8\xe1\xc6\x38\x62\xc9\x63\x3d\x36\x20\xb8\xcf\x0d\xf2\x49\x2c\x38\x8a\x19\xdb\x11\x15\xfc\x52\x0d\x95\xca\xe0\xd8\x02\x3c\x3b\x1e\x4f\xad\x54\x2f\xc1\x66\xa4\xee\x85\xc0\x6f\x6c\xee\x84\x60\x98\x24\x06\x37\x48\x02\xd2\x27\x17\xb1\xb9\x19\x8e\x02\x08\x88\xe5\x18\x73\xac\x6e\x06\xfe\x39\xe2\x21\xe3\xa7\x12\x48\x40\x8a\x55\xf2\x50\x41\xc3\x0d\x4a\x75\x31\xd6\xe0\x99\xa0\x17\xeb\xa5\x5a\x27\x47\x66\x91\x5a\x6f\x52\x68\x25\xa2\x32\xfe\xa9\xba\xb6\x76\x8c\x48\x65\x57\x8e\x4c\x43\xa3\x19\xaa\xbc\x32\xaf\x05\xa1\xac\x61\x1f\x1c\x27\x96\xcc\xc8\xf1\x61\xad\x01\x7b\xbc\x96\x38\x10\x28\x87\x9d\xee\x09\xf4\xa5\x12\x99\xaf\x2a\x9f\xf0\x57\x98\x8b\x24\xe1\x71\x76\xdc\xc6\x78\xd6\xc4\x59\xa5\x97\x36\x67\x15\x94\xca\x92\x49\xb9\x86\x50\x2b\x1a\xc9\xf1\x8b\x53\xb4\x63\xef\x68\x63\xae\xce\x8e\xb8\x04\x95\x08\xa8\x13\xdd\x57\x3e\xcb\x11\x58\xa9\xe1\x4a\x6b\x49\x21\xc7\xb6\x72\xcc\x68\x57\x25\x38\x22\x95\xea\x49\xa2\x49\x47\x98\x6c\x22\x1d\x3a\x2e\x47\x40\xbd\x9b\xaf\xb6\x07\xd5\x77\xac\xe5\xa9\x38\x77\x2f\xa4\xbe\x7b\x73\x6f\x17\xc4\x7d\x50\x40\x3d\xc4\xf2\xf3\xc3\xdb\x40\x38\x1d\x97\x1f\xbd\x44\x72\x2b\xfb\x73\x1c\x9a\x34\x9a\x73\x63\xc6\x85\xdd\x6c\xa0\xd8\x0f\xb4\x38\x2b\x7e\x4b\xf4\x1a\xda\x29\x68\xa9\xf0\x86\xb9\x13\x39\x92\xd3\x31\x06\xd3\x0b\xcd\x21\x73\x32\x8a\x0a\x03\x4b\x52\xcc\x6f\x55\x95\x31\x25\xcc\x0a\xd8\xc3\x04\x81\x18\x4c\x93\x82\xbc\xa8\x6d\xac\xa7\xa9\x40\x01\xe6\x49\xae\x88\xa9\x93\xc7\x53\x6c\x94\x39\xec\x44\x5c\x4e\xef\x1b\xad\x0d\xc0\x27\x79\xad\x40\xa2\x9d\x94\x03\x79\x38\x7a\xe0\x80\xda\x44\x64\x27\xad\xe4\xd0\x64\x10\x72\xf2\xeb\x34\x9a\xa6\x1e\x11\xda\x41\x05\x7d\xf9\x2c\xb5\x94\xd6\xf2\xe8\x62\x55\x4b\x90\xc3\x1e\xa7\x32\xca\xc9\xb7\x53\x5e\x31\x79\xa0\x9e\x4c\x83\x7a\x0d\xa6\x49\xdd\x24\x32\x9e\x71\x07\xd4\x84\x90\x4d\x0a\xac\x8d\x41\x59\xa1\xb4\x2a\xab\x61\xf0\x50\x0b\x91\x43\x8e\x5a\x0f\x2e\xe4\xc4\x3e\xd2\x35\xed\x8c\x27\xfa\x13\x08\x51\x94\x15\xf8\xb4\x9b\x13\x9d\x9d\xde\x03\x37\xa1\xdd\x0a\xe0\x04\x16\xf8\xe0\xb6\x47\x68\x27\xef\xf0\x4b\xed\x89\x1a\x55\xdd\x0c\x43\x48\x65\x07\x1f\x92\x93\x67\xb6\x4e\x0f\x05\x4b\x4f\x0e\xfe\x07\x28\xdd\x83\x93\x47\x85\xfe\x03\x64\x3e\x1c\x5c\x5e\x38\xf0\xdf\x26\x4c\x0f\x35\xe1\xc7\x8e\xa3\x7e\xd4\xeb\xcd\xff\x69\xa1\xff\x80\xd8\xfb\x29\xf3\x00\xa1\x95\x2e\x0f\x5e\x44\xdf\xd4\xe4\x01\x1a\x8f\x55\xe4\xed\x72\x3c\xab\x1e\x6f\x11\xe4\x53\x49\x06\xee\xac\xc2\xd3\xf3\x81\xc8\xde\xa5\x6b\xf9\x40\x7e\x2f\x1f\x48\xf5\xa4\xbf\xcf\x07\x00\x67\x9c\x21\x8e\x87\xf8\x96\x78\x16\xb5\x07\x8c\x6b\xfb\x2c\x05\xe9\x50\xaf\x00\xe1\x50\x53\x03\xe4\xa6\x77\x92\xb9\xc5\x74\x56\x4c\xad\x2c\x0e\x95\x32\x97\x15\xe4\xe1\x03\x5e\x9d\x5d\x1b\xc4\xe9\x10\x27\x52\xf2\xa1\xb8\x2c\xb1\x75\xa3\x65\x24\xb9\xde\xd7\x9f\xe6\x99\xd3\xc3\x21\x64\x76\xda\xd7\xda\x55\xda\xc6\xae\x0d\xca\x61\x88\xd3\x75\xc2\x69\xc8\x75\x2f\xbb\xce\x58\x58\xa1\x5c\xae\x91\x15\xab\x22\xae\xdc\x02\x08\x95\x2e\xe3\xb8\xce\x44\xae\x81\x33\x93\x1c\x67\xac\xf1\x70\x6c\x79\x4b\x6c\xc5\x7f\xf8\x93\xab\x4d\x9d\x96\xf5\x0a\xbb\x1d\xd0\xda\x0d\x80\xd6\xae\x99\x8d\xc7\x9a\xc3\xe0\xa8\xe5\xf4\xd0\x82\x8c\xc4\xcc\x9b\xf5\x62\x2e\x29\x94\x97\xda\x0f\x07\x4c\xcc\x4e\x9f\xa6\xbf\x52\x67\xb7\x16\x3a\x8b\xd7\x9d\xe5\x07\x72\x4d\x32\xd2\xbc\x85\xf1\xe2\xdc\x9d\x1d\x5e\xd8\xf5\xb1\xd0\xae\xb9\xb1\xa7\x89\x5d\xa7\x94\xc3\x04\xa7\x6d\xf0\x44\x7f\xf2\x38\xc9\x03\x33\x95\x41\x62\xfa\x5c\xf7\x85\x03\x18\x72\x00\x67\xf9\x80\xdc\x24\xe9\x56\xd7\xe9\x68\x83\x67\xf6\x88\x72\x25\x4e\x59\x72\x8f\x56\xd7\x98\x77\x72\x2d\x33\x59\x1d\x56\xa0\xab\x94\x12\x87\xae\x57\x11\xcc\xb0\x2e\xbd\xee\xc8\x15\xcd\xe1\xdc\x60\xc3\x78\x9e\x01\x08\xef\x66\x08\x9b\xbf\xb3\x79\x00\x03\x3f\x24\x4f\xd8\xc6\xfe\x8b\xe0\xdf\x40\xb6\x90\x84\xd1\xa7\x84\x7e\xff\x2d\xbf\xff\xbb\x96\xdf\xb7\x6c\xf3\x33\x58\xeb\x50\x18\xbe\xfd\x67\x6f\x0f\xd8\xee\x87\x07\xe3\xbb\x05\x7b\x11\x3b\x7e\xde\x52\xfc\x33\xdb\xf3\xcf\xfa\xbb\xbf\xe7\xfb\x39\xce\x8e\x9f\xdb\xa0\x7b\xa1\xdd\x8c\x1c\xf2\x21\xc4\x3e\xb1\xdd\x44\x14\xbb\x1d\xd6\xcd\x01\x96\xf7\xd3\xcb\x01\x42\x2b\x9d\x1c\xfc\x75\xe5\xa6\x3a\x0e\xd0\x78\xac\x2a\x6e\x97\xe3\x59\xd5\x70\x8b\x20\xbd\x06\x7e\xec\x70\xfa\xce\x2a\xec\x1d\x4e\x13\x06\x12\xb2\xf5\xf7\x65\xb8\xcf\x20\xbf\x86\x69\x57\x6e\x04\xb5\xa4\xe6\x51\xdb\xdd\x0d\xc2\x68\xf9\xf5\x8d\xd5\xa9\x50\x4f\x5c\x10\xb4\xa4\x1e\x64\x77\x32\x04\xb6\x43\xbd\xe4\xca\xdf\xd4\xd4\x39\xb6\x7b\x3d\xdb\x54\x9a\x30\x95\x5f\x4d\xe4\x76\x67\x24\xea\x1e\xa7\xd2\xfb\x87\x45\x1b\x64\x36\xd5\x79\x57\x58\xb4\x31\x7c\x2f\x4d\x7e\xa9\xb0\x68\x48\x82\x3e\x2c\xfa\xd8\x51\xd1\x9d\x05\x78\x62\x54\x44\x21\x22\xb5\xa8\x87\x87\x1f\x50\xfc\x33\x00\x00\xff\xff\x43\xbb\x54\x60\x00\x40\x00\x00") +var _templatesGo = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x9b\x5b\x6f\x23\xc7\x72\xc7\x9f\xc5\x4f\x31\x47\xc0\x39\x90\x82\x3d\xd2\xdc\x2f\x02\xfc\x72\x76\x1d\xc0\x0f\xb1\x01\x5f\x1e\x82\x6c\x60\xcc\xa5\x47\x61\x2c\x91\x1b\x92\xb2\x57\x5e\xf8\xbb\xa7\x7f\x55\x35\x22\x57\x1c\x52\x12\x25\xc1\x9b\xcb\x02\xb3\x22\x7b\xba\xab\xab\xba\xab\xfe\x75\xe9\xe6\xf9\x79\xf0\x76\xde\xb9\xe0\xd2\xcd\xdc\xa2\x5e\xb9\x2e\x68\x6e\x83\xcb\xf9\xdf\x9b\xe9\xac\xab\x57\xf5\xd9\xc4\x77\x58\xce\x6f\x16\xad\x5b\x5e\xf0\x79\xe5\xae\x3f\x5c\xf9\x7e\xcb\xf3\xe9\x6c\xba\x3a\xef\xfe\xb3\x9e\x5d\xce\xcf\xcf\xba\x79\xfb\x8b\x5b\x4c\x2f\x67\xf3\x85\xdb\xdd\xed\x9d\xf4\xea\xa7\x57\x7b\xfa\x28\xa5\xbf\xb7\xf3\xeb\x0f\xf3\xa5\x3b\xbb\xbd\xbe\x1a\xe9\xbb\xa8\xa7\x57\xcb\x07\x67\xd5\x5e\x7b\x27\xd5\x2e\x8f\x9b\xf3\xa6\xb9\x7d\x78\x4a\x3a\xed\x9f\x91\x1e\x8f\x9a\x70\x39\x9d\xd5\xab\x45\xfd\xe0\x9c\x43\xbf\xbd\xd3\x0e\x9d\x1e\x35\xf3\xcd\xec\x97\xd9\xfc\xb7\xd9\x83\x33\x0f\xfd\xf6\xce\x3c\x74\x7a\x68\xe6\xbb\x4f\x67\x97\x73\xde\xbc\xfb\x2e\xf8\xf6\xbb\x1f\x83\xaf\xdf\x7d\xf3\xe3\x5f\x26\x93\x0f\x75\xfb\x4b\x7d\xe9\xd6\xfd\x27\x93\xa9\x27\xb4\x58\x05\x27\x93\xa3\xe3\xe6\xd6\xb7\x1c\xfb\x0f\x50\x5f\xb8\xe5\xf2\xfc\xf2\xf7\xe9\x07\x1a\xfa\xeb\x15\x7f\xa6\x73\xfd\xff\x7c\x3a\xbf\x59\x4d\xaf\xf8\x32\x97\x01\x1f\xea\xd5\x7f\x9c\xc3\x39\x1f\x68\x58\xae\x16\xd3\xd9\xa5\xbc\x5b\x4d\xaf\xdd\xf1\xe4\x74\x32\xe9\x6f\x66\x6d\x60\x16\xf1\xbd\xab\xbb\x13\x3e\x04\xff\xf6\xef\x4c\xfb\x26\x98\xd5\xd7\x2e\xd0\x61\xa7\xc1\xc9\xd0\xea\x16\x8b\xf9\xe2\x34\xf8\x34\x39\xba\xfc\x5d\xbe\x05\x17\x5f\x05\x70\x75\xf6\xad\xfb\x0d\x22\x6e\x71\x22\x6c\xf3\xfd\x1f\x37\x7d\xef\xbf\x43\xf6\xf4\x74\x72\x34\xed\x65\xc0\x5f\xbe\x0a\x66\xd3\x2b\x48\x1c\x2d\xdc\xea\x66\x31\xe3\xeb\x9b\xc0\x8b\x74\xf6\x35\xd4\xfb\x93\x63\x08\x05\x7f\xfd\xaf\x8b\xe0\xaf\xbf\x1e\x2b\x27\x32\x97\xa7\xf1\xc7\x64\x72\xf4\x6b\xbd\x08\x9a\x9b\x3e\xd0\x79\x74\x92\xc9\xd1\xcf\xca\xce\x57\xc1\x74\x7e\xf6\x76\xfe\xe1\xf6\xe4\x6f\xbe\xcf\x1b\xcf\x9b\x1f\xd5\x5e\x7d\x3d\x70\x7a\xf6\xf6\xca\xef\xd3\x89\x17\xff\x85\xf8\x81\x8c\xd2\xdf\x41\xc8\x77\x54\xbe\xad\xd1\xb3\x75\xf6\x0f\x58\x3f\x39\x7d\x43\x8f\x89\x7f\xb7\xba\xfd\xe0\x82\x7a\xb9\x74\x2b\x96\xfc\xa6\x5d\x41\x45\xe4\xb3\xfd\xf0\xd3\xcc\xfa\x79\x10\xcc\x97\x67\xff\xec\xb7\xf5\x1b\xff\xe5\x6e\x9c\x6d\xe1\xd0\xbe\x41\x41\xf6\xd0\xff\xd3\x6d\x9c\x1c\x2d\xa7\xbf\xcb\xf7\xe9\x6c\x95\xa7\x93\xa3\x6b\x20\x32\xb8\x23\xfa\x2f\xfe\xab\x34\xfe\xe8\x35\x24\x40\x4d\xce\xf8\xc4\x3c\xa2\x2a\x27\xfd\xf4\xfe\x5c\xa7\xc1\xb7\x7e\x8a\x93\x53\x9b\x81\x39\x4d\xca\x7e\x7a\xc6\xec\x7e\xf0\xee\xb1\x3f\x78\x76\xfc\x58\xe1\xe6\xf3\xa1\x30\xba\x77\x28\xbc\xfa\xa1\x1b\x9c\x7f\x4e\x00\xd1\x1e\x22\x80\x70\x9e\xc6\x9d\xa0\x5b\x14\x4c\xfa\xdd\x44\xbe\x59\xbe\x9b\x2e\x3c\x89\x66\x3e\xbf\xda\x1c\x5d\x5f\x2d\x1f\x90\xfc\x76\xa9\x82\x7b\x7c\xa9\x5b\xf7\xe9\x8f\x8d\xd1\xa6\x12\x68\xf9\xcf\x40\xcd\x3b\xf1\x20\xef\x36\x30\xcb\x2b\xb9\x6a\xc5\xc9\xf1\xfb\x8f\x51\xff\xfe\x63\xd9\xbc\xff\x18\x96\xfe\x09\xed\xa9\xde\x7f\xcc\x9d\x6f\xb7\xb6\xde\xf7\xe9\xe2\xf7\x1f\x53\xdf\xaf\xf5\xed\xad\xff\x1e\xf3\xd9\x3f\xb5\xff\xec\xc2\x8d\xf7\x9d\xbe\x73\xc9\x46\x1b\xfd\x5b\x4f\x2b\xf2\xf3\xf9\xf6\xca\xd3\x77\xfe\x29\xfc\xd3\xfb\x27\xcd\x3c\x1d\xff\x37\xf3\x7d\xca\x70\x83\x0f\x9b\x9b\x27\x2b\xde\x7f\x4c\xfc\xf8\xac\x57\x1e\xa2\x6e\xb3\xdf\xf1\x80\x47\xe3\x12\x9b\xbd\x8c\xe1\xd0\x60\x55\x1b\x38\xe6\x0d\x70\xc7\xca\xbd\xf1\xaf\x8e\x77\xba\xf8\x63\xff\xfa\xf4\x4e\xdd\xc7\x29\xc0\xc4\x3f\x89\xa5\x6e\x32\x21\xa6\x7a\x87\x87\x7b\x65\x78\x08\x77\xee\xe0\x42\x0c\xde\x53\xbb\xa7\x3c\x9f\x30\xab\x8b\x60\x9f\x14\x01\xe6\x73\x11\xc4\xd5\x9b\x00\x3b\xb8\xd8\x34\x93\x93\x34\x0e\x4f\xa5\x1d\xed\xbe\x50\xed\xff\x69\x36\xfd\x78\x12\xa5\x79\x15\xa6\x71\x5a\xfa\x61\xe1\xa9\x07\xb6\x9a\xd9\xff\x26\xb2\x7e\x12\x01\x2f\x02\x93\x13\xd6\x2e\xe4\xff\x3f\xee\x36\xa0\x7e\xb3\x57\x73\x71\x46\x07\xe9\x6d\xe9\x75\xaa\x8a\x54\x2f\x6b\xff\xae\xf3\xfa\x97\xf8\x77\x91\x7f\x4a\xaf\x77\x7d\xa1\x7a\x58\xd6\xda\x2f\x47\x97\x3d\xdd\x3c\xf7\x7f\xfd\xf7\xd8\xbf\x4f\x7d\x5b\x9c\xa9\x0e\xf3\xb9\x49\xbd\x1e\xf2\xce\xcf\x93\xfa\x27\x43\xe7\x23\xd5\xf9\xd4\xf7\xc9\xbc\xde\x47\x7e\x5c\xeb\x9f\xdc\xb7\xf5\xe8\xbe\xff\x5b\xfa\x7e\x19\xf4\x3d\x5f\x95\xff\xdc\x44\xca\x4f\xe7\xdb\x1c\xf3\x79\xfe\x1a\x3f\x77\x53\xea\xdf\xd6\x8f\xeb\x23\xfd\x8b\xcd\xe4\x7e\x5c\xea\xfb\x24\x3c\x9e\x87\x7e\xb0\x2d\x3f\xbe\xad\x74\x9e\x3a\x57\x9b\xeb\xfc\xf7\xca\xa9\x8c\xd8\x1a\xf6\x05\xbf\xcc\x81\x8d\xa5\x7e\xde\xba\xd1\xf7\xa9\xa7\xd5\x86\xba\x9e\x91\xef\x53\x23\xa7\xa7\x93\x23\x63\xa7\x6b\x0c\x9f\xd8\x5d\xed\xfb\x17\x3c\xa9\xf6\x89\x2a\x9d\x9f\xf5\x0c\x7d\x5b\x1d\x29\x6f\x49\xa5\xe3\x58\x3f\xda\x93\x4c\xf7\x25\xf2\x34\x2a\xf6\x20\xd7\x75\x82\x4e\x81\xfc\x8d\xce\x07\x9e\x34\xb5\xf2\x5f\xf4\xca\x4b\xe3\xfb\x86\x85\xae\x1d\xe3\x4b\x64\xcf\x95\x2e\x7b\xc4\x1a\x87\x7e\x7c\xd2\x2b\x4f\x0e\x19\x12\xdd\xa3\xca\xcf\x51\x19\xf6\xe4\xec\x77\x6c\xfb\x11\xeb\xd3\x19\x3f\xb4\x95\x95\xea\x48\xe6\xbf\x47\xb5\xae\x47\x5e\xab\x8e\x84\x9d\xf6\xed\xa0\x91\xe9\x7e\xb2\xd7\x55\x6e\xba\xd2\xab\x8e\x64\xac\x81\xed\x7f\x98\xab\x6c\x4d\xa8\xb2\xc1\x77\xdc\x2b\x0d\x64\x62\x4f\x42\xa7\x63\xe1\x3d\x63\x2f\xd0\x99\x81\xff\x44\xf7\xb3\x44\x07\x23\xdb\x9b\x5c\x71\x12\x1d\x45\x5f\x3b\xe3\x8d\x36\xf4\x92\xf5\xe9\x9d\xee\x75\xdd\x29\xbe\xa2\xd3\xd8\x0b\xfb\x86\xbe\xf2\x2e\xf7\xed\x5d\xa9\xfb\x54\xc4\x6a\x03\xe8\x6b\x99\xe8\x5c\xf0\xc1\x3b\xf6\x37\xf5\x4f\xd2\xaa\x5e\xb1\xbe\x65\xaf\xfa\xc8\x7b\xf4\x93\xb1\xd8\x14\xfb\x8b\xbe\x20\x4f\xc7\xbe\x46\xaa\x17\x19\x3c\x57\xba\xe7\xf4\x87\x7e\x6e\x76\x93\xb7\xba\xbe\xac\x29\xf2\x60\x23\xec\x3b\x3e\xc1\x65\xba\x7e\xd8\x5c\x64\x7b\x94\xd4\x2a\x2b\x7b\x57\xa5\x6a\x1b\xf8\x04\x6c\x82\xf5\x63\xcf\xb0\x25\xf4\x29\x76\x6a\xf7\x60\x82\x33\x7d\xce\x6c\x5d\xd8\x23\xd7\xa9\x1d\xc2\x0b\xbe\x05\x1b\x62\x7f\x90\x15\xfb\xcb\x0b\xd3\x79\xf4\x30\x54\x3d\xa9\x4d\x97\xe5\x1d\xeb\x9d\xab\x3c\x8c\x45\x7f\x5c\xaf\x74\xe1\xa9\x74\xaa\xa7\x59\xad\x76\x8b\x3f\x44\x67\x1b\x3f\xb6\x32\x9b\x17\x7d\xc3\x5e\x6b\xdd\xcb\xa6\x52\x3d\xa5\xbd\x2e\x0c\x9f\x1a\xb5\x83\xde\xfc\x25\xeb\xc3\xda\x97\x91\xee\x85\x8b\xd4\x86\xd1\xc3\x06\x3b\x2d\x55\x07\xe4\x3d\xfb\xd9\x2b\xcf\xf0\x0e\x1e\xb2\xc6\xa2\xd3\xd8\x7b\xac\xf2\x82\x95\xac\x3f\xb8\xc9\xde\xb1\xf6\xc8\xd2\xa7\xea\xe7\xfb\x44\xf1\x04\x1d\x42\x26\xd6\x89\x39\xc2\x6c\xdb\x57\xc7\xb1\x8e\x91\x35\x47\xd7\x33\xb3\xb7\xfd\xbe\x1a\x8c\x7f\xbe\xa7\x86\xca\x96\x9f\x5e\xbf\xda\xef\xa4\xe9\x71\x88\x8b\xde\x60\xfd\x35\x1c\xf4\x26\xfb\xe6\x9d\xf3\x2a\xf9\x82\xdc\xf3\x5b\xcd\x5f\xff\xf5\xfa\xea\x20\x27\x8d\x13\x40\x29\x5b\x1c\x80\x37\x84\x36\x5e\x3b\xe9\xd4\x9c\x74\xdf\xa9\x93\x06\x04\x70\x56\x28\x18\xb4\x01\x95\x72\x30\xac\x5a\x01\x5f\x1c\x7d\xab\x00\x1b\x35\x1a\x3c\xd2\x0e\x40\xe2\xf8\xe0\x01\x20\x05\xc4\x68\x07\xc8\xf3\x46\xe7\xc0\xd8\x00\x9b\xdc\x9c\x30\x3c\x40\x0b\x20\x69\xcc\x70\x0a\x33\x5e\x94\x5f\x9c\x60\x66\x81\x46\xa5\x0e\x09\x3e\x00\x3d\x40\x05\xa3\xc1\xf8\x7b\x03\x10\xc0\x1a\x07\xc5\x3c\xd2\x96\x5a\xb0\x90\xab\x41\x01\xca\x18\x8c\x00\x1a\x7d\x6b\x05\x7b\x82\x0b\x01\xfe\x5e\x1d\x03\x46\x8f\x3c\x12\x54\x17\x0a\x1e\xc8\x0b\x18\x25\x06\x0a\x80\x23\x8e\x33\x6c\x15\xac\x6a\x0b\x62\x00\x11\xe4\xaa\xcc\x39\x31\x46\xd6\x28\xd2\x35\x6d\x0c\x0c\xe8\x07\x0f\x99\x39\x1f\x82\x9c\xce\xc0\x08\x10\x62\x1f\x9b\x58\x65\x1d\x9c\x3a\x00\xcd\xda\x24\x16\x6c\x01\x6e\xf4\x8d\x58\x7b\xff\x2e\xac\x95\x0e\x8e\x53\x64\x6f\x15\xcc\x9c\xd3\xfd\x65\x2d\x09\x6a\xaa\x52\x81\x14\xc0\x41\x06\x71\xc4\x95\x3a\x0a\xe4\xc3\xa9\x01\x68\x80\x3c\x0e\x02\xbd\x00\x8c\x49\x0c\xf2\x54\x81\x34\x36\xc7\x10\x46\x23\x20\x95\x29\x1f\xe8\x19\xeb\x0e\xc0\x3d\x22\xa1\x58\x6b\xfa\xf3\xa1\x6a\x4d\x6b\x0b\xb0\xb6\xeb\x42\xfb\x81\x6b\x4d\xea\x10\xf8\xda\x12\xea\x35\x40\x6c\x4c\xa4\x21\xd5\x48\xff\x6c\x30\xfb\x9e\x92\xe7\x8b\x24\xc9\xd8\x21\x81\x52\x5c\x2b\x66\x48\x02\x6c\x41\x14\x4e\x75\xb3\xcf\x58\x32\x0d\xad\x24\xd6\xe0\x18\x9d\x6c\x6b\x0d\xd8\xd1\xe9\xa2\x56\x27\xcf\xdc\x15\x38\xe4\xd4\xa6\x04\xdb\x9c\xda\x54\xe6\x34\xf0\x22\xd8\x01\x77\xe8\xcf\xdc\xe0\x2a\x78\x04\x5f\x12\x10\x14\x3a\x2f\xb6\x0e\x8e\x10\x14\x8a\xbd\x44\x16\x70\x5a\x40\x4d\x00\x2f\xc1\xe9\x10\xd4\x34\xfa\x8e\x71\xb1\x05\x33\x92\xb4\x1b\x56\xde\xb7\xb3\xda\x12\x83\x2a\x53\x9c\x80\xa7\x1d\x76\xb6\xb5\x09\x87\x99\xd8\x16\x99\xb5\x75\x8d\x94\xc8\xb7\xed\x6a\x6b\xfc\x63\x4d\x6a\x17\xff\x2f\x6a\x4d\xa3\x22\x98\x1d\x15\xe5\x53\xcd\x28\x8d\xc2\x38\x8e\xb3\x57\x30\xa3\x83\x33\x76\x32\x02\x32\x3d\x9c\x2f\xca\x82\xe3\x1a\x82\x01\xd7\xaa\x93\xc6\x49\xa0\xbc\x9d\x55\x84\x30\x0c\xa2\x6c\x14\xb2\x2e\xb5\x22\x05\x0d\xbe\xe3\x70\x70\xc8\x18\x93\x38\x9a\x46\x9d\x87\x8b\xd5\xd9\x62\x34\x91\x29\xba\x64\x87\x85\xf2\xd0\x99\xa1\xe1\x24\x88\xc4\x71\x36\x18\x23\xd1\xb2\x33\x67\x2f\xd9\x4f\xa2\xce\xbc\xb0\x68\x58\x8c\xbc\x53\xe3\x47\x9e\xc4\xaa\x05\x64\x94\x55\xa3\x59\x60\x51\x5a\xd6\xec\xe9\xc4\xb9\x06\x02\xad\x05\x0a\xe2\x94\x7b\x95\x17\xc3\xa4\x4a\x80\x33\x24\xe8\x81\x97\x22\x57\xbe\x89\xee\x89\xc4\xa3\x48\x03\x9e\xd6\x32\x73\x9c\x20\x81\x14\x55\x84\xca\x9c\x3e\x01\x85\xb3\xec\x53\x82\x91\x4a\x41\x82\xac\x01\x20\x19\xb2\x6d\x82\x23\xc0\xa9\xb7\xec\x50\xb2\x48\xcb\xe4\xc9\x9c\x9c\x65\x17\xb4\x01\x30\xac\x17\x32\x4b\xa6\xd5\xe8\xbc\xc8\x45\x86\xcb\x5f\xd6\xa5\x69\x35\x10\x01\xc0\x4a\xcb\x50\xc9\x34\xd9\x1b\x82\x23\xe4\x26\xd3\xa8\x7a\x5d\x07\x82\x39\xe6\x91\xac\x3d\xb3\xac\x2f\x53\x50\x2c\x2d\xcb\xc9\x6d\x1d\x98\x1f\x70\x65\xff\xe9\xe3\x6c\x1e\xfa\xa0\x07\x64\xe0\xe8\x11\x7d\xa9\x82\x88\xfe\xd4\x0a\xba\xe8\x1c\xeb\xc7\x5c\x04\x1a\x00\xa8\xec\x17\xba\x62\xc1\x1f\x7b\x5e\xb4\xba\xe7\x99\x65\xdf\xa5\x55\x37\x12\x02\xd2\x5c\xe9\xb0\x4f\x89\x65\xa8\xe8\x26\xc1\x0a\x7a\x20\x95\x16\xab\x6c\x90\xe9\xa3\xa3\xac\x7b\x66\x55\x19\xf4\xa2\xb1\xcf\x00\x69\x6c\xba\x9d\xd9\x67\xd9\xbf\xc6\x32\xce\x5a\x03\x2a\x00\x5c\x02\xdc\x5a\x83\xce\xdc\xf4\x99\x35\x27\x00\x63\xad\x99\x3b\x4e\xb4\xba\x83\x8e\x11\x7c\x49\x16\x5a\x58\x55\xa9\xd3\xf7\x38\x23\xf4\x0c\xc7\xc3\x9e\x57\xa6\x1f\xe8\x00\x74\x71\x14\xc8\x8f\xde\xb2\x26\x61\xb7\x0d\xf0\xe8\x06\xfc\xb0\x9f\x43\xa0\xbb\x0e\xb8\x76\x01\xfc\xe1\xc9\xde\x3d\x22\xf7\xc1\x7d\x5f\xaa\x77\x6f\xe8\x01\xb8\xfe\x5a\x89\xde\x36\xef\x06\xe9\x69\x95\x3f\x15\xd3\x93\xa2\xac\xca\x24\x7a\x05\x4c\x7f\x66\x9a\x37\x84\x1e\x69\xa7\x69\x83\xb3\x30\x07\xa4\x96\xba\x94\x85\x4c\x68\x28\x21\x0b\xc8\xc7\x7b\xea\xa3\xd4\xdb\x40\x00\xd0\x96\x9a\x05\xe9\x46\x66\xdf\x49\x1b\x48\x61\x24\x0d\xac\xd7\x88\xcf\xdf\x76\xa8\x99\x58\x5d\x45\x52\x9f\x4c\x11\x23\xcc\xd6\xe7\x0f\xb1\xb5\x81\xc6\x3c\xa4\x63\x43\x9f\xd4\xfa\xc5\xf6\x77\xa0\x09\x0a\x50\xc7\xa3\x9d\xcf\x20\xb0\xa0\xae\xd5\x01\x79\xb0\x52\xf1\x30\x16\x02\x81\x20\xd4\x42\xa2\x01\x1d\x52\x4d\xd5\x4a\xab\x91\x45\x86\xb4\x95\x7d\x1e\x1e\x52\x1c\x64\x90\xfa\xb3\xa1\xab\x58\x9d\xa5\x7f\xf1\x48\xe8\x85\xc7\x80\x36\xf5\x18\x10\x16\xe4\x7a\x38\xf4\x7a\x6e\x86\x33\x4a\xea\xbe\x95\x3e\x26\xbf\x19\x25\x74\x80\xcd\xbe\x6e\x76\xb3\x5b\x1e\xb3\xe0\x28\x89\xff\x6c\x0b\xbe\x69\x6e\xff\x47\xe5\x36\x3b\x15\xba\xd2\xba\x0c\xf9\x4e\x61\x87\x01\xbb\x14\xfa\x9e\xcc\x07\xea\xf2\x3d\x2a\x1b\x6a\xbc\x75\xf1\x65\x44\x81\xef\x8d\x7e\xb4\xee\x8e\xf3\xfe\xb2\x6a\x3b\xc2\xbf\x29\x6c\x12\xfe\xd9\x59\xc4\x9d\xfc\x07\x27\x11\x72\x94\xdc\x9b\x96\x3a\x0b\x9e\x87\x63\xbf\x54\xe1\x90\xa0\x0e\xc8\x8e\xed\xe8\x0d\x2d\x25\x00\x96\xca\x53\xa9\x5a\x4d\x10\x54\xd5\x1a\xb0\xf7\x76\x94\x81\x5b\x91\xa0\xae\x33\x57\x63\x41\x7d\x65\xc7\x4f\xd0\xca\xac\x9a\x45\xe0\x86\x1b\xe3\x88\x25\x8f\xf5\xd8\x80\xe0\x3e\x37\xc8\x27\xb1\xe0\x28\xa6\xb0\x23\x2a\xf8\xa5\x1a\x2a\x95\xc1\xc2\x02\x3c\x3b\x1e\x4f\xad\x54\x2f\xc1\x66\xa4\xee\x85\xc0\xaf\x30\x77\x42\x30\x4c\x12\x83\x1b\x24\x01\x19\x92\x8b\xd8\xdc\x0c\x47\x01\x04\xc4\x72\x8c\x59\xa8\x9b\x81\x7f\x8e\x78\xc8\xf8\xa9\x04\x12\x90\x62\x95\x3c\x54\xd0\x70\x83\x52\x5d\x8c\x35\x78\x26\xe8\xc5\x7a\xa9\xd6\xc9\x91\x59\xa4\xd6\x9b\x94\x5a\x89\xa8\x8d\x7f\xaa\xae\x9d\x1d\x23\x52\xd9\x95\x23\xd3\xd0\x68\x86\x2a\xaf\xcc\x6b\x41\x28\x6b\x38\x04\xc7\x89\x25\x33\x72\x7c\xd8\x68\xc0\x1e\x6f\x24\x0e\x04\xca\x61\xaf\x7b\x02\x7d\xa9\x44\xe6\xeb\xca\x27\xfc\x95\xe6\x22\x49\x78\x9c\x1d\xb7\x31\x9e\x35\x71\x56\xe9\xa5\xcd\x59\x05\xa5\xb6\x64\x52\xae\x21\x34\x8a\x46\x72\xfc\xe2\x14\xed\xd8\x3b\xda\x98\xab\xb7\x23\x2e\x41\x25\x02\xea\x44\xf7\x95\xcf\x72\x04\x56\x69\xb8\xd2\x59\x52\xc8\xb1\xad\x1c\x33\xda\x55\x09\x8e\x48\xa5\x7a\x92\x68\xd2\x11\x26\xdb\x48\x87\x8e\xcb\x11\xd0\xe0\xe6\xeb\xdd\x41\xf5\x67\xd6\xf2\x5c\x9c\xbb\x17\x52\x7f\x7e\x73\x6f\x1f\xc4\x3d\x29\xa0\x1e\x63\xf9\xe5\xe1\x6d\x24\x9c\x8e\xab\x27\x97\x48\x5e\xcd\x19\xbf\xc4\xa1\x49\xab\x39\x37\x66\x5c\xda\xcd\x06\x8a\xfd\x40\x8b\xb3\xe2\xb7\x44\xaf\xa1\x9d\x82\x56\x0a\x6f\x98\x3b\x91\x23\x39\x1d\x63\x30\xbd\xd0\x1c\x32\x27\xa3\xa8\x30\xb0\x24\xc5\xfc\x4e\x55\x19\x53\xc2\xac\x80\x3d\x4c\x10\x88\xc1\x34\x29\xc8\x8b\xda\xc6\x7a\x9a\x0a\x14\x60\x9e\xe4\x8a\x98\x3a\x79\x3c\xc5\x46\x99\xc3\x4e\xc4\xe5\xf4\xbe\xd5\xda\x00\x7c\x92\xd7\x0a\x24\xda\x49\x39\x90\x87\xa3\x07\x0e\xa8\x4d\x44\x76\xd2\x4a\x0e\x4d\x06\x21\x27\xbf\x4e\xa3\x69\xea\x11\xa1\x1d\x54\xd0\x97\xcf\x52\x4b\xe9\x2c\x8f\x2e\xd7\xb5\x04\x39\xec\x71\x2a\xa3\x9c\x7c\x3b\xe5\x15\x93\x07\xea\xc9\x34\xa8\xd7\x60\x9a\xd4\x4d\x22\xe3\x19\x77\x40\x4d\x08\xd9\xa4\xc0\xda\x1a\x94\x95\x4a\xab\xb6\x1a\x06\x0f\xb5\x10\x39\xe4\x68\xf4\xe0\x42\x4e\xec\x23\x5d\xd3\xde\x78\xa2\x3f\x81\x10\x45\x59\x81\x4f\xbb\x39\xd1\xdb\xe9\x3d\x70\x13\xda\xad\x00\x4e\x60\x81\x0f\x6e\x7b\x84\x76\xf2\x0e\xbf\xd4\x9e\xa8\x51\x35\xed\x38\x84\xd4\x76\xf0\x21\x39\x79\x66\xeb\xf4\x50\xb0\xf4\xec\xe0\x7f\x84\xd2\x3d\x38\x79\x54\xe8\x3f\x42\xe6\xe9\xe0\xf2\xca\x81\xff\x2e\x61\x06\xa8\x09\x9f\x1c\x47\xbd\x30\xd4\xfc\xa0\xd7\x9b\xff\xaf\x85\xfe\x23\x62\x1f\xa6\xcc\x23\x84\xd6\xba\x3c\x7a\x11\x7d\x5b\x93\x47\x68\x3c\x56\x91\x77\xcb\xf1\xa2\x7a\xbc\x43\x90\x2f\x25\x19\xf8\x6c\x15\x9e\x9f\x0f\x44\xf6\x2e\xdd\xc8\x07\xf2\x7b\xf9\x40\xaa\x27\xfd\x43\x3e\x00\x38\xe3\x0c\x71\x3c\xc4\xb7\xc4\xb3\xa8\x3d\x60\xdc\xd8\x67\x29\x48\x87\x7a\x05\x08\x87\x9a\x1a\x20\xb7\x83\x93\xcc\x2d\xa6\xb3\x62\x6a\x6d\x71\xa8\x94\xb9\xac\x20\x0f\x1f\xf0\xea\xec\xda\x20\x4e\x87\x38\x91\x92\x0f\xc5\x65\x89\xad\x5b\x2d\x23\xc9\xf5\xbe\xe1\x34\xcf\x9c\x1e\x0e\x21\xb3\xd3\xbe\xce\xae\xd2\xb6\x76\x6d\x50\x0e\x43\x9c\xae\x13\x4e\x43\xae\x7b\xd9\x75\xc6\xd2\x0a\xe5\x72\x8d\xac\x5c\x17\x71\xe5\x16\x40\xa8\x74\x19\xc7\x75\x26\x72\x0d\x9c\x99\xe4\x38\x85\xc6\xc3\xb1\xe5\x2d\xb1\x15\xff\xe1\x4f\xae\x36\xf5\x5a\xd6\x2b\xed\x76\x40\x67\x37\x00\x3a\xbb\x66\x56\x14\x9a\xc3\xe0\xa8\xe5\xf4\xd0\x82\x8c\xc4\xcc\x9b\xf5\x62\x2e\x29\x94\x57\xda\x0f\x07\x4c\xcc\x4e\x9f\x76\xb8\x52\x67\xb7\x16\x7a\x8b\xd7\x9d\xe5\x07\x72\x4d\x32\xd2\xbc\x85\xf1\xe2\xdc\x9d\x1d\x5e\xd8\xf5\xb1\xd0\xae\xb9\xb1\xa7\x89\x5d\xa7\x94\xc3\x04\xa7\x6d\xf0\x44\x7f\xf2\x38\xc9\x03\x33\x95\x41\x62\xfa\x5c\xf7\x85\x03\x18\x72\x00\x67\xf9\x80\xdc\x24\xe9\xd7\xd7\xe9\x68\x83\x67\xf6\x88\x72\x25\x4e\x59\x72\x8f\x4e\xd7\x98\x77\x72\x2d\x33\x59\x1f\x56\xa0\xab\x94\x12\xc7\xae\x57\x11\xcc\xb0\x2e\x83\xee\xc8\x15\xcd\xf1\xdc\x60\xcb\x78\x5e\x00\x08\x3f\xcf\x10\xb6\x7f\x67\xf3\x00\x06\x3e\x25\x4f\xd8\xc5\xfe\xab\xe0\xdf\x48\xb6\x90\x84\xd1\x97\xe4\xc2\xff\xbf\xfc\xfe\xbf\xb5\xfc\xbe\x63\x9b\x5f\xc0\x5a\xc7\xc2\xf0\xdd\x3f\x7b\x7b\xc0\x76\x9f\x1e\x8c\xef\x17\xec\x55\xec\xf8\x8b\x2e\xc5\xff\xa4\xbf\xfb\x7b\xb9\x9f\xe3\xec\xf9\xb9\x0d\xba\x17\xda\xcd\xc8\x31\x1f\x42\xec\x13\xdb\x4d\x44\xb1\xdb\x71\xdd\x1c\x61\xf9\x30\xbd\x1c\x21\xb4\xd6\xc9\xd1\x5f\x57\x6e\xab\xe3\x08\x8d\xc7\xaa\xe2\x6e\x39\x5e\x54\x0d\x77\x08\x32\x68\xe0\xd3\xc3\xe9\xb4\x8c\xb3\x22\x7c\x15\x05\x3c\x38\x9c\x26\x0c\x24\x64\x1b\xee\xcb\x70\x9f\x41\x7e\x0d\xd3\xad\xdd\x08\x6a\x49\xcd\xa3\xb1\xbb\x1b\x84\xd1\xf2\xeb\x1b\xab\x53\xa1\x9e\xb8\x20\x68\x49\x3d\xc8\xee\x64\x08\x6c\x87\x7a\xc9\x95\xbf\xa9\xa9\x73\x6c\xf7\x7a\x76\xa9\x34\x61\x2a\xbf\x9a\xc8\xed\xce\x48\xd4\x3f\x4e\xa5\x0f\x0f\x8b\xb6\xc8\x6c\xab\xf3\xbe\xb0\x68\x6b\xf8\x41\x9a\xfc\x5a\x61\xd1\x98\x04\x43\x58\xf4\xe4\xa8\xe8\x35\x95\xf8\x99\x51\x11\x85\x88\xd4\xa2\x1e\x1e\x7e\x40\xf1\xdf\x01\x00\x00\xff\xff\x23\x49\x69\x80\x00\x40\x00\x00") func templatesGoBytes() ([]byte, error) { return bindataRead( @@ -398,7 +398,7 @@ func templatesGo() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates.go", size: 32768, mode: os.FileMode(420), modTime: time.Unix(1468853854, 0)} + info := bindataFileInfo{name: "templates.go", size: 24576, mode: os.FileMode(420), modTime: time.Unix(1468603699, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/manifest/run.go b/manifest/run.go index ed421116f1..86fe7fbbdd 100644 --- a/manifest/run.go +++ b/manifest/run.go @@ -81,14 +81,19 @@ func (r *Run) Start() error { links[key] = true } + missingEnv := []string{} for key, val := range s.Environment { eok := val != "" _, exok := existing[key] _, lok := links[key] if !eok && !exok && !lok { - return fmt.Errorf("env expected: %s", key) + missingEnv = append(missingEnv, key) } } + + if len(missingEnv) > 0 { + return fmt.Errorf("env expected: %s", strings.Join(missingEnv, ", ")) + } } // preload system-level stream names diff --git a/vendor/github.com/convox/version/version.go b/vendor/github.com/convox/version/version.go index fbca8516c8..a6f44ce1d5 100644 --- a/vendor/github.com/convox/version/version.go +++ b/vendor/github.com/convox/version/version.go @@ -37,7 +37,7 @@ func (vs Versions) Resolve(version string) (v Version, err error) { // Get all versions as Versions type func All() (Versions, error) { - res, err := http.Get("http://convox.s3.amazonaws.com/release/versions.json") + res, err := http.Get("https://convox.s3.amazonaws.com/release/versions.json") if err != nil { return nil, err