Existe-t-il un moyen de définir des raccourcis pour les valeurs fréquemment utilisées dérivées des paramètres du modèle CloudFormation?
Par exemple, j'ai un script qui crée une pile de projets multi-AZ avec le nom ELB project
et deux instances derrière l'ELB appelées project-1
et project-2
. Je passe uniquement le paramètre ELBHostName
au modèle et plus tard je l'utilise pour construire:
"Fn::Join": [
".", [
{ "Fn::Join": [ "", [ { "Ref": "ELBHostName" }, "-1" ] ] },
{ "Ref": "EnvironmentVersioned" },
{ "Ref": "HostedZone" }
]
]
Cette construction ou très similaire est répétée plusieurs fois dans le modèle - pour créer le nom d'hôte EC2, les enregistrements Route53, etc.
Au lieu de répéter cela encore et encore, je voudrais attribuer la sortie de ce Fn::Join
à une variable quelconque et ne fait référence qu'à cela, tout comme je peux avec "Ref":
déclaration.
Idéalement, quelque chose comme:
Var::HostNameFull = "Fn::Join": [ ... ]
...
{ "Name": { "Ref": "Var::HostNameFull" } }
ou quelque chose de similaire simple.
Est-ce possible avec Amazon CloudFormation?
Je cherchais la même fonctionnalité. L'utilisation d'une pile imbriquée comme SpoonMeiser l'a suggéré m'est venue à l'esprit, mais j'ai réalisé que ce dont j'avais réellement besoin était de fonctions personnalisées. Heureusement, CloudFormation permet d'utiliser AWS :: CloudFormation :: CustomResource qui, avec un peu de travail, permet de faire exactement cela. Cela semble exagéré pour les variables uniquement (quelque chose que je dirais qui aurait dû être dans CloudFormation en premier lieu), mais cela fait le travail, et, en plus, permet toute la flexibilité de (faites votre choix de python/nœud /Java). Il convient de noter que les fonctions lambda coûtent de l'argent, mais nous parlons ici de quelques centimes, sauf si vous créez/supprimez vos piles plusieurs fois par heure.
La première étape consiste à créer une fonction lambda sur cette page qui ne fait que prendre la valeur d'entrée et la copier dans la sortie. Nous pourrions avoir la fonction lambda faire toutes sortes de choses folles, mais une fois que nous avons la fonction d'identité, tout le reste est facile. Alternativement, nous pourrions avoir la fonction lambda en cours de création dans la pile elle-même. Étant donné que j'utilise plusieurs piles dans 1 compte, j'aurais tout un tas de fonctions et de rôles lambda restants (et toutes les piles doivent être créées avec --capabilities=CAPABILITY_IAM
, car il a également besoin d'un rôle.
index.handler
Copiez-collez ensuite le code ci-dessous dans le champ de code. Le haut de la fonction est le code du cfn-response python module , qui ne s'auto-installe que si la fonction lambda est créée via CloudFormation, pour une raison étrange. La fonction handler
est assez explicite.
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")
Vous pouvez maintenant tester la fonction lambda en sélectionnant le bouton "Test" et sélectionnez "CloudFormation Create Request" comme exemple de modèle. Vous devriez voir dans votre journal que les variables qui y sont alimentées sont retournées.
Maintenant que nous avons cette fonction lambda, nous pouvons l'utiliser dans les modèles CloudFormation. Prenez d'abord note de la fonction lambda Arn (allez sur la page d'accueil lambda , cliquez sur la fonction qui vient d'être créée, Arn devrait être en haut à droite, quelque chose comme arn:aws:lambda:region:12345:function:CloudFormationIdentity
).
Maintenant, dans votre modèle, dans la section des ressources, spécifiez vos variables comme:
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]]]]
Je spécifie d'abord une variable Identity
qui contient l'Arn pour la fonction lambda. Mettre ceci dans une variable ici, signifie que je n'ai qu'à le spécifier une fois. Je fais toutes mes variables de type Custom::Variable
. CloudFormation vous permet d'utiliser n'importe quel nom de type commençant par Custom::
pour les ressources personnalisées.
Notez que la variable Identity
contient deux fois Arn pour la fonction lambda. Une fois pour spécifier la fonction lambda à utiliser. La deuxième fois comme valeur de la variable.
Maintenant que j'ai la variable Identity
, je peux définir de nouvelles variables en utilisant ServiceToken: !GetAtt [Identity, Arn]
(Je pense que le code JSON devrait ressembler à "ServiceToken": {"Fn::GetAtt": ["Identity", "Arn"]}
). Je crée 2 nouvelles variables, chacune avec 2 champs: Name et Arn. Dans le reste de mon modèle, je peux utiliser !GetAtt [ClientBucketVar, Name]
ou !GetAtt [ClientBucketVar, Arn]
chaque fois que j'en ai besoin.
Lorsque vous travaillez avec des ressources personnalisées, si la fonction lambda se bloque, vous êtes bloqué entre 1 et 2 heures, car CloudFormation attend une réponse de la fonction (bloquée) pendant une heure avant d'abandonner. Par conséquent, il peut être utile de spécifier un court délai d'attente pour la pile lors du développement de votre fonction lambda.
Je n'ai pas de réponse, mais je voulais souligner que vous pouvez vous épargner beaucoup de douleur en utilisant Fn::Sub
au lieu de Fn::Join
{ "Fn::Sub": "${ELBHostName"}-1.${EnvironmentVersioned}.${HostedZone}"}
Remplace
"Fn::Join": [
".", [
{ "Fn::Join": [ "", [ { "Ref": "ELBHostName" }, "-1" ] ] },
{ "Ref": "EnvironmentVersioned" },
{ "Ref": "HostedZone" }
]
]
Non, je l'ai essayé, mais est venu vide. La façon qui me paraissait logique était de créer une entrée de mappages appelée "CustomVariables" et de faire en sorte qu'elle héberge toutes mes variables. Cela fonctionne pour les chaînes simples, mais vous ne pouvez pas utiliser Intrinsics (Refs, Fn :: Joins, etc.) à l'intérieur des mappages .
Travaux:
"Mappings" : {
"CustomVariables" : {
"Variable1" : { "Value" : "foo" },
"Variable2" : { "Value" : "bar" }
}
}
Ne fonctionnera pas:
"Variable3" : { "Value" : { "Ref" : "AWS::Region" } }
Ce n'est qu'un exemple. Vous ne mettriez pas une référence autonome dans une variable.
Vous pouvez utiliser une pile imbriquée qui résout toutes vos variables dans ses sorties, puis utiliser Fn::GetAtt
pour lire les sorties de cette pile
Vous pouvez utiliser des modèles imbriqués dans lesquels vous "résolvez" toutes vos variables dans le modèle externe et les transmettez à un autre modèle.