Есть ли способ определить ярлыки для часто используемых значений, полученных из параметров шаблона CloudFormation?
Например, у меня есть скрипт, который создает стек проекта Multi-AZ с именем ELB. project
и два экземпляра за ELB называются project-1
и project-2
. Я только прохожу ELBHostName
параметр в шаблон, а затем использовать его для создания:
"Fn::Join": [
".", [
{ "Fn::Join": [ "", [ { "Ref": "ELBHostName" }, "-1" ] ] },
{ "Ref": "EnvironmentVersioned" },
{ "Ref": "HostedZone" }
]
]
Эта или очень похожая конструкция повторяется много раз в шаблоне - для создания имени хоста EC2, записей Route53 и т. Д.
Вместо того, чтобы повторять это снова и снова, я хотел бы назначить вывод этого Fn::Join
к какой-либо переменной и ссылаться только на нее, так же, как я могу с "Ref":
заявление.
В идеале что-то вроде:
Var::HostNameFull = "Fn::Join": [ ... ]
...
{ "Name": { "Ref": "Var::HostNameFull" } }
или что-то подобное простое.
Возможно ли это с Amazon CloudFormation?
У меня нет ответа, но я хочу отметить, что вы можете избавить себя от боли, используя Fn::Sub
на месте Fn::Join
{ "Fn::Sub": "${ELBHostName"}-1.${EnvironmentVersioned}.${HostedZone}"}
Заменяет
"Fn::Join": [
".", [
{ "Fn::Join": [ "", [ { "Ref": "ELBHostName" }, "-1" ] ] },
{ "Ref": "EnvironmentVersioned" },
{ "Ref": "HostedZone" }
]
]
Я искал такую же функциональность. Мне пришло в голову использование вложенного стека, как предлагал SpoonMeiser, но потом я понял, что на самом деле мне нужны специальные функции. К счастью, CloudFormation позволяет использовать AWS :: CloudFormation :: CustomResource что, приложив немного усилий, позволяет сделать именно это. Это кажется излишним только для переменных (что, я бы сказал, должно было быть в CloudFormation в первую очередь), но он выполняет свою работу и, кроме того, обеспечивает всю гибкость (выберите python / node на ваш выбор). /Ява). Следует отметить, что лямбда-функции стоят денег, но мы говорим здесь о копейках, если вы не создаете / не удаляете свои стеки несколько раз в час.
Первый шаг - создать лямбда-функцию на этой странице который ничего не делает, кроме как берет входное значение и копирует его на выход. У нас может быть лямбда-функция, которая будет делать всевозможные сумасшедшие вещи, но когда у нас есть функция идентификации, все остальное становится проще. В качестве альтернативы мы могли бы создать лямбда-функцию в самом стеке. Поскольку я использую много стеков в одной учетной записи, у меня будет целая куча оставшихся лямбда-функций и ролей (и все стеки должны быть созданы с помощью --capabilities=CAPABILITY_IAM
, поскольку ему также нужна роль.
index.handler
Затем скопируйте и вставьте приведенный ниже код в поле кода. Вверху функции находится код из Модуль Python cfn-response, который по какой-то странной причине устанавливается автоматически только в том случае, если лямбда-функция создается через CloudFormation. В handler
функция довольно понятна.
from __future__ import print_function
import json
try:
from urllib2 import HTTPError, build_opener, HTTPHandler, Request
except ImportError:
from urllib.error import HTTPError
from urllib.request import build_opener, HTTPHandler, Request
SUCCESS = "SUCCESS"
FAILED = "FAILED"
def send(event, context, response_status, reason=None, response_data=None, physical_resource_id=None):
response_data = response_data or {}
response_body = json.dumps(
{
'Status': response_status,
'Reason': reason or "See the details in CloudWatch Log Stream: " + context.log_stream_name,
'PhysicalResourceId': physical_resource_id or context.log_stream_name,
'StackId': event['StackId'],
'RequestId': event['RequestId'],
'LogicalResourceId': event['LogicalResourceId'],
'Data': response_data
}
)
if event["ResponseURL"] == "http://pre-signed-S3-url-for-response":
print("Would send back the following values to Cloud Formation:")
print(response_data)
return
opener = build_opener(HTTPHandler)
request = Request(event['ResponseURL'], data=response_body)
request.add_header('Content-Type', '')
request.add_header('Content-Length', len(response_body))
request.get_method = lambda: 'PUT'
try:
response = opener.open(request)
print("Status code: {}".format(response.getcode()))
print("Status message: {}".format(response.msg))
return True
except HTTPError as exc:
print("Failed executing HTTP request: {}".format(exc.code))
return False
def handler(event, context):
responseData = event['ResourceProperties']
send(event, context, SUCCESS, None, responseData, "CustomResourcePhysicalID")
Теперь вы можете протестировать лямбда-функцию, нажав кнопку «Тест» и выбрав «Запрос на создание CloudFormation» в качестве образца шаблона. Вы должны увидеть в своем журнале, что переменные, переданные в него, возвращаются.
Теперь, когда у нас есть эта лямбда-функция, мы можем использовать ее в шаблонах CloudFormation. Сначала обратите внимание на лямбда-функцию Arn (перейдите к домашняя страница лямбда, щелкните только что созданную функцию, Arn должен быть вверху справа, что-то вроде arn:aws:lambda:region:12345:function:CloudFormationIdentity
).
Теперь в вашем шаблоне в разделе ресурсов укажите свои переменные, например:
Identity:
Type: "Custom::Variable"
Properties:
ServiceToken: "arn:aws:lambda:region:12345:function:CloudFormationIdentity"
Arn: "arn:aws:lambda:region:12345:function:CloudFormationIdentity"
ClientBucketVar:
Type: "Custom::Variable"
Properties:
ServiceToken: !GetAtt [Identity, Arn]
Name: !Join ["-", [my-client-bucket, !Ref ClientName]]
Arn: !Join [":", [arn, aws, s3, "", "", !Join ["-", [my-client-bucket, !Ref ClientName]]]]
ClientBackupBucketVar:
Type: "Custom::Variable"
Properties:
ServiceToken: !GetAtt [Identity, Arn]
Name: !Join ["-", [my-client-bucket, !Ref ClientName, backup]]
Arn: !Join [":", [arn, aws, s3, "", "", !Join ["-", [my-client-bucket, !Ref ClientName, backup]]]]
Сначала я указываю Identity
переменная, содержащая Arn для лямбда-функции. Ввод здесь в переменную означает, что мне нужно указать это только один раз. Я делаю все свои переменные типа Custom::Variable
. CloudFormation позволяет использовать любое имя типа, начинающееся с Custom::
для пользовательских ресурсов.
Обратите внимание, что Identity
переменная дважды содержит Arn для лямбда-функции. Один раз указать используемую лямбда-функцию. Второй раз как значение переменной.
Теперь, когда у меня есть Identity
переменной, я могу определить новые переменные, используя ServiceToken: !GetAtt [Identity, Arn]
(Я думаю, что код JSON должен быть чем-то вроде "ServiceToken": {"Fn::GetAtt": ["Identity", "Arn"]}
). Я создаю 2 новые переменные, в каждой по 2 поля: Имя и Арн. В остальной части моего шаблона я могу использовать !GetAtt [ClientBucketVar, Name]
или !GetAtt [ClientBucketVar, Arn]
всякий раз, когда мне это нужно.
При работе с пользовательскими ресурсами, если лямбда-функция дает сбой, вы застреваете на 1-2 часа, потому что CloudFormation ожидает ответа от (сбойной) функции в течение часа, прежде чем отказаться. Поэтому было бы неплохо указать короткий тайм-аут для стека при разработке вашей лямбда-функции.
Нет. Я попробовал, но ничего не вышло. Для меня это имело смысл создать запись Mappings под названием «CustomVariables» и разместить в ней все мои переменные. Это работает для простых строк, но вы не может использовать Intrinsics (Refs, Fn :: Joins и т. д.) внутри Mappings.
Работает:
"Mappings" : {
"CustomVariables" : {
"Variable1" : { "Value" : "foo" },
"Variable2" : { "Value" : "bar" }
}
}
Не получится:
"Variable3" : { "Value" : { "Ref" : "AWS::Region" } }
Это просто пример. Вы не стали бы помещать отдельную ссылку в переменную.
Вы можете использовать вложенный стек, который разрешает все ваши переменные на своих выходах, а затем использовать Fn::GetAtt
читать выходные данные из этого стека
Вы можете использовать вложенные шаблоны, в которых вы «разрешаете» все свои переменные во внешнем шаблоне и передаете их другому шаблону.