Repository: aws-cloudformation/cfn-lint

Issue:
E1010 Invalid GetAtt error message could be better
*cfn-lint version: (`cfn-lint --version`)* 0.7.1

Code:
```yaml
  ElasticSearchHostname:
    Value:
      Fn::GetAtt: ElasticSearchDomain.DomainEndpoint
```

(I mixed short and long form)

Current:
```
E1010 Invalid GetAtt E.l.a.s.t.i.c.S.e.a.r.c.h.D.o.m.a.i.n...D.o.m.a.i.n.E.n.d.p.o.i.n.t for resource ElasticSearchHostname
cfn/x.cfn.yaml:342:7
```

Better:
```
E1010 GetAtt expects an array of length 2, not String for resource ElasticSearchHostname
```

(nb, this is also an error in an *output*, not *resource*, but I didn't even notice that until filing the bug report. I guess my eyes skip over everything except the line number)


Discussion:
So it looks like we're not properly differentiating against the long form and the short form in YAML.  We'll need to catch that, perhaps as a separate error.

Also, Fn::GetAtt is always retrieving an attribute from a resource (not a property or parameter) -- that's what we're referring to here.  You could argue `resource` is redundant since it's always a resource and we could remove it.

Resolving patch:
diff --git a/src/cfnlint/decode/cfn_yaml.py b/src/cfnlint/decode/cfn_yaml.py
--- a/src/cfnlint/decode/cfn_yaml.py
+++ b/src/cfnlint/decode/cfn_yaml.py
@@ -176,6 +176,7 @@ def construct_getatt(node):
     """
     Reconstruct !GetAtt into a list
     """
+
     if isinstance(node.value, (six.string_types)):
         return list_node(node.value.split('.'), node.start_mark, node.end_mark)
     if isinstance(node.value, list):
diff --git a/src/cfnlint/rules/functions/GetAtt.py b/src/cfnlint/rules/functions/GetAtt.py
--- a/src/cfnlint/rules/functions/GetAtt.py
+++ b/src/cfnlint/rules/functions/GetAtt.py
@@ -14,6 +14,7 @@
   OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 """
+import six
 from cfnlint import CloudFormationLintRule
 from cfnlint import RuleMatch
 import cfnlint.helpers
@@ -44,8 +45,12 @@ def match(self, cfn):
                 message = 'Invalid GetAtt for {0}'
                 matches.append(RuleMatch(getatt, message.format('/'.join(map(str, getatt[:-1])))))
                 continue
-            resname = getatt[-1][0]
-            restype = '.'.join(getatt[-1][1:])
+            if isinstance(getatt[-1], six.string_types):
+                resname, restype = getatt[-1].split('.')
+            else:
+                resname = getatt[-1][0]
+                restype = '.'.join(getatt[-1][1:])
+
             if resname in valid_getatts:
                 if restype not in valid_getatts[resname] and '*' not in valid_getatts[resname]:
                     message = 'Invalid GetAtt {0}.{1} for resource {2}'


Test patch:
diff --git a/test/fixtures/templates/bad/functions/getatt.yaml b/test/fixtures/templates/bad/functions/getatt.yaml
new file mode 100644
--- /dev/null
+++ b/test/fixtures/templates/bad/functions/getatt.yaml
@@ -0,0 +1,10 @@
+---
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+  myElasticSearch:
+    Type: AWS::Elasticsearch::Domain
+    Properties: {}
+Outputs:
+  ElasticSearchHostname:
+    Value:
+      Fn::GetAtt: ElasticSearchDomain.DomainEndpoint
diff --git a/test/fixtures/templates/good/functions/getatt.yaml b/test/fixtures/templates/good/functions/getatt.yaml
new file mode 100644
--- /dev/null
+++ b/test/fixtures/templates/good/functions/getatt.yaml
@@ -0,0 +1,10 @@
+---
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+  myElasticSearch:
+    Type: AWS::Elasticsearch::Domain
+    Properties: {}
+Outputs:
+  ElasticSearchHostname:
+    Value:
+      Fn::GetAtt: myElasticSearch.DomainEndpoint
diff --git a/test/rules/functions/test_get_att.py b/test/rules/functions/test_get_att.py
--- a/test/rules/functions/test_get_att.py
+++ b/test/rules/functions/test_get_att.py
@@ -24,6 +24,9 @@ def setUp(self):
         """Setup"""
         super(TestRulesGetAtt, self).setUp()
         self.collection.register(GetAtt())
+        self.success_templates = [
+            'fixtures/templates/good/functions/getatt.yaml'
+        ]
 
     def test_file_positive(self):
         """Test Positive"""
@@ -32,3 +35,7 @@ def test_file_positive(self):
     def test_file_negative(self):
         """Test failure"""
         self.helper_file_negative('fixtures/templates/bad/generic.yaml', 1)
+
+    def test_file_negative_getatt(self):
+        """Test failure"""
+        self.helper_file_negative('fixtures/templates/bad/functions/getatt.yaml', 1)