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:
parent
b670962c5e
commit
5602c4e73e
@ -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"""
|
||||
|
@ -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):
|
||||
|
@ -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.")
|
||||
|
Loading…
Reference in New Issue
Block a user