Repository: Sceptre/sceptre

Issue:
Better error handling with invalid templates
### Subject of the issue
The template handler does not provide a helpful message when sceptre cannot resolve a template from an http source.   It would be nice to improve the error handling for that.

### Your environment
* version of sceptre: 3.1
* version of python: 3.9
* which OS/distro: MAC OSX

### Steps to reproduce

1. Create a sceptre config and reference a non-existing template using the http template handler

contents of `dev/my-template.yaml`:
```
template:
  type: http
  url: https://my-bucket.s3.amazonaws.com/templates/my-template.yaml
stack_name: my-stack
```
2. run `sceptre launch dev`
get the following message..
 
```
"An error occurred (ValidationError) when calling the UpdateStack operation: Template format error: unsupported structure."
```

3. run `sceptre launch dev/mytemplate.yaml` will output the following message..
```
---
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message><Key>templates/my-template.yaml</Key><RequestId>YSEQKP3YV3RZ9FEJ</RequestId><HostId>Tc/l2Ne92PC1WZ3zoa4HhydMjuk3PRh+puZ0CAaC8YvnthVPYpBJiLBFQpbIFfSG+7+3b7LA5N4=</HostId></Error>
```

### Expected behaviour
More helpful message would be nice.


Discussion:

Patch:
diff --git a/sceptre/template_handlers/http.py b/sceptre/template_handlers/http.py
index 31ac126..983e228 100644
--- a/sceptre/template_handlers/http.py
+++ b/sceptre/template_handlers/http.py
@@ -1,15 +1,15 @@
 # -*- coding: utf-8 -*-
 import pathlib
-import requests
 import tempfile
-import sceptre.template_handlers.helper as helper
+from urllib.parse import urlparse
 
+import requests
 from requests.adapters import HTTPAdapter
-from requests.exceptions import InvalidURL, ConnectTimeout
 from requests.packages.urllib3.util.retry import Retry
+
+import sceptre.template_handlers.helper as helper
 from sceptre.exceptions import UnsupportedTemplateFileTypeError
 from sceptre.template_handlers import TemplateHandler
-from urllib.parse import urlparse
 
 HANDLER_OPTION_KEY = "http_template_handler"
 HANDLER_RETRIES_OPTION_PARAM = "retries"
@@ -77,23 +77,22 @@ class Http(TemplateHandler):
 
         return template
 
-    def _get_template(self, url, retries, timeout):
+    def _get_template(self, url: str, retries: int, timeout: int) -> str:
         """
         Get template from the web
         :param url: The url to the template
-        :type: str
         :param retries: The number of retry attempts.
-        :rtype: int
         :param timeout: The timeout for the session in seconds.
-        :rtype: int
+        :raises: :class:`requests.exceptions.HTTPError`: When a download error occurs
         """
         self.logger.debug("Downloading file from: %s", url)
         session = self._get_retry_session(retries=retries)
-        try:
-            response = session.get(url, timeout=timeout)
-            return response.content
-        except (InvalidURL, ConnectTimeout) as e:
-            raise e
+        response = session.get(url, timeout=timeout)
+
+        # If the response was unsuccessful, raise an error.
+        response.raise_for_status()
+
+        return response.content
 
     def _get_retry_session(self,
                            retries,


Test patch:
diff --git a/tests/test_template_handlers/test_http.py b/tests/test_template_handlers/test_http.py
index 5db89ec..8947ec6 100644
--- a/tests/test_template_handlers/test_http.py
+++ b/tests/test_template_handlers/test_http.py
@@ -1,11 +1,12 @@
 # -*- coding: utf-8 -*-
 import json
+from unittest.mock import patch
 
 import pytest
+from requests.exceptions import HTTPError
 
 from sceptre.exceptions import UnsupportedTemplateFileTypeError
 from sceptre.template_handlers.http import Http
-from unittest.mock import patch
 
 
 class TestHttp(object):
@@ -20,6 +21,16 @@ class TestHttp(object):
         result = template_handler.handle()
         assert result == b"Stuff is working"
 
+    def test_get_template__request_error__raises_error(self, requests_mock):
+        url = "https://raw.githubusercontent.com/acme/bucket.yaml"
+        requests_mock.get(url, content=b"Error message", status_code=404)
+        template_handler = Http(
+            name="vpc",
+            arguments={"url": url},
+        )
+        with pytest.raises(HTTPError):
+            template_handler.handle()
+
     def test_handler_unsupported_type(self):
         handler = Http("http_handler", {'url': 'https://raw.githubusercontent.com/acme/bucket.unsupported'})
         with pytest.raises(UnsupportedTemplateFileTypeError):