forked from Sceptre/sceptre
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a web (http) template handler to allow referencing templates directly from the web. Supports standard template files (.yaml, .json, .template), jinja and python templates.
- Loading branch information
Showing
6 changed files
with
190 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# -*- coding: utf-8 -*- | ||
import pathlib | ||
import os | ||
import requests | ||
import tempfile | ||
import sceptre.template_handlers.helper as helper | ||
|
||
from sceptre.exceptions import UnsupportedTemplateFileTypeError | ||
from sceptre.template_handlers import TemplateHandler | ||
from urllib.parse import urlparse | ||
|
||
|
||
class Http(TemplateHandler): | ||
""" | ||
Template handler that can resolve templates from the web. Standard CFN templates | ||
with extension (.json, .yaml, .template) are deployed directly from memory | ||
while references to jinja (.j2) and python (.py) templates are downloaded, | ||
transformed into CFN templates then deployed to AWS. | ||
""" | ||
def __init__(self, *args, **kwargs): | ||
super(Http, self).__init__(*args, **kwargs) | ||
|
||
def schema(self): | ||
return { | ||
"type": "object", | ||
"properties": { | ||
"url": {"type": "string"} | ||
}, | ||
"required": ["url"] | ||
} | ||
|
||
def handle(self): | ||
""" | ||
handle template from web | ||
""" | ||
url = self.arguments["url"] | ||
path = pathlib.Path(urlparse(url).path) | ||
|
||
if path.suffix not in self.supported_template_extensions: | ||
raise UnsupportedTemplateFileTypeError( | ||
"Template has file extension %s. Only %s are supported.", | ||
path.suffix, ",".join(self.supported_template_extensions) | ||
) | ||
|
||
try: | ||
template = self._get_template(url) | ||
if path.suffix in self.jinja_template_extensions + self.python_template_extensions: | ||
file = tempfile.NamedTemporaryFile(prefix=path.stem) | ||
self.logger.debug("Template file saved to: %s", file.name) | ||
with file as f: | ||
f.write(template) | ||
f.seek(0) | ||
f.read() | ||
if path.suffix in self.jinja_template_extensions: | ||
template = helper.render_jinja_template( | ||
os.path.dirname(f.name), | ||
os.path.basename(f.name), | ||
{"sceptre_user_data": self.sceptre_user_data} | ||
) | ||
elif path.suffix in self.python_template_extensions: | ||
template = helper.call_sceptre_handler( | ||
f.name, | ||
self.sceptre_user_data | ||
) | ||
|
||
except Exception as e: | ||
helper.print_template_traceback(path) | ||
raise e | ||
|
||
return template | ||
|
||
def _get_template(self, url): | ||
""" | ||
Get template from the web | ||
:param url: The url to the template | ||
:type: str | ||
:returns: The body of the CloudFormation template. | ||
:rtype: str | ||
""" | ||
self.logger.debug("Downloading file from: %s", url) | ||
try: | ||
response = requests.get(url) | ||
return response.content | ||
except requests.exceptions.RequestException as e: | ||
self.logger.fatal(e) | ||
raise e |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# -*- coding: utf-8 -*- | ||
import json | ||
import pytest | ||
|
||
from sceptre.exceptions import UnsupportedTemplateFileTypeError | ||
from sceptre.template_handlers.http import Http | ||
from unittest.mock import patch | ||
|
||
|
||
class TestHttp(object): | ||
|
||
def test_get_template(self, requests_mock): | ||
url = "https://raw.githubusercontent.com/acme/bucket.yaml" | ||
requests_mock.get(url, content=b"Stuff is working") | ||
template_handler = Http( | ||
name="vpc", | ||
arguments={"url": url}, | ||
) | ||
result = template_handler.handle() | ||
assert result == b"Stuff is working" | ||
|
||
def test_handler_unsupported_type(self): | ||
handler = Http("http_handler", {'url': 'https://raw.githubusercontent.com/acme/bucket.unsupported'}) | ||
with pytest.raises(UnsupportedTemplateFileTypeError): | ||
handler.handle() | ||
|
||
@pytest.mark.parametrize("url", [ | ||
("https://raw.githubusercontent.com/acme/bucket.json"), | ||
("https://raw.githubusercontent.com/acme/bucket.yaml"), | ||
("https://raw.githubusercontent.com/acme/bucket.template") | ||
]) | ||
@patch('sceptre.template_handlers.http.Http._get_template') | ||
def test_handler_raw_template(self, mock_get_template, url): | ||
mock_get_template.return_value = {} | ||
handler = Http("http_handler", {'url': url}) | ||
handler.handle() | ||
assert mock_get_template.call_count == 1 | ||
|
||
@patch('sceptre.template_handlers.helper.render_jinja_template') | ||
@patch('sceptre.template_handlers.http.Http._get_template') | ||
def test_handler_jinja_template(slef, mock_get_template, mock_render_jinja_template): | ||
mock_get_template_response = { | ||
"Description": "test template", | ||
"AWSTemplateFormatVersion": "2010-09-09", | ||
"Resources": { | ||
"touchNothing": { | ||
"Type": "AWS::CloudFormation::WaitConditionHandle" | ||
} | ||
} | ||
} | ||
mock_get_template.return_value = json.dumps(mock_get_template_response).encode('utf-8') | ||
handler = Http("http_handler", {'url': 'https://raw.githubusercontent.com/acme/bucket.j2'}) | ||
handler.handle() | ||
assert mock_render_jinja_template.call_count == 1 | ||
|
||
@patch('sceptre.template_handlers.helper.call_sceptre_handler') | ||
@patch('sceptre.template_handlers.http.Http._get_template') | ||
def test_handler_python_template(self, mock_get_template, mock_call_sceptre_handler): | ||
mock_get_template_response = { | ||
"Description": "test template", | ||
"AWSTemplateFormatVersion": "2010-09-09", | ||
"Resources": { | ||
"touchNothing": { | ||
"Type": "AWS::CloudFormation::WaitConditionHandle" | ||
} | ||
} | ||
} | ||
mock_get_template.return_value = json.dumps(mock_get_template_response).encode('utf-8') | ||
handler = Http("http_handler", {'url': 'https://raw.githubusercontent.com/acme/bucket.py'}) | ||
handler.handle() | ||
assert mock_call_sceptre_handler.call_count == 1 |