// Copyright 2015-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. package project import ( "fmt" "github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/adapter" "github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/context" "github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/entity" "github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/entity/service" "github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/entity/task" "github.com/sirupsen/logrus" "github.com/aws/amazon-ecs-cli/ecs-cli/modules/commands/flags" composeutils "github.com/aws/amazon-ecs-cli/ecs-cli/modules/utils/compose" "github.com/aws/amazon-ecs-cli/ecs-cli/modules/utils/regcredio" "github.com/docker/libcompose/project" ) // Project is the starting point for the compose app to interact with and issue commands // It acts as a blanket for the ECS context and entities created as a part of this compose project type Project interface { Name() string Parse() error Context() *context.ECSContext ContainerConfigs() []adapter.ContainerConfig // TODO change this to a pointer to slice? VolumeConfigs() *adapter.Volumes Entity() entity.ProjectEntity // commands Create() error Start() error Up() error Info(string) (project.InfoSet, error) Run(commandOverrides map[string][]string) error Scale(count int) error Stop() error Down() error } // ecsProject struct is an implementation of Project. type ecsProject struct { containerConfigs []adapter.ContainerConfig volumes *adapter.Volumes ecsContext *context.ECSContext ecsRegistryCreds *regcredio.ECSRegistryCredsOutput // TODO: track a map of entities [taskDefinition -> Entity] // 1 task definition for every disjoint set of containers in the compose file entity entity.ProjectEntity } // NewProject creates a new instance of the ECS Compose Project func NewProject(ecsContext *context.ECSContext) Project { p := &ecsProject{ ecsContext: ecsContext, containerConfigs: []adapter.ContainerConfig{}, volumes: adapter.NewVolumes(), } if ecsContext.IsService { p.entity = service.NewService(ecsContext) } else { p.entity = task.NewTask(ecsContext) } return p } // Name returns the name of the project func (p *ecsProject) Name() string { return p.Context().Context.ProjectName } // Context returns the ecsContext of the project, which encompasses the cli configurations required to setup this project func (p *ecsProject) Context() *context.ECSContext { return p.ecsContext } // VolumeConfigs returns a map of Volume Configuration loaded from compose yaml file func (p *ecsProject) VolumeConfigs() *adapter.Volumes { return p.volumes } func (p *ecsProject) ContainerConfigs() []adapter.ContainerConfig { return p.containerConfigs } // Entity returns the project entity that operates on the compose file and integrates with ecs func (p *ecsProject) Entity() entity.ProjectEntity { return p.entity } // Parse reads the ecsContext and sets appropriate project fields func (p *ecsProject) Parse() error { ecsContext := p.ecsContext // initialize the ECS context and project entity fields if err := ecsContext.Open(); err != nil { return err } if err := p.Entity().LoadContext(); err != nil { return err } if err := p.parseCompose(); err != nil { return err } // Populates ecs-params onto project ecsContext if err := p.parseECSParams(); err != nil { return err } if err := p.parseECSRegistryCreds(); err != nil { return err } return p.transformTaskDefinition() } // parseCompose sets data from the compose files on the ecsProject, including ContainerConfigs and VolumeConfigs func (p *ecsProject) parseCompose() error { logrus.Debug("Parsing the compose yaml...") // check for Compose version and call appropriate parsing function version, err := p.checkComposeVersion() if err != nil { return err } switch version { case "", "1", "1.0", "2", "2.0": configs, err := p.parseV1V2() if err != nil { return err } // TODO: set this in parseV1V2 itself? p.containerConfigs = *configs case "3", "3.0": configs, err := p.parseV3() if err != nil { return err } p.containerConfigs = *configs default: return fmt.Errorf("Unsupported Docker Compose version found: %s", version) } // libcompose sanitizes the project name and removes any non alpha-numeric character. // The following undoes that and sets the project name as user defined it. return p.ecsContext.SetProjectName() } // parseECSParams sets data from the ecs-params.yml file on the ecsProject.context func (p *ecsProject) parseECSParams() error { logrus.Debug("Parsing the ecs-params yaml...") ecsParamsFileName := p.ecsContext.CLIContext.GlobalString(flags.ECSParamsFileNameFlag) ecsParams, err := composeutils.ReadECSParams(ecsParamsFileName) if err != nil { return err } p.ecsContext.ECSParams = ecsParams return nil } func (p *ecsProject) parseECSRegistryCreds() error { logrus.Debug("Parsing the ecs-registry-creds yaml...") registryCredsFileName := p.ecsContext.CLIContext.GlobalString(flags.RegistryCredsFileNameFlag) regCreds, err := regcredio.ReadCredsOutput(registryCredsFileName) if err != nil { return err } p.ecsRegistryCreds = regCreds return nil } // transformTaskDefinition converts the compose yml and ecs-params yml into an // ECS task definition and loads it onto the project entity func (p *ecsProject) transformTaskDefinition() error { ecsContext := p.ecsContext // convert to task definition logrus.Debug("Transforming yaml to task definition...") taskRoleArn := ecsContext.CLIContext.GlobalString(flags.TaskRoleArnFlag) requiredCompatibilities := ecsContext.CommandConfig.LaunchType taskDefinitionName := ecsContext.ProjectName convertParams := composeutils.ConvertTaskDefParams{ TaskDefName: taskDefinitionName, TaskRoleArn: taskRoleArn, RequiredCompatibilites: requiredCompatibilities, Volumes: p.VolumeConfigs(), ContainerConfigs: p.ContainerConfigs(), // TODO Change to pointer on project? ECSParams: ecsContext.ECSParams, ECSRegistryCreds: p.ecsRegistryCreds, } taskDefinition, err := composeutils.ConvertToTaskDefinition(convertParams) if err != nil { return err } p.entity.SetTaskDefinition(taskDefinition) return nil } //* ----------------- commands ----------------- */ func (p *ecsProject) Create() error { return p.entity.Create() } func (p *ecsProject) Start() error { return p.entity.Start() } func (p *ecsProject) Up() error { return p.entity.Up() } func (p *ecsProject) Info(desiredStatus string) (project.InfoSet, error) { return p.entity.Info(true, desiredStatus) } func (p *ecsProject) Run(commandOverrides map[string][]string) error { return p.entity.Run(commandOverrides) } func (p *ecsProject) Scale(count int) error { return p.entity.Scale(count) } func (p *ecsProject) Stop() error { return p.entity.Stop() } func (p *ecsProject) Down() error { return p.entity.Down() }