Implement support for SSM parameter store
This commit adds initial support for the Simple System Manager client. It currently only mocks the following api endpoints: - delete_parameter() - put_parameter() - get_parameters()
This commit is contained in:
parent
df84675ae6
commit
783a1d73b4
@ -46,3 +46,4 @@ Moto is written by Steve Pulec with contributions from:
|
|||||||
* [Justin Wiley](https://github.com/SectorNine50)
|
* [Justin Wiley](https://github.com/SectorNine50)
|
||||||
* [Adam Stauffer](https://github.com/adamstauffer)
|
* [Adam Stauffer](https://github.com/adamstauffer)
|
||||||
* [Guy Templeton](https://github.com/gjtempleton)
|
* [Guy Templeton](https://github.com/gjtempleton)
|
||||||
|
* [Michael van Tellingen](https://github.com/mvantellingen)
|
||||||
|
@ -31,6 +31,7 @@ from .ses import mock_ses, mock_ses_deprecated # flake8: noqa
|
|||||||
from .sns import mock_sns, mock_sns_deprecated # flake8: noqa
|
from .sns import mock_sns, mock_sns_deprecated # flake8: noqa
|
||||||
from .sqs import mock_sqs, mock_sqs_deprecated # flake8: noqa
|
from .sqs import mock_sqs, mock_sqs_deprecated # flake8: noqa
|
||||||
from .sts import mock_sts, mock_sts_deprecated # flake8: noqa
|
from .sts import mock_sts, mock_sts_deprecated # flake8: noqa
|
||||||
|
from .ssm import mock_ssm # flake8: noqa
|
||||||
from .route53 import mock_route53, mock_route53_deprecated # flake8: noqa
|
from .route53 import mock_route53, mock_route53_deprecated # flake8: noqa
|
||||||
from .swf import mock_swf, mock_swf_deprecated # flake8: noqa
|
from .swf import mock_swf, mock_swf_deprecated # flake8: noqa
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ from moto.s3 import s3_backends
|
|||||||
from moto.ses import ses_backends
|
from moto.ses import ses_backends
|
||||||
from moto.sns import sns_backends
|
from moto.sns import sns_backends
|
||||||
from moto.sqs import sqs_backends
|
from moto.sqs import sqs_backends
|
||||||
|
from moto.ssm import ssm_backends
|
||||||
from moto.sts import sts_backends
|
from moto.sts import sts_backends
|
||||||
|
|
||||||
BACKENDS = {
|
BACKENDS = {
|
||||||
@ -56,6 +57,7 @@ BACKENDS = {
|
|||||||
'ses': ses_backends,
|
'ses': ses_backends,
|
||||||
'sns': sns_backends,
|
'sns': sns_backends,
|
||||||
'sqs': sqs_backends,
|
'sqs': sqs_backends,
|
||||||
|
'ssm': ssm_backends,
|
||||||
'sts': sts_backends,
|
'sts': sts_backends,
|
||||||
'route53': route53_backends,
|
'route53': route53_backends,
|
||||||
'lambda': lambda_backends,
|
'lambda': lambda_backends,
|
||||||
|
6
moto/ssm/__init__.py
Normal file
6
moto/ssm/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from .models import ssm_backends
|
||||||
|
from ..core.models import base_decorator
|
||||||
|
|
||||||
|
ssm_backend = ssm_backends['us-east-1']
|
||||||
|
mock_ssm = base_decorator(ssm_backends)
|
65
moto/ssm/models.py
Normal file
65
moto/ssm/models.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from moto.core import BaseBackend, BaseModel
|
||||||
|
from moto.ec2 import ec2_backends
|
||||||
|
|
||||||
|
|
||||||
|
class Parameter(BaseModel):
|
||||||
|
def __init__(self, name, value, type, description, keyid):
|
||||||
|
self.name = name
|
||||||
|
self.type = type
|
||||||
|
self.description = description
|
||||||
|
self.keyid = keyid
|
||||||
|
|
||||||
|
if self.type == 'SecureString':
|
||||||
|
self.value = self.encrypt(value)
|
||||||
|
else:
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def encrypt(self, value):
|
||||||
|
return 'kms:{}:'.format(self.keyid or 'default') + value
|
||||||
|
|
||||||
|
def decrypt(self, value):
|
||||||
|
if self.type != 'SecureString':
|
||||||
|
return value
|
||||||
|
|
||||||
|
prefix = 'kms:{}:'.format(self.keyid or 'default')
|
||||||
|
if value.startswith(prefix):
|
||||||
|
return value[len(prefix):]
|
||||||
|
|
||||||
|
def response_object(self, decrypt=False):
|
||||||
|
return {
|
||||||
|
'Name': self.name,
|
||||||
|
'Type': self.type,
|
||||||
|
'Value': self.decrypt(self.value) if decrypt else self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleSystemManagerBackend(BaseBackend):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._parameters = {}
|
||||||
|
|
||||||
|
def delete_parameter(self, name):
|
||||||
|
try:
|
||||||
|
del self._parameters[name]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_parameters(self, names, with_decryption):
|
||||||
|
result = []
|
||||||
|
for name in names:
|
||||||
|
if name in self._parameters:
|
||||||
|
result.append(self._parameters[name])
|
||||||
|
return result
|
||||||
|
|
||||||
|
def put_parameter(self, name, description, value, type, keyid, overwrite):
|
||||||
|
if not overwrite and name in self._parameters:
|
||||||
|
return
|
||||||
|
self._parameters[name] = Parameter(
|
||||||
|
name, value, type, description, keyid)
|
||||||
|
|
||||||
|
|
||||||
|
ssm_backends = {}
|
||||||
|
for region, ec2_backend in ec2_backends.items():
|
||||||
|
ssm_backends[region] = SimpleSystemManagerBackend()
|
56
moto/ssm/responses.py
Normal file
56
moto/ssm/responses.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import json
|
||||||
|
|
||||||
|
from moto.core.responses import BaseResponse
|
||||||
|
from .models import ssm_backends
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleSystemManagerResponse(BaseResponse):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ssm_backend(self):
|
||||||
|
return ssm_backends[self.region]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def request_params(self):
|
||||||
|
try:
|
||||||
|
return json.loads(self.body)
|
||||||
|
except ValueError:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _get_param(self, param, default=None):
|
||||||
|
return self.request_params.get(param, default)
|
||||||
|
|
||||||
|
def delete_parameter(self):
|
||||||
|
name = self._get_param('Name')
|
||||||
|
self.ssm_backend.delete_parameter(name)
|
||||||
|
return json.dumps({})
|
||||||
|
|
||||||
|
def get_parameters(self):
|
||||||
|
names = self._get_param('Names')
|
||||||
|
with_decryption = self._get_param('WithDecryption')
|
||||||
|
|
||||||
|
result = self.ssm_backend.get_parameters(names, with_decryption)
|
||||||
|
|
||||||
|
response = {
|
||||||
|
'Parameters': [],
|
||||||
|
'InvalidParameters': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
for parameter in result:
|
||||||
|
param_data = parameter.response_object(with_decryption)
|
||||||
|
response['Parameters'].append(param_data)
|
||||||
|
|
||||||
|
return json.dumps(response)
|
||||||
|
|
||||||
|
def put_parameter(self):
|
||||||
|
name = self._get_param('Name')
|
||||||
|
description = self._get_param('Description')
|
||||||
|
value = self._get_param('Value')
|
||||||
|
type_ = self._get_param('Type')
|
||||||
|
keyid = self._get_param('KeyId')
|
||||||
|
overwrite = self._get_param('Overwrite', False)
|
||||||
|
|
||||||
|
self.ssm_backend.put_parameter(
|
||||||
|
name, description, value, type_, keyid, overwrite)
|
||||||
|
return json.dumps({})
|
10
moto/ssm/urls.py
Normal file
10
moto/ssm/urls.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from .responses import SimpleSystemManagerResponse
|
||||||
|
|
||||||
|
url_bases = [
|
||||||
|
"https?://ssm.(.+).amazonaws.com",
|
||||||
|
]
|
||||||
|
|
||||||
|
url_paths = {
|
||||||
|
'{0}/$': SimpleSystemManagerResponse.dispatch,
|
||||||
|
}
|
114
tests/test_ssm/test_ssm_boto3.py
Normal file
114
tests/test_ssm/test_ssm_boto3.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import boto3
|
||||||
|
import sure # noqa
|
||||||
|
|
||||||
|
from moto import mock_ssm
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ssm
|
||||||
|
def test_delete_parameter():
|
||||||
|
client = boto3.client('ssm', region_name='us-east-1')
|
||||||
|
|
||||||
|
client.put_parameter(
|
||||||
|
Name='test',
|
||||||
|
Description='A test parameter',
|
||||||
|
Value='value',
|
||||||
|
Type='String')
|
||||||
|
|
||||||
|
response = client.get_parameters(Names=['test'])
|
||||||
|
len(response['Parameters']).should.equal(1)
|
||||||
|
|
||||||
|
client.delete_parameter(Name='test')
|
||||||
|
|
||||||
|
response = client.get_parameters(Names=['test'])
|
||||||
|
len(response['Parameters']).should.equal(0)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ssm
|
||||||
|
def test_put_parameter():
|
||||||
|
client = boto3.client('ssm', region_name='us-east-1')
|
||||||
|
|
||||||
|
client.put_parameter(
|
||||||
|
Name='test',
|
||||||
|
Description='A test parameter',
|
||||||
|
Value='value',
|
||||||
|
Type='String')
|
||||||
|
|
||||||
|
response = client.get_parameters(
|
||||||
|
Names=[
|
||||||
|
'test'
|
||||||
|
],
|
||||||
|
WithDecryption=False)
|
||||||
|
|
||||||
|
len(response['Parameters']).should.equal(1)
|
||||||
|
response['Parameters'][0]['Name'].should.equal('test')
|
||||||
|
response['Parameters'][0]['Value'].should.equal('value')
|
||||||
|
response['Parameters'][0]['Type'].should.equal('String')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ssm
|
||||||
|
def test_put_parameter_secure_default_kms():
|
||||||
|
client = boto3.client('ssm', region_name='us-east-1')
|
||||||
|
|
||||||
|
client.put_parameter(
|
||||||
|
Name='test',
|
||||||
|
Description='A test parameter',
|
||||||
|
Value='value',
|
||||||
|
Type='SecureString')
|
||||||
|
|
||||||
|
response = client.get_parameters(
|
||||||
|
Names=[
|
||||||
|
'test'
|
||||||
|
],
|
||||||
|
WithDecryption=False)
|
||||||
|
|
||||||
|
len(response['Parameters']).should.equal(1)
|
||||||
|
response['Parameters'][0]['Name'].should.equal('test')
|
||||||
|
response['Parameters'][0]['Value'].should.equal('kms:default:value')
|
||||||
|
response['Parameters'][0]['Type'].should.equal('SecureString')
|
||||||
|
|
||||||
|
response = client.get_parameters(
|
||||||
|
Names=[
|
||||||
|
'test'
|
||||||
|
],
|
||||||
|
WithDecryption=True)
|
||||||
|
|
||||||
|
len(response['Parameters']).should.equal(1)
|
||||||
|
response['Parameters'][0]['Name'].should.equal('test')
|
||||||
|
response['Parameters'][0]['Value'].should.equal('value')
|
||||||
|
response['Parameters'][0]['Type'].should.equal('SecureString')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ssm
|
||||||
|
def test_put_parameter_secure_custom_kms():
|
||||||
|
client = boto3.client('ssm', region_name='us-east-1')
|
||||||
|
|
||||||
|
client.put_parameter(
|
||||||
|
Name='test',
|
||||||
|
Description='A test parameter',
|
||||||
|
Value='value',
|
||||||
|
Type='SecureString',
|
||||||
|
KeyId='foo')
|
||||||
|
|
||||||
|
response = client.get_parameters(
|
||||||
|
Names=[
|
||||||
|
'test'
|
||||||
|
],
|
||||||
|
WithDecryption=False)
|
||||||
|
|
||||||
|
len(response['Parameters']).should.equal(1)
|
||||||
|
response['Parameters'][0]['Name'].should.equal('test')
|
||||||
|
response['Parameters'][0]['Value'].should.equal('kms:foo:value')
|
||||||
|
response['Parameters'][0]['Type'].should.equal('SecureString')
|
||||||
|
|
||||||
|
response = client.get_parameters(
|
||||||
|
Names=[
|
||||||
|
'test'
|
||||||
|
],
|
||||||
|
WithDecryption=True)
|
||||||
|
|
||||||
|
len(response['Parameters']).should.equal(1)
|
||||||
|
response['Parameters'][0]['Name'].should.equal('test')
|
||||||
|
response['Parameters'][0]['Value'].should.equal('value')
|
||||||
|
response['Parameters'][0]['Type'].should.equal('SecureString')
|
Loading…
Reference in New Issue
Block a user