SSM Parameter Store improvements in GetParameterHistory & GetParameters (#3984)

* Including labels and versions in SSM Get Parameters

* implementing NextToken and MaxResults into the SSM Get Parameter History functionality

* Implementing unit tests and some lint refactoring for NextToken implementation in get_parameter_history
This commit is contained in:
Austin Hendrix 2021-06-04 05:12:35 -05:00 committed by GitHub
parent b670962c5e
commit 5602c4e73e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 194 additions and 12 deletions

View File

@ -37,6 +37,7 @@ from .exceptions import (
PARAMETER_VERSION_LIMIT = 100
PARAMETER_HISTORY_MAX_RESULTS = 50
class Parameter(BaseModel):
@ -1071,7 +1072,7 @@ class SimpleSystemManagerBackend(BaseBackend):
return result
def get_parameters(self, names, with_decryption):
result = []
result = {}
if len(names) > 10:
raise ValidationException(
@ -1082,9 +1083,15 @@ class SimpleSystemManagerBackend(BaseBackend):
)
)
for name in names:
if name in self._parameters:
result.append(self.get_parameter(name, with_decryption))
for name in set(names):
if name.split(":")[0] in self._parameters:
try:
param = self.get_parameter(name, with_decryption)
if param is not None:
result[name] = param
except ParameterVersionNotFound:
pass
return result
def get_parameters_by_path(
@ -1129,10 +1136,36 @@ class SimpleSystemManagerBackend(BaseBackend):
next_token = None
return values, next_token
def get_parameter_history(self, name, with_decryption):
def get_parameter_history(self, name, with_decryption, next_token, max_results=50):
if max_results > PARAMETER_HISTORY_MAX_RESULTS:
raise ValidationException(
"1 validation error detected: "
"Value '{}' at 'maxResults' failed to satisfy constraint: "
"Member must have value less than or equal to {}.".format(
max_results, PARAMETER_HISTORY_MAX_RESULTS
)
)
if name in self._parameters:
return self._parameters[name]
return None
history = self._parameters[name]
return self._get_history_nexttoken(history, next_token, max_results)
return None, None
def _get_history_nexttoken(self, history, next_token, max_results):
if next_token is None:
next_token = 0
next_token = int(next_token)
max_results = int(max_results)
history_to_return = history[next_token : next_token + max_results]
if (
len(history_to_return) == max_results
and len(history) > next_token + max_results
):
new_next_token = next_token + max_results
return history_to_return, str(new_next_token)
return history_to_return, None
def _match_filters(self, parameter, filters=None):
"""Return True if the given parameter matches all the filters"""

View File

@ -178,13 +178,13 @@ class SimpleSystemManagerResponse(BaseResponse):
response = {"Parameters": [], "InvalidParameters": []}
for parameter in result:
for name, parameter in result.items():
param_data = parameter.response_object(with_decryption, self.region)
response["Parameters"].append(param_data)
param_names = [param.name for param in result]
valid_param_names = [name for name, parameter in result.items()]
for name in names:
if name not in param_names:
if name not in valid_param_names:
response["InvalidParameters"].append(name)
return json.dumps(response)
@ -266,8 +266,12 @@ class SimpleSystemManagerResponse(BaseResponse):
def get_parameter_history(self):
name = self._get_param("Name")
with_decryption = self._get_param("WithDecryption")
next_token = self._get_param("NextToken")
max_results = self._get_param("MaxResults", 50)
result = self.ssm_backend.get_parameter_history(name, with_decryption)
result, new_next_token = self.ssm_backend.get_parameter_history(
name, with_decryption, next_token, max_results
)
if result is None:
error = {
@ -283,6 +287,9 @@ class SimpleSystemManagerResponse(BaseResponse):
)
response["Parameters"].append(param_data)
if new_next_token is not None:
response["NextToken"] = new_next_token
return json.dumps(response)
def label_parameter_version(self):

View File

@ -12,7 +12,7 @@ from botocore.exceptions import ClientError
import pytest
from moto import mock_ec2, mock_ssm
from moto.ssm.models import PARAMETER_VERSION_LIMIT
from moto.ssm.models import PARAMETER_VERSION_LIMIT, PARAMETER_HISTORY_MAX_RESULTS
from tests import EXAMPLE_AMI_ID
@ -1816,3 +1816,145 @@ def test_parameter_overwrite_fails_when_limit_reached_and_oldest_version_has_lab
error["Message"].should.match(
r"the oldest version, can't be deleted because it has a label associated with it. Move the label to another version of the parameter, and try again."
)
@mock_ssm
def test_get_parameters_includes_invalid_parameter_when_requesting_invalid_version():
client = boto3.client("ssm", region_name="us-east-1")
parameter_name = "test-param"
versions_to_create = 5
for i in range(versions_to_create):
client.put_parameter(
Name=parameter_name,
Value="value-%d" % (i + 1),
Type="String",
Overwrite=True,
)
response = client.get_parameters(
Names=[
"test-param:%d" % (versions_to_create + 1),
"test-param:%d" % (versions_to_create - 1),
]
)
len(response["InvalidParameters"]).should.equal(1)
response["InvalidParameters"][0].should.equal(
"test-param:%d" % (versions_to_create + 1)
)
len(response["Parameters"]).should.equal(1)
response["Parameters"][0]["Name"].should.equal("test-param")
response["Parameters"][0]["Value"].should.equal("value-4")
response["Parameters"][0]["Type"].should.equal("String")
@mock_ssm
def test_get_parameters_includes_invalid_parameter_when_requesting_invalid_label():
client = boto3.client("ssm", region_name="us-east-1")
parameter_name = "test-param"
versions_to_create = 5
for i in range(versions_to_create):
client.put_parameter(
Name=parameter_name,
Value="value-%d" % (i + 1),
Type="String",
Overwrite=True,
)
client.label_parameter_version(
Name=parameter_name, ParameterVersion=1, Labels=["test-label"]
)
response = client.get_parameters(
Names=[
"test-param:test-label",
"test-param:invalid-label",
"test-param",
"test-param:2",
]
)
len(response["InvalidParameters"]).should.equal(1)
response["InvalidParameters"][0].should.equal("test-param:invalid-label")
len(response["Parameters"]).should.equal(3)
@mock_ssm
def test_get_parameters_should_only_return_unique_requests():
client = boto3.client("ssm", region_name="us-east-1")
parameter_name = "test-param"
client.put_parameter(Name=parameter_name, Value="value", Type="String")
response = client.get_parameters(Names=["test-param", "test-param"])
len(response["Parameters"]).should.equal(1)
@mock_ssm
def test_get_parameter_history_should_throw_exception_when_MaxResults_is_too_large():
client = boto3.client("ssm", region_name="us-east-1")
parameter_name = "test-param"
for _ in range(100):
client.put_parameter(
Name=parameter_name, Value="value", Type="String", Overwrite=True
)
with pytest.raises(ClientError) as ex:
client.get_parameter_history(
Name=parameter_name, MaxResults=PARAMETER_HISTORY_MAX_RESULTS + 1
)
error = ex.value.response["Error"]
error["Code"].should.equal("ValidationException")
error["Message"].should.equal(
"1 validation error detected: "
"Value '{}' at 'maxResults' failed to satisfy constraint: "
"Member must have value less than or equal to 50.".format(
PARAMETER_HISTORY_MAX_RESULTS + 1
)
)
@mock_ssm
def test_get_parameter_history_NextTokenImplementation():
client = boto3.client("ssm", region_name="us-east-1")
parameter_name = "test-param"
for _ in range(100):
client.put_parameter(
Name=parameter_name, Value="value", Type="String", Overwrite=True
)
response = client.get_parameter_history(
Name=parameter_name, MaxResults=PARAMETER_HISTORY_MAX_RESULTS
) # fetch first 50
param_history = response["Parameters"]
next_token = response.get("NextToken", None)
while next_token is not None:
response = client.get_parameter_history(
Name=parameter_name, MaxResults=7, NextToken=next_token
) # fetch small amounts to test MaxResults can change
param_history.extend(response["Parameters"])
next_token = response.get("NextToken", None)
len(param_history).should.equal(100)
@mock_ssm
def test_get_parameter_history_exception_when_requesting_invalid_parameter():
client = boto3.client("ssm", region_name="us-east-1")
with pytest.raises(ClientError) as ex:
client.get_parameter_history(Name="invalid_parameter_name")
error = ex.value.response["Error"]
error["Code"].should.equal("ParameterNotFound")
error["Message"].should.equal("Parameter invalid_parameter_name not found.")