Support getting cloudformation targets for the SSM backend.
SendCommand allows filtering for instances by tags. This adds support for filtering by a cloud formation stack resource when creating command invocations.
This commit is contained in:
parent
12807bb6f0
commit
1b1fc4c030
@ -5,10 +5,12 @@ from collections import defaultdict
|
|||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
from moto.core.exceptions import RESTError
|
from moto.core.exceptions import RESTError
|
||||||
from moto.ec2 import ec2_backends
|
from moto.ec2 import ec2_backends
|
||||||
|
from moto.cloudformation import cloudformation_backends
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
|
||||||
class Parameter(BaseModel):
|
class Parameter(BaseModel):
|
||||||
@ -67,7 +69,7 @@ class Command(BaseModel):
|
|||||||
instance_ids=None, max_concurrency='', max_errors='',
|
instance_ids=None, max_concurrency='', max_errors='',
|
||||||
notification_config=None, output_s3_bucket_name='',
|
notification_config=None, output_s3_bucket_name='',
|
||||||
output_s3_key_prefix='', output_s3_region='', parameters=None,
|
output_s3_key_prefix='', output_s3_region='', parameters=None,
|
||||||
service_role_arn='', targets=None):
|
service_role_arn='', targets=None, backend_region='us-east-1'):
|
||||||
|
|
||||||
if instance_ids is None:
|
if instance_ids is None:
|
||||||
instance_ids = []
|
instance_ids = []
|
||||||
@ -105,6 +107,14 @@ class Command(BaseModel):
|
|||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
self.service_role_arn = service_role_arn
|
self.service_role_arn = service_role_arn
|
||||||
self.targets = targets
|
self.targets = targets
|
||||||
|
self.backend_region = backend_region
|
||||||
|
|
||||||
|
# Get instance ids from a cloud formation stack target.
|
||||||
|
stack_instance_ids = [self.get_instance_ids_by_stack_ids(target['Values']) for
|
||||||
|
target in self.targets if
|
||||||
|
target['Key'] == 'tag:aws:cloudformation:stack-name']
|
||||||
|
|
||||||
|
self.instance_ids += list(itertools.chain.from_iterable(stack_instance_ids))
|
||||||
|
|
||||||
# Create invocations with a single run command plugin.
|
# Create invocations with a single run command plugin.
|
||||||
self.invocations = []
|
self.invocations = []
|
||||||
@ -112,6 +122,18 @@ class Command(BaseModel):
|
|||||||
self.invocations.append(
|
self.invocations.append(
|
||||||
self.invocation_response(instance_id, "aws:runShellScript"))
|
self.invocation_response(instance_id, "aws:runShellScript"))
|
||||||
|
|
||||||
|
def get_instance_ids_by_stack_ids(self, stack_ids):
|
||||||
|
instance_ids = []
|
||||||
|
cloudformation_backend = cloudformation_backends[self.backend_region]
|
||||||
|
for stack_id in stack_ids:
|
||||||
|
stack_resources = cloudformation_backend.list_stack_resources(stack_id)
|
||||||
|
instance_resources = [
|
||||||
|
instance.id for instance in stack_resources
|
||||||
|
if instance.type == "AWS::EC2::Instance"]
|
||||||
|
instance_ids.extend(instance_resources)
|
||||||
|
|
||||||
|
return instance_ids
|
||||||
|
|
||||||
def response_object(self):
|
def response_object(self):
|
||||||
r = {
|
r = {
|
||||||
'CommandId': self.command_id,
|
'CommandId': self.command_id,
|
||||||
@ -191,6 +213,11 @@ class SimpleSystemManagerBackend(BaseBackend):
|
|||||||
self._resource_tags = defaultdict(lambda: defaultdict(dict))
|
self._resource_tags = defaultdict(lambda: defaultdict(dict))
|
||||||
self._commands = []
|
self._commands = []
|
||||||
|
|
||||||
|
# figure out what region we're in
|
||||||
|
for region, backend in ssm_backends.items():
|
||||||
|
if backend == self:
|
||||||
|
self._region = region
|
||||||
|
|
||||||
def delete_parameter(self, name):
|
def delete_parameter(self, name):
|
||||||
try:
|
try:
|
||||||
del self._parameters[name]
|
del self._parameters[name]
|
||||||
@ -311,7 +338,8 @@ class SimpleSystemManagerBackend(BaseBackend):
|
|||||||
output_s3_region=kwargs.get('OutputS3Region', ''),
|
output_s3_region=kwargs.get('OutputS3Region', ''),
|
||||||
parameters=kwargs.get('Parameters', {}),
|
parameters=kwargs.get('Parameters', {}),
|
||||||
service_role_arn=kwargs.get('ServiceRoleArn', ''),
|
service_role_arn=kwargs.get('ServiceRoleArn', ''),
|
||||||
targets=kwargs.get('Targets', []))
|
targets=kwargs.get('Targets', []),
|
||||||
|
backend_region=self._region)
|
||||||
|
|
||||||
self._commands.append(command)
|
self._commands.append(command)
|
||||||
return {
|
return {
|
||||||
|
@ -5,11 +5,12 @@ import botocore.exceptions
|
|||||||
import sure # noqa
|
import sure # noqa
|
||||||
import datetime
|
import datetime
|
||||||
import uuid
|
import uuid
|
||||||
|
import json
|
||||||
|
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
from nose.tools import assert_raises
|
from nose.tools import assert_raises
|
||||||
|
|
||||||
from moto import mock_ssm
|
from moto import mock_ssm, mock_cloudformation
|
||||||
|
|
||||||
|
|
||||||
@mock_ssm
|
@mock_ssm
|
||||||
@ -708,3 +709,78 @@ def test_get_command_invocation():
|
|||||||
CommandId=cmd_id,
|
CommandId=cmd_id,
|
||||||
InstanceId=instance_id,
|
InstanceId=instance_id,
|
||||||
PluginName='FAKE')
|
PluginName='FAKE')
|
||||||
|
|
||||||
|
@mock_ssm
|
||||||
|
@mock_cloudformation
|
||||||
|
def test_get_command_invocations_from_stack():
|
||||||
|
stack_template = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Description": "Test Stack",
|
||||||
|
"Resources": {
|
||||||
|
"EC2Instance1": {
|
||||||
|
"Type": "AWS::EC2::Instance",
|
||||||
|
"Properties": {
|
||||||
|
"ImageId": "ami-test-image-id",
|
||||||
|
"KeyName": "test",
|
||||||
|
"InstanceType": "t2.micro",
|
||||||
|
"Tags": [
|
||||||
|
{
|
||||||
|
"Key": "Test Description",
|
||||||
|
"Value": "Test tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "Test Name",
|
||||||
|
"Value": "Name tag for tests"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Outputs": {
|
||||||
|
"test": {
|
||||||
|
"Description": "Test Output",
|
||||||
|
"Value": "Test output value",
|
||||||
|
"Export": {
|
||||||
|
"Name": "Test value to export"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PublicIP": {
|
||||||
|
"Value": "Test public ip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cloudformation_client = boto3.client(
|
||||||
|
'cloudformation',
|
||||||
|
region_name='us-east-1')
|
||||||
|
|
||||||
|
stack_template_str = json.dumps(stack_template)
|
||||||
|
|
||||||
|
response = cloudformation_client.create_stack(
|
||||||
|
StackName='test_stack',
|
||||||
|
TemplateBody=stack_template_str,
|
||||||
|
Capabilities=('CAPABILITY_IAM', ))
|
||||||
|
|
||||||
|
client = boto3.client('ssm', region_name='us-east-1')
|
||||||
|
|
||||||
|
ssm_document = 'AWS-RunShellScript'
|
||||||
|
params = {'commands': ['#!/bin/bash\necho \'hello world\'']}
|
||||||
|
|
||||||
|
response = client.send_command(
|
||||||
|
Targets=[{
|
||||||
|
'Key': 'tag:aws:cloudformation:stack-name',
|
||||||
|
'Values': ('test_stack', )}],
|
||||||
|
DocumentName=ssm_document,
|
||||||
|
Parameters=params,
|
||||||
|
OutputS3Region='us-east-2',
|
||||||
|
OutputS3BucketName='the-bucket',
|
||||||
|
OutputS3KeyPrefix='pref')
|
||||||
|
|
||||||
|
cmd = response['Command']
|
||||||
|
cmd_id = cmd['CommandId']
|
||||||
|
instance_ids = cmd['InstanceIds']
|
||||||
|
|
||||||
|
invocation_response = client.get_command_invocation(
|
||||||
|
CommandId=cmd_id,
|
||||||
|
InstanceId=instance_ids[0],
|
||||||
|
PluginName='aws:runShellScript')
|
||||||
|
Loading…
Reference in New Issue
Block a user