Skip to content

Commit

Permalink
Merge pull request #907 from mvantellingen/ssm
Browse files Browse the repository at this point in the history
Implement support for SSM parameter store
  • Loading branch information
spulec committed May 11, 2017
2 parents df84675 + 783a1d7 commit 0934b43
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 0 deletions.
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@ Moto is written by Steve Pulec with contributions from:
* [Justin Wiley](https://github.com/SectorNine50)
* [Adam Stauffer](https://github.com/adamstauffer)
* [Guy Templeton](https://github.com/gjtempleton)
* [Michael van Tellingen](https://github.com/mvantellingen)
1 change: 1 addition & 0 deletions moto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from .sns import mock_sns, mock_sns_deprecated # flake8: noqa
from .sqs import mock_sqs, mock_sqs_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 .swf import mock_swf, mock_swf_deprecated # flake8: noqa

Expand Down
2 changes: 2 additions & 0 deletions moto/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from moto.ses import ses_backends
from moto.sns import sns_backends
from moto.sqs import sqs_backends
from moto.ssm import ssm_backends
from moto.sts import sts_backends

BACKENDS = {
Expand Down Expand Up @@ -56,6 +57,7 @@
'ses': ses_backends,
'sns': sns_backends,
'sqs': sqs_backends,
'ssm': ssm_backends,
'sts': sts_backends,
'route53': route53_backends,
'lambda': lambda_backends,
Expand Down
6 changes: 6 additions & 0 deletions moto/ssm/__init__.py
Original file line number Diff line number Diff line change
@@ -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 changes: 65 additions & 0 deletions moto/ssm/models.py
Original file line number Diff line number Diff line change
@@ -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 changes: 56 additions & 0 deletions moto/ssm/responses.py
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 0 deletions moto/ssm/urls.py
Original file line number Diff line number Diff line change
@@ -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 changes: 114 additions & 0 deletions tests/test_ssm/test_ssm_boto3.py
Original file line number Diff line number Diff line change
@@ -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')

0 comments on commit 0934b43

Please sign in to comment.