Skip to content

Commit

Permalink
Merge pull request #444 - Attribute Checker
Browse files Browse the repository at this point in the history
New Command: ecs-cli check-attributes
  • Loading branch information
PettitWesley authored Mar 5, 2019
2 parents be660fb + 14ca4ab commit a872871
Show file tree
Hide file tree
Showing 12 changed files with 506 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ bin/
*.orig
ecs-cli/vendor/pkg
.vscode/*
ecs-cli/.vscode/*
ecs-cli/.idea
.idea/*
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Line Interface](http://aws.amazon.com/cli/) product detail page.
- [Viewing Container Logs](#viewing-container-logs)
- [Using FIPS Endpoints](#using-fips-endpoints)
- [Using Private Registry Authentication](#using-private-registry-authentication)
- [Checking for Missing Attributes and Debugging Reason: Attribute Errors](#Checking-for-Missing-Attributes-and-Debugging-Reason:-Attribute-Errors)
- [Amazon ECS CLI Commands](#amazon-ecs-cli-commands)
- [Contributing to the CLI](#contributing-to-the-cli)
- [License](#license)
Expand Down Expand Up @@ -1008,6 +1009,23 @@ INFO[0018] Started container... container=bf35a813-dd76-4fe0-b5a2-c1334c2331f4/l

For more information about using private registries with ECS, see [Private Registry Authentication for Tasks](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/private-auth.html).

### Checking for Missing Attributes and Debugging Reason: Attribute Errors

Sometimes, when you try to Run a Task, the API will return the error message `"Reasons : ["ATTRIBUTE"]"`. This occurs because your container instances are missing an attribute required by your Task Definition. You can debug these failures using the `ecs-cli check-attributes` command.

Here's an example of the command in action:

```
$ ecs-cli check-attributes --container-instances 28c5abd2-360e-41a0-81d8-0afca2d08d9b,45510138-f24f-47c6-a418-71c46dd51f88,ae66e18e-1d46-47ff-81c5-647f0f1426ce,dffe7f91-8faa-4e00-983b-c58fd279cf6d --cluster practice-cluster --region us-east-2 --task-def fluentd-kinesis
Container Instance Missing Attributes
dffe7f91-8faa-4e00-983b-c58fd279cf6d None
28c5abd2-360e-41a0-81d8-0afca2d08d9b com.amazonaws.ecs.capability.logging-driver.fluentd
45510138-f24f-47c6-a418-71c46dd51f88 None
ae66e18e-1d46-47ff-81c5-647f0f1426ce com.amazonaws.ecs.capability.logging-driver.fluentd
```

The command outputs a table of container instances and which attributes they are missing. In this case, the Task Definition requires the Fluentd log driver, but 2 container instances lack support for it.

## Amazon ECS CLI Commands

For a complete list of commands, see the
Expand Down
2 changes: 2 additions & 0 deletions ecs-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/version"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/commands/attributechecker"
)

func main() {
Expand All @@ -54,6 +55,7 @@ func main() {
imageCommand.ImagesCommand(),
licenseCommand.LicenseCommand(),
composeCommand.ComposeCommand(composeFactory),
attributecheckercommand.AttributecheckerCommand(),
logsCommand.LogCommand(),
regcredsCommand.RegistryCredsCommand(),
}
Expand Down
172 changes: 172 additions & 0 deletions ecs-cli/modules/cli/attributechecker/attribute_checker_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// 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 attributechecker

import (
"fmt"
"os"
"strings"

ecsclient "github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/ecs"
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/commands/flags"
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/config"
"github.com/aws/aws-sdk-go/aws"
"github.com/docker/libcompose/project"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)

const (
displayTitle = true
containerInstanceHeader = "Container Instance"
missingAttributesHeader = "Missing Attributes"
)

var infoColumns = []string{containerInstanceHeader, missingAttributesHeader}

//AttributeChecker will compare task def and containers instances attributes and outputs missing attributes
func AttributeChecker(c *cli.Context) {
err := validateAttributeCheckerFlags(c)
if err != nil {
logrus.Fatal("Error executing 'Attribute Checker': ", err)
}
rdwr, err := config.NewReadWriter()
if err != nil {
logrus.Fatal("Error executing 'Attribute Checker': ", err)
}
commandConfig, err := config.NewCommandConfig(c, rdwr)
if err != nil {
logrus.Fatal("Error executing 'Attribute Checker': ", err)
}

ecsClient := ecsclient.NewECSClient(commandConfig)

taskDefAttributeNames, err := taskdefattributesCheckRequest(c, ecsClient)
if err != nil {
logrus.Fatal("Error executing 'Attribute Checker': ", err)
}
if len(taskDefAttributeNames) == 0 {
logrus.Info("The given task definition does not have any attributes")
return
}

descrContainerInstancesResponse, err := describeContainerInstancesAttributeMap(c, ecsClient, commandConfig)
if err != nil {
logrus.Fatal("Error executing 'Attribute Checker': ", err)
}

compareOutput := compare(taskDefAttributeNames, descrContainerInstancesResponse)
result := ConvertToInfoSet(compareOutput)
os.Stdout.WriteString(result.String(infoColumns, displayTitle))
}

func contains(containerInstanceAttributeNames []*string, tdAttrNames *string) bool {
for _, containerInstAttrNames := range containerInstanceAttributeNames {
if *containerInstAttrNames == *tdAttrNames {
return true
}
}
return false
}

//compares between container instances and Task definition
func compare(taskDefAttributeNames []*string, descrContainerInstancesResponse map[string][]*string) map[string]string {
attributeCheckerResult := make(map[string]string)
for containerInstanceARN, containerInstanceAttributeNames := range descrContainerInstancesResponse {
var missingAttributes []string
for _, tdAttrNames := range taskDefAttributeNames {
if !contains(containerInstanceAttributeNames, tdAttrNames) {
missingAttributes = append(missingAttributes, *tdAttrNames)
}
}
missingAttributesNames := strings.Join(missingAttributes, ", ")
if len(missingAttributesNames) == 0 {
missingAttributesNames = "None"
}
containerInstance := strings.Split(containerInstanceARN, "/")
attributeCheckerResult[containerInstance[1]] = missingAttributesNames
}
return attributeCheckerResult
}

// DescribeContainerInstancesAttributeMap and get a map with Container instance ARN and Container instances attribute Names
func describeContainerInstancesAttributeMap(context *cli.Context, ecsClient ecsclient.ECSClient, commandConfig *config.CommandConfig) (map[string][]*string, error) {
if err := validateCluster(commandConfig.Cluster, ecsClient); err != nil {
return nil, err
}
var containerInstanceIdentifiers []*string
containerInstanceIdentifier := context.String(flags.ContainerInstancesFlag)
splitValues := strings.Split(containerInstanceIdentifier, ",")
containerInstanceIdentifiers = aws.StringSlice(splitValues)

descrContainerInstancesAttributes, err := ecsClient.GetAttributesFromDescribeContainerInstances(containerInstanceIdentifiers)
if err != nil {
return nil, errors.Wrapf(err, fmt.Sprintf("Failed to Describe Container Instances, please check region/containerInstance/cluster values"))
}
return descrContainerInstancesAttributes, err
}

// validateCluster validates if the cluster exists in ECS and is in "ACTIVE" state.
func validateCluster(clusterName string, ecsClient ecsclient.ECSClient) error {
isClusterActive, err := ecsClient.IsActiveCluster(clusterName)
if err != nil {
return err
}

if !isClusterActive {
return fmt.Errorf("Cluster '%s' is not active. Ensure that it exists", clusterName)
}
return nil
}

//taskdefattributesCheckRequest describes task def and gets all attribute Names from the task definition
func taskdefattributesCheckRequest(context *cli.Context, ecsClient ecsclient.ECSClient) ([]*string, error) {

taskDefIdentifier := context.String(flags.TaskDefinitionFlag)

descrTaskDefinition, err := ecsClient.DescribeTaskDefinition(taskDefIdentifier)
if err != nil {
return nil, errors.Wrapf(err, fmt.Sprintf("Failed to Describe TaskDefinition, please check the region/taskDefinition values"))
}
var taskattributeNames []*string
for _, taskDefAttributesName := range descrTaskDefinition.RequiresAttributes {
taskattributeNames = append(taskattributeNames, taskDefAttributesName.Name)
}
return taskattributeNames, err
}

//validates all required flags are passed to run the command
func validateAttributeCheckerFlags(context *cli.Context) error {
if taskDefIdentifier := context.String(flags.TaskDefinitionFlag); taskDefIdentifier == "" {
return fmt.Errorf("TaskDefinition must be specified with the --%s flag", flags.TaskDefinitionFlag)
}
if containerInstanceIdentifier := context.String(flags.ContainerInstancesFlag); containerInstanceIdentifier == "" {
return fmt.Errorf("ContainerInstance(s) must be specified with the --%s flag", flags.ContainerInstancesFlag)
}
return nil
}

//ConvertToInfoSet transforms the Map of containerARN and MissingAttributes into a formatted set of fields
func ConvertToInfoSet(compareOutput map[string]string) project.InfoSet {
result := project.InfoSet{}
for key, element := range compareOutput {
info := project.Info{
containerInstanceHeader: key,
missingAttributesHeader: element,
}
result = append(result, info)
}
return result
}
Loading

0 comments on commit a872871

Please sign in to comment.